Skip to content

Commit d1a2e70

Browse files
authored
Merge pull request #5284 from unisonweb/lsp/improved-mismatch-ranges
2 parents ae8ceda + e3c10a4 commit d1a2e70

File tree

7 files changed

+327
-79
lines changed

7 files changed

+327
-79
lines changed

parser-typechecker/src/Unison/PrintError.hs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ renderTypeError e env src = case e of
535535
src
536536
[styleAnnotated Type1 foundLeaf]
537537
[styleAnnotated Type2 expectedLeaf],
538+
missingDelayHint,
538539
unitHint,
539540
intLiteralSyntaxTip mismatchSite expectedType,
540541
debugNoteLoc
@@ -555,6 +556,20 @@ renderTypeError e env src = case e of
555556
debugSummary note
556557
]
557558
where
559+
missingDelayHint = case additionalInfo of
560+
Nothing -> ""
561+
Just MissingDelay ->
562+
Pr.lines
563+
[ "I expected the expression to be delayed, but it was not.",
564+
"Are you missing a `do`?"
565+
]
566+
Just SuperfluousDelay ->
567+
Pr.lines
568+
[ "",
569+
"I didn't expect this expression to be delayed, but it was.",
570+
"Are you using a `do` where you don't need one,",
571+
"or are you missing a `()` to force an expression?"
572+
]
558573
unitHintMsg =
559574
"\nHint: Actions within a block must have type "
560575
<> style Type2 (renderType' env expectedLeaf)

parser-typechecker/src/Unison/Typechecker.hs

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Unison.Typechecker
1010
isEqual,
1111
isSubtype,
1212
fitsScheme,
13+
isMismatchMissingDelay,
1314
Env (..),
1415
Notes (..),
1516
Resolution (..),
@@ -32,6 +33,7 @@ import Data.Text qualified as Text
3233
import Data.Tuple qualified as Tuple
3334
import Unison.ABT qualified as ABT
3435
import Unison.Blank qualified as B
36+
import Unison.Builtin.Decls qualified as BuiltinDecls
3537
import Unison.Codebase.BuiltinAnnotation (BuiltinAnnotation)
3638
import Unison.Name qualified as Name
3739
import Unison.Prelude
@@ -43,6 +45,7 @@ import Unison.Syntax.Name qualified as Name (unsafeParseText, unsafeParseVar)
4345
import Unison.Term (Term)
4446
import Unison.Term qualified as Term
4547
import Unison.Type (Type)
48+
import Unison.Type qualified as Type
4649
import Unison.Typechecker.Context qualified as Context
4750
import Unison.Typechecker.TypeLookup qualified as TL
4851
import Unison.Typechecker.TypeVar qualified as TypeVar
@@ -126,6 +129,33 @@ synthesize ppe pmccSwitch env t =
126129
(TypeVar.liftTerm t)
127130
in Result.hoist (pure . runIdentity) $ fmap TypeVar.lowerType result
128131

132+
-- | @subtype a b@ is @Right b@ iff @f x@ is well-typed given
133+
-- @x : a@ and @f : b -> t@. That is, if a value of type `a`
134+
-- can be passed to a function expecting a `b`, then `subtype a b`
135+
-- returns `Right b`. This function returns @Left note@ with information
136+
-- about the reason for subtyping failure otherwise.
137+
--
138+
-- Example: @subtype (forall a. a -> a) (Int -> Int)@ returns @Right (Int -> Int)@.
139+
-- subtype :: Var v => Type v -> Type v -> Either Note (Type v)
140+
-- subtype t1 t2 = error "todo"
141+
-- let (t1', t2') = (ABT.vmap TypeVar.Universal t1, ABT.vmap TypeVar.Universal t2)
142+
-- in case Context.runM (Context.subtype t1' t2')
143+
-- (Context.MEnv Context.env0 [] Map.empty True) of
144+
-- Left e -> Left e
145+
-- Right _ -> Right t2
146+
147+
-- | Returns true if @subtype t1 t2@ returns @Right@, false otherwise
148+
-- isSubtype :: Var v => Type v -> Type v -> Bool
149+
-- isSubtype t1 t2 = case subtype t1 t2 of
150+
-- Left _ -> False
151+
-- Right _ -> True
152+
153+
-- | Returns true if the two type are equal, up to alpha equivalence and
154+
-- order of quantifier introduction. Note that alpha equivalence considers:
155+
-- `forall b a . a -> b -> a` and
156+
-- `forall a b . a -> b -> a` to be different types
157+
-- equals :: Var v => Type v -> Type v -> Bool
158+
-- equals t1 t2 = isSubtype t1 t2 && isSubtype t2 t1
129159
isSubtype :: (Var v) => Type v loc -> Type v loc -> Bool
130160
isSubtype t1 t2 =
131161
handleCompilerBug (Context.isSubtype (tvar $ void t1) (tvar $ void t2))
@@ -450,30 +480,12 @@ wellTyped ppe env term =
450480
enable =
451481
Context.PatternMatchCoverageCheckAndKindInferenceSwitch'Enabled
452482

453-
-- | @subtype a b@ is @Right b@ iff @f x@ is well-typed given
454-
-- @x : a@ and @f : b -> t@. That is, if a value of type `a`
455-
-- can be passed to a function expecting a `b`, then `subtype a b`
456-
-- returns `Right b`. This function returns @Left note@ with information
457-
-- about the reason for subtyping failure otherwise.
458-
--
459-
-- Example: @subtype (forall a. a -> a) (Int -> Int)@ returns @Right (Int -> Int)@.
460-
-- subtype :: Var v => Type v -> Type v -> Either Note (Type v)
461-
-- subtype t1 t2 = error "todo"
462-
-- let (t1', t2') = (ABT.vmap TypeVar.Universal t1, ABT.vmap TypeVar.Universal t2)
463-
-- in case Context.runM (Context.subtype t1' t2')
464-
-- (Context.MEnv Context.env0 [] Map.empty True) of
465-
-- Left e -> Left e
466-
-- Right _ -> Right t2
467-
468-
-- | Returns true if @subtype t1 t2@ returns @Right@, false otherwise
469-
-- isSubtype :: Var v => Type v -> Type v -> Bool
470-
-- isSubtype t1 t2 = case subtype t1 t2 of
471-
-- Left _ -> False
472-
-- Right _ -> True
473-
474-
-- | Returns true if the two type are equal, up to alpha equivalence and
475-
-- order of quantifier introduction. Note that alpha equivalence considers:
476-
-- `forall b a . a -> b -> a` and
477-
-- `forall a b . a -> b -> a` to be different types
478-
-- equals :: Var v => Type v -> Type v -> Bool
479-
-- equals t1 t2 = isSubtype t1 t2 && isSubtype t2 t1
483+
-- | Checks if the mismatch between two types is due to a missing delay, if so returns a tag for which type is
484+
-- missing the delay
485+
isMismatchMissingDelay :: (Var v) => Type v loc -> Type v loc -> Maybe (Either (Type v loc) (Type v loc))
486+
isMismatchMissingDelay typeA typeB
487+
| isSubtype (Type.arrow () (Type.ref () BuiltinDecls.unitRef) (typeA $> ())) (typeB $> ()) =
488+
Just (Left typeA)
489+
| isSubtype (ABT.tm (ABT.tm (Type.Ref BuiltinDecls.unitRef) `Type.Arrow` (typeB $> ()))) (typeA $> ()) =
490+
Just (Right typeB)
491+
| otherwise = Nothing

parser-typechecker/src/Unison/Typechecker/TypeError.hs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Unison.Pattern (Pattern)
99
import Unison.Prelude hiding (whenM)
1010
import Unison.Type (Type)
1111
import Unison.Type qualified as Type
12+
import Unison.Typechecker qualified as Typechecker
1213
import Unison.Typechecker.Context qualified as C
1314
import Unison.Typechecker.Extractor qualified as Ex
1415
import Unison.Typechecker.TypeVar (lowerType)
@@ -22,14 +23,21 @@ data BooleanMismatch = CondMismatch | AndMismatch | OrMismatch | GuardMismatch
2223
data ExistentialMismatch = IfBody | ListBody | CaseBody
2324
deriving (Show)
2425

26+
-- | Additional mismatch info, which can be useful for providing hints in error messages.
27+
data MismatchInfo
28+
= MissingDelay
29+
| SuperfluousDelay
30+
deriving (Show, Eq, Ord)
31+
2532
data TypeError v loc
2633
= Mismatch
2734
{ foundType :: C.Type v loc, -- overallType1
2835
expectedType :: C.Type v loc, -- overallType2
2936
foundLeaf :: C.Type v loc, -- leaf1
3037
expectedLeaf :: C.Type v loc, -- leaf2
3138
mismatchSite :: C.Term v loc,
32-
note :: C.ErrorNote v loc
39+
note :: C.ErrorNote v loc,
40+
additionalInfo :: Maybe MismatchInfo
3341
}
3442
| BooleanMismatch
3543
{ getBooleanMismatch :: BooleanMismatch,
@@ -322,12 +330,14 @@ generalMismatch = do
322330
-- it's likely we're missing some arguments from a function.
323331

324332
case Type.cleanups [sub foundType, sub expectedType, sub foundLeaf, sub expectedLeaf] of
325-
[ft, et, fl, el] ->
326-
case mayNeedArgs of
327-
Just needArgs ->
328-
pure $ FunctionUnderApplied ft et fl el mismatchSite n (lowerType <$> needArgs)
329-
Nothing ->
330-
pure $ Mismatch ft et fl el mismatchSite n
333+
[ft, et, fl, el] -> do
334+
let delayMismatch = Typechecker.isMismatchMissingDelay foundType expectedType
335+
case (mayNeedArgs, delayMismatch) of
336+
(_, Just (Left {})) -> pure $ Mismatch ft et fl el mismatchSite n (Just MissingDelay)
337+
(_, Just (Right {})) -> pure $ Mismatch ft et fl el mismatchSite n (Just SuperfluousDelay)
338+
(Just needArgs, _) -> pure $ FunctionUnderApplied ft et fl el mismatchSite n (lowerType <$> needArgs)
339+
_ -> do
340+
pure $ Mismatch ft et fl el mismatchSite n Nothing
331341
_ -> error "generalMismatch: Mismatched type binding"
332342
where
333343
findUnderApplication found expected

unison-cli/src/Unison/LSP/FileAnalysis.hs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Unison.LSP.FileAnalysis
1010
fileAnalysisWorker,
1111
getFileDefLocations,
1212
getFileNames,
13+
analyseNotes,
1314
)
1415
where
1516

@@ -74,6 +75,7 @@ import Unison.Syntax.Name qualified as Name
7475
import Unison.Syntax.Parser qualified as Parser
7576
import Unison.Syntax.TypePrinter qualified as TypePrinter
7677
import Unison.Term qualified as Term
78+
import Unison.Typechecker qualified as Typechecker
7779
import Unison.Typechecker.Context qualified as Context
7880
import Unison.Typechecker.TypeError qualified as TypeError
7981
import Unison.UnisonFile qualified as UF
@@ -224,7 +226,8 @@ fileAnalysisWorker = forever do
224226
analyseFile :: (Lspish m) => (Foldable f) => Uri -> Text -> PPED.PrettyPrintEnvDecl -> f (Note Symbol Ann) -> m ([Diagnostic], [RangedCodeAction])
225227
analyseFile fileUri srcText pped notes = do
226228
let ppe = PPED.suffixifiedPPE pped
227-
(noteDiags, noteActions) <- analyseNotes fileUri ppe (Text.unpack srcText) notes
229+
Env {codebase} <- ask
230+
(noteDiags, noteActions) <- analyseNotes codebase fileUri ppe (Text.unpack srcText) notes
228231
pure (noteDiags, noteActions)
229232

230233
-- | Returns diagnostics which show a warning diagnostic when editing a term that's conflicted in the
@@ -272,18 +275,31 @@ getTokenMap tokens =
272275
)
273276
& fold
274277

