1module Type.Unify (unify) where
3import Control.Applicative ((<|>))
4import Control.Monad.Error (ErrorT, throwError, runErrorT)
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.Annotation as A
13import qualified AST.Variable as Var
14import qualified Type.State as TS
16import Type.PrettyPrint
17import qualified Type.Hint as Hint
18import Elm.Utils ((|>))
21unify :: A.Region -> Variable -> Variable -> StateT TS.SolverState IO ()
22unify region variable1 variable2 =
23 do result <- runErrorT (unifyHelp region variable1 variable2)
24 either TS.addHint return result
27-- ACTUALLY UNIFY STUFF
29type Unify = ErrorT Hint.Hint (StateT TS.SolverState IO)
35 -> UF.Point Descriptor
36 -> UF.Point Descriptor
38typeError region hint t1 t2 =
39 do msg <- liftIO (Hint.create region hint t1 t2)
43unifyHelp :: A.Region -> Variable -> Variable -> Unify ()
44unifyHelp region variable1 variable2 = do
45 equivalent <- liftIO $ UF.equivalent variable1 variable2
48 else actuallyUnify region variable1 variable2
51actuallyUnify :: A.Region -> Variable -> Variable -> Unify ()
52actuallyUnify region variable1 variable2 = do
53 desc1 <- liftIO $ UF.descriptor variable1
54 desc2 <- liftIO $ UF.descriptor variable2
55 let unifyHelp' = unifyHelp region
57 (name', flex', rank', alias') = combinedDescriptors desc1 desc2
61 if rank desc1 < rank desc2 then UF.union variable2 variable1
62 else UF.union variable1 variable2
63 UF.modifyDescriptor variable1 $ \desc ->
64 desc { structure = structure desc1
72 if rank desc1 < rank desc2 then UF.union variable2 variable1
73 else UF.union variable1 variable2
74 UF.modifyDescriptor variable2 $ \desc ->
75 desc { structure = structure desc2
81 merge = if rank desc1 < rank desc2 then merge1 else merge2
83 fresh :: Maybe (Term1 Variable) -> Unify Variable
85 v <- liftIO . UF.fresh $ Descriptor
86 { structure = structure
97 liftIO $ UF.modifyDescriptor v $ \desc -> desc { flex = Flexible }
98 unifyHelp' variable1 variable2
100 unifyNumber svar (Var.Canonical home name) =
102 Var.BuiltIn | name `elem` ["Int","Float"] -> flexAndUnify svar
103 Var.Local | List.isPrefixOf "number" name -> flexAndUnify svar
105 let hint = "Looks like something besides an Int or Float is being used as a number."
107 typeError region (Just hint) variable1 variable2
109 comparableError maybe =
110 typeError region (Just $ Maybe.fromMaybe msg maybe) variable1 variable2
113 "Looks like you want something comparable, but the only valid comparable\n\
114 \types are Int, Float, Char, String, lists, or tuples."
116 appendableError maybe =
117 typeError region (Just $ Maybe.fromMaybe msg maybe) variable1 variable2
120 "Looks like you want something appendable, but the only Strings, Lists,\n\
121 \and Text can be appended with the (++) operator."
123 unifyComparable v (Var.Canonical home name) =
125 Var.BuiltIn | name `elem` ["Int","Float","Char","String"] -> flexAndUnify v
126 Var.Local | List.isPrefixOf "comparable" name -> flexAndUnify v
127 _ -> comparableError Nothing
129 unifyComparableStructure varSuper varFlex =
130 do struct <- liftIO $ collectApps varFlex
132 Other -> comparableError Nothing
133 List v -> do flexAndUnify varSuper
134 unifyHelp' v =<< liftIO (variable $ Is Comparable)
137 comparableError $ Just "Cannot compare a tuple with more than 6 elements."
139 do flexAndUnify varSuper
140 cmpVars <- liftIO $ forM [1..length vs] $ \_ -> variable (Is Comparable)
141 zipWithM_ unifyHelp' vs cmpVars
143 unifyAppendable varSuper varFlex =
144 do struct <- liftIO $ collectApps varFlex
146 List _ -> flexAndUnify varSuper
147 _ -> appendableError Nothing
150 typeError region (Just hint) variable1 variable2
153 "Could not unify rigid type variable '" ++ render (pretty Never var) ++ "'.\n" ++
154 "The problem probably relates to the type variable being shared between a\n\
155 \top-level type annotation and a related let-bound type annotation."
158 case (flex desc1, flex desc2, name desc1, name desc2) of
159 (Is super1, Is super2, _, _)
160 | super1 == super2 -> merge
161 (Is Number, Is Comparable, _, _) -> merge1
162 (Is Comparable, Is Number, _, _) -> merge2
164 (Is Number, _, _, Just name) -> unifyNumber variable1 name
165 (_, Is Number, Just name, _) -> unifyNumber variable2 name
167 (Is Comparable, _, _, Just name) -> unifyComparable variable1 name
168 (_, Is Comparable, Just name, _) -> unifyComparable variable2 name
169 (Is Comparable, _, _, _) -> unifyComparableStructure variable1 variable2
170 (_, Is Comparable, _, _) -> unifyComparableStructure variable2 variable1
172 (Is Appendable, _, _, Just name)
173 | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable1
174 (_, Is Appendable, Just name, _)
175 | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable2
176 (Is Appendable, _, _, _) -> unifyAppendable variable1 variable2
177 (_, Is Appendable, _, _) -> unifyAppendable variable2 variable1
179 (Rigid, _, _, _) -> rigidError variable1
180 (_, Rigid, _, _) -> rigidError variable2
181 _ -> typeError region Nothing variable1 variable2
183 case (structure desc1, structure desc2) of
184 (Nothing, Nothing) | flex desc1 == Flexible && flex desc1 == Flexible -> merge
185 (Nothing, _) | flex desc1 == Flexible -> merge2
186 (_, Nothing) | flex desc2 == Flexible -> merge1
188 (Just (Var1 v), _) -> unifyHelp' v variable2
189 (_, Just (Var1 v)) -> unifyHelp' v variable1
191 (Nothing, _) -> superUnify
192 (_, Nothing) -> superUnify
194 (Just type1, Just type2) ->
195 case (type1,type2) of
196 (App1 term1 term2, App1 term1' term2') ->
198 unifyHelp' term1 term1'
199 unifyHelp' term2 term2'
200 (Fun1 term1 term2, Fun1 term1' term2') ->
202 unifyHelp' term1 term1'
203 unifyHelp' term2 term2'
205 (EmptyRecord1, EmptyRecord1) ->
208 (Record1 fields ext, EmptyRecord1) | Map.null fields -> unifyHelp' ext variable2
209 (EmptyRecord1, Record1 fields ext) | Map.null fields -> unifyHelp' ext variable1
211 (Record1 _ _, Record1 _ _) ->
212 recordUnify region fresh variable1 variable2
214 _ -> typeError region Nothing variable1 variable2
221 -> (Maybe (Term1 Variable) -> Unify Variable)
225recordUnify region fresh variable1 variable2 =
226 do (ExpandedRecord fields1 ext1) <- liftIO (gatherFields variable1)
227 (ExpandedRecord fields2 ext2) <- liftIO (gatherFields variable2)
229 unifyOverlappingFields region fields1 fields2
231 let freshRecord fields ext =
232 fresh (Just (Record1 fields ext))
234 let uniqueFields1 = diffFields fields1 fields2
235 let uniqueFields2 = diffFields fields2 fields1
237 let addFieldMismatchError missingFields =
238 let msg = fieldMismatchError missingFields
240 typeError region (Just msg) variable1 variable2
243 (Empty _, Empty _) ->
244 case Map.null uniqueFields1 && Map.null uniqueFields2 of
246 False -> typeError region Nothing variable1 variable2
248 (Empty var1, Extension var2) ->
249 case (Map.null uniqueFields1, Map.null uniqueFields2) of
250 (_, False) -> addFieldMismatchError uniqueFields2
251 (True, True) -> unifyHelp region var1 var2
253 do subRecord <- freshRecord uniqueFields1 var1
254 unifyHelp region subRecord var2
256 (Extension var1, Empty var2) ->
257 case (Map.null uniqueFields1, Map.null uniqueFields2) of
258 (False, _) -> addFieldMismatchError uniqueFields1
259 (True, True) -> unifyHelp region var1 var2
261 do subRecord <- freshRecord uniqueFields2 var2
262 unifyHelp region var1 subRecord
264 (Extension var1, Extension var2) ->
265 case (Map.null uniqueFields1, Map.null uniqueFields2) of
267 unifyHelp region var1 var2
270 do subRecord <- freshRecord uniqueFields2 var2
271 unifyHelp region var1 subRecord
274 do subRecord <- freshRecord uniqueFields1 var1
275 unifyHelp region subRecord var2
278 do record1' <- freshRecord uniqueFields1 =<< fresh Nothing
279 record2' <- freshRecord uniqueFields2 =<< fresh Nothing
280 unifyHelp region record1' var2
281 unifyHelp region var1 record2'
284unifyOverlappingFields
286 -> Map.Map String [Variable]
287 -> Map.Map String [Variable]
289unifyOverlappingFields region fields1 fields2 =
290 Map.intersectionWith (zipWith (unifyHelp region)) fields1 fields2
296diffFields :: Map.Map String [a] -> Map.Map String [a] -> Map.Map String [a]
297diffFields fields1 fields2 =
298 let eat (_:xs) (_:ys) = eat xs ys
301 Map.union (Map.intersectionWith eat fields1 fields2) fields1
302 |> Map.filter (not . null)
305data ExpandedRecord = ExpandedRecord
306 { _fields :: Map.Map String [Variable]
307 , _extension :: Extension
310data Extension = Empty Variable | Extension Variable
313gatherFields :: Variable -> IO ExpandedRecord
315 do desc <- UF.descriptor var
316 case structure desc of
317 (Just (Record1 fields ext)) ->
318 do (ExpandedRecord deeperFields rootExt) <- gatherFields ext
319 return (ExpandedRecord (Map.unionWith (++) fields deeperFields) rootExt)
321 (Just EmptyRecord1) ->
322 return (ExpandedRecord Map.empty (Empty var))
325 return (ExpandedRecord Map.empty (Extension var))
328-- assumes that one of the dicts has stuff in it
329fieldMismatchError :: Map.Map String a -> String
330fieldMismatchError missingFields =
331 case Map.keys missingFields of
334 "Looks like a record is missing the field '" ++ key ++ "'.\n " ++
335 "Maybe there is a misspelling in a record access or record update?"
337 "Looks like one record is missing fields "
338 ++ List.intercalate ", " (init keys) ++ ", and " ++ last keys
341combinedDescriptors :: Descriptor -> Descriptor
342 -> (Maybe Var.Canonical, Flex, Int, Maybe Var.Canonical)
343combinedDescriptors desc1 desc2 =
344 (name', flex', rank', alias')
347 rank' = min (rank desc1) (rank desc2)
349 alias' :: Maybe Var.Canonical
350 alias' = alias desc1 <|> alias desc2
352 name' :: Maybe Var.Canonical
353 name' = case (name desc1, name desc2) of
354 (Just name1, Just name2) ->
355 case (flex desc1, flex desc2) of
356 (_, Flexible) -> Just name1
357 (Flexible, _) -> Just name2
358 (Is Number, Is _) -> Just name1
359 (Is _, Is Number) -> Just name2
360 (Is _, Is _) -> Just name1
362 (Just name1, _) -> Just name1
363 (_, Just name2) -> Just name2
367 flex' = case (flex desc1, flex desc2) of
370 (Is Number, Is _) -> Is Number
371 (Is _, Is Number) -> Is Number
372 (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