Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pattern type check recovery #7711

Merged
merged 24 commits into from
Mar 6, 2020
Merged

Conversation

auduchinok
Copy link
Member

@auduchinok auduchinok commented Oct 8, 2019

The problem this PR solves is almost every pattern type check error currently raises an exception making the rest of the match expression not type checked at all (so nothing of Highlighting, Find Usages, Rename, etc work). An example with named arguments for an active pattern (which is not allowed):

Screenshot 2019-10-08 at 19 04 40

This PR adds recovery to the most of such errors making IDE features work as expected:

Screenshot 2019-10-08 at 19 05 13

The difference is especially evident on match expressions with many clauses, due to all subsequent clauses were skipped on an error.

A naive approach to recover in most cases would be to produce typed Wild patterns (as it's already done in few places) but, due to how match expressions and bindings are compiled to decision trees, in many cases it'd make subsequent match clauses considered never matched, which doesn't look good:

Screenshot 2019-10-08 at 19 19 31

This PR uses a different approach and introduces a new typed pattern for error and a fake Error decision tree test. These added parts of decision trees never match other parts of the tree so errors never make other parts considered never matched.

A tricky part here is extra patterns in the case of union case fields and literals. To prevent any effect on resulting decision tree, the arguments are type checked with fresh inference types and resulting names added to type check environment without creating new typed patterns. Due to variables for these patterns are not present in the tree, dummy unit expressions are created as initializers so Pattern Match Compilation doesn't fail to find them.

Screenshot 2019-10-08 at 19 24 20

An example for unresolved function arguments:

Screenshot 2019-10-08 at 19 54 18

Other cases producing errors, like patterns with attributes or optional values, are all supported too:

Screenshot 2019-10-08 at 19 27 44

@auduchinok auduchinok changed the title Add pattern type check recovery [WIP] Add pattern type check recovery Oct 8, 2019
@Krzysztof-Cieslak
Copy link
Contributor

❤️

@forki
Copy link
Contributor

forki commented Oct 8, 2019

Cool stuff

@cartermp cartermp requested review from TIHan and dsyme October 8, 2019 16:41
@auduchinok auduchinok changed the title [WIP] Add pattern type check recovery Add pattern type check recovery Oct 8, 2019
@auduchinok
Copy link
Member Author

Needs some baseline updates.

Copy link
Contributor

@dsyme dsyme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good, though I'm concerned about the use of catch-all error handlers, are they certainly necessary, or are they for recovery after reported errors?

src/fsharp/TypeChecker.fs Outdated Show resolved Hide resolved
src/fsharp/TypeChecker.fs Outdated Show resolved Hide resolved
src/fsharp/TypeChecker.fs Outdated Show resolved Hide resolved
src/fsharp/TypeChecker.fs Outdated Show resolved Hide resolved
src/fsharp/TypeChecker.fs Outdated Show resolved Hide resolved
@auduchinok
Copy link
Member Author

auduchinok commented Oct 9, 2019

@dsyme These catch-all and forget handlers were added to places where it could fail and we couldn't recover afterwards. I could try listing particular exceptions but in some cases it'd probably be a long list, and since we don't need exception info at recovery point I just don't even look at what exception is.

@dsyme
Copy link
Contributor

dsyme commented Oct 10, 2019

@dsyme These catch-all and forget handlers were added to places where it could fail and we couldn't recover afterwards. I could try listing particular exceptions but in some cases it'd probably be a long list, and since we don't need exception info at recovery point I just don't even look at what exception is.

I feel we do need to dig into this. IIRC the right pattern is this one:

    try something
    with e -> 
        errorRecovery e someRange
        error-result

For example, TcConst may raise error without recovery. Callers to TcConst add recovery via the above. Normally this will result in the error-result but in some cases like StopProcessingException in F# Interactive the error propagation will continue.

@auduchinok
Copy link
Member Author

@dsyme I've added errorRecovery and tried to minimize the diff.

@auduchinok auduchinok closed this Feb 27, 2020
@auduchinok auduchinok reopened this Feb 27, 2020
@auduchinok
Copy link
Member Author

It's ready.
There's a failing SourceBuild_Linux build remaining but it doesn't seem to be related, does it?

@cartermp
Copy link
Contributor

cartermp commented Mar 4, 2020

It's unrelated. The failures there are CI unreliability problems that we have on our .NET linux pools.

@@ -63,12 +63,16 @@ but here has type

neg16.fs(85,8,85,18): typecheck error FS0039: The pattern discriminator 'FooA++' is not defined.

neg16.fs(85,7,85,22): typecheck error FS0025: Incomplete pattern matches on this expression.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is minor, but the code for this test:

  let (|``FooA++``|) (x:int) = x
  let (``FooA++``(x)) = 10

Normally it would error saying that FooA++ is not defined because using "++" in the name is not allowed. But now it will include an additional error saying that the pattern matches are incomplete, but it isn't a valid match construct to begin with.

Again, this is minor and not a blocker.

@@ -4,7 +4,10 @@
// Regression test for FSharp1.0:3744 - Unable to apply attributes on individual patterns in a tupled pattern match let binding - Implementation doesn't match spec
// Test against error emitted when attributes applied within pattern

//<Expects id="FS0683" span="(11,6-11,33)" status="error">Attributes are not allowed within patterns</Expects>
//<Expects id="FS0683" span="(14,6-14,27)" status="error">Attributes are not allowed within patterns</Expects>
//<Expects id="FS0842" span="(14,8-14,23)" status="error">This attribute is not valid for use on this language element</Expects>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better considering it is now showing multiple errors on both attributes along with better ranges.

The only minor knit I have is that it is also giving an error saying the "attribute is not valid for use on this language element" - but the real error is that attributes are just not allowed in patterns.

@@ -1,6 +1,6 @@
// #Regression #Conformance #PatternMatching #Unions
// Verify error if two union rules capture values with different types
//<Expects id="FS0001" status="error" span="(8,7-9,11)">This expression was expected to have type. 'int' .but here has type. 'float'</Expects>
//<Expects id="FS0001" status="error" span="(9,10-9,11)">This expression was expected to have type. 'int' .but here has type. 'float'</Expects>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a much more accurate range :)

| _ -> ())

