diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md index 27e8ed7350a..642ff9398f4 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md @@ -29,6 +29,7 @@ * Allow object expression without overrides. ([Language suggestion #632](https://github.com/fsharp/fslang-suggestions/issues/632), [PR #17387](https://github.com/dotnet/fsharp/pull/17387)) * Enable FSharp 9.0 Language Version ([Issue #17497](https://github.com/dotnet/fsharp/issues/17438)), [PR](https://github.com/dotnet/fsharp/pull/17500))) * Enable LanguageFeature.EnforceAttributeTargets in F# 9.0. ([Issue #17514](https://github.com/dotnet/fsharp/issues/17558), [PR #17516](https://github.com/dotnet/fsharp/pull/17558)) +* Parser: better recovery for unfinished patterns ([PR #17231](https://github.com/dotnet/fsharp/pull/17231), [PR #17232](https://github.com/dotnet/fsharp/pull/17232))) * Enable consuming generic arguments defined as `allows ref struct` in C# ([Issue #17597](https://github.com/dotnet/fsharp/issues/17597) ### Changed diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index fa45baa3c16..e355a31e2f3 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -172,6 +172,7 @@ let parse_error_rich = Some(fun (ctxt: ParseErrorContext<_>) -> %type headBindingPattern %type appTypeNullableInParens %type atomTypeNonAtomicDeprecated +%type atomTypeOrAnonRecdType %type atomicExprAfterType %type typedSequentialExprBlock %type atomicExpr @@ -3550,7 +3551,7 @@ headBindingPattern: { let mColonColon = rhs parseState 2 SynPat.ListCons($1, $3, rhs2 parseState 1 3, { ColonColonRange = mColonColon }) } - | headBindingPattern COLON_COLON recover + | headBindingPattern COLON_COLON ends_coming_soon_or_recover { let mColonColon = rhs parseState 2 let pat2 = SynPat.Wild(mColonColon.EndRange) SynPat.ListCons($1, pat2, rhs2 parseState 1 2, { ColonColonRange = mColonColon }) } @@ -3679,7 +3680,21 @@ constrPattern: SynPat.LongIdent(lid, None, None, args, vis, m) } | COLON_QMARK atomTypeOrAnonRecdType %prec pat_isinst - { SynPat.IsInst($2, lhs parseState) } + { let m = unionRanges (rhs parseState 1) $2.Range + SynPat.IsInst($2, m) } + + | COLON_QMARK recover %prec pat_isinst + { let mColon = rhs parseState 1 + if not $2 then + reportParseErrorAt mColon (FSComp.SR.parsExpectingPattern ()) + let ty = SynType.FromParseError(mColon.EndRange) + SynPat.IsInst(ty, mColon) } + + | COLON_QMARK %prec pat_isinst + { let mColon = rhs parseState 1 + let ty = SynType.FromParseError(mColon.EndRange) + reportParseErrorAt mColon (FSComp.SR.parsExpectingType ()) + SynPat.IsInst(ty, mColon) } | atomicPattern { $1 } @@ -3856,6 +3871,8 @@ parenPattern: | parenPattern COLON recover { let mColon = rhs parseState 2 + if not $3 then + reportParseErrorAt mColon (FSComp.SR.parsExpectingPattern ()) let ty = SynType.FromParseError(mColon.EndRange) SynPat.Typed($1, ty, unionRanges $1.Range mColon) } @@ -3869,6 +3886,8 @@ parenPattern: | parenPattern COLON_COLON recover { let mColonColon = rhs parseState 2 + if not $3 then + reportParseErrorAt mColonColon (FSComp.SR.parsExpectingPattern ()) let pat2 = SynPat.Wild(mColonColon.EndRange) SynPat.ListCons($1, pat2, rhs2 parseState 1 2, { ColonColonRange = mColonColon }) } diff --git a/tests/service/data/SyntaxTree/MatchClause/Missing pat 05.fs b/tests/service/data/SyntaxTree/MatchClause/Missing pat 05.fs new file mode 100644 index 00000000000..34b8bb6fb33 --- /dev/null +++ b/tests/service/data/SyntaxTree/MatchClause/Missing pat 05.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| + +() \ No newline at end of file diff --git a/tests/service/data/SyntaxTree/MatchClause/Missing pat 05.fs.bsl b/tests/service/data/SyntaxTree/MatchClause/Missing pat 05.fs.bsl new file mode 100644 index 00000000000..e9693065f21 --- /dev/null +++ b/tests/service/data/SyntaxTree/MatchClause/Missing pat 05.fs.bsl @@ -0,0 +1,18 @@ +ImplFile + (ParsedImplFileInput + ("/root/MatchClause/Missing pat 05.fs", false, QualifiedNameOfFile Module, + [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), [], (3,0--4,1), + { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--4,1)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in expression diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 01.fs b/tests/service/data/SyntaxTree/Pattern/IsInst 01.fs new file mode 100644 index 00000000000..c8d91e018b0 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 01.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| :? T -> () + +() diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 01.fs.bsl b/tests/service/data/SyntaxTree/Pattern/IsInst 01.fs.bsl new file mode 100644 index 00000000000..3b690e54512 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 01.fs.bsl @@ -0,0 +1,21 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/IsInst 01.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (IsInst + (LongIdent (SynLongIdent ([T], [], [None])), (4,2--4,6)), + None, Const (Unit, (4,10--4,12)), (4,2--4,12), Yes, + { ArrowRange = Some (4,7--4,9) + BarRange = Some (4,0--4,1) })], (3,0--4,12), + { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--4,12)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 02.fs b/tests/service/data/SyntaxTree/Pattern/IsInst 02.fs new file mode 100644 index 00000000000..c51d9658d56 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 02.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| :? T + +() diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 02.fs.bsl b/tests/service/data/SyntaxTree/Pattern/IsInst 02.fs.bsl new file mode 100644 index 00000000000..dd1e86fabe4 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 02.fs.bsl @@ -0,0 +1,23 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/IsInst 02.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (IsInst + (LongIdent (SynLongIdent ([T], [], [None])), (4,2--4,6)), + None, ArbitraryAfterError ("patternClauses2", (4,6--4,6)), + (4,2--4,6), Yes, { ArrowRange = None + BarRange = Some (4,0--4,1) })], + (3,0--4,6), { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--4,6)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in pattern matching. Expected '->' or other token. diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 03.fs b/tests/service/data/SyntaxTree/Pattern/IsInst 03.fs new file mode 100644 index 00000000000..1497d452b01 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 03.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| :? -> () + +() diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 03.fs.bsl b/tests/service/data/SyntaxTree/Pattern/IsInst 03.fs.bsl new file mode 100644 index 00000000000..320a8c97f75 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 03.fs.bsl @@ -0,0 +1,22 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/IsInst 03.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (IsInst (FromParseError (4,4--4,4), (4,2--4,4)), None, + Const (Unit, (4,8--4,10)), (4,2--4,10), Yes, + { ArrowRange = Some (4,5--4,7) + BarRange = Some (4,0--4,1) })], (3,0--4,10), + { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--4,10)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(4,2)-(4,4) parse error Expecting type diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 04.fs b/tests/service/data/SyntaxTree/Pattern/IsInst 04.fs new file mode 100644 index 00000000000..27391569dca --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 04.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| :? + +() diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 04.fs.bsl b/tests/service/data/SyntaxTree/Pattern/IsInst 04.fs.bsl new file mode 100644 index 00000000000..1cf58c17dbc --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 04.fs.bsl @@ -0,0 +1,23 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/IsInst 04.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (IsInst (FromParseError (4,4--4,4), (4,2--4,4)), None, + ArbitraryAfterError ("patternClauses2", (4,4--4,4)), + (4,2--4,4), Yes, { ArrowRange = None + BarRange = Some (4,0--4,1) })], + (3,0--4,4), { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--4,4)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(4,2)-(4,4) parse error Expecting type +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in pattern matching. Expected '->' or other token. diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 05.fs b/tests/service/data/SyntaxTree/Pattern/IsInst 05.fs new file mode 100644 index 00000000000..5e7445b6b28 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 05.fs @@ -0,0 +1,7 @@ +module Module + +match () with +| :? +| _ -> () + +() diff --git a/tests/service/data/SyntaxTree/Pattern/IsInst 05.fs.bsl b/tests/service/data/SyntaxTree/Pattern/IsInst 05.fs.bsl new file mode 100644 index 00000000000..136e844c2ef --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/IsInst 05.fs.bsl @@ -0,0 +1,24 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/IsInst 05.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (Or + (IsInst (FromParseError (4,4--4,4), (4,2--4,4)), + Wild (5,2--5,3), (4,2--5,3), { BarRange = (5,0--5,1) }), + None, Const (Unit, (5,7--5,9)), (4,2--5,9), Yes, + { ArrowRange = Some (5,4--5,6) + BarRange = Some (4,0--4,1) })], (3,0--5,9), + { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--5,9)); + Expr (Const (Unit, (7,0--7,2)), (7,0--7,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--7,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(4,2)-(4,4) parse error Expecting type