Skip to content

Commit 7af9d0a

Browse files
committed
add effect type mismatch hints:
- unexpected effects - missing effects when a skolem fails to unify
1 parent 0bee513 commit 7af9d0a

File tree

11 files changed

+122
-25
lines changed

11 files changed

+122
-25
lines changed

src/Type/InferMonad.hs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,8 +571,8 @@ occursInContext tv extraFree
571571
Unification errors
572572
--------------------------------------------------------------------------}
573573
unifyError :: Context -> Range -> UnifyError -> Type -> Type -> Inf a
574-
unifyError context range (NoMatchEffect eff1 eff2) _ _
575-
= unifyError context range NoMatch eff2 eff1
574+
unifyError context range (NoMatchEffect eff1 eff2 effectMismatch) _ _
575+
= unifyError context range (NoMatch (Just effectMismatch)) eff2 eff1
576576
unifyError context range err xtp1 xtp2
577577
= do free <- freeInGamma
578578
tp1 <- subst xtp1 >>= normalizeX False free
@@ -589,6 +589,7 @@ unifyError' env context range err tp1 tp2
589589
,(text ("inferred " ++ nameType), nice2)
590590
]
591591
++ nomatch
592+
++ effectDiffs
592593
++ extra
593594
++ hint
594595
)
@@ -617,10 +618,36 @@ unifyError' env context range err tp1 tp2
617618
then "effect"
618619
else "type"
619620

621+
effectDiffs
622+
= case err of
623+
NoMatch (Just diff) -> effectDiffs' diff
624+
NoMatchEffect _ _ diff -> effectDiffs' diff
625+
_ -> []
626+
627+
effectDiffs' diff = unexpectedMessages ++ missingMessages
628+
where
629+
niceUnexpected = map (Pretty.niceType env) (unexpectedEffectLabels diff)
630+
niceMissing = map (Pretty.niceType env) (missingEffectLabels diff)
631+
unexpectedMessages = if null niceUnexpected
632+
then []
633+
else [(text "unexpected", hsep (punctuate comma niceUnexpected))]
634+
635+
missingMessages = if null niceMissing
636+
then []
637+
else [(text "missing",
638+
vsep [
639+
hsep (punctuate comma niceMissing),
640+
hsep [
641+
text "Consider using",
642+
Pretty.keyword env "mask",
643+
text "or adding it to the function's effect type"
644+
]
645+
]
646+
)]
620647

