diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index a58c104587e..83233af819b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -19,6 +19,7 @@ ### Added * Generate new `Equals` overload to avoid boxing for structural comparison ([PR #16857](https://github.com/dotnet/fsharp/pull/16857)) +* Parser: better recovery for unfinished patterns ([PR #17231](https://github.com/dotnet/fsharp/pull/17231)) ### Changed * Enforce `AttributeTargets.Interface` ([PR #17173](https://github.com/dotnet/fsharp/pull/17173)) diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 96058711135..71547ba6d1b 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -3454,6 +3454,17 @@ headBindingPattern: { let mColonColon = rhs parseState 2 SynPat.ListCons($1, $3, rhs2 parseState 1 3, { ColonColonRange = mColonColon }) } + | headBindingPattern COLON_COLON recover + { let mColonColon = rhs parseState 2 + let pat2 = SynPat.Wild(mColonColon.EndRange) + SynPat.ListCons($1, pat2, rhs2 parseState 1 2, { ColonColonRange = mColonColon }) } + + | headBindingPattern COLON_COLON + { let mColonColon = rhs parseState 2 + reportParseErrorAt mColonColon (FSComp.SR.parsExpectingPattern ()) + let pat2 = SynPat.Wild(mColonColon.EndRange) + SynPat.ListCons($1, pat2, rhs2 parseState 1 2, { ColonColonRange = mColonColon }) } + | tuplePatternElements %prec pat_tuple { let pats, commas = $1 let pats, commas = normalizeTuplePat pats commas @@ -3760,6 +3771,17 @@ parenPattern: { let mColonColon = rhs parseState 2 SynPat.ListCons($1, $3, rhs2 parseState 1 3, { ColonColonRange = mColonColon }) } + | parenPattern COLON_COLON recover + { let mColonColon = rhs parseState 2 + let pat2 = SynPat.Wild(mColonColon.EndRange) + SynPat.ListCons($1, pat2, rhs2 parseState 1 2, { ColonColonRange = mColonColon }) } + + | parenPattern COLON_COLON + { let mColonColon = rhs parseState 2 + reportParseErrorAt mColonColon (FSComp.SR.parsExpectingPattern ()) + let pat2 = SynPat.Wild(mColonColon.EndRange) + SynPat.ListCons($1, pat2, rhs2 parseState 1 2, { ColonColonRange = mColonColon }) } + | constrPattern { $1 } tupleParenPatternElements: diff --git a/tests/service/PatternMatchCompilationTests.fs b/tests/service/PatternMatchCompilationTests.fs index 9b7fe1ff127..5d0645483c7 100644 --- a/tests/service/PatternMatchCompilationTests.fs +++ b/tests/service/PatternMatchCompilationTests.fs @@ -756,7 +756,7 @@ let z as = "(10,7--10,9): Unexpected keyword 'as' in binding"; "(10,5--10,6): Expecting pattern"; "(11,10--11,12): Unexpected keyword 'as' in binding. Expected '=' or other token."; - "(12,9--12,11): Unexpected keyword 'as' in binding"; + "(12,6--12,8): Expecting pattern"; "(13,8--13,10): Unexpected keyword 'as' in binding"; "(14,8--14,10): Unexpected keyword 'as' in binding"; "(15,8--15,10): Unexpected keyword 'as' in pattern. Expected ')' or other token."; @@ -776,6 +776,8 @@ let z as = "(8,29--8,30): This expression was expected to have type\u001d 'unit' \u001dbut here has type\u001d 'int'"; "(9,26--9,27): This expression was expected to have type\u001d 'unit' \u001dbut here has type\u001d 'int'"; "(10,14--10,15): This expression was expected to have type\u001d ''a * 'b' \u001dbut here has type\u001d 'int'"; + "(12,16--12,18): This expression was expected to have type\u001d ''a list' \u001dbut here has type\u001d 'int'"; + "(12,4--12,13): Incomplete pattern matches on this expression. For example, the value '[]' may indicate a case not covered by the pattern(s)."; "(15,4--15,5): The pattern discriminator 'r' is not defined."; "(15,4--15,12): Incomplete pattern matches on this expression." ] @@ -1176,7 +1178,7 @@ let as :? z = "(10,7--10,9): Unexpected keyword 'as' in binding"; "(10,5--10,6): Expecting pattern"; "(11,10--11,12): Unexpected keyword 'as' in binding. Expected '=' or other token."; - "(12,9--12,11): Unexpected keyword 'as' in binding"; + "(12,6--12,8): Expecting pattern"; "(13,8--13,10): Unexpected keyword 'as' in binding"; "(14,8--14,10): Unexpected keyword 'as' in binding"; "(15,13--15,15): Unexpected keyword 'as' in pattern. Expected '(' or other token."; @@ -1204,6 +1206,8 @@ let as :? z = "(9,22--9,26): The type 'unit' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion."; "(10,13--10,14): The type 'i' is not defined."; "(10,10--10,14): The type ''a * 'b' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion."; + "(12,15--12,16): The type 'm' is not defined."; + "(12,12--12,16): The type ''a list' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion."; "(16,4--16,5): The pattern discriminator 't' is not defined."; "(16,14--16,15): The type 'u' is not defined."; "(16,11--16,15): This runtime coercion or type test from type\u001d 'a \u001d to \u001d 'b \u001dinvolves an indeterminate type based on information prior to this program point. Runtime type tests are not allowed on some types. Further type annotations are needed." diff --git a/tests/service/data/SyntaxTree/Pattern/Cons 01.fs b/tests/service/data/SyntaxTree/Pattern/Cons 01.fs new file mode 100644 index 00000000000..1240aaa7f2c --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 01.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| _ :: _ -> () + +() diff --git a/tests/service/data/SyntaxTree/Pattern/Cons 01.fs.bsl b/tests/service/data/SyntaxTree/Pattern/Cons 01.fs.bsl new file mode 100644 index 00000000000..c48f71c8ab2 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 01.fs.bsl @@ -0,0 +1,22 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/Cons 01.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (ListCons + (Wild (4,2--4,3), Wild (4,7--4,8), (4,2--4,8), + { ColonColonRange = (4,4--4,6) }), None, + Const (Unit, (4,12--4,14)), (4,2--4,14), Yes, + { ArrowRange = Some (4,9--4,11) + BarRange = Some (4,0--4,1) })], (3,0--4,14), + { MatchKeyword = (3,0--3,5) + WithKeyword = (3,9--3,13) }), (3,0--4,14)); + 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/Cons 02.fs b/tests/service/data/SyntaxTree/Pattern/Cons 02.fs new file mode 100644 index 00000000000..942db13cf52 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 02.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| _ :: -> () + +() diff --git a/tests/service/data/SyntaxTree/Pattern/Cons 02.fs.bsl b/tests/service/data/SyntaxTree/Pattern/Cons 02.fs.bsl new file mode 100644 index 00000000000..a88741e666c --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 02.fs.bsl @@ -0,0 +1,24 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/Cons 02.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (ListCons + (Wild (4,2--4,3), Wild (4,6--4,6), (4,2--4,6), + { ColonColonRange = (4,4--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 [])) + +(4,4)-(4,6) parse error Expecting pattern diff --git a/tests/service/data/SyntaxTree/Pattern/Cons 03.fs b/tests/service/data/SyntaxTree/Pattern/Cons 03.fs new file mode 100644 index 00000000000..e42f6aaa9f6 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 03.fs @@ -0,0 +1,6 @@ +module Module + +match () with +| _ :: + +() diff --git a/tests/service/data/SyntaxTree/Pattern/Cons 03.fs.bsl b/tests/service/data/SyntaxTree/Pattern/Cons 03.fs.bsl new file mode 100644 index 00000000000..fda13199e97 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 03.fs.bsl @@ -0,0 +1,25 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/Cons 03.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (ListCons + (Wild (4,2--4,3), Wild (4,6--4,6), (4,2--4,6), + { ColonColonRange = (4,4--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 [])) + +(4,4)-(4,6) parse error Expecting pattern +(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/Cons 04.fs b/tests/service/data/SyntaxTree/Pattern/Cons 04.fs new file mode 100644 index 00000000000..6f487679eb8 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 04.fs @@ -0,0 +1,7 @@ +module Module + +match () with +| _ :: +| _ -> () + +() diff --git a/tests/service/data/SyntaxTree/Pattern/Cons 04.fs.bsl b/tests/service/data/SyntaxTree/Pattern/Cons 04.fs.bsl new file mode 100644 index 00000000000..90d727b5f30 --- /dev/null +++ b/tests/service/data/SyntaxTree/Pattern/Cons 04.fs.bsl @@ -0,0 +1,26 @@ +ImplFile + (ParsedImplFileInput + ("/root/Pattern/Cons 04.fs", false, QualifiedNameOfFile Module, [], [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Expr + (Match + (Yes (3,0--3,13), Const (Unit, (3,6--3,8)), + [SynMatchClause + (Or + (ListCons + (Wild (4,2--4,3), Wild (4,6--4,6), (4,2--4,6), + { ColonColonRange = (4,4--4,6) }), 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,4)-(4,6) parse error Expecting pattern