Skip to content

Commit 031e5c5

Browse files
committed
Merge pull request #1406 from purescript/421
Fix #421, match type instance heads eagerly
2 parents 1c78f66 + e7e30d1 commit 031e5c5

File tree

12 files changed

+100
-157
lines changed

12 files changed

+100
-157
lines changed

examples/failing/438.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- @shouldFailWith NoInstanceFound
1+
-- @shouldFailWith PossiblyInfiniteInstance
22

33
-- See issue 438 for details: this test is mainly here to test that code like
44
-- this doesn't cause the compiler to loop.

examples/passing/Superclasses2.purs renamed to examples/failing/Superclasses5.purs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
-- @shouldFailWith NoInstanceFound
2+
13
module Main where
24

35
import Prelude

src/Language/PureScript/AST/Declarations.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ data Expr
386386
-- at superclass implementations when searching for a dictionary, the type class name and
387387
-- instance type, and the type class dictionaries in scope.
388388
--
389-
| TypeClassDictionary Bool Constraint (M.Map (Maybe ModuleName) (M.Map (Qualified ProperName) (M.Map (Qualified Ident) TypeClassDictionaryInScope)))
389+
| TypeClassDictionary Constraint (M.Map (Maybe ModuleName) (M.Map (Qualified ProperName) (M.Map (Qualified Ident) TypeClassDictionaryInScope)))
390390
-- |
391391
-- A typeclass dictionary accessor, the implementation is left unspecified until CoreFn desugaring.
392392
--

src/Language/PureScript/AST/Traversals.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ accumTypes f = everythingOnValues mappend forDecls forValues (const mempty) (con
396396
forDecls (TypeDeclaration _ ty) = f ty
397397
forDecls _ = mempty
398398

399-
forValues (TypeClassDictionary _ (_, cs) _) = mconcat (map f cs)
399+
forValues (TypeClassDictionary (_, cs) _) = mconcat (map f cs)
400400
forValues (SuperClassDictionary _ tys) = mconcat (map f tys)
401401
forValues (TypedValue _ _ ty) = f ty
402402
forValues _ = mempty

src/Language/PureScript/Docs/ParseAndDesugar.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Control.Monad
1111
import Control.Applicative
1212

1313
import Control.Monad.Trans.Except
14-
import Control.Monad.Writer.Strict (WriterT(), runWriterT)
14+
import Control.Monad.Writer.Strict (runWriterT)
1515
import Control.Monad.Error.Class (MonadError(..))
1616
import Control.Monad.IO.Class (MonadIO(..))
1717

src/Language/PureScript/Errors.hs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import Language.PureScript.Pretty
4040
import Language.PureScript.Types
4141
import Language.PureScript.Names
4242
import Language.PureScript.Kinds
43-
import Language.PureScript.TypeClassDictionaries
4443

4544
import qualified Text.PrettyPrint.Boxes as Box
4645

@@ -108,8 +107,9 @@ data SimpleErrorMessage
108107
| TypesDoNotUnify Type Type
109108
| KindsDoNotUnify Kind Kind
110109
| ConstrainedTypeUnified Type Type
111-
| OverlappingInstances (Qualified ProperName) [Type] [DictionaryValue]
110+
| OverlappingInstances (Qualified ProperName) [Type] [Qualified Ident]
112111
| NoInstanceFound (Qualified ProperName) [Type]
112+
| PossiblyInfiniteInstance (Qualified ProperName) [Type]
113113
| CannotDerive (Qualified ProperName) [Type]
114114
| CannotFindDerivingType ProperName
115115
| DuplicateLabel String (Maybe Expr)
@@ -233,6 +233,7 @@ errorCode em = case unwrapErrorMessage em of
233233
ConstrainedTypeUnified{} -> "ConstrainedTypeUnified"
234234
OverlappingInstances{} -> "OverlappingInstances"
235235
NoInstanceFound{} -> "NoInstanceFound"
236+
PossiblyInfiniteInstance{} -> "PossiblyInfiniteInstance"
236237
CannotDerive{} -> "CannotDerive"
237238
CannotFindDerivingType{} -> "CannotFindDerivingType"
238239
DuplicateLabel{} -> "DuplicateLabel"
@@ -544,10 +545,12 @@ prettyPrintSingleError full level e = prettyPrintErrorMessage <$> onTypesInError
544545
]
545546
goSimple (OverlappingInstances nm ts ds) =
546547
paras [ line $ "Overlapping instances found for " ++ show nm ++ " " ++ unwords (map prettyPrintTypeAtom ts) ++ ":"
547-
, paras $ map prettyPrintDictionaryValue ds
548+
, line $ intercalate ", " (map show ds)
548549
]
549550
goSimple (NoInstanceFound nm ts) =
550551
line $ "No instance found for " ++ show nm ++ " " ++ unwords (map prettyPrintTypeAtom ts)
552+
goSimple (PossiblyInfiniteInstance nm ts) =
553+
line $ "Instance for " ++ show nm ++ " " ++ unwords (map prettyPrintTypeAtom ts) ++ " is possibly infinite."
551554
goSimple (CannotDerive nm ts) =
552555
line $ "Cannot derive " ++ show nm ++ " instance for " ++ unwords (map prettyPrintTypeAtom ts)
553556
goSimple (CannotFindDerivingType nm) =
@@ -736,19 +739,6 @@ prettyPrintSingleError full level e = prettyPrintErrorMessage <$> onTypesInError
736739
indent :: Box.Box -> Box.Box
737740
indent = Box.moveRight 2
738741