275-
analyseNotes :: forall m f. (Lspish m, Foldable f) => Uri -> PrettyPrintEnv -> String -> f (Note Symbol Ann) -> m ([Diagnostic], [RangedCodeAction])
276-
analyseNotes fileUri ppe src notes = do
278+
analyseNotes ::
279+
forall f m.
280+
(Foldable f, MonadIO m) =>
281+
(Codebase.Codebase IO Symbol Ann) ->
282+
Uri ->
283+
PrettyPrintEnv ->
284+
String ->
285+
f (Note Symbol Ann) ->
286+
m ([Diagnostic], [RangedCodeAction])
287+
analyseNotes codebase fileUri ppe src notes = do
277288
foldMapM go notes
278289
where
279290
go :: Note Symbol Ann -> m ([Diagnostic], [RangedCodeAction])
280291
go note = case note of
281292
Result.TypeError errNote@(Context.ErrorNote {cause}) -> do
282293
let typeErr = TypeError.typeErrorFromNote errNote
283294
ranges = case typeErr of
284-
TypeError.Mismatch {mismatchSite} -> singleRange $ ABT.annotation mismatchSite
285-
TypeError.BooleanMismatch {mismatchSite} -> singleRange $ ABT.annotation mismatchSite
286-
TypeError.ExistentialMismatch {mismatchSite} -> singleRange $ ABT.annotation mismatchSite
295+
TypeError.Mismatch {mismatchSite, foundType, expectedType}
296+
| -- If it's a delay mismatch, the error is likely with the block definition (e.g. missing 'do') so we highlight the whole block.
297+
Just _ <- Typechecker.isMismatchMissingDelay foundType expectedType ->
298+
singleRange $ ABT.annotation mismatchSite
299+
-- Otherwise we highlight the leafe nodes of the block
300+
| otherwise -> leafNodeRanges "mismatch" mismatchSite
301+
TypeError.BooleanMismatch {mismatchSite} -> leafNodeRanges "mismatch" mismatchSite
302+
TypeError.ExistentialMismatch {mismatchSite} -> leafNodeRanges "mismatch" mismatchSite
287303
TypeError.FunctionUnderApplied {mismatchSite} -> singleRange $ ABT.annotation mismatchSite
288304
TypeError.FunctionApplication {f} -> singleRange $ ABT.annotation f
289305
TypeError.NotFunctionApplication {f} -> singleRange $ ABT.annotation f
@@ -381,6 +397,10 @@ analyseNotes fileUri ppe src notes = do
381397
Context.OtherBug _s -> todoAnnotation
382398
pure (noteDiagnostic note ranges, [])
383399

