kempe-0.2.0.1: src/Kempe/Check/Pattern.hs
-- | Check pattern match exhaustiveness, since we don't really handle that in
-- source code.
--
-- This is pretty easy because of how patterns work in Kempe.
--
-- Some of this code is from Dickinson, but we don't need the Maranget approach
-- because the pattern matching is simpler in Kempe.
module Kempe.Check.Pattern ( checkModuleExhaustive
) where
import Control.Monad (forM_)
import Control.Monad.State.Strict (State, execState)
import Data.Coerce (coerce)
import Data.Foldable (toList, traverse_)
import Data.Foldable.Ext
import qualified Data.IntMap.Strict as IM
import qualified Data.IntSet as IS
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
import Kempe.AST
import Kempe.Error
import Kempe.Name
import Kempe.Unique
import Lens.Micro (Lens')
import Lens.Micro.Mtl (modifying)
checkAtom :: PatternEnv -> Atom c b -> Maybe (Error b)
checkAtom env (Case l ls) =
let (ps, as) = NE.unzip ls in
if isExhaustive env ps
then foldMapAlternative (foldMapAlternative (checkAtom env)) as
else Just (InexhaustiveMatch l)
checkAtom _ _ = Nothing
checkDecl :: PatternEnv -> KempeDecl a c b -> Maybe (Error b)
checkDecl env (FunDecl _ _ _ _ as) = foldMapAlternative (checkAtom env) as
checkDecl _ _ = Nothing
checkModule :: PatternEnv -> Declarations a c b -> Maybe (Error b)
checkModule env = foldMapAlternative (checkDecl env)
checkModuleExhaustive :: Declarations a c b -> Maybe (Error b)
checkModuleExhaustive m =
let env = runPatternM $ patternEnvDecls m
in checkModule env m
data PatternEnv = PatternEnv { allCons :: IM.IntMap IS.IntSet -- ^ all constructors indexed by type
, types :: IM.IntMap Int -- ^ all types indexed by constructor
}
allConsLens :: Lens' PatternEnv (IM.IntMap IS.IntSet)
allConsLens f s = fmap (\x -> s { allCons = x }) (f (allCons s))
typesLens :: Lens' PatternEnv (IM.IntMap Int)
typesLens f s = fmap (\x -> s { types = x }) (f (types s))
type PatternM = State PatternEnv
patternEnvDecls :: Declarations a c b -> PatternM ()
patternEnvDecls = traverse_ declAdd
declAdd :: KempeDecl a c b -> PatternM ()
declAdd FunDecl{} = pure ()
declAdd ExtFnDecl{} = pure ()
declAdd Export{} = pure ()
declAdd (TyDecl _ (Name _ (Unique i) _) _ ls) = do
forM_ ls $ \(Name _ (Unique j) _, _) ->
modifying typesLens (IM.insert j i)
let cons = IS.fromList $ toList (unUnique . unique . fst <$> ls)
modifying allConsLens (IM.insert i cons)
runPatternM :: PatternM a -> PatternEnv
runPatternM = flip execState (PatternEnv mempty mempty)
internalError :: a
internalError = error "Internal error: lookup in a PatternEnv failed"
-- given a constructor name, get the IntSet of all constructors of that type
assocUniques :: PatternEnv -> Name a -> IS.IntSet
assocUniques env (Name _ (Unique i) _) =
let ty = IM.findWithDefault internalError i (types env)
in IM.findWithDefault internalError ty (allCons env)
hasWildcard :: Foldable t => t (Pattern c b) -> Bool
hasWildcard = any isWildcard where
isWildcard PatternWildcard{} = True
isWildcard _ = False
-- | Only works on well-typed stuff
isExhaustive :: PatternEnv -> NonEmpty (Pattern c b) -> Bool
isExhaustive _ (PatternWildcard{}:|_) = True
isExhaustive _ (PatternInt{}:|ps) = hasWildcard ps
isExhaustive _ (PatternBool _ True:|PatternBool _ False:_) = True
isExhaustive _ (PatternBool _ False:|PatternBool _ True:_) = True
-- doesn't technically work since you could have True True False but like...
-- don't do that
isExhaustive _ (PatternBool{}:|ps) = hasWildcard ps
isExhaustive env ps@(PatternCons{}:|_) = hasWildcard ps || isCompleteSet env (fmap patternName ps)
isCompleteSet :: PatternEnv -> NonEmpty (TyName a) -> Bool
isCompleteSet env ns@(n:|_) =
let allU = assocUniques env n
ty = coerce (unique <$> toList ns)
in IS.null (allU IS.\\ IS.fromList ty)