621648
(message,hint)
622649
= case err of
623-
NoMatch -> (nameType ++ "s do not match",[])
650+
NoMatch _ -> (nameType ++ "s do not match",[])
624651
NoMatchKind -> ("kinds do not match",[])
625652
NoMatchSkolem kind
626653
-> ("abstract types do not match",if (not (null extra))
@@ -630,7 +657,7 @@ unifyError' env context range err tp1 tp2
630657
else text "an higher-rank type escapes its scope?")])
631658
NoSubsume -> ("type is not polymorphic enough",[(text "hint",text "give a higher-rank type annotation to a function parameter?")])
632659
Infinite -> ("types do not match (due to an infinite type)",[(text "hint",text "give a type to the function definition?")])
633-
NoMatchEffect{}-> ("effects do not match",[])
660+
NoMatchEffect _ _ _ -> ("effects do not match",[])
634661
NoArgMatch n m -> if (m<0)
635662
then ("only functions can be applied",[])
636663
else ("application has too " ++ (if (n > m) then "few" else "many") ++ " arguments"

src/Type/Unify.hs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ module Type.Unify ( Unify, UnifyError(..), runUnify, runUnifyEx
1818
, matchArguments
1919
, matchShape, pureMatchShape
2020
, extractNormalizeEffect
21+
, unexpectedEffectLabels
22+
, missingEffectLabels
2123
) where
2224

2325
import Control.Applicative
@@ -75,7 +77,7 @@ overlaps range free tp1 tp2
7577
fo1 = take hi (map snd fixed1 ++ map (unOptional . snd) optional1 ++ map snd implicit1)
7678
fo2 = take hi (map snd fixed2 ++ map (unOptional . snd) optional2 ++ map snd implicit2)
7779
in if (length fo1 /= length fo2)
78-
then unifyError NoMatch -- one has more fixed arguments than the other can ever get
80+
then unifyError noMatchTypes -- one has more fixed arguments than the other can ever get
7981
else do unifies fo1 fo2
8082
return ()
8183

@@ -87,10 +89,10 @@ matchNamed matchSome range free tp n {- given args -} named mbExpResTp
8789
= do rho1 <- instantiate range tp
8890
case splitFunType rho1 of
8991
Nothing
90-
-> unifyError NoMatch
92+
-> unifyError noMatchTypes
9193
Just (pars,_,resTp)
9294
-> if (n + length named > length pars)
93-
then unifyError NoMatch
95+
then unifyError noMatchTypes
9496
else let npars = drop n pars
9597
names = map fst npars
9698
in if (all (\name -> name `elem` names) named)
@@ -102,8 +104,8 @@ matchNamed matchSome range free tp n {- given args -} named mbExpResTp
102104
let rest = [(nm,tp) | (nm,tp) <- npars, not (nm `elem` named)]
103105
if (matchSome || all isOptionalOrImplicit rest)
104106
then subst rho1
105-
else unifyError NoMatch
106-
else unifyError NoMatch
107+
else unifyError noMatchTypes
108+
else unifyError noMatchTypes
107109

108110

109111
-- | Does a function type match the given arguments? if the first argument 'matchSome' is true,
@@ -124,15 +126,15 @@ matchArguments matchSome range free tp fixed named mbExpResTp
124126

125127
Just (pars,_,resTp)
126128
-> if (length fixed + length named > length pars)
127-
then unifyError NoMatch
129+
then unifyError noMatchTypes
128130
else do -- trace (" matchArguments: " ++ show (map pretty pars, map pretty fixed, map pretty named)) $ return ()
129131
-- subsume fixed parameters
130132
let parsNotNamedArg = filter (\(nm,tp) -> nm `notElem` map fst named) pars
131133
let (fpars,rest) = splitAt (length fixed) parsNotNamedArg
132134
mapM_ (\(tpar,targ) -> subsumeSubst range free (unOptional tpar) targ) (zip (map snd fpars) fixed)
133135
-- subsume named parameters
134136
mapM_ (\(name,targ) -> case lookup name pars of
135-
Nothing -> unifyError NoMatch
137+
Nothing -> unifyError noMatchTypes
136138
Just tpar -> subsumeSubst range free (unOptional tpar) targ
137139
) named
138140
-- check if the result type matches
@@ -143,7 +145,7 @@ matchArguments matchSome range free tp fixed named mbExpResTp
143145
-- check the rest is optional or implicit
144146
if (matchSome || all isOptionalOrImplicit rest)
145147
then do subst rho1
146-
else unifyError NoMatch
148+
else unifyError noMatchTypes
147149

148150
subsumeSubst :: Range -> Tvs -> Type -> Type -> Unify (Type,Rho, Core.Expr -> Core.Expr)
149151
subsumeSubst range free tp1 tp2
@@ -160,9 +162,9 @@ matchShape tp1 tp2
160162
codom <- nub <$>
161163
mapM (\(_,t) -> case t of
162164
TVar tv -> return tv
163-
_ -> unifyError NoMatch) (subList sub)
165+
_ -> unifyError noMatchTypes) (subList sub)
164166
let oneToOne = (length dom == length codom)
165-
if oneToOne then return () else unifyError NoMatch
167+
if oneToOne then return () else unifyError noMatchTypes
166168

167169
pureMatchShape :: Type -> Type -> Bool
168170
pureMatchShape tp1 tp2
@@ -272,8 +274,9 @@ unify f1@(TFun args1 eff1 res1) f2@(TFun args2 eff2 res2) | length args1 == leng
272274
withError (effErr) (unify eff1 eff2)
273275
where
274276
-- specialize to sub-part of the type for effect unification errors
275-
effErr NoMatch = NoMatchEffect eff1 eff2
276-
effErr (NoMatchEffect _ _) = NoMatchEffect eff1 eff2
277+
effErr (NoMatch diff) = NoMatchEffect eff1 eff2 (maybeEffectMismatch diff)
278+
279+
effErr (NoMatchEffect _ _ diff) = NoMatchEffect eff1 eff2 diff
277280
effErr err = err
278281

279282
-- quantified types
@@ -321,9 +324,19 @@ unify tp1 (TSyn _ _ tp2)
321324
unify (TVar (TypeVar _ kind Skolem)) (TVar (TypeVar _ _ Skolem))
322325
= unifyError (NoMatchSkolem kind)
323326

327+
-- expected a skolem, got some other effect type
328+
unify (TVar (TypeVar _ kind Skolem)) tp2 | isKindEffect kind
329+
= -- trace ("no match (left is skolem): " ++ show (pretty tp2)) $
330+
unifyError $ NoMatch (Just $ effectMismatchMissing tp2)
331+
332+
-- expected `total`, got tp2
333+
unify tp1 tp2 | isEffectEmpty tp1
334+
= -- trace ("no match (left is total): " ++ show (pretty tp2)) $
335+
unifyError $ NoMatch (Just $ effectMismatchUnexpected tp2)
336+
324337
unify tp1 tp2
325338
= -- trace ("no match: " ++ show (pretty tp1, pretty tp2)) $
326-
unifyError NoMatch
339+
unifyError noMatchTypes
327340

328341