400+
leafNodeRanges label mismatchSite = do
401+
let locs = ABT.annotation <$> expressionLeafNodes mismatchSite
402+
(r, rs) <- withNeighbours (locs >>= aToR)
403+
pure (r, (label,) <$> rs)
384404
-- Diagnostics with this return value haven't been properly configured yet.
385405
todoAnnotation = []
386406
singleRange :: Ann -> [(Range, [a])]
@@ -432,7 +452,6 @@ analyseNotes fileUri ppe src notes = do
432452
typeHoleReplacementCodeActions diags v typ
433453
| not (isUserBlank v) = pure []
434454
| otherwise = do
435-
Env {codebase} <- ask
436455
let cleanedTyp = Context.generalizeAndUnTypeVar typ -- TODO: is this right?
437456
refs <- liftIO . Codebase.runTransaction codebase $ Codebase.termsOfType codebase cleanedTyp
438457
forMaybe (toList refs) $ \ref -> runMaybeT $ do
@@ -586,3 +605,38 @@ mkDocumentSymbols parsedFile typecheckedFile =
586605
name <- maybeToList $ Name.parseText (Var.name sym)
587606
range <- maybeToList $ annToRange ann
588607
pure $ UDocumentSymbol name (Just typ) TermSymbol range []
608+
609+
-- | Crawl a term and find the nodes which actually influence its return type. This is useful for narrowing down a giant
610+
-- "This let/do block has the wrong type" into "This specific line returns the wrong type"
611+
-- This is just a heuristic.
612+
expressionLeafNodes :: Term.Term2 vt at ap v a -> [Term.Term2 vt at ap v a]
613+
expressionLeafNodes abt =
614+
case ABT.out abt of
615+
ABT.Var {} -> [abt]
616+
ABT.Cycle r -> expressionLeafNodes r
617+
ABT.Abs _ r -> expressionLeafNodes r
618+
ABT.Tm f -> case f of
619+
Term.Int {} -> [abt]
620+
Term.Nat {} -> [abt]
621+
Term.Float {} -> [abt]
622+
Term.Boolean {} -> [abt]
623+
Term.Text {} -> [abt]
624+
Term.Char {} -> [abt]
625+
Term.Blank {} -> [abt]
626+
Term.Ref {} -> [abt]
627+
Term.Constructor {} -> [abt]
628+
Term.Request {} -> [abt]
629+
-- Not 100% sure whether the error should appear on the handler or action, maybe both?
630+
Term.Handle handler _action -> expressionLeafNodes handler
631+
Term.App _a _b -> [abt]
632+
Term.Ann a _ -> expressionLeafNodes a
633+
Term.List {} -> [abt]
634+
Term.If _cond a b -> expressionLeafNodes a <> expressionLeafNodes b
635+
Term.And {} -> [abt]
636+
Term.Or {} -> [abt]
637+
Term.Lam a -> expressionLeafNodes a
638+
Term.LetRec _isTop _bindings body -> expressionLeafNodes body
639+
Term.Let _isTop _bindings body -> expressionLeafNodes body
640+
Term.Match _a cases -> cases & foldMap \(Term.MatchCase {matchBody}) -> expressionLeafNodes matchBody
641+
Term.TermLink {} -> [abt]
642+
Term.TypeLink {} -> [abt]

0 commit comments

Comments
 (0)