packages feed

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)