@@ -4,7 +4,7 @@
44-- |
55-- Type class entailment
66--
7−module Language.PureScript.TypeChecker.Entailment (entails) where
7+module Language.PureScript.TypeChecker.Entailment (Context, replaceTypeClassDictionaries) where
88
99import Prelude ()
1010import Prelude.Compat
@@ -16,8 +16,9 @@ import qualified Data.Map as M
1616
1717import Control.Arrow (Arrow(..))
1818import Control.Monad.State
19+import Control.Monad.Writer
1920import Control.Monad.Error.Class (MonadError(..))
20−import Control.Monad.Writer.Class (MonadWriter(..))
21+import Control.Monad.Supply.Class (MonadSupply(..))
2122
2223import Language.PureScript.Crash
2324import Language.PureScript.AST
@@ -28,22 +29,47 @@ import Language.PureScript.TypeClassDictionaries
2829import Language.PureScript.Types
2930import qualified Language.PureScript.Constants as C
3031
32+-- | The 'Context' tracks those constraints which can be satisfied.
33+type Context = M.Map (Maybe ModuleName)
34+ (M.Map (Qualified (ProperName 'ClassName))
35+ (M.Map (Qualified Ident)
36+ TypeClassDictionaryInScope))
37+
38+-- | Merge two type class contexts
39+combineContexts :: Context -> Context -> Context
40+combineContexts = M.unionWith (M.unionWith M.union)
41+
42+-- | Replace type class dictionary placeholders with inferred type class dictionaries
43+replaceTypeClassDictionaries
44+ :: (MonadError MultipleErrors m, MonadWriter MultipleErrors m, MonadSupply m)
45+ => Bool
46+ -> ModuleName
47+ -> Expr
48+ -> m (Expr, [(Ident, Constraint)])
49+replaceTypeClassDictionaries shouldGeneralize mn =
50+ let (_, f, _) = everywhereOnValuesTopDownM return (WriterT . go) return
51+ in flip evalStateT M.empty . runWriterT . f
52+ where
53+ go (TypeClassDictionary constraint dicts) = entails shouldGeneralize mn dicts constraint
54+ go other = return (other, [])
55+
3156-- |
3257-- Check that the current set of type class dictionaries entail the specified type class goal, and, if so,
3358-- return a type class dictionary reference.
3459--
3560entails
3661 :: forall m
37− . (MonadError MultipleErrors m, MonadWriter MultipleErrors m)
38− => ModuleName
39− -> M.Map (Maybe ModuleName) (M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) TypeClassDictionaryInScope))
62+ . (MonadError MultipleErrors m, MonadWriter MultipleErrors m, MonadSupply m)
63+ => Bool
64+ -> ModuleName
65+ -> Context
4066 -> Constraint
41− -> m Expr
42−entails moduleName context = solve
67+ -> StateT Context m (Expr, [(Ident, Constraint)])
68+entails shouldGeneralize moduleName context = solve
4369 where
44− forClassName :: Qualified (ProperName 'ClassName) -> [Type] -> [TypeClassDictionaryInScope]
45− forClassName cn@(Qualified (Just mn) _) tys = concatMap (findDicts cn) (Nothing : Just mn : map Just (mapMaybe ctorModules tys))
46− forClassName _ _ = internalError "forClassName: expected qualified class name"
70+ forClassName :: Context -> Qualified (ProperName 'ClassName) -> [Type] -> [TypeClassDictionaryInScope]
71+ forClassName ctx cn@(Qualified (Just mn) _) tys = concatMap (findDicts ctx cn) (Nothing : Just mn : map Just (mapMaybe ctorModules tys))
72+ forClassName _ _ _ = internalError "forClassName: expected qualified class name"
4773
4874 ctorModules :: Type -> Maybe ModuleName
4975 ctorModules (TypeConstructor (Qualified (Just mn) _)) = Just mn
@@ -51,37 +77,58 @@ entails moduleName context = solve
5177 ctorModules (TypeApp ty _) = ctorModules ty
5278 ctorModules _ = Nothing
5379
54− findDicts :: Qualified (ProperName 'ClassName) -> Maybe ModuleName -> [TypeClassDictionaryInScope]
55− findDicts cn = maybe [] M.elems . (>>= M.lookup cn) . flip M.lookup context
80+ findDicts :: Context -> Qualified (ProperName 'ClassName) -> Maybe ModuleName -> [TypeClassDictionaryInScope]
81+ findDicts ctx cn = maybe [] M.elems . (>>= M.lookup cn) . flip M.lookup ctx
5682
57− solve :: Constraint -> m Expr
83+ solve :: Constraint -> StateT Context m (Expr, [(Ident, Constraint)])
5884 solve (className, tys) = do
59− dict <- go 0 className tys
60− return $ dictionaryValueToValue dict
85+ (dict, unsolved) <- go 0 className tys
86+ return (dictionaryValueToValue dict, unsolved)
6187 where
62− go :: Int -> Qualified (ProperName 'ClassName) -> [Type] -> m DictionaryValue
88+ go :: Int -> Qualified (ProperName 'ClassName) -> [Type] -> StateT Context m (DictionaryValue, [(Ident, Constraint)])
6389 go work className' tys' | work > 1000 = throwError . errorMessage $ PossiblyInfiniteInstance className' tys'
6490 go work className' tys' = do
91+ -- Get the inferred constraint context so far, and merge it with the global context
92+ inferred <- get
6593 let instances = do
66− tcd <- forClassName className' tys'
94+ tcd <- forClassName (combineContexts context inferred) className' tys'
6795 -- Make sure the type unifies with the type in the type instance definition
6896 subst <- maybeToList . (>>= verifySubstitution) . fmap concat $ zipWithM (typeHeadsAreEqual moduleName) tys' (tcdInstanceTypes tcd)
6997 return (subst, tcd)
70− (subst, tcd) <- unique instances
71− -- Solve any necessary subgoals
72− args <- solveSubgoals subst (tcdDependencies tcd)
73− return $ foldr (\(superclassName, index) dict -> SubclassDictionaryValue dict superclassName index)
74− (mkDictionary (tcdName tcd) args)
75− (tcdPath tcd)
98+ solution <- lift $ unique instances
99+ case solution of
100+ Left (subst, tcd) -> do
101+ -- Solve any necessary subgoals
102+ (args, unsolved) <- solveSubgoals subst (tcdDependencies tcd)
103+ let match = foldr (\(superclassName, index) dict -> SubclassDictionaryValue dict superclassName index)
104+ (mkDictionary (tcdName tcd) args)
105+ (tcdPath tcd)
106+ return (match, unsolved)
107+ Right unsolved@(unsolvedClassName@(Qualified _ pn), unsolvedTys) -> do
108+ -- Generate a fresh name for the unsolved constraint's new dictionary
109+ ident <- freshIdent ("dict" ++ runProperName pn)
110+ let qident = Qualified Nothing ident
111+ -- Store the new dictionary in the Context so that we can solve this goal in
112+ -- future.
113+ let newDict = TypeClassDictionaryInScope qident [] unsolvedClassName unsolvedTys Nothing
114+ newContext = M.singleton Nothing (M.singleton unsolvedClassName (M.singleton qident newDict))
115+ modify (combineContexts newContext)
116+ return (LocalDictionaryValue qident, [(ident, unsolved)])
76117 where
77118
78− unique :: [(a, TypeClassDictionaryInScope)] -> m (a, TypeClassDictionaryInScope)
79− unique [] = throwError . errorMessage $ NoInstanceFound className' tys'
80− unique [a] = return a
119+ unique :: [(a, TypeClassDictionaryInScope)] -> m (Either (a, TypeClassDictionaryInScope) Constraint)
120+ unique [] | shouldGeneralize && all canBeGeneralized tys' = return $ Right (className, tys)
121+ | otherwise = throwError . errorMessage $ NoInstanceFound className' tys'
122+ unique [a] = return $ Left a
81123 unique tcds | pairwise overlapping (map snd tcds) = do
82124 tell . errorMessage $ OverlappingInstances className' tys' (map (tcdName . snd) tcds)
83− return (head tcds)
84− | otherwise = return (minimumBy (compare `on` length . tcdPath . snd) tcds)
125+ return $ Left (head tcds)
126+ | otherwise = return $ Left (minimumBy (compare `on` length . tcdPath . snd) tcds)
127+
128+ canBeGeneralized :: Type -> Bool
129+ canBeGeneralized TUnknown{} = True
130+ canBeGeneralized Skolem{} = True
131+ canBeGeneralized _ = False
85132
86133 -- |
87134 -- Check if two dictionaries are overlapping
@@ -98,11 +145,12 @@ entails moduleName context = solve
98145 -- Create dictionaries for subgoals which still need to be solved by calling go recursively
99146 -- E.g. the goal (Show a, Show b) => Show (Either a b) can be satisfied if the current type
100147 -- unifies with Either a b, and we can satisfy the subgoals Show a and Show b recursively.
101− solveSubgoals :: [(String, Type)] -> Maybe [Constraint] -> m (Maybe [DictionaryValue])
102− solveSubgoals _ Nothing = return Nothing
148+ solveSubgoals :: [(String, Type)] -> Maybe [Constraint] -> StateT Context m (Maybe [DictionaryValue], [(Ident, Constraint)])
149+ solveSubgoals _ Nothing = return (Nothing, [])
103150 solveSubgoals subst (Just subgoals) = do
104− dict <- traverse (uncurry (go (work + 1)) . second (map (replaceAllTypeVars subst))) subgoals
105− return $ Just dict
151+ zipped <- traverse (uncurry (go (work + 1)) . second (map (replaceAllTypeVars subst))) subgoals
152+ let (dicts, unsolved) = unzip zipped
153+ return (Just dicts, concat unsolved)
106154
107155 -- Make a dictionary from subgoal dictionaries by applying the correct function
108156 mkDictionary :: Qualified Ident -> Maybe [DictionaryValue] -> DictionaryValue
@@ -134,6 +182,7 @@ entails moduleName context = solve
134182-- and return a substitution from type variables to types which makes the type heads unify.
135183--
136184typeHeadsAreEqual :: ModuleName -> Type -> Type -> Maybe [(String, Type)]
185+typeHeadsAreEqual _ (TUnknown u1) (TUnknown u2) | u1 == u2 = Just []
137186typeHeadsAreEqual _ (Skolem _ s1 _ _) (Skolem _ s2 _ _) | s1 == s2 = Just []
138187typeHeadsAreEqual _ t (TypeVar v) = Just [(v, t)]
139188typeHeadsAreEqual _ (TypeConstructor c1) (TypeConstructor c2) | c1 == c2 = Just []