From 3675ca557c87f0d602f88d6768d50f70714f53dd Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 9 Jun 2026 12:49:30 +0200 Subject: [PATCH 1/5] test(checker): add RED tests for CompiledName on multi-value bindings (#6131) Tests assert FS0755 is emitted at type-check time when [] is applied to a tuple/record/wildcard destructuring binding. These FAIL on current main; sprint 02 will make them GREEN. Baseline on main: 6 failed (the FS0755 tests), 4 succeeded (the still-compiles tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Attributes/CompiledNameMultipleValues.fs | 68 +++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 2 files changed, 69 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs diff --git a/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs b/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs new file mode 100644 index 00000000000..3b574cf640b --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs @@ -0,0 +1,68 @@ +namespace FSharp.Compiler.ComponentTests.Attributes + +open Xunit +open FSharp.Test.Compiler + +module CompiledNameMultipleValues = + + [] + []\nlet a, b = 1, 2")>] + []\nlet (a, b) = 1, 2")>] + []\nlet ((a, b), c) = (1, 2), 3")>] + []\nlet { F = a; G = b } = { F = 1; G = 2 }")>] + []\nlet a, _ = 1, 2")>] + []\nlet (Pair(a, b)) = Pair(1, 2)")>] + []\nlet (Some (a, b)) = Some (1, 2)")>] + []\nlet [| a; b |] = [| 1; 2 |]")>] + []\nlet a :: b = [1; 2]")>] + let ``CompiledName on multi-value let-binding produces FS0755`` (source: string) = + FSharp source + |> typecheck + |> shouldFail + |> withErrorCode 755 + |> ignore + + [] + let ``CompiledName on tuple binding fires FS0755 exactly once`` () = + let source = "module M\n[]\nlet a, b, c = 1, 2, 3" + let result = + FSharp source + |> typecheck + |> shouldFail + + let diag755Count = + result.Output.Diagnostics + |> List.filter (fun d -> d.Error = Error 755) + |> List.length + Assert.Equal(1, diag755Count) + + [] + let ``CompiledName on single-value let-binding still compiles`` () = + FSharp "module M\n[]\nlet a = 1" + |> compile + |> shouldSucceed + |> withWarnings [] + |> ignore + + [] + [] + [] + [] + let ``Multi-value let-binding without CompiledName still compiles`` (source: string) = + FSharp source + |> compile + |> shouldSucceed + |> withWarnings [] + |> ignore + + [] + []\nlet (Some x) = Some 1")>] + []\nlet (Box x) = Box 1")>] + let ``CompiledName on single-value destructure does not fire FS0755`` (source: string) = + let result = FSharp source |> typecheck + let diags = + match result with + | CompilationResult.Success r -> r.Diagnostics + | CompilationResult.Failure r -> r.Diagnostics + let fs755 = diags |> List.filter (fun d -> d.Error = Error 755) + Assert.Empty(fs755) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 954c02b8aca..fe82c647ac1 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -500,6 +500,7 @@ + From 6a8b27098fc44c123bfcbb8305cd4e50751d2122 Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 9 Jun 2026 13:47:52 +0200 Subject: [PATCH 2/5] fix(checker): emit FS0755 for CompiledName on multi-value bindings (#6131) [] let a, b = 1, 2 silently propagated the same compiled name to every value introduced by the destructuring pattern, crashing later with FS0192 / FS2014 (duplicate IL entries) during IL writing. Detect destructuring patterns in TcNormalizedBinding (tuple/record/list/cons/or/ands/wild etc) and emit the existing FS0755 (tcCompiledNameAttributeMisused) diagnostic once, at the binding pattern range, during type checking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Checking/Expressions/CheckExpressions.fs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 4932830a352..cc29f81bf0c 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -11256,6 +11256,47 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt // Use that as the single source of truth. let valAttribs = TcAttrs attrTgt false attrs + // CompiledName cannot be applied to a binding that destructures into multiple + // values (e.g. tuple/record/list patterns). Without this check the same compiled + // name silently propagates to every introduced value, producing duplicate IL + // entries (FS0192/FS2014) far from the source. See dotnet/fsharp#6131. + match valAttribs with + | ValAttribString g WellKnownValAttributes.CompiledNameAttribute _ -> + // Patterns that publish 0 or 1 vals and are not themselves a destructuring shape. + // These do not produce duplicate compiled names, so [] is harmless + // (or a no-op for unit / wildcard) and must not trigger a spurious FS0755. + let rec isSimpleValBindingPat p = + match p with + | SynPat.Named _ + | SynPat.Wild _ + | SynPat.Null _ + | SynPat.IsInst _ + | SynPat.Const(SynConst.Unit, _) -> true + | SynPat.Typed(p, _, _) + | SynPat.Attrib(p, _, _) + | SynPat.Paren(p, _) + | SynPat.FromParseError(p, _) -> isSimpleValBindingPat p + | SynPat.As(l, r, _) -> isSimpleValBindingPat l && isSimpleValBindingPat r + | SynPat.LongIdent(argPats = argPats) -> + // A long-id pattern such as `Some x` or `MyDU(a, b)` publishes one val per + // val-shaped sub-pattern. It is simple only when every argument pattern is + // itself simple - otherwise the destructure publishes multiple vals and the + // CompiledName attribute would be silently propagated to each (FS0192/FS2014). + argPats.Patterns |> List.forall isSimpleValBindingPat + | SynPat.Tuple _ + | SynPat.Record _ + | SynPat.ArrayOrList _ + | SynPat.ListCons _ + | SynPat.Ands _ + | SynPat.Or _ + | SynPat.Const _ + | SynPat.OptionalVal _ + | SynPat.QuoteExpr _ + | SynPat.InstanceMember _ -> false + if not (isSimpleValBindingPat pat) then + errorR(Error(FSComp.SR.tcCompiledNameAttributeMisused(), pat.Range)) + | _ -> () + let retAttribs = let (SynValData(_, SynValInfo(_, SynArgInfo(retAttrs, _, _)), _)) = valSynData retAttrs From 8fd8b56d7c457e75d698bfe2ab6e01697a5a710f Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 9 Jun 2026 15:13:49 +0200 Subject: [PATCH 3/5] docs(release-notes): note FS0755 fix for CompiledName on multi-value bindings (#6131) Adds 11.0.100 release-note entry. Pure docs/formatting follow-up to the type-checker 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 965957dbc0c..66a8b44eed8 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Fix `[]` silently producing duplicate IL entries (FS0192/FS2014) when applied to a multi-value let-binding (e.g. `let a, b = 1, 2`); now emits FS0755 at type-check time. ([Issue #6131](https://github.com/dotnet/fsharp/issues/6131)) * Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.…` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760)) * Deduplicate format specifier locations in computation expressions so editor tooling no longer reports duplicate entries for the same `%` specifier. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791)) * Reject non-function bindings for single-case and partial active pattern names with FS1209, matching the existing multi-case behavior. ([PR #19763](https://github.com/dotnet/fsharp/pull/19763)) From 37e3a34f3bb1ff01b63b2a13441b311483fea407 Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 9 Jun 2026 17:47:39 +0200 Subject: [PATCH 4/5] refactor(checker): use semantic val-count for FS0755 on CompiledName (#6131) Replace the ~30-line syntactic SynPat walker (isSimpleValBindingPat) in TcNormalizedBinding with a single Map.count valSchemes > 1 check inside MakeAndPublishVals. The post-generalization val schemes are the authoritative count of values published by the binding, so the check is exact instead of approximate. This removes false positives the walker produced (single-field record destructure, single-element array, single-binding cons, let a, _ = ...) and closes a false negative on let (x as y) = 1, which previously slipped through the walker and ICE'd in IlxGen.MergeOptions during IL writing. Negative tests added for single-value destructures and active-pattern function bindings; the let (x as y) = 1 regression is added to the positive set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Checking/Expressions/CheckExpressions.fs | 58 ++++++------------- .../Attributes/CompiledNameMultipleValues.fs | 7 ++- 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index cc29f81bf0c..2fec25cf265 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1525,6 +1525,23 @@ let MakeAndPublishVal (cenv: cenv) env (altActualParent, inSig, declKind, valRec vspec let MakeAndPublishVals (cenv: cenv) env (altActualParent, inSig, declKind, valRecInfo, valSchemes, attrs, xmlDoc, literalValue) = + let g = cenv.g + + // [] on a binding that publishes more than one val (e.g. tuple, + // record, list, cons, `let x as y = ...` destructures, multi-case active patterns) + // would silently propagate the same compiled name to every introduced value and + // produce duplicate IL entries (FS0192/FS2014) during codegen. Catch it here where + // the post-generalization val count is authoritative. See dotnet/fsharp#6131. + if Map.count valSchemes > 1 then + match attrs with + | ValAttribString g WellKnownValAttributes.CompiledNameAttribute _ -> + let m = + (Map.toList valSchemes, range0) + ||> List.foldBack (fun (_, ValScheme(id = id)) acc -> + if Range.equals acc range0 then id.idRange else unionRanges id.idRange acc) + errorR(Error(FSComp.SR.tcCompiledNameAttributeMisused(), m)) + | _ -> () + Map.foldBack (fun name (valscheme: ValScheme) values -> Map.add name (MakeAndPublishVal cenv env (altActualParent, inSig, declKind, valRecInfo, valscheme, attrs, xmlDoc, literalValue, false), valscheme.GeneralizedType) values) @@ -11256,47 +11273,6 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt // Use that as the single source of truth. let valAttribs = TcAttrs attrTgt false attrs - // CompiledName cannot be applied to a binding that destructures into multiple - // values (e.g. tuple/record/list patterns). Without this check the same compiled - // name silently propagates to every introduced value, producing duplicate IL - // entries (FS0192/FS2014) far from the source. See dotnet/fsharp#6131. - match valAttribs with - | ValAttribString g WellKnownValAttributes.CompiledNameAttribute _ -> - // Patterns that publish 0 or 1 vals and are not themselves a destructuring shape. - // These do not produce duplicate compiled names, so [] is harmless - // (or a no-op for unit / wildcard) and must not trigger a spurious FS0755. - let rec isSimpleValBindingPat p = - match p with - | SynPat.Named _ - | SynPat.Wild _ - | SynPat.Null _ - | SynPat.IsInst _ - | SynPat.Const(SynConst.Unit, _) -> true - | SynPat.Typed(p, _, _) - | SynPat.Attrib(p, _, _) - | SynPat.Paren(p, _) - | SynPat.FromParseError(p, _) -> isSimpleValBindingPat p - | SynPat.As(l, r, _) -> isSimpleValBindingPat l && isSimpleValBindingPat r - | SynPat.LongIdent(argPats = argPats) -> - // A long-id pattern such as `Some x` or `MyDU(a, b)` publishes one val per - // val-shaped sub-pattern. It is simple only when every argument pattern is - // itself simple - otherwise the destructure publishes multiple vals and the - // CompiledName attribute would be silently propagated to each (FS0192/FS2014). - argPats.Patterns |> List.forall isSimpleValBindingPat - | SynPat.Tuple _ - | SynPat.Record _ - | SynPat.ArrayOrList _ - | SynPat.ListCons _ - | SynPat.Ands _ - | SynPat.Or _ - | SynPat.Const _ - | SynPat.OptionalVal _ - | SynPat.QuoteExpr _ - | SynPat.InstanceMember _ -> false - if not (isSimpleValBindingPat pat) then - errorR(Error(FSComp.SR.tcCompiledNameAttributeMisused(), pat.Range)) - | _ -> () - let retAttribs = let (SynValData(_, SynValInfo(_, SynArgInfo(retAttrs, _, _)), _)) = valSynData retAttrs diff --git a/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs b/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs index 3b574cf640b..9628a248599 100644 --- a/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs +++ b/tests/FSharp.Compiler.ComponentTests/Attributes/CompiledNameMultipleValues.fs @@ -10,11 +10,11 @@ module CompiledNameMultipleValues = []\nlet (a, b) = 1, 2")>] []\nlet ((a, b), c) = (1, 2), 3")>] []\nlet { F = a; G = b } = { F = 1; G = 2 }")>] - []\nlet a, _ = 1, 2")>] []\nlet (Pair(a, b)) = Pair(1, 2)")>] []\nlet (Some (a, b)) = Some (1, 2)")>] []\nlet [| a; b |] = [| 1; 2 |]")>] []\nlet a :: b = [1; 2]")>] + []\nlet (x as y) = 1")>] let ``CompiledName on multi-value let-binding produces FS0755`` (source: string) = FSharp source |> typecheck @@ -58,6 +58,11 @@ module CompiledNameMultipleValues = [] []\nlet (Some x) = Some 1")>] []\nlet (Box x) = Box 1")>] + []\nlet a, _ = 1, 2")>] + []\nlet { F = a } = { F = 1; G = 2 }")>] + []\nlet [| a |] = [| 1 |]")>] + []\nlet a :: _ = [1; 2]")>] + []\nlet (|Even|Odd|) n = if n % 2 = 0 then Even else Odd")>] let ``CompiledName on single-value destructure does not fire FS0755`` (source: string) = let result = FSharp source |> typecheck let diags = From b615381e54bdefe6dbbcd1ecf04edd64dde9b10b Mon Sep 17 00:00:00 2001 From: Copilot Date: Tue, 9 Jun 2026 19:58:53 +0200 Subject: [PATCH 5/5] chore(ci): retrigger build after transient auth-token failures (#6131) All 12 failing jobs on AzDO build 1456244 reported 'Unable to obtain an authenticated token for running job ... dnceng-public'. The 18 sibling jobs (Linux, MacOS, several Windows configs, CheckCodeFormatting) passed against the same commit, confirming the failures are infrastructure-only. Empty commit to force a new CI run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>