From 99c62b082e880ad9081a5fbcdda552049904b0f8 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 28 May 2026 15:19:26 +0200 Subject: [PATCH 1/3] Add failing tests for Sequential in record/anon-record (#17826) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/FSharp.Compiler.Service.Tests/SynExprTests.fs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs b/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs index 69e85d816b5..8d8a0448902 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 From 30e8ba93a5aefc8786d9900583ad3ffc8f95d1d3 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 28 May 2026 15:32:12 +0200 Subject: [PATCH 2/3] Fix shouldBeParenthesizedInContext for Sequential in record fields (#17826) Adds a match arm so SynExpr.Sequential inside SynExpr.Record or SynExpr.AnonRecd is reported as requiring its parentheses. Without the parens the semicolon is reparsed as a field separator, silently changing semantics or producing a parse error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Service/SynExpr.fs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Compiler/Service/SynExpr.fs b/src/Compiler/Service/SynExpr.fs index d1d0c6eb4c3..8a81d77193e 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('!' | '~'))) From 4e420225983e5347be75c71eb21bc0b3b2740c74 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 28 May 2026 15:54:54 +0200 Subject: [PATCH 3/3] Format and release notes for #17826 fix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) 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 bf4e0a957b5..2a61f766baa 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -63,6 +63,7 @@ * Fix signature conformance: overloaded member with unit parameter `M(())` now matches sig `member M: unit -> unit`. ([Issue #19596](https://github.com/dotnet/fsharp/issues/19596), [PR #19615](https://github.com/dotnet/fsharp/pull/19615)) * 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