739-
-- |
740-
-- Render a DictionaryValue fit for human consumption in error messages
741-
--
742-
prettyPrintDictionaryValue :: DictionaryValue -> Box.Box
743-
prettyPrintDictionaryValue (LocalDictionaryValue _) = line "Dictionary in scope"
744-
prettyPrintDictionaryValue (GlobalDictionaryValue nm) = line (show nm)
745-
prettyPrintDictionaryValue (DependentDictionaryValue nm args) = paras [ line $ (show nm) ++ " via"
746-
, indent $ paras $ map prettyPrintDictionaryValue args
747-
]
748-
prettyPrintDictionaryValue (SubclassDictionaryValue sup nm _) = paras [ line $ (show nm) ++ " via superclass"
749-
, indent $ prettyPrintDictionaryValue sup
750-
]
751-
752742
-- |
753743
-- Pretty print and export declaration
754744
--

src/Language/PureScript/Pretty/Values.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ literals = mkPattern' match
7474
]
7575
match (OperatorSection op (Right val)) = return $ "(" ++ prettyPrintValue op ++ " " ++ prettyPrintValue val ++ ")"
7676
match (OperatorSection op (Left val)) = return $ "(" ++ prettyPrintValue val ++ " " ++ prettyPrintValue op ++ ")"
77-
match (TypeClassDictionary _ (name, tys) _) = return $ "<<dict " ++ show name ++ " " ++ unwords (map prettyPrintTypeAtom tys) ++ ">>"
77+
match (TypeClassDictionary (name, tys) _) = return $ "<<dict " ++ show name ++ " " ++ unwords (map prettyPrintTypeAtom tys) ++ ">>"
7878
match (SuperClassDictionary name _) = return $ "<<superclass dict " ++ show name ++ ">>"
7979
match (TypedValue _ val _) = prettyPrintValue' val
8080
match (PositionedValue _ _ val) = prettyPrintValue' val

src/Language/PureScript/TypeChecker.hs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ checkTypeSynonyms = void . replaceAllTypeSynonyms
130130
-- * Process module imports
131131
--
132132
typeCheckAll :: Maybe ModuleName -> ModuleName -> [DeclarationRef] -> [Declaration] -> Check [Declaration]
133-
typeCheckAll mainModuleName moduleName exps ds = mapM go ds <* mapM_ checkOrphanFixities ds
133+
typeCheckAll mainModuleName moduleName _ ds = mapM go ds <* mapM_ checkOrphanFixities ds
134134
where
135135
go :: Declaration -> Check Declaration
136136
go (DataDeclaration dtype name args dctors) = do
@@ -236,16 +236,16 @@ typeCheckAll mainModuleName moduleName exps ds = mapM go ds <* mapM_ checkOrphan
236236
mapM_ (checkTypeClassInstance moduleName) tys
237237
forM_ deps $ mapM_ (checkTypeClassInstance moduleName) . snd
238238
checkOrphanInstance moduleName className tys
239-
let dict = TypeClassDictionaryInScope (Qualified (Just moduleName) dictName) className tys (Just deps) TCDRegular isInstanceExported
239+
let dict = TypeClassDictionaryInScope (Qualified (Just moduleName) dictName) [] className tys (Just deps) TCDRegular
240240
addTypeClassDictionaries (Just moduleName) . M.singleton className $ M.singleton (canonicalizeDictionary dict) dict
241241
return d
242242

243243
where
244244

245245
checkOrphanInstance :: ModuleName -> Qualified ProperName -> [Type] -> Check ()
246-
checkOrphanInstance mn (Qualified (Just mn') _) tys
247-
| mn == mn' || any checkType tys = return ()
248-
| otherwise = throwError . errorMessage $ OrphanInstance dictName className tys
246+
checkOrphanInstance mn (Qualified (Just mn') _) tys'
247+
| mn == mn' || any checkType tys' = return ()
248+
| otherwise = throwError . errorMessage $ OrphanInstance dictName className tys'
249249
where
250250
checkType :: Type -> Bool
251251
checkType (TypeVar _) = False
@@ -255,14 +255,6 @@ typeCheckAll mainModuleName moduleName exps ds = mapM go ds <* mapM_ checkOrphan
255255
checkType _ = error "Invalid type in instance in checkOrphanInstance"
256256
checkOrphanInstance _ _ _ = error "Unqualified class name in checkOrphanInstance"
257257

258-
isInstanceExported :: Bool
259-
isInstanceExported = any exportsInstance exps
260-
261-
exportsInstance :: DeclarationRef -> Bool
262-
exportsInstance (TypeInstanceRef name) | name == dictName = True
263-
exportsInstance (PositionedDeclarationRef _ _ r) = exportsInstance r
264-
exportsInstance _ = False
265-
266258
-- |
267259
-- This function adds the argument kinds for a type constructor so that they may appear in the externs file,
268260
-- extracted from the kind of the type constructor itself.

src/Language/PureScript/TypeChecker/Entailment.hs

Lines changed: 48 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,11 @@ import Language.PureScript.TypeClassDictionaries
4141
import Language.PureScript.Types
4242
import qualified Language.PureScript.Constants as C
4343

44-
newtype Work = Work Integer deriving (Show, Eq, Ord, Num)
45-
4644
-- |
4745
-- Check that the current set of type class dictionaries entail the specified type class goal, and, if so,
4846
-- return a type class dictionary reference.
4947
--
50-
entails :: Environment -> ModuleName -> M.Map (Maybe ModuleName) (M.Map (Qualified ProperName) (M.Map (Qualified Ident) TypeClassDictionaryInScope)) -> Constraint -> Bool -> Check Expr
48+
entails :: Environment -> ModuleName -> M.Map (Maybe ModuleName) (M.Map (Qualified ProperName) (M.Map (Qualified Ident) TypeClassDictionaryInScope)) -> Constraint -> Check Expr
5149
entails env moduleName context = solve
5250
where
5351
forClassName :: Qualified ProperName -> [TypeClassDictionaryInScope]
@@ -56,56 +54,57 @@ entails env moduleName context = solve
5654
findDicts :: Qualified ProperName -> Maybe ModuleName -> [TypeClassDictionaryInScope]
5755
findDicts cn = maybe [] M.elems . (>>= M.lookup cn) . flip M.lookup context
5856

59-
solve :: Constraint -> Bool -> Check Expr
60-
solve (className, tys) trySuperclasses = do
61-
let dicts = flip evalStateT (Work 0) $ go trySuperclasses className tys
62-
checkOverlaps dicts
57+
solve :: Constraint -> Check Expr
58+
solve (className, tys) = do
59+
dict <- go 0 className tys
60+
return $ dictionaryValueToValue dict
6361
where
64-
go :: Bool -> Qualified ProperName -> [Type] -> StateT Work [] DictionaryValue
65-
go trySuperclasses' className' tys' = do
66-
workDone <- get
67-
guard $ workDone < 1000
68-
modify (1 +)
69-
directInstances <|> superclassInstances
62+
go :: Int -> Qualified ProperName -> [Type] -> Check DictionaryValue
63+
go work className' tys' | work > 1000 = throwError . errorMessage $ PossiblyInfiniteInstance className' tys'
64+
go work className' tys' = do
65+
let instances = do
66+
tcd <- forClassName className'
67+
-- Make sure the type unifies with the type in the type instance definition
68+
subst <- maybeToList . (>>= verifySubstitution) . fmap concat $ zipWithM (typeHeadsAreEqual moduleName env) tys' (tcdInstanceTypes tcd)
69+
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 (canonicalizeDictionary tcd) args)
75+
(tcdPath tcd)
7076
where
71-
directInstances :: StateT Work [] DictionaryValue
72-
directInstances = do
73-
tcd <- lift $ forClassName className'
74-
-- Make sure the type unifies with the type in the type instance definition
75-
subst <- lift . maybeToList . (>>= verifySubstitution) . fmap concat $ zipWithM (typeHeadsAreEqual moduleName env) tys' (tcdInstanceTypes tcd)
76-
-- Solve any necessary subgoals
77-
args <- solveSubgoals subst (tcdDependencies tcd)
78-
return $ mkDictionary (canonicalizeDictionary tcd) args
77+
78+
unique :: [(a, TypeClassDictionaryInScope)] -> Check (a, TypeClassDictionaryInScope)
79+
unique [] = throwError . errorMessage $ NoInstanceFound className' tys'
80+
unique [a] = return a
81+
unique tcds | pairwise overlapping (map snd tcds) = throwError . errorMessage $ OverlappingInstances className' tys' (map (tcdName . snd) tcds)
82+
| otherwise = return (minimumBy (compare `on` length . tcdPath . snd) tcds)
7983

80-
superclassInstances :: StateT Work [] DictionaryValue
81-
superclassInstances = do
82-
guard trySuperclasses'
83-
(subclassName, (args, _, implies)) <- lift $ M.toList (typeClasses env)
84-
-- Try each superclass
85-
(index, (superclass, suTyArgs)) <- lift $ zip [0..] implies
86-
-- Make sure the type class name matches the superclass name
87-
guard $ className' == superclass
88-
-- Make sure the types unify with the types in the superclass implication
89-
subst <- lift . maybeToList . (>>= verifySubstitution) . fmap concat $ zipWithM (typeHeadsAreEqual moduleName env) tys' suTyArgs
90-
-- Finally, satisfy the subclass constraint
91-
args' <- lift . maybeToList $ mapM ((`lookup` subst) . fst) args
92-
suDict <- go True subclassName args'
93-
return $ SubclassDictionaryValue suDict superclass index
84+
-- |
85+
-- Check if two dictionaries are overlapping
86+
--
87+
-- Dictionaries which are subclass dictionaries cannot overlap, since otherwise the overlap would have
88+
-- been caught when constructing superclass dictionaries.
89+
overlapping :: TypeClassDictionaryInScope -> TypeClassDictionaryInScope -> Bool
90+
overlapping TypeClassDictionaryInScope{ tcdPath = _ : _ } _ = False
91+
overlapping _ TypeClassDictionaryInScope{ tcdPath = _ : _ } = False
92+
overlapping tcd1 tcd2 = tcdName tcd1 /= tcdName tcd2
9493

95-
-- Create dictionaries for subgoals which still need to be solved by calling go recursively
96-
-- E.g. the goal (Show a, Show b) => Show (Either a b) can be satisfied if the current type
97-
-- unifies with Either a b, and we can satisfy the subgoals Show a and Show b recursively.
98-
solveSubgoals :: [(String, Type)] -> Maybe [Constraint] -> StateT Work [] (Maybe [DictionaryValue])
99-
solveSubgoals _ Nothing = return Nothing
100-
solveSubgoals subst (Just subgoals) = do
101-
dict <- mapM (uncurry (go True) . second (map (replaceAllTypeVars subst))) subgoals
102-
return $ Just dict
94+
-- Create dictionaries for subgoals which still need to be solved by calling go recursively
95+
-- E.g. the goal (Show a, Show b) => Show (Either a b) can be satisfied if the current type
96+
-- unifies with Either a b, and we can satisfy the subgoals Show a and Show b recursively.
97+
solveSubgoals :: [(String, Type)] -> Maybe [Constraint] -> Check (Maybe [DictionaryValue])
98+
solveSubgoals _ Nothing = return Nothing
99+
solveSubgoals subst (Just subgoals) = do
100+
dict <- mapM (uncurry (go (work + 1)) . second (map (replaceAllTypeVars subst))) subgoals
101+
return $ Just dict
103102

104-
-- Make a dictionary from subgoal dictionaries by applying the correct function
105-
mkDictionary :: Qualified Ident -> Maybe [DictionaryValue] -> DictionaryValue
106-
mkDictionary fnName Nothing = LocalDictionaryValue fnName
107-
mkDictionary fnName (Just []) = GlobalDictionaryValue fnName
108-
mkDictionary fnName (Just dicts) = DependentDictionaryValue fnName dicts
103+
-- Make a dictionary from subgoal dictionaries by applying the correct function
104+
mkDictionary :: Qualified Ident -> Maybe [DictionaryValue] -> DictionaryValue
105+
mkDictionary fnName Nothing = LocalDictionaryValue fnName
106+
mkDictionary fnName (Just []) = GlobalDictionaryValue fnName
107+
mkDictionary fnName (Just dicts) = DependentDictionaryValue fnName dicts
109108

110109
-- Turn a DictionaryValue into a Expr
111110
dictionaryValueToValue :: DictionaryValue -> Expr
@@ -122,46 +121,7 @@ entails env moduleName context = solve
122121
let grps = groupBy ((==) `on` fst) subst
123122
guard (all (pairwise (unifiesWith env) . map snd) grps)
124123
return $ map head grps
125-
-- |
126-
-- Check for overlapping instances
127-
--
128-
checkOverlaps :: [DictionaryValue] -> Check Expr
129-
checkOverlaps dicts =
130-
case [ (d1, d2) | d1 <- dicts, d2 <- dicts, d1 `overlapping` d2 ] of
131-
ds@(_ : _) -> throwError . errorMessage $ OverlappingInstances className tys $ nub (map fst ds)
132-
_ -> case chooseSimplestDictionaries dicts of
133-
[] -> throwError . errorMessage $ NoInstanceFound className tys
134-
d : _ -> return $ dictionaryValueToValue d
135-
-- Choose the simplest DictionaryValues from a list of candidates
136-
-- The reason for this function is as follows:
137-
-- When considering overlapping instances, we don't want to consider the same dictionary
138-
-- to be an overlap of itself when obtained as a superclass of another class.
139-
-- Observing that we probably don't want to select a superclass instance when an instance
140-
-- is available directly, and that there is no way for a superclass instance to actually
141-
-- introduce an overlap that wouldn't have been there already, we simply remove dictionaries
142-
-- obtained as superclass instances if there are simpler instances available.
143-
chooseSimplestDictionaries :: [DictionaryValue] -> [DictionaryValue]
144-
chooseSimplestDictionaries ds = case filter isSimpleDictionaryValue ds of
145-
[] -> ds
146-
simple -> simple
147-
isSimpleDictionaryValue SubclassDictionaryValue{} = False
148-
isSimpleDictionaryValue (DependentDictionaryValue _ ds) = all isSimpleDictionaryValue ds
149-
isSimpleDictionaryValue _ = True
150-
-- |
151-
-- Check if two dictionaries are overlapping
152-
--
153-
-- Dictionaries which are subclass dictionaries cannot overlap, since otherwise the overlap would have
154-
-- been caught when constructing superclass dictionaries.
155-
--
156-
overlapping :: DictionaryValue -> DictionaryValue -> Bool
157-
overlapping (LocalDictionaryValue nm1) (LocalDictionaryValue nm2) | nm1 == nm2 = False
158-
overlapping (GlobalDictionaryValue nm1) (GlobalDictionaryValue nm2) | nm1 == nm2 = False
159-
overlapping (DependentDictionaryValue nm1 ds1) (DependentDictionaryValue nm2 ds2)
160-
| nm1 == nm2 = or $ zipWith overlapping ds1 ds2
161-
overlapping SubclassDictionaryValue{} _ = False
162-
overlapping _ SubclassDictionaryValue{} = False
163-
overlapping _ _ = True
164-
124+
165125
valUndefined :: Expr
166126
valUndefined = Var (Qualified (Just (ModuleName [ProperName C.prim])) (Ident C.undefined))
167127

src/Language/PureScript/TypeChecker/Subsumption.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ subsumes' val ty1 (KindedType ty2 _) =
6969
subsumes val ty1 ty2
7070
subsumes' (Just val) (ConstrainedType constraints ty1) ty2 = do
7171
dicts <- getTypeClassDictionaries
72-
subsumes' (Just $ foldl App val (map (flip (TypeClassDictionary True) dicts) constraints)) ty1 ty2
72+
subsumes' (Just $ foldl App val (map (flip TypeClassDictionary dicts) constraints)) ty1 ty2
7373
subsumes' val (TypeApp f1 r1) (TypeApp f2 r2) | f1 == tyObject && f2 == tyObject = do
7474
let
7575
(ts1, r1') = rowToList r1

0 commit comments

Comments
 (0)