diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index ffc82a89e1..cb2b268f5d 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -66,6 +66,7 @@ * Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `true` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801)) * Parser: recover on unfinished if and binary expressions ([PR #19724](https://github.com/dotnet/fsharp/pull/19724)) +* Fix `SynExpr.shouldBeParenthesizedInContext` to report parentheses as required around `SynExpr.Sequential` expressions used as record or anonymous-record field values, so the IDE "remove unnecessary parentheses" analyzer no longer breaks code like `{| A = ((); B = 3) |}`. ([Issue #17826](https://github.com/dotnet/fsharp/issues/17826)) ### Added diff --git a/src/Compiler/Service/SynExpr.fs b/src/Compiler/Service/SynExpr.fs index d1d0c6eb4c..8a81d77193 100644 --- a/src/Compiler/Service/SynExpr.fs +++ b/src/Compiler/Service/SynExpr.fs @@ -1093,6 +1093,11 @@ module SynExpr = | SynInterpolatedStringPart.FillExpr(qualifiers = Some _) -> true | _ -> false) + // {| A = (1; 2) |} + // { A = (1; 2) } + // Removing the parens would let the semicolon be parsed as a field separator. + | (SynExpr.Record _ | SynExpr.AnonRecd _), SynExpr.Sequential _ -> true + // { (!x) with … } | SynExpr.Record(copyInfo = Some(SynExpr.Paren(expr = Is inner), _)), SynExpr.App(isInfix = false; funcExpr = FuncExpr.SymbolicOperator(StartsWith('!' | '~'))) diff --git a/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs b/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs index 69e85d816b..8d8a044890 100644 --- a/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs @@ -58,6 +58,17 @@ let exprs: obj array list = } " |] + // https://github.com/dotnet/fsharp/issues/17826 + // Sequential expressions used as record / anonymous record field values + // require their enclosing parentheses - otherwise the semicolon is parsed + // as a field separator. + [|[Needed]; "{| A = (1; 2) |}"|] + [|[Needed]; "{ A = (1; 2) }"|] + [|[Needed]; "{| A = ((); B = 3) |}"|] + [|[Needed]; "{| A = (printfn \"hi\"; 3) |}"|] + [|[Needed]; "{ existing with A = (1; 2) }"|] + [|[Needed; Needed]; "{| A = (1; 2); B = (3; 4) |}"|] + [|[Needed; Unneeded]; "{ Outer = ({ Inner = (1; 2) }) }"|] ] #if !NET6_0_OR_GREATER