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.Maybe as Maybe
9import qualified Data.UnionFind.IO as UF
10import Text.PrettyPrint (render)
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
17import Type.PrettyPrint
18import Elm.Utils ((|>))
21unify :: R.Region -> Variable -> Variable -> StateT TS.SolverState IO ()
22unify region variable1 variable2 =
23 do result <- runExceptT (unifyHelp variable1 variable2)
29 TS.addError region (Error.Mismatch mismatch)
32-- ACTUALLY UNIFY STUFF
34type Unify = ExceptT Error.Mismatch (StateT TS.SolverState IO)
39 -> UF.Point Descriptor
40 -> UF.Point Descriptor
42typeError hint leftType rightType =
43 do leftSrcType <- liftIO (toSrcType leftType)
44 rightSrcType <- liftIO (toSrcType rightType)
45 throwError (Error.MismatchInfo hint leftSrcType rightSrcType)
48unifyHelp :: Variable -> Variable -> Unify ()
49unifyHelp variable1 variable2 =
50 do equivalent <- liftIO $ UF.equivalent variable1 variable2
53 else actuallyUnify variable1 variable2
56actuallyUnify :: Variable -> Variable -> Unify ()
57actuallyUnify variable1 variable2 = do
58 desc1 <- liftIO $ UF.descriptor variable1
59 desc2 <- liftIO $ UF.descriptor variable2
60 let (name', flex', rank', alias') = combinedDescriptors desc1 desc2
65 if rank desc1 < rank desc2
66 then UF.union variable2 variable1
67 else UF.union variable1 variable2
68 UF.modifyDescriptor variable1 $ \desc ->
69 desc { structure = structure desc1
78 if rank desc1 < rank desc2
79 then UF.union variable2 variable1
80 else UF.union variable1 variable2
81 UF.modifyDescriptor variable2 $ \desc ->
82 desc { structure = structure desc2
89 if rank desc1 < rank desc2 then merge1 else merge2
91 fresh :: Maybe (Term1 Variable) -> Unify Variable
93 do v <- liftIO . UF.fresh $ Descriptor
94 { structure = structure
105 do liftIO $ UF.modifyDescriptor v $ \desc -> desc { flex = Flexible }
106 unifyHelp variable1 variable2
108 unifyNumber svar (Var.Canonical home name) =
110 Var.BuiltIn | name `elem` ["Int","Float"] -> flexAndUnify svar
111 Var.Local | List.isPrefixOf "number" name -> flexAndUnify svar
113 let hint = "Looks like something besides an Int or Float is being used as a number."
115 typeError (Just hint) variable1 variable2
117 comparableError maybe =
118 typeError (Just $ Maybe.fromMaybe msg maybe) variable1 variable2
121 "Looks like you want something comparable, but the only valid comparable\n\
122 \types are Int, Float, Char, String, lists, and tuples."
124 appendableError maybe =
125 typeError (Just $ Maybe.fromMaybe msg maybe) variable1 variable2
128 "Looks like you want something appendable, but the only Strings, Lists,\n\
129 \and Text can be appended with the (++) operator."
131 unifyComparable v (Var.Canonical home name) =
133 Var.BuiltIn | name `elem` ["Int","Float","Char","String"] -> flexAndUnify v
134 Var.Local | List.isPrefixOf "comparable" name -> flexAndUnify v
135 _ -> comparableError Nothing
137 unifyComparableStructure varSuper varFlex =
138 do struct <- liftIO $ collectApps varFlex
141 comparableError Nothing
144 do flexAndUnify varSuper
145 unifyHelp v =<< liftIO (variable $ Is Comparable)
149 comparableError $ Just "Cannot compare a tuple with more than 6 elements."
151 do flexAndUnify varSuper
152 cmpVars <- liftIO $ forM [1..length vs] $ \_ -> variable (Is Comparable)
153 zipWithM_ unifyHelp vs cmpVars
155 unifyAppendable varSuper varFlex =
156 do struct <- liftIO $ collectApps varFlex
158 List _ -> flexAndUnify varSuper
159 _ -> appendableError Nothing
162 typeError (Just hint) variable1 variable2
165 "Could not unify rigid type variable '" ++ render (pretty Never var) ++ "'.\n" ++
166 "The problem probably relates to the type variable being shared between a\n\
167 \top-level type annotation and a related let-bound type annotation."
170 case (flex desc1, flex desc2, name desc1, name desc2) of
171 (Is super1, Is super2, _, _)
172 | super1 == super2 -> merge
173 (Is Number, Is Comparable, _, _) -> merge1
174 (Is Comparable, Is Number, _, _) -> merge2
176 (Is Number, _, _, Just name) -> unifyNumber variable1 name
177 (_, Is Number, Just name, _) -> unifyNumber variable2 name
179 (Is Comparable, _, _, Just name) -> unifyComparable variable1 name
180 (_, Is Comparable, Just name, _) -> unifyComparable variable2 name
181 (Is Comparable, _, _, _) -> unifyComparableStructure variable1 variable2
182 (_, Is Comparable, _, _) -> unifyComparableStructure variable2 variable1
184 (Is Appendable, _, _, Just name)
185 | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable1
186 (_, Is Appendable, Just name, _)
187 | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable2
188 (Is Appendable, _, _, _) -> unifyAppendable variable1 variable2
189 (_, Is Appendable, _, _) -> unifyAppendable variable2 variable1
191 (Rigid, _, _, _) -> rigidError variable1
192 (_, Rigid, _, _) -> rigidError variable2
193 _ -> typeError Nothing variable1 variable2
195 case (structure desc1, structure desc2) of
196 (Nothing, Nothing) | flex desc1 == Flexible && flex desc1 == Flexible ->
199 (Nothing, _) | flex desc1 == Flexible ->
202 (_, Nothing) | flex desc2 == Flexible ->
205 (Just (Var1 v), _) ->
206 unifyHelp v variable2
208 (_, Just (Var1 v)) ->
209 unifyHelp variable1 v
217 (Just type1, Just type2) ->
218 case (type1,type2) of
219 (App1 term1 term2, App1 term1' term2') ->
221 unifyHelp term1 term1'
222 unifyHelp term2 term2'
223 (Fun1 term1 term2, Fun1 term1' term2') ->
225 unifyHelp term1 term1'
226 unifyHelp term2 term2'
228 (EmptyRecord1, EmptyRecord1) ->
231 (Record1 fields ext, EmptyRecord1) | Map.null fields -> unifyHelp ext variable2
232 (EmptyRecord1, Record1 fields ext) | Map.null fields -> unifyHelp variable1 ext
234 (Record1 _ _, Record1 _ _) ->
235 recordUnify fresh variable1 variable2
237 _ -> typeError Nothing variable1 variable2
243 :: (Maybe (Term1 Variable) -> Unify Variable)
247recordUnify fresh variable1 variable2 =
248 do (ExpandedRecord fields1 ext1) <- liftIO (gatherFields variable1)
249 (ExpandedRecord fields2 ext2) <- liftIO (gatherFields variable2)
251 unifyOverlappingFields fields1 fields2
253 let freshRecord fields ext =
254 fresh (Just (Record1 fields ext))
256 let uniqueFields1 = diffFields fields1 fields2
257 let uniqueFields2 = diffFields fields2 fields1
259 let addFieldMismatchError missingFields =
260 let msg = fieldMismatchError missingFields
262 typeError (Just msg) variable1 variable2
265 (Empty _, Empty _) ->
266 case Map.null uniqueFields1 && Map.null uniqueFields2 of
268 False -> typeError Nothing variable1 variable2
270 (Empty var1, Extension var2) ->
271 case (Map.null uniqueFields1, Map.null uniqueFields2) of
272 (_, False) -> addFieldMismatchError uniqueFields2
273 (True, True) -> unifyHelp var1 var2
275 do subRecord <- freshRecord uniqueFields1 var1
276 unifyHelp subRecord var2
278 (Extension var1, Empty var2) ->
279 case (Map.null uniqueFields1, Map.null uniqueFields2) of
280 (False, _) -> addFieldMismatchError uniqueFields1
281 (True, True) -> unifyHelp var1 var2
283 do subRecord <- freshRecord uniqueFields2 var2
284 unifyHelp var1 subRecord
286 (Extension var1, Extension var2) ->
287 case (Map.null uniqueFields1, Map.null uniqueFields2) of
292 do subRecord <- freshRecord uniqueFields2 var2
293 unifyHelp var1 subRecord
296 do subRecord <- freshRecord uniqueFields1 var1
297 unifyHelp subRecord var2
300 do record1' <- freshRecord uniqueFields1 =<< fresh Nothing
301 record2' <- freshRecord uniqueFields2 =<< fresh Nothing
302 unifyHelp record1' var2
303 unifyHelp var1 record2'
306unifyOverlappingFields
307 :: Map.Map String [Variable]
308 -> Map.Map String [Variable]
310unifyOverlappingFields fields1 fields2 =
311 Map.intersectionWith (zipWith unifyHelp) fields1 fields2
317diffFields :: Map.Map String [a] -> Map.Map String [a] -> Map.Map String [a]
318diffFields fields1 fields2 =
319 let eat (_:xs) (_:ys) = eat xs ys
322 Map.union (Map.intersectionWith eat fields1 fields2) fields1
323 |> Map.filter (not . null)
326data ExpandedRecord = ExpandedRecord
327 { _fields :: Map.Map String [Variable]
328 , _extension :: Extension
331data Extension = Empty Variable | Extension Variable
334gatherFields :: Variable -> IO ExpandedRecord
336 do desc <- UF.descriptor var
337 case structure desc of
338 (Just (Record1 fields ext)) ->
339 do (ExpandedRecord deeperFields rootExt) <- gatherFields ext
340 return (ExpandedRecord (Map.unionWith (++) fields deeperFields) rootExt)
342 (Just EmptyRecord1) ->
343 return (ExpandedRecord Map.empty (Empty var))
346 return (ExpandedRecord Map.empty (Extension var))
349-- assumes that one of the dicts has stuff in it
350fieldMismatchError :: Map.Map String a -> String
351fieldMismatchError missingFields =
352 case Map.keys missingFields of
355 "Looks like a record is missing the field '" ++ key ++ "'.\n"
356 ++ "Maybe there is a misspelling?"
358 "Looks like a record is missing fields "
359 ++ List.intercalate ", " (init keys) ++ ", and " ++ last keys
365 -> (Maybe Var.Canonical, Flex, Int, Alias Variable)
366combinedDescriptors desc1 desc2 =
367 (name', flex', rank', alias')
371 min (rank desc1) (rank desc2)
373 alias' :: Alias Variable
375 alias desc1 <|> alias desc2
377 name' :: Maybe Var.Canonical
379 case (name desc1, name desc2) of
380 (Just name1, Just name2) ->
381 case (flex desc1, flex desc2) of
382 (_, Flexible) -> Just name1
383 (Flexible, _) -> Just name2
384 (Is Number, Is _) -> Just name1
385 (Is _, Is Number) -> Just name2
386 (Is _, Is _) -> Just name1
388 (Just name1, _) -> Just name1
389 (_, Just name2) -> Just name2
394 case (flex desc1, flex desc2) of
397 (Is Number, Is _) -> Is Number
398 (Is _, Is Number) -> Is Number
399 (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