329342
-- | Unify a type variable with a type
@@ -337,7 +350,7 @@ unifyTVar tv@(TypeVar id kind Meta) tp
337350
_ -> unifyError Infinite
338351
else case etp of
339352
TVar (TypeVar _ _ Bound)
340-
-> unifyError NoMatch -- can't unify with bound variables
353+
-> unifyError noMatchTypes -- can't unify with bound variables
341354
TVar tv2@(TypeVar id2 _ Meta) | id <= id2
342355
-> if (id < id2)
343356
then unifyTVar tv2 (TVar tv)
@@ -477,11 +490,34 @@ data Res a = Ok !a !St
477490
| Err UnifyError !St
478491
data St = St{ uniq :: !Int, sub :: !Sub }
479492

493+
data EffectMismatch = EffectMismatch
494+
{ unexpectedEffectLabels :: [Type]
495+
, missingEffectLabels :: [Type]
496+
} deriving Show
497+
498+
maybeEffectMismatch (Just diff) = diff
499+
maybeEffectMismatch Nothing = EffectMismatch [] []
500+
501+
concreteEffectLabels typ = head ++ tailList
502+
where
503+
(head, tail) = extractOrderedEffect typ
504+
tailList =
505+
if isEffectEmpty tail || isMeta tail then [] else [tail]
506+
507+
isMeta (TVar (TypeVar _ kind Meta)) = True
508+
isMeta _ = False
509+
510+
effectMismatchUnexpected unexpected = EffectMismatch (concreteEffectLabels unexpected) []
511+
512+
effectMismatchMissing missing = EffectMismatch [] (concreteEffectLabels missing)
513+
514+
noMatchTypes = NoMatch Nothing
515+
480516
data UnifyError
481-
= NoMatch
517+
= NoMatch (Maybe EffectMismatch)
482518
| NoMatchKind
483519
| NoMatchSkolem Kind
484-
| NoMatchEffect Type Type
520+
| NoMatchEffect Type Type EffectMismatch
485521
| NoSubsume
486522
| Infinite
487523
| NoArgMatch Int Int

test/algeff/wrong/eff-rec2.kk.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ test/algeff/wrong/eff-rec2@kk(39,32): type error: effects do not match
66
@@@
77
}
88
inferred effect: <console,div,exn|_e>
9-
expected effect: <exn,console>
9+
expected effect: <exn,console>
10+
unexpected : div

test/type/wrong/effect3.kk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fun main(): io-noexn ()
2+
println("")
3+
throw("error!")

test/type/wrong/effect3.kk.out

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
test/type/wrong/effect3.kk(1,23): type error: effects do not match
2+
context : fun main(): io-noexn ()
3+
println("")
4+
throw("error!")
5+
term :
6+
println("")
7+
throw("error!")
8+
inferred effect: <exn,console/console|_e>
9+
expected effect: io-noexn
10+
unexpected : exn

test/type/wrong/effect4.kk

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fun io-action(): io ()
2+
()
3+
4+
fun main(action: () -> <io-noexn|e> ()): <io|e> int
5+
var x := 1
6+
io-action()
7+
action()
8+
x

test/type/wrong/effect4.kk.out

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
test/type/wrong/effect4.kk(6, 3): type error: effects do not match
2+
context : io-action()
3+
term : io-action()
4+
inferred effect: <local<_h>,alloc<global>,console,div,fsys,ndet,net,read<global>,ui,write<global>|$e>
5+
expected effect: <alloc<global>,console,div,exn,fsys,ndet,net,read<global>,ui,write<global>,local<_h>|_e1>
6+
missing : exn
7+
Consider using mask or adding it to the function's effect type

test/type/wrong/scheduler1.kk.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ test/type/wrong/scheduler1@kk(18, 7): type error: effects do not match
33
term : q
44
inferred effect: <alloc<global>|_e>
55
expected effect: total
6-
because : Polymorphic values cannot have an effect
6+
unexpected : alloc<global>
7+
because : Polymorphic values cannot have an effect

test/type/wrong/scheduler5.kk.out

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ test/type/wrong/scheduler5@kk(40, 9): type error: effects do not match
22
context : spawn(f)
33
term : f
44
inferred effect: <pure,process,st<global>|$e>
5-
expected effect: <pure,alloc<global>,process,read<global>,write<global>,exn>
5+
expected effect: <pure,alloc<global>,process,read<global>,write<global>,exn>
6+
missing : exn
7+
Consider using mask or adding it to the function's effect type

test/type/wrong/st1.kk.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ test/type/wrong/st1@kk(1, 9): type error: effects do not match
33
term : ref
44
inferred effect: <alloc<_h>|_e>
55
expected effect: total
6-
because : Generalized values cannot have an effect
6+
unexpected : alloc<_h>
7+
because : Generalized values cannot have an effect

0 commit comments

Comments
 (0)