let names = NameMap.layer names1 names2
let takenNames = Set.union takenNames1 takenNames2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for doing a layer and union here? Could you put in a comment explaining why?

// so type T = Case of name: int * value: int
// | Case(value = v)
// will become
// | Case(_, v)
let result = Array.zeroCreate numArgTys
let extraPatterns = List ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is extraPatterns used for? Could put in a comment explaining that?

Copy link
Contributor

@TIHan TIHan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and a great improvement to error recovery! Only a few comments.

The only thing I would really like is adding more comments in the code as I noted.

@KevinRansom KevinRansom merged commit d1a3d07 into dotnet:master Mar 6, 2020
@KevinRansom
Copy link
Member

@auduchinok, if you can submit a quick PR with the additional clarifications @TIHan asked for please.

Thanks

KEvin

@TIHan
Copy link
Contributor

TIHan commented Mar 6, 2020

@auduchinok I know the PR is merged, but I would really like the comments added to the code that I noted. Is it ok that you create a separate PR for that?

@auduchinok
Copy link
Member Author

@TIHan sure, will do.

@KevinRansom KevinRansom mentioned this pull request Jun 11, 2020
nosami pushed a commit to xamarin/visualfsharp that referenced this pull request Feb 23, 2021
* TcPat recovery: simple pats

* TcPat recovery: report union case and active pattern items before checking args

* TcPat recovery: type check attributes

* TcPat recovery: literal patterns, union case named args

* TcPat recovery: literal patterns, union case args

* TcPat recovery: active patterns named args

* TcPat recovery: union case 2

* TcPat recovery: update tests

* TcPat recovery: different names/types in Or patterns

* Move tests file to common place

* Fix tupled union case args

* Lost change

* Fix test project file include

* Review fix: use errorRecovery when catching exceptions

* Minimize diff

* Minimize diff

* Update FCS project analysis test baselines

* Merge fixes

* Update test

* Fix fsharpqa baselines

* Another fsharpqa baselines update

* Update desktop suite baselines

* Update vsbsl
@auduchinok auduchinok deleted the tcPat-recovery branch September 9, 2022 17:30
if numArgs < numArgTys then
if numArgs <> 0 && numArgTys <> 0 then
errorR (UnionCaseWrongArguments (env.DisplayEnv, numArgTys, numArgs, m))
args @ (List.init (numArgTys - numArgs) (fun _ -> SynPat.Wild (m.MakeSynthetic()))), extraPatterns
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is too complex - all the cases could do with some comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants