git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Update orphan instance check to use covering sets
commit26b73d679aaa2ec72dfc8aa118cc4b2b4993fd50 authorLiam Goodacre <goodacre.liam@gmail.com> dateFri 30 Dec 2016, 18:18 (10 years ago) parent914481c
14 files changed+125−23
examples/failing/OrphanInstanceFunDepCycle.purs+5 examples/failing/OrphanInstanceFunDepCycle/Lib.purs+4 examples/failing/OrphanInstanceNullary.purs+4 examples/failing/OrphanInstanceNullary/Lib.purs+2 examples/failing/OrphanInstanceWithDetermined.purs+5 examples/failing/OrphanInstanceWithDetermined/Lib.purs+5 examples/passing/NonOrphanInstanceFunDepExtra.purs+8 examples/passing/NonOrphanInstanceFunDepExtra/Lib.purs+4 examples/passing/NonOrphanInstanceMulti.purs+7 examples/passing/NonOrphanInstanceMulti/Lib.purs+4 purescript.cabal+5 src/Language/PureScript/Environment.hs+42−9 src/Language/PureScript/Errors.hs+1−1 src/Language/PureScript/TypeChecker.hs+29−13
examples/failing/OrphanInstanceFunDepCycle.pursnew file+5
@@ -0,0 +1,5 @@
1+-- @shouldFailWith OrphanInstance
2+module Main where
3+import Lib
4+data L
5+instance clr :: C L R
examples/failing/OrphanInstanceFunDepCycle/Lib.pursnew file+4
@@ -0,0 +1,4 @@
1+module Lib where
2+-- covering sets: {{l}, {r}}
3+class C l r | l -> r, r -> l
4+data R
examples/failing/OrphanInstanceNullary.pursnew file+4
@@ -0,0 +1,4 @@
1+-- @shouldFailWith OrphanInstance
2+module Test where
3+import Lib
4+instance c :: C
examples/failing/OrphanInstanceNullary/Lib.pursnew file+2
@@ -0,0 +1,2 @@
1+module Lib where
2+class C
examples/failing/OrphanInstanceWithDetermined.pursnew file+5
@@ -0,0 +1,5 @@
1+-- @shouldFailWith OrphanInstance
2+module Main where
3+import Lib
4+data R
5+instance cflr :: C F L R
examples/failing/OrphanInstanceWithDetermined/Lib.pursnew file+5
@@ -0,0 +1,5 @@
1+module Lib where
2+-- covering sets: {{f, l}}
3+class C f l r | l -> r
4+data F
5+data L
examples/passing/NonOrphanInstanceFunDepExtra.pursnew file+8
@@ -0,0 +1,8 @@
1+-- Both f and l must be known, thus can be in separate modules
2+module Main where
3+import Control.Monad.Eff.Console (log)
4+import Lib
5+data F
6+data R
7+instance cflr :: C F L R
8+main = log "Done"
examples/passing/NonOrphanInstanceFunDepExtra/Lib.pursnew file+4
@@ -0,0 +1,4 @@
1+module Lib where
2+-- covering sets: {{f, l}}
3+class C f l r | l -> r
4+data L
examples/passing/NonOrphanInstanceMulti.pursnew file+7
@@ -0,0 +1,7 @@
1+-- Both l and r must be known, thus can be in separate modules
2+module Main where
3+import Control.Monad.Eff.Console (log)
4+import Lib
5+data L
6+instance clr :: C L R
7+main = log "Done"
examples/passing/NonOrphanInstanceMulti/Lib.pursnew file+4
@@ -0,0 +1,4 @@
1+module Lib where
2+-- covering sets: {{l, r}}
3+class C l r
4+data R
purescript.cabal+5
@@ -40,6 +40,8 @@ extra-source-files: examples/passing/*.purs
4040 , examples/passing/ModuleExportQualified/*.purs
4141 , examples/passing/ModuleExportSelf/*.purs
4242 , examples/passing/NonConflictingExports/*.purs
43+ , examples/passing/NonOrphanInstanceMulti/*.purs
44+ , examples/passing/NonOrphanInstanceFunDepExtra/*.purs
4345 , examples/passing/OperatorAliasElsewhere/*.purs
4446 , examples/passing/Operators/*.purs
4547 , examples/passing/PendingConflictingImports/*.purs
@@ -80,6 +82,9 @@ extra-source-files: examples/passing/*.purs
8082 , examples/failing/ImportModule/*.purs
8183 , examples/failing/InstanceExport/*.purs
8284 , examples/failing/OrphanInstance/*.purs
85+ , examples/failing/OrphanInstanceFunDepCycle/*.purs
86+ , examples/failing/OrphanInstanceWithDetermined/*.purs
87+ , examples/failing/OrphanInstanceNullary/*.purs
8388 , examples/warning/*.purs
8489 , examples/warning/*.js
8590 , examples/warning/UnusedExplicitImportTypeOp/*.purs
src/Language/PureScript/Environment.hs+42−9
@@ -8,11 +8,13 @@ import Data.Aeson.TH
88import qualified Data.Aeson as A
99import qualified Data.Map as M
1010import qualified Data.Set as S
11−import Data.Maybe (fromMaybe)
11+import Data.Maybe (fromMaybe, mapMaybe)
1212import Data.Text (Text)
1313import qualified Data.Text as T
1414import Data.List (nub)
15+import Data.Tree (Tree, rootLabel)
1516import qualified Data.Graph as G
17+import Data.Foldable (toList)
1618
1719import Language.PureScript.Crash
1820import Language.PureScript.Kinds
@@ -57,6 +59,8 @@ data TypeClassData = TypeClassData
5759 -- ^ A set of indexes of type argument that are fully determined by other
5860 -- arguments via functional dependencies. This can be computed from both
5961 -- typeClassArguments and typeClassDependencies.
62+ , typeClassCoveringSets :: S.Set (S.Set Int)
63+ -- ^ A sets of arguments that can be used to infer all other arguments.
6064 } deriving Show
6165
6266-- | A functional dependency indicates a relationship between two sets of
@@ -75,8 +79,11 @@ initEnvironment :: Environment
7579initEnvironment = Environment M.empty primTypes M.empty M.empty M.empty primClasses primKinds
7680
7781-- |
78−-- A constructor for TypeClassData that computes which type class arguments are fully determined.
82+-- A constructor for TypeClassData that computes which type class arguments are fully determined
83+-- and argument covering sets.
7984-- Fully determined means that this argument cannot be used when selecting a type class instance.
85+-- A covering set is a minimal collection of arguments that can be used to find an instance and
86+-- therefore determine all other type arguments.
8087--
8188-- An example of the difference between determined and fully determined would be with the class:
8289-- ```class C a b c | a -> b, b -> a, b -> c```
@@ -84,7 +91,8 @@ initEnvironment = Environment M.empty primTypes M.empty M.empty M.empty primClas
8491-- Both `a` and `b` can be used in selecting a type class instance. However, `c` cannot - it is
8592-- fully determined by `a` and `b`.
8693--
87−-- Define a graph of type class arguments with edges being fundep determiners to determined.
94+-- Define a graph of type class arguments with edges being fundep determiners to determined. Each
95+-- argument also has a self looping edge.
8896-- An argument is fully determined if doesn't appear at the start of a path of strongly connected components.
8997-- An argument is not fully determined otherwise.
9098--
@@ -97,26 +105,51 @@ makeTypeClassData
97105 -> [Constraint]
98106 -> [FunctionalDependency]
99107 -> TypeClassData
100−makeTypeClassData args m s deps = TypeClassData args m s deps determinedArgs
108+makeTypeClassData args m s deps = TypeClassData args m s deps determinedArgs coveringSets
101109 where
110+ argumentIndicies = [0 .. length args - 1]
111+
112+ -- each argument determines themselves
113+ identities = (\i -> (i, [i])) <$> argumentIndicies
114+
102115 -- list all the edges in the graph: for each fundep an edge exists for each determiner to each determined
103− contributingDeps = M.fromListWith (++) $ do
116+ contributingDeps = M.fromListWith (++) $ identities ++ do
104117 fd <- deps
105118 src <- fdDeterminers fd
106119 (src, fdDetermined fd) : map (, []) (fdDetermined fd)
107120
108− -- here we build a graph of which arguments determine other arguments
109− (depGraph, _, fromKey) = G.graphFromEdges ((\(n, v) -> (n, n, nub v)) <$> M.toList contributingDeps)
121+ -- build a graph of which arguments determine other arguments
122+ (depGraph, fromVertex, fromKey) = G.graphFromEdges ((\(n, v) -> (n, n, nub v)) <$> M.toList contributingDeps)
110123
111124 -- do there exist any arguments that contribute to `arg` that `arg` doesn't contribute to
125+ isFunDepDetermined :: Int -> Bool
112126 isFunDepDetermined arg = case fromKey arg of
113− Nothing -> False -- not mentioned in fundeps
127+ Nothing -> internalError "Unknown argument index in makeTypeClassData"
114128 Just v -> let contributesToVar = G.reachable (G.transposeG depGraph) v
115129 varContributesTo = G.reachable depGraph v
116130 in any (\r -> not (r `elem` varContributesTo)) contributesToVar
117131
118132 -- find all the arguments that are determined
119− determinedArgs = S.fromList $ filter isFunDepDetermined [0 .. length args - 1]
133+ determinedArgs :: S.Set Int
134+ determinedArgs = S.fromList $ filter isFunDepDetermined argumentIndicies
135+
136+ argFromVertex :: G.Vertex -> Int
137+ argFromVertex index = let (_, arg, _) = fromVertex index in arg
138+
139+ isVertexDetermined :: G.Vertex -> Bool
140+ isVertexDetermined = isFunDepDetermined . argFromVertex
141+
142+ -- from an scc find the non-determined args
143+ sccNonDetermined :: Tree G.Vertex -> Maybe [Int]
144+ sccNonDetermined tree
145+ -- if any arg in an scc is determined then all of them are
146+ | isVertexDetermined (rootLabel tree) = Nothing
147+ | otherwise = Just (argFromVertex <$> toList tree)
148+
149+ -- find the covering sets
150+ coveringSets :: S.Set (S.Set Int)
151+ coveringSets = let funDepSets = sequence (mapMaybe sccNonDetermined (G.scc depGraph))
152+ in S.fromList (S.fromList <$> funDepSets)
120153
121154-- |
122155-- The visibility of a name in scope
src/Language/PureScript/Errors.hs+1−1
@@ -716,7 +716,7 @@ prettyPrintSingleError (PPEOptions codeColor full level showDocs) e = flip evalS
716716 , Box.vcat Box.left (map typeAtomAsBox ts)
717717 ]
718718 , line "is an orphan instance."
719− , line "An orphan instance is an instance which is defined in neither the class module nor the data type module."
719+ , line "An orphan instance is one which is defined in a module that is unrelated to either the class or the collection of data types that the instance is defined for."
720720 , line "Consider moving the instance, if possible, or using a newtype wrapper."
721721 ]
722722 renderSimpleErrorMessage (InvalidNewtype name) =
src/Language/PureScript/TypeChecker.hs+29−13
@@ -18,7 +18,7 @@ import Control.Monad.Supply.Class (MonadSupply)
1818import Control.Monad.Writer.Class (MonadWriter(..))
1919import Control.Lens ((^..), _1, _2)
2020
21−import Data.Foldable (for_, traverse_)
21+import Data.Foldable (for_, traverse_, toList)
2222import Data.List (nub, nubBy, (\\), sort, group)
2323import Data.Maybe
2424import qualified Data.Map as M
@@ -298,7 +298,7 @@ typeCheckAll moduleName _ = traverse go
298298 Nothing -> internalError "typeCheckAll: Encountered unknown type class in instance declaration"
299299 Just typeClass -> do
300300 sequence_ (zipWith (checkTypeClassInstance typeClass) [0..] tys)
301− checkOrphanInstance dictName className tys
301+ checkOrphanInstance dictName className typeClass tys
302302 _ <- traverseTypeInstanceBody checkInstanceMembers body
303303 let dict = TypeClassDictionaryInScope (Qualified (Just moduleName) dictName) [] className tys (Just deps)
304304 addTypeClassDictionaries (Just moduleName) . M.singleton className $ M.singleton (tcdValue dict) dict
@@ -324,19 +324,35 @@ typeCheckAll moduleName _ = traverse go
324324 | otherwise = firstDuplicate xs
325325 firstDuplicate _ = Nothing
326326
327− checkOrphanInstance :: Ident -> Qualified (ProperName 'ClassName) -> [Type] -> m ()
328− checkOrphanInstance dictName className@(Qualified (Just mn') _) tys'
329− | moduleName == mn' || any checkType tys' = return ()
327+ checkOrphanInstance :: Ident -> Qualified (ProperName 'ClassName) -> TypeClassData -> [Type] -> m ()
328+ checkOrphanInstance dictName className@(Qualified (Just mn') _) typeClass tys'
329+ | moduleName == mn' || moduleName `S.member` nonOrphanModules = return ()
330330 | otherwise = throwError . errorMessage $ OrphanInstance dictName className tys'
331331 where
332− checkType :: Type -> Bool
333− checkType (TypeVar _) = False
334− checkType (TypeLevelString _) = False
335− checkType (TypeConstructor (Qualified (Just mn'') _)) = moduleName == mn''
336− checkType (TypeConstructor (Qualified Nothing _)) = internalError "Unqualified type name in checkOrphanInstance"
337− checkType (TypeApp t1 _) = checkType t1
338− checkType _ = internalError "Invalid type in instance in checkOrphanInstance"
339− checkOrphanInstance _ _ _ = internalError "Unqualified class name in checkOrphanInstance"
332+ typeModule :: Type -> Maybe ModuleName
333+ typeModule (TypeVar _) = Nothing
334+ typeModule (TypeLevelString _) = Nothing
335+ typeModule (TypeConstructor (Qualified (Just mn'') _)) = Just mn''
336+ typeModule (TypeConstructor (Qualified Nothing _)) = internalError "Unqualified type name in checkOrphanInstance"
337+ typeModule (TypeApp t1 _) = typeModule t1
338+ typeModule _ = internalError "Invalid type in instance in checkOrphanInstance"
339+
340+ modulesByTypeIndex :: M.Map Int (Maybe ModuleName)
341+ modulesByTypeIndex = M.fromList (zip [0 ..] (typeModule <$> tys'))
342+
343+ lookupModule :: Int -> S.Set ModuleName
344+ lookupModule idx = case M.lookup idx modulesByTypeIndex of
345+ Just ms -> S.fromList (toList ms)
346+ Nothing -> internalError "Unknown type index in checkOrphanInstance"
347+
348+ -- If the instance is declared in a module that wouldn't be found based on a covering set
349+ -- then it is considered an orphan - because we'd have a situation in which we expect an
350+ -- instance but can't find it. So a valid module must be applicable across *all* covering
351+ -- sets - therefore we take the intersection of covering set modules.
352+ nonOrphanModules :: S.Set ModuleName
353+ nonOrphanModules = foldl1 S.intersection (foldMap lookupModule `S.map` typeClassCoveringSets typeClass)
354+
355+ checkOrphanInstance _ _ _ _ = internalError "Unqualified class name in checkOrphanInstance"
340356
341357 -- |
342358 -- This function adds the argument kinds for a type constructor so that they may appear in the externs file,