1module Type.Unify (unify) where
3import Control.Applicative ((<|>))
4import Control.Monad.Except (ExceptT, throwError, runExceptT)
5import Control.Monad.State as State
6import qualified Data.List as List
7import qualified Data.Map as Map
8import qualified Data.UnionFind.IO as UF
9import Text.PrettyPrint (render)
11import qualified AST.Type as AstType
12import qualified AST.Variable as Var
13import qualified Reporting.Region as R
14import qualified Reporting.Error.Type as Error
15import qualified Type.State as TS
16import Type.Type as Type
17import Type.PrettyPrint
18import Elm.Utils ((|>))
26 -> StateT TS.SolverState IO ()
27unify hint region variable1 variable2 =
28 do result <- runExceptT (unifyHelp variable1 variable2)
33 Left (RawMismatch (leftType, rightType, note)) ->
34 TS.addError region $ Error.Mismatch $
35 Error.MismatchInfo hint leftType rightType note
38-- ACTUALLY UNIFY STUFF
41 ExceptT RawMismatch (StateT TS.SolverState IO)
45 RawMismatch (AstType.Canonical, AstType.Canonical, Maybe String)
49 :: UF.Point Descriptor
50 -> UF.Point Descriptor
53typeError leftType rightType note =
54 do leftSrcType <- liftIO (toSrcType leftType)
55 rightSrcType <- liftIO (toSrcType rightType)
56 throwError (RawMismatch (leftSrcType, rightSrcType, note))
59unifyHelp :: Variable -> Variable -> Unify ()
60unifyHelp variable1 variable2 =
61 do equivalent <- liftIO $ UF.equivalent variable1 variable2
64 else actuallyUnify variable1 variable2
67actuallyUnify :: Variable -> Variable -> Unify ()
68actuallyUnify variable1 variable2 = do
69 desc1 <- liftIO $ UF.descriptor variable1
70 desc2 <- liftIO $ UF.descriptor variable2
71 let (name', flex', rank', alias') = combinedDescriptors desc1 desc2
76 if rank desc1 < rank desc2
77 then UF.union variable2 variable1
78 else UF.union variable1 variable2
79 UF.modifyDescriptor variable1 $ \desc ->
80 desc { structure = structure desc1
89 if rank desc1 < rank desc2
90 then UF.union variable2 variable1
91 else UF.union variable1 variable2
92 UF.modifyDescriptor variable2 $ \desc ->
93 desc { structure = structure desc2
100 if rank desc1 < rank desc2 then merge1 else merge2
102 fresh :: Maybe (Term1 Variable) -> Unify Variable
104 do v <- liftIO . UF.fresh $ Descriptor
105 { structure = structure
116 do liftIO $ UF.modifyDescriptor v $ \desc -> desc { flex = Flexible }
117 unifyHelp variable1 variable2
119 unifyNumber svar (Var.Canonical home name) =
121 Var.BuiltIn | name `elem` ["Int","Float"] -> flexAndUnify svar
122 Var.Local | List.isPrefixOf "number" name -> flexAndUnify svar
124 typeError variable1 variable2 Nothing
126 comparableError maybeHint =
127 typeError variable1 variable2 $ Just $
128 "Note: comparable types include Int, Float, Char, String, lists, and tuples."
129 ++ maybe "" ("\n"++) maybeHint
132 typeError variable1 variable2 $ Just $
133 "Note: appendable types include only Strings, Lists, and Text."
135 unifyComparable v (Var.Canonical home name) =
137 Var.BuiltIn | name `elem` ["Int","Float","Char","String"] -> flexAndUnify v
138 Var.Local | List.isPrefixOf "comparable" name -> flexAndUnify v
139 _ -> comparableError Nothing
141 unifyComparableStructure varSuper varFlex =
142 do struct <- liftIO $ collectApps varFlex
145 comparableError Nothing
148 do flexAndUnify varSuper
149 unifyHelp v =<< liftIO (variable $ Is Comparable)
153 comparableError $ Just $
154 "Furthermore, it cannot be a tuple with more than 6 elements."
156 do flexAndUnify varSuper
157 cmpVars <- liftIO $ forM [1..length vs] $ \_ -> variable (Is Comparable)
158 zipWithM_ unifyHelp vs cmpVars
160 unifyAppendable varSuper varFlex =
161 do struct <- liftIO $ collectApps varFlex
163 List _ -> flexAndUnify varSuper
167 typeError variable1 variable2 $ Just $
168 rigidErrorMessage (render (pretty Never var))
171 case (flex desc1, flex desc2, name desc1, name desc2) of
172 (Is super1, Is super2, _, _)
173 | super1 == super2 -> merge
174 (Is Number, Is Comparable, _, _) -> merge1
175 (Is Comparable, Is Number, _, _) -> merge2
177 (Is Number, _, _, Just name) -> unifyNumber variable1 name
178 (_, Is Number, Just name, _) -> unifyNumber variable2 name
180 (Is Comparable, _, _, Just name) -> unifyComparable variable1 name
181 (_, Is Comparable, Just name, _) -> unifyComparable variable2 name
182 (Is Comparable, _, _, _) -> unifyComparableStructure variable1 variable2
183 (_, Is Comparable, _, _) -> unifyComparableStructure variable2 variable1
185 (Is Appendable, _, _, Just name)
186 | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable1
187 (_, Is Appendable, Just name, _)
188 | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable2
189 (Is Appendable, _, _, _) -> unifyAppendable variable1 variable2
190 (_, Is Appendable, _, _) -> unifyAppendable variable2 variable1
192 (Rigid, _, _, _) -> rigidError variable1
193 (_, Rigid, _, _) -> rigidError variable2
194 _ -> typeError variable1 variable2 Nothing
196 case (structure desc1, structure desc2) of
197 (Nothing, Nothing) | flex desc1 == Flexible && flex desc1 == Flexible ->
200 (Nothing, _) | flex desc1 == Flexible ->
203 (_, Nothing) | flex desc2 == Flexible ->
206 (Just (Var1 v), _) ->
207 unifyHelp v variable2
209 (_, Just (Var1 v)) ->
210 unifyHelp variable1 v
218 (Just type1, Just type2) ->
219 case (type1,type2) of
220 (App1 term1 term2, App1 term1' term2') ->
222 unifyHelp term1 term1'
223 unifyHelp term2 term2'
224 (Fun1 term1 term2, Fun1 term1' term2') ->
226 unifyHelp term1 term1'
227 unifyHelp term2 term2'
229 (EmptyRecord1, EmptyRecord1) ->
232 (Record1 fields ext, EmptyRecord1) | Map.null fields -> unifyHelp ext variable2
233 (EmptyRecord1, Record1 fields ext) | Map.null fields -> unifyHelp variable1 ext
235 (Record1 _ _, Record1 _ _) ->
236 recordUnify fresh variable1 variable2
238 _ -> typeError variable1 variable2 Nothing
243rigidErrorMessage :: String -> String
244rigidErrorMessage var =
245 "Could not unify user provided type variable `" ++ var ++ "`. The most likely cases are:\n\n"
246 ++ " 1. The type you wrote down is too general. It says any type can go through\n"
247 ++ " but in fact only a certain type can.\n"
248 ++ " 2. A type variable is probably being shared between two type annotations.\n"
249 ++ " They are treated as different things in the current implementation, so\n"
250 ++ " Try commenting out some type annotations and see what happens."
256 :: (Maybe (Term1 Variable) -> Unify Variable)
260recordUnify fresh variable1 variable2 =
261 do (ExpandedRecord fields1 ext1) <- liftIO (gatherFields variable1)
262 (ExpandedRecord fields2 ext2) <- liftIO (gatherFields variable2)
264 unifyOverlappingFields fields1 fields2
266 let freshRecord fields ext =
267 fresh (Just (Record1 fields ext))
269 let uniqueFields1 = diffFields fields1 fields2
270 let uniqueFields2 = diffFields fields2 fields1
272 let addFieldMismatchError missingFields =
273 typeError variable1 variable2 $ Just $
274 fieldMismatchError missingFields
277 (Empty _, Empty _) ->
278 case Map.null uniqueFields1 && Map.null uniqueFields2 of
280 False -> typeError variable1 variable2 Nothing
282 (Empty var1, Extension var2) ->
283 case (Map.null uniqueFields1, Map.null uniqueFields2) of
284 (_, False) -> addFieldMismatchError uniqueFields2
285 (True, True) -> unifyHelp var1 var2
287 do subRecord <- freshRecord uniqueFields1 var1
288 unifyHelp subRecord var2
290 (Extension var1, Empty var2) ->
291 case (Map.null uniqueFields1, Map.null uniqueFields2) of
292 (False, _) -> addFieldMismatchError uniqueFields1
293 (True, True) -> unifyHelp var1 var2
295 do subRecord <- freshRecord uniqueFields2 var2
296 unifyHelp var1 subRecord
298 (Extension var1, Extension var2) ->
299 case (Map.null uniqueFields1, Map.null uniqueFields2) of
304 do subRecord <- freshRecord uniqueFields2 var2
305 unifyHelp var1 subRecord
308 do subRecord <- freshRecord uniqueFields1 var1
309 unifyHelp subRecord var2
312 do record1' <- freshRecord uniqueFields1 =<< fresh Nothing
313 record2' <- freshRecord uniqueFields2 =<< fresh Nothing
314 unifyHelp record1' var2
315 unifyHelp var1 record2'
318unifyOverlappingFields
319 :: Map.Map String [Variable]
320 -> Map.Map String [Variable]
322unifyOverlappingFields fields1 fields2 =
323 Map.intersectionWith (zipWith unifyHelp) fields1 fields2
329diffFields :: Map.Map String [a] -> Map.Map String [a] -> Map.Map String [a]
330diffFields fields1 fields2 =
331 let eat (_:xs) (_:ys) = eat xs ys
334 Map.union (Map.intersectionWith eat fields1 fields2) fields1
335 |> Map.filter (not . null)
338data ExpandedRecord = ExpandedRecord
339 { _fields :: Map.Map String [Variable]
340 , _extension :: Extension
343data Extension = Empty Variable | Extension Variable
346gatherFields :: Variable -> IO ExpandedRecord
348 do desc <- UF.descriptor var
349 case structure desc of
350 (Just (Record1 fields ext)) ->
351 do (ExpandedRecord deeperFields rootExt) <- gatherFields ext
352 return (ExpandedRecord (Map.unionWith (++) fields deeperFields) rootExt)
354 (Just EmptyRecord1) ->
355 return (ExpandedRecord Map.empty (Empty var))
358 return (ExpandedRecord Map.empty (Extension var))
361-- assumes that one of the dicts has stuff in it
362fieldMismatchError :: Map.Map String a -> String
363fieldMismatchError missingFields =
364 case Map.keys missingFields of
367 "Looks like a record is missing the field `" ++ key ++ "`"
369 "Looks like a record is missing fields "
370 ++ List.intercalate ", " (init keys) ++ ", and " ++ last keys
376 -> (Maybe Var.Canonical, Flex, Int, Alias Variable)
377combinedDescriptors desc1 desc2 =
378 (name', flex', rank', alias')
382 min (rank desc1) (rank desc2)
384 alias' :: Alias Variable
386 alias desc1 <|> alias desc2
388 name' :: Maybe Var.Canonical
390 case (name desc1, name desc2) of
391 (Just name1, Just name2) ->
392 case (flex desc1, flex desc2) of
393 (_, Flexible) -> Just name1
394 (Flexible, _) -> Just name2
395 (Is Number, Is _) -> Just name1
396 (Is _, Is Number) -> Just name2
397 (Is _, Is _) -> Just name1
399 (Just name1, _) -> Just name1
400 (_, Just name2) -> Just name2
405 case (flex desc1, flex desc2) of
408 (Is Number, Is _) -> Is Number
409 (Is _, Is Number) -> Is Number
410 (Is super, Is _) -> Is super
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list