From 80b181b372472e19e56a4e0f3254b9c98fe5c3fb Mon Sep 17 00:00:00 2001 From: T-Gro Date: Tue, 16 Jun 2026 15:07:38 +0200 Subject: [PATCH 1/6] Nest augmentation-member closures inside their declaring type under --realsig+ A closure synthesized inside a member declared in an intrinsic augmentation (`type C with member ...`) was emitted as a sibling of `C` in the enclosing module class instead of nested inside `C`. Under --realsig+ a source-private member of `C` is IL `private` (type-scoped), so the sibling closure could not reach it and the CLR raised MethodAccessException at first invocation. Members declared in the type's own body were always nested correctly. GenMethodForBinding now normalizes eenv.cloc to the member's declaring type for all non-extension members under --realsig+, so the closure nests inside the type (idempotent for members that already had it). The program semantics are unchanged and the private member stays IL `private`. Fixes #19933 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/CodeGen/IlxGen.fs | 24 ++ .../Regression_RealsigAugmentationClosure.fs | 210 ++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 4 files changed, 236 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs 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 b89cf11dc7e..3118a23cf32 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 `MethodAccessException` under `--realsig+` when a closure (inner `let rec`, `task`/`async` state machine, or quotation splice) inside a member defined in an intrinsic type augmentation (`type C with member ...`) accesses a `private` member of `C`. The synthesized closure is now nested inside the declaring type instead of beside it in the module class. ([Issue #19933](https://github.com/dotnet/fsharp/issues/19933), [PR #19948](https://github.com/dotnet/fsharp/pull/19948)) * Tooltip "Full name" now shows demangled companion module names (e.g. `MyType.func` instead of `MyTypeModule.func`). ([Issue #17335](https://github.com/dotnet/fsharp/issues/17335), [PR #19867](https://github.com/dotnet/fsharp/pull/19867)) * Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851)) * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index de180807269..4b98ec10f04 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -9519,6 +9519,30 @@ and GenMethodForBinding let g = cenv.g let m = v.Range + // Closures synthesized inside a member body (inner `let rec`, `task`/`async` state + // machines, quotation-splice helpers) are nested in the IL type identified by + // eenv.cloc. Under --realsig+ a source-`private` member compiles to IL `private` + // (type-scoped), so a closure that calls it must nest inside the declaring type, not + // beside it in the module class, or the CLR raises MethodAccessException at runtime. + // + // Members declared in the type's own definition already reach here with the declaring + // type in eenv.cloc, but members declared in an intrinsic augmentation (`type C with + // member ...`) reach here with only the enclosing module in scope, because the + // augmentation is a separate definition group from the type. Normalize eenv.cloc to the + // declaring type for every non-extension member so closure placement is consistent + // (idempotent for members that already have it). Real extension members are compiled as + // static methods in their own module and must not be re-homed. + // + // This only matters under --realsig+: with the legacy --realsig- visibility a + // module-level sibling closure can still reach the (IL `assembly`) member, so the + // placement is left unchanged there to avoid perturbing existing IL. + let eenv = + match v.MemberInfo with + | Some _ when g.realsig && not v.IsExtensionMember -> + let declTref = mspec.MethodRef.DeclaringTypeRef + AddEnclosingToEnv eenv declTref.Enclosing declTref.Name None + | _ -> eenv + // If a method has a witness-passing version of the code, then suppress // the generation of any witness in the non-witness passing version of the code let eenv = diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs new file mode 100644 index 00000000000..0a8b06941c8 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs @@ -0,0 +1,210 @@ +namespace EmittedIL.RealInternalSignature + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +/// Regression tests for dotnet/fsharp#19933. +/// +/// A closure synthesized inside a member body that is declared in an *intrinsic +/// augmentation* (`type C with member ...`) used to be emitted as a sibling of `C` +/// in the enclosing module class, rather than nested inside `C`. Under `--realsig+` +/// a source-`private` member of `C` compiles to IL `private` (type-scoped), so the +/// sibling closure could not reach it and the CLR raised `MethodAccessException` at +/// first invocation. Members declared in the type's own body were always nested +/// correctly; the fix makes augmentation members consistent with them. +/// +/// These programs are legal F# (the type checker accepts private access from any +/// lexical position within the declaring type, including inner lambdas / `task` / +/// quotations). The bug was purely in IL closure placement; the fix does not change +/// access semantics — the private member stays IL `private`. +/// +/// Each test runs under both realsig settings so a regression in either path is +/// caught. The non-inlinable private member is essential: a trivial body is inlined +/// away by the optimizer before codegen, which hides the bug. +module Regression_RealsigAugmentationClosure = + + let private compileOptimized realsig source = + FSharp source + |> withRealInternalSignature realsig + |> asExe + |> withOptimize + |> ignoreWarnings + + let private compileRunSucceeds realsig source = + source |> compileOptimized realsig |> compileExeAndRun |> shouldSucceed |> ignore + + /// Bare inner `let rec` in an augmentation member, calling a type-private static + /// of a generic type. This is the canonical #19933 shape. + [] + let ``Augmentation inner-rec calls type-private static of generic type`` (realsig: bool) = + """module Sample +type Holder<'T>() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type Holder<'T> with + member _.Run() = + let rec h n = if n = 0 then Holder<'T>.Secret() else h (n - 1) + h 5 +[] +let main _ = + Holder.Set 41 + if Holder().Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Same shape on a non-generic type. + [] + let ``Augmentation inner-rec calls type-private static of non-generic type`` (realsig: bool) = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type C with + member _.Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C().Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Type-private *instance* member accessed from an inner-rec in an augmentation. + [] + let ``Augmentation inner-rec calls type-private instance method`` (realsig: bool) = + """module Sample +type C() = + let mutable backing = 0 + member _.Set v = backing <- v + member private _.Secret() = backing + 1 +type C with + member this.Run() = + let rec h n = if n = 0 then this.Secret() else h (n - 1) + h 5 +[] +let main _ = + let c = C() in c.Set 41 + if c.Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// `task { }` state machine in an augmentation member referencing a type-private + /// member directly — no inner-rec; the state machine itself is the closure. + [] + let ``Augmentation task computation expression calls type-private member`` (realsig: bool) = + """module Sample +open System.Threading.Tasks +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type C with + member _.Run() : Task = task { return C.Secret() } +[] +let main _ = + C.Set 41 + if (C().Run()).Result = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// `async { }` variant, to confirm the fix is not task-specific. + [] + let ``Augmentation async computation expression calls type-private member`` (realsig: bool) = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type C with + member _.Run() = async { return C.Secret() } +[] +let main _ = + C.Set 41 + if (C().Run() |> Async.RunSynchronously) = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Mutual recursion (`let rec ... and ...`) in an augmentation member. + [] + let ``Augmentation mutual inner-rec calls type-private member`` (realsig: bool) = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type C with + member _.Run() = + let rec ev n = if n = 0 then C.Secret() else od (n - 1) + and od n = if n = 0 then C.Secret() else ev (n - 1) + ev 5 +[] +let main _ = + C.Set 41 + if C().Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// A trivial private member forced non-inlinable with `[]`, + /// proving the failure is a real CLR access check and not constant folding. + [] + let ``Augmentation inner-rec calls NoCompilerInlining type-private`` (realsig: bool) = + """module Sample +type C() = + [] + static member private Secret() = 1 +type C with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret()) + h 5 0 +[] +let main _ = if C().Run() = 5 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Quotation splice whose value is computed by an inner-rec in the augmentation. + [] + let ``Augmentation quotation splice driven by inner-rec calls type-private`` (realsig: bool) = + """module Sample +open Microsoft.FSharp.Quotations +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type C with + member _.MakeQ() : Expr = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret()) + let v = h 3 0 + <@ %%(Expr.Value v) : int @> +[] +let main _ = + C.Set 9 + match C().MakeQ() with + | Patterns.Value(o, _) -> if unbox o = 30 then 0 else 1 + | _ -> 2 +""" + |> compileRunSucceeds realsig + + /// Type declared inside a namespace (not a module), with the augmentation in the + /// same namespace. Exercises the namespace branch of the closure compile-location. + [] + let ``Augmentation inner-rec on namespace-scoped type calls type-private`` (realsig: bool) = + """namespace MyNs +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 +type C with + member _.Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +module Main = + [] + let main _ = + C.Set 41 + if C().Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 3fc031a525f..b29e2601b82 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -268,6 +268,7 @@ + From 075497666f397d3026a3a4099bb230781d2f15b3 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Tue, 16 Jun 2026 15:08:46 +0200 Subject: [PATCH 2/6] Correct PR number in release note --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3118a23cf32..b0f5888d8de 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,6 +1,6 @@ ### Fixed -* Fix `MethodAccessException` under `--realsig+` when a closure (inner `let rec`, `task`/`async` state machine, or quotation splice) inside a member defined in an intrinsic type augmentation (`type C with member ...`) accesses a `private` member of `C`. The synthesized closure is now nested inside the declaring type instead of beside it in the module class. ([Issue #19933](https://github.com/dotnet/fsharp/issues/19933), [PR #19948](https://github.com/dotnet/fsharp/pull/19948)) +* Fix `MethodAccessException` under `--realsig+` when a closure (inner `let rec`, `task`/`async` state machine, or quotation splice) inside a member defined in an intrinsic type augmentation (`type C with member ...`) accesses a `private` member of `C`. The synthesized closure is now nested inside the declaring type instead of beside it in the module class. ([Issue #19933](https://github.com/dotnet/fsharp/issues/19933), [PR #19955](https://github.com/dotnet/fsharp/pull/19955)) * Tooltip "Full name" now shows demangled companion module names (e.g. `MyType.func` instead of `MyTypeModule.func`). ([Issue #17335](https://github.com/dotnet/fsharp/issues/17335), [PR #19867](https://github.com/dotnet/fsharp/pull/19867)) * Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851)) * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) From d2b26ba20ff794c94ccd5b9d48f0704b27f21305 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Tue, 16 Jun 2026 15:11:28 +0200 Subject: [PATCH 3/6] Add realsig-codegen skill mined from #19933 investigation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/instructions/CodeGen.instructions.md | 2 + .github/skills/realsig-codegen/SKILL.md | 61 ++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 .github/skills/realsig-codegen/SKILL.md diff --git a/.github/instructions/CodeGen.instructions.md b/.github/instructions/CodeGen.instructions.md index 379901aeeef..95bafb12c75 100644 --- a/.github/instructions/CodeGen.instructions.md +++ b/.github/instructions/CodeGen.instructions.md @@ -4,3 +4,5 @@ applyTo: --- Read `docs/representations.md`. + +For `--realsig+` visibility / closure-placement bugs (MethodAccessException under realsig+, IL `private` vs `assembly`, where synthesized closures/state-machines/TLR-lifts nest via `eenv.cloc`), see the `realsig-codegen` skill. diff --git a/.github/skills/realsig-codegen/SKILL.md b/.github/skills/realsig-codegen/SKILL.md new file mode 100644 index 00000000000..0a6965fd742 --- /dev/null +++ b/.github/skills/realsig-codegen/SKILL.md @@ -0,0 +1,61 @@ +--- +name: realsig-codegen +description: Debug and fix --realsig+ (RealInternalSignature) codegen bugs in IlxGen — MethodAccessException / FieldAccessException / TypeAccessException at runtime, IL `private` vs `assembly` visibility, closure and TLR-lift placement (cloc / NestedTypeRefForCompLoc / effectiveCloc / moduleCloc). Use when a program compiles cleanly but crashes only under --realsig+, when IL accessibility differs between realsig modes, or when reasoning about where compiler-synthesized closures/state-machines/quotation helpers are nested. +--- + +# --realsig+ Codegen (IlxGen) + +## Mental model + +- `--realsig-` (legacy default): source `private` → IL `assembly`; `internal` → IL `assembly`. Visibility intent is hidden; almost everything intra-assembly is reachable. +- `--realsig+`: source `private` → IL `private` (type-scoped); `internal` → IL `assembly`. Matches C# expectations. Flag exists since F# 8 GA; documented in `fsc --help`. +- A compiler-synthesized helper (closure for an inner `let rec`, a `task`/`async`/`seq` state machine, a quotation-splice helper, or a TLR-lifted static) is emitted as its own IL type, nested under the type identified by `eenv.cloc`. +- ECMA-335: a **nested** type may access its **enclosing** type's `private` members; a **sibling** nested type may NOT. So under `--realsig+`, a synthesized helper that calls a `private` member must nest **inside** the declaring type, or the CLR throws `MethodAccessException`/`FieldAccessException`/`TypeAccessException` at first invocation. +- This is the usual root cause of "compiles clean, crashes only under `--realsig+`": the source is legal (the type checker allows `private` access from any lexical position within the type, including inner lambdas), but the helper landed beside the type instead of inside it. + +## Key code locations (src/Compiler/CodeGen/IlxGen.fs) + +- `GetIlxClosureFreeVars` builds the closure type-ref: `let ilCloTypeRef = NestedTypeRefForCompLoc eenv.cloc cloName`. Whatever `eenv.cloc` is here decides nesting. +- `GenMethodForBinding` generates a member body; `eenvForMeth` is built from the incoming `eenv`. The body (and its closures) run lazily via `DelayCodeGenMethodForExpr`, capturing that env. +- `AddEnclosingToEnv eenv enclosing name ns` sets `cloc.Enclosing = enclosing @ [name]` (the canonical way to push a type onto cloc). `mspec.MethodRef.DeclaringTypeRef` gives a member's exact IL declaring-type path (`Enclosing` + `Name`). +- `effectiveCloc` / `moduleCloc` (PR #19882): TLR-lifted vals route to a stable module/init-class location; the TLR private-ref guard in `InnerLambdasToTopLevelFuncs.SelectTLRVals` refuses lifting an inner-rec that references a type-scoped `private` val under realsig+ (otherwise it would lose access when lifted to the module). +- `ComputeMemberAccess hidden accessibility realsig` (≈line 485): the single point that maps source accessibility → IL access under realsig. + +## Gotcha: the optimizer hides the bug in minimal repros + +A trivial `private` member (e.g. `= 1`) is **inlined away** by the F# optimizer before codegen, so the call site disappears and the crash vanishes under `--optimize+`. To force a faithful repro, make the member non-inlinable: read mutable state (`backing + 1`) or mark it `[]`. NOTE: `[]` is the F# **optimizer** attribute; `[]` is the **JIT** attribute — for compiler-inlining experiments use `NoCompilerInlining`. + +## Repro methodology + +1. Use a **shipped** SDK fsc as a still-broken control: `& "C:\Program Files\dotnet\sdk\\FSharp\fsc.dll"` (or `dotnet `). Compile the same source with `--realsig+` and `--realsig-`; the bug is the delta. +2. Always pass `--optimize+` (and a non-inlinable private) so the call survives to runtime. +3. Inspect IL with ildasm and read **nesting by indentation**: a `.class … C` at 2-space indent with a child `.class … h@N` at 4-space indent = nested (good). Same indent = sibling (the bug). Confirm with the full type name in field refs, e.g. `M/C/h@8` (nested) vs `M/h@8` (sibling). +4. Runtimeconfig template for running the produced exe: + `{"runtimeOptions":{"tfm":"net10.0","framework":{"name":"Microsoft.NETCore.App","version":"10.0.9"}}}` +5. Compile: `dotnet --target:exe --out:X.dll --realsig+ --optimize+ -r:FSharp.Core.dll X.fs` then `dotnet X.dll`. + +## Instrumentation pattern + +`dprintf` is not in scope in `GetIlxClosureFreeVars`; use `eprintfn` to stderr. To pin where two cases diverge, print `String.concat "/" eenv.cloc.Enclosing`, `v.LogicalName`, `v.IsExtensionMember`, and `v.ApparentEnclosingEntity`, and capture `System.Environment.StackTrace` guarded by a predicate (e.g. `if v.LogicalName = "Run"`). Rebuild FCS + fsc Release, compile a minimal intrinsic-vs-augmentation pair, diff the logs. Remove all instrumentation before committing. + +## Worked example: #19933 (PR #19955) + +Members declared in an **intrinsic augmentation** (`type C with member ...`) reached `GenMethodForBinding` with only the module in `eenv.cloc` (the augmentation is a separate definition group, so the type was not in the realsig dict-routing path that intrinsic members use), so their closures nested in the module as siblings of `C` → `MethodAccessException` under realsig+. Fix: normalize `eenv.cloc` to `mspec.MethodRef.DeclaringTypeRef` for every non-extension member under `g.realsig` at the top of `GenMethodForBinding` (idempotent for members that already have it; skip `v.IsExtensionMember` — real extension members live in their own module). Gating on `g.realsig` avoids perturbing realsig- IL baselines. One fix covers `let rec`, `task`/`async`, and quotation-splice closures because all go through the same `NestedTypeRefForCompLoc eenv.cloc` site. + +## Diagnostics reality (don't mis-cite) + +- `FS0193` is the catch-all default in `CompilerDiagnostics.fs` (`| _ -> 193`), not a specific check. +- `FS0491` = `csMemberIsNotAccessible2` (`FSComp.txt`, raised from `ConstraintSolver.fs`) on overload resolution finding 0 accessible candidates; its "from inner lambda expressions" clause is about **protected**, not **private**. +- There is no existing source-level guard for "private member captured into a lambda": an instance `member private this.Secret` called from `let f () = this.Secret()` inside another member compiles cleanly today. + +## Tests + +- Live under `tests/FSharp.Compiler.ComponentTests/EmittedIL/...`, namespace `EmittedIL.RealInternalSignature`. +- Helpers: `FSharp src |> withRealInternalSignature realsig |> asExe |> withOptimize |> ignoreWarnings`, then `compileExeAndRun |> shouldSucceed` for runtime tests, or `verifyILPresent` / `verifyILNotPresent` for IL-structural assertions. +- Use `[]` so both realsig settings run and a regression in either path is caught. +- realsig baselines are the `*.RealInternalSignatureOn.*` / `*.RealInternalSignatureOff.*` `.il.bsl` pairs; regenerate with `TEST_UPDATE_BSL=1` and pair every diff with its `.fs` + a one-line semantic summary (e.g. "closure renested under declaring type"). +- IL shape changes can shift `tests/ILVerify` baselines — see the `ilverify-failure` skill. + +## Build/run on Windows (when the cwd is content-excluded) + +If the `powershell` tool rejects commands because it validates the first token against the repo path, invoke executables by absolute path: `& "C:\Program Files\Git\cmd\git.exe" …`, `& "C:\Program Files\dotnet\dotnet.exe" build …`. Run the built component-test dll directly: `dotnet exec <…\FSharp.Compiler.ComponentTests.dll> --filter-class "*Pattern*"` (xUnit v3 simple filters: `--filter-class` / `--filter-method` / `--filter-namespace`, `*` wildcard). Ensure the matching shared runtime exists under `\.dotnet\shared\Microsoft.NETCore.App\`. From 6dd57ba95bbaef938ed8b60e417e1b1edd8e0b90 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Tue, 16 Jun 2026 15:17:43 +0200 Subject: [PATCH 4/6] Add name to agentic-workflows agent; add code blocks to realsig-codegen skill The skill-and-agent validator (triggered by adding a skill) requires every agent frontmatter to declare a name; agentic-workflows.agent.md was missing it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/agents/agentic-workflows.agent.md | 1 + .github/skills/realsig-codegen/SKILL.md | 37 ++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md index f7e5eb4f1cd..b84bd918b94 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.github/agents/agentic-workflows.agent.md @@ -1,4 +1,5 @@ --- +name: agentic-workflows description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing disable-model-invocation: true --- diff --git a/.github/skills/realsig-codegen/SKILL.md b/.github/skills/realsig-codegen/SKILL.md index 0a6965fd742..646ebd26c9d 100644 --- a/.github/skills/realsig-codegen/SKILL.md +++ b/.github/skills/realsig-codegen/SKILL.md @@ -30,13 +30,42 @@ A trivial `private` member (e.g. `= 1`) is **inlined away** by the F# optimizer 1. Use a **shipped** SDK fsc as a still-broken control: `& "C:\Program Files\dotnet\sdk\\FSharp\fsc.dll"` (or `dotnet `). Compile the same source with `--realsig+` and `--realsig-`; the bug is the delta. 2. Always pass `--optimize+` (and a non-inlinable private) so the call survives to runtime. 3. Inspect IL with ildasm and read **nesting by indentation**: a `.class … C` at 2-space indent with a child `.class … h@N` at 4-space indent = nested (good). Same indent = sibling (the bug). Confirm with the full type name in field refs, e.g. `M/C/h@8` (nested) vs `M/h@8` (sibling). -4. Runtimeconfig template for running the produced exe: - `{"runtimeOptions":{"tfm":"net10.0","framework":{"name":"Microsoft.NETCore.App","version":"10.0.9"}}}` -5. Compile: `dotnet --target:exe --out:X.dll --realsig+ --optimize+ -r:FSharp.Core.dll X.fs` then `dotnet X.dll`. +4. Minimal repro shape (crashes only under `--realsig+`): + +```fsharp +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + static member private Secret() = backing + 1 // non-inlinable -> survives to runtime +type C with // intrinsic augmentation + member _.Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +``` + +5. Compile and run (runtimeconfig pins the shared runtime): + +```bash +dotnet --target:exe --out:X.dll --realsig+ --optimize+ -r:FSharp.Core.dll X.fs +# X.runtimeconfig.json: +# {"runtimeOptions":{"tfm":"net10.0","framework":{"name":"Microsoft.NETCore.App","version":"10.0.9"}}} +dotnet X.dll +``` ## Instrumentation pattern -`dprintf` is not in scope in `GetIlxClosureFreeVars`; use `eprintfn` to stderr. To pin where two cases diverge, print `String.concat "/" eenv.cloc.Enclosing`, `v.LogicalName`, `v.IsExtensionMember`, and `v.ApparentEnclosingEntity`, and capture `System.Environment.StackTrace` guarded by a predicate (e.g. `if v.LogicalName = "Run"`). Rebuild FCS + fsc Release, compile a minimal intrinsic-vs-augmentation pair, diff the logs. Remove all instrumentation before committing. +`dprintf` is not in scope in `GetIlxClosureFreeVars`; use `eprintfn` to stderr. To pin where two cases diverge, log the closure's enclosing cloc and the member identity, and capture a stack trace guarded by a predicate: + +```fsharp +// in GenMethodForBinding, after `let m = v.Range` +eprintfn "MFBDBG v=%s ext=%b cloc=[%s] apparent=%s" + v.LogicalName v.IsExtensionMember + (String.concat "/" eenv.cloc.Enclosing) + (match v.ApparentEnclosingEntity with Parent e -> e.LogicalName | ParentNone -> "") +if v.LogicalName = "Run" then eprintfn "STACK:\n%s" System.Environment.StackTrace +``` + +Rebuild FCS + fsc Release, compile a minimal intrinsic-vs-augmentation pair, diff the logs. Remove all instrumentation before committing. ## Worked example: #19933 (PR #19955) From 7ec8e3e866632af779b474c2f26c7f628fbc0998 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Tue, 16 Jun 2026 17:20:43 +0200 Subject: [PATCH 5/6] Strengthen #19933 tests: IL-structural locks + many more augmentation shapes Adversarial review (5 models) found the original tests under-powered: runtime-only (no IL-nesting assertion, so they passed even on the buggy compiler under realsig-), the instance-member case used an inlinable private that hid the bug, and most member kinds were uncovered. - Add Regression_RealsigAugmentationClosure_StructuralAssertions.fs: under --realsig+ assert the closure nests under the declaring type (verifyILPresent "Type/closure@") and is not a module sibling (verifyILNotPresent). These FAIL on the pre-fix compiler and PASS on the fix, so they actually guard the IL shape. - Mark every private member [] so the call survives the optimizer (the previous instance test guarded nothing). - Cover property getter/setter, indexer, static method, operator, seq, mutual rec, nested closures, generic method (typar threading), two-type closure-name collision, record/DU, nested-module and namespace-scoped types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Regression_RealsigAugmentationClosure.fs | 287 ++++++++++++++++-- ...ugmentationClosure_StructuralAssertions.fs | 182 +++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 3 files changed, 445 insertions(+), 25 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs index 0a8b06941c8..8d4631a5c23 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs @@ -4,24 +4,29 @@ open Xunit open FSharp.Test open FSharp.Test.Compiler -/// Regression tests for dotnet/fsharp#19933. +/// Runtime regression tests for dotnet/fsharp#19933. /// -/// A closure synthesized inside a member body that is declared in an *intrinsic -/// augmentation* (`type C with member ...`) used to be emitted as a sibling of `C` -/// in the enclosing module class, rather than nested inside `C`. Under `--realsig+` -/// a source-`private` member of `C` compiles to IL `private` (type-scoped), so the -/// sibling closure could not reach it and the CLR raised `MethodAccessException` at -/// first invocation. Members declared in the type's own body were always nested +/// A closure synthesized inside a member declared in an *intrinsic augmentation* +/// (`type C with member ...`) used to be emitted as a sibling of `C` in the +/// enclosing module class rather than nested inside `C`. Under `--realsig+` a +/// source-`private` member of `C` compiles to IL `private` (type-scoped), so the +/// sibling closure could not reach it and the CLR raised `MethodAccessException` +/// at first invocation. Members declared in the type's own body were always nested /// correctly; the fix makes augmentation members consistent with them. /// /// These programs are legal F# (the type checker accepts private access from any /// lexical position within the declaring type, including inner lambdas / `task` / -/// quotations). The bug was purely in IL closure placement; the fix does not change -/// access semantics — the private member stays IL `private`. +/// `seq` / quotations). The bug was purely in IL closure placement; the fix does +/// not change access semantics — the private member stays IL `private`. +/// +/// IMPORTANT: every private member here is marked `[]` (or +/// reads non-inlinable state). A trivial private body is inlined by the optimizer +/// before codegen, which removes the call site and HIDES the bug — such a test +/// would pass even on the buggy compiler and guard nothing. The IL-nesting shape +/// is additionally locked by `Regression_RealsigAugmentationClosure_StructuralAssertions.fs`. /// /// Each test runs under both realsig settings so a regression in either path is -/// caught. The non-inlinable private member is essential: a trivial body is inlined -/// away by the optimizer before codegen, which hides the bug. +/// caught at runtime. module Regression_RealsigAugmentationClosure = let private compileOptimized realsig source = @@ -34,14 +39,15 @@ module Regression_RealsigAugmentationClosure = let private compileRunSucceeds realsig source = source |> compileOptimized realsig |> compileExeAndRun |> shouldSucceed |> ignore - /// Bare inner `let rec` in an augmentation member, calling a type-private static - /// of a generic type. This is the canonical #19933 shape. + /// Canonical #19933 shape: bare inner `let rec` in an augmentation member of a + /// generic type, calling a type-private static. [] let ``Augmentation inner-rec calls type-private static of generic type`` (realsig: bool) = """module Sample type Holder<'T>() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type Holder<'T> with member _.Run() = @@ -61,6 +67,7 @@ let main _ = type C() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type C with member _.Run() = @@ -73,13 +80,17 @@ let main _ = """ |> compileRunSucceeds realsig - /// Type-private *instance* member accessed from an inner-rec in an augmentation. + /// Type-private *instance* method accessed from an inner-rec in an augmentation. + /// `[]` is essential — without it the trivial instance getter + /// inlines to an `assembly` field read and the test passes even on the buggy + /// compiler. [] let ``Augmentation inner-rec calls type-private instance method`` (realsig: bool) = """module Sample type C() = let mutable backing = 0 member _.Set v = backing <- v + [] member private _.Secret() = backing + 1 type C with member this.Run() = @@ -92,8 +103,104 @@ let main _ = """ |> compileRunSucceeds realsig - /// `task { }` state machine in an augmentation member referencing a type-private - /// member directly — no inner-rec; the state machine itself is the closure. + /// Augmentation property getter with an inner-rec calling a type-private member. + [] + let ``Augmentation property getter inner-rec calls type-private`` (realsig: bool) = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + member _.Prop = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C().Prop = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Augmentation property setter with an inner-rec calling a type-private member. + [] + let ``Augmentation property setter inner-rec calls type-private`` (realsig: bool) = + """module Sample +type C() = + static let mutable result = 0 + static member Get() = result + [] + static member private Secret(v) = v + 1 + static member internal Store v = result <- v +type C with + member _.Prop + with set (v: int) = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(v)) + C.Store(h 1 0) +[] +let main _ = + let c = C() + c.Prop <- 41 + if C.Get() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Augmentation indexed property (`Item with get(i)`). + [] + let ``Augmentation indexer inner-rec calls type-private`` (realsig: bool) = + """module Sample +type C() = + [] + static member private Secret(i) = i + 1 +type C with + member _.Item + with get (i: int) = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(i)) + h 1 0 +[] +let main _ = if C().[41] = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Augmentation STATIC method (not instance) with an inner-rec calling a private. + [] + let ``Augmentation static method inner-rec calls type-private`` (realsig: bool) = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + static member Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C.Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Operator (`static member (+)`) declared in an augmentation. + [] + let ``Augmentation operator inner-rec calls type-private`` (realsig: bool) = + """module Sample +type C(v: int) = + member _.V = v + [] + static member private Secret(x: int) = x + 1 +type C with + static member (+) (a: C, b: C) = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(a.V)) + C(h b.V 0) +[] +let main _ = if (C(41) + C(1)).V = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// `task { }` state machine in an augmentation referencing a type-private member. [] let ``Augmentation task computation expression calls type-private member`` (realsig: bool) = """module Sample @@ -101,6 +208,7 @@ open System.Threading.Tasks type C() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type C with member _.Run() : Task = task { return C.Secret() } @@ -111,13 +219,14 @@ let main _ = """ |> compileRunSucceeds realsig - /// `async { }` variant, to confirm the fix is not task-specific. + /// `async { }` variant. [] let ``Augmentation async computation expression calls type-private member`` (realsig: bool) = """module Sample type C() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type C with member _.Run() = async { return C.Secret() } @@ -128,6 +237,24 @@ let main _ = """ |> compileRunSucceeds realsig + /// `seq { }` state machine in an augmentation. + [] + let ``Augmentation seq computation expression calls type-private member`` (realsig: bool) = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + member _.Run() = seq { yield C.Secret() } +[] +let main _ = + C.Set 41 + if (C().Run() |> Seq.head) = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + /// Mutual recursion (`let rec ... and ...`) in an augmentation member. [] let ``Augmentation mutual inner-rec calls type-private member`` (realsig: bool) = @@ -135,6 +262,7 @@ let main _ = type C() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type C with member _.Run() = @@ -148,20 +276,107 @@ let main _ = """ |> compileRunSucceeds realsig - /// A trivial private member forced non-inlinable with `[]`, - /// proving the failure is a real CLR access check and not constant folding. + /// Nested closures (a lambda inside the inner-rec) touching a type-private member. [] - let ``Augmentation inner-rec calls NoCompilerInlining type-private`` (realsig: bool) = + let ``Augmentation nested closures call type-private member`` (realsig: bool) = """module Sample type C() = + static let mutable backing = 0 + static member Set v = backing <- v [] - static member private Secret() = 1 + static member private Secret() = backing + 1 type C with member _.Run() = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret()) + let rec h n = + let inner () = C.Secret() + if n = 0 then inner () else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C().Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Generic class with a generic augmentation method whose closure captures both + /// the class typar 'T and the method typar 'U and calls a type-private member. + [] + let ``Augmentation generic method threads typars and calls type-private`` (realsig: bool) = + """module Sample +type Holder<'T>() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type Holder<'T> with + member _.M<'U>(u: 'U) = + let rec h n (acc: 'U) = if n = 0 then (acc, Holder<'T>.Secret()) else h (n - 1) acc + h 5 u +[] +let main _ = + Holder.Set 41 + let (_, s) = Holder().M("x") + if s = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Two augmentation members of DIFFERENT generic types in the same module whose + /// inner-recs share the local name `h` — verifies no IL type-name collision after + /// re-homing the closures under their respective types. + [] + let ``Augmentations on two types with same closure name do not collide`` (realsig: bool) = + """module Sample +type Alpha<'T>() = + [] + static member private S() = 1 +type Beta<'U>() = + [] + static member private S() = 2 +type Alpha<'T> with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Alpha<'T>.S()) + h 5 0 +type Beta<'U> with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Beta<'U>.S()) h 5 0 [] -let main _ = if C().Run() = 5 then 0 else 1 +let main _ = + if Alpha().Run() = 5 && Beta().Run() = 10 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Record-type augmentation. + [] + let ``Augmentation on record type inner-rec calls type-private`` (realsig: bool) = + """module Sample +type R = { X: int } with + [] + static member private Secret(v) = v + 1 +type R with + member this.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + R.Secret(this.X)) + h 1 0 +[] +let main _ = if { X = 41 }.Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// DU-type augmentation. + [] + let ``Augmentation on DU type inner-rec calls type-private`` (realsig: bool) = + """module Sample +type D = + | A of int + [] + static member private Secret(v) = v + 1 +type D with + member this.Run() = + let x = match this with A v -> v + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + D.Secret(x)) + h 1 0 +[] +let main _ = if (A 41).Run() = 42 then 0 else 1 """ |> compileRunSucceeds realsig @@ -173,6 +388,7 @@ open Microsoft.FSharp.Quotations type C() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type C with member _.MakeQ() : Expr = @@ -188,14 +404,14 @@ let main _ = """ |> compileRunSucceeds realsig - /// Type declared inside a namespace (not a module), with the augmentation in the - /// same namespace. Exercises the namespace branch of the closure compile-location. + /// Type declared inside a namespace (not a module), augmentation in same namespace. [] let ``Augmentation inner-rec on namespace-scoped type calls type-private`` (realsig: bool) = """namespace MyNs type C() = static let mutable backing = 0 static member Set v = backing <- v + [] static member private Secret() = backing + 1 type C with member _.Run() = @@ -206,5 +422,26 @@ module Main = let main _ = C.Set 41 if C().Run() = 42 then 0 else 1 +""" + |> compileRunSucceeds realsig + + /// Type declared in a nested module, augmentation in the same nested module. + [] + let ``Augmentation inner-rec on nested-module type calls type-private`` (realsig: bool) = + """module Top +module Inner = + type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 + type C with + member _.Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + Inner.C.Set 41 + if Inner.C().Run() = 42 then 0 else 1 """ |> compileRunSucceeds realsig diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs new file mode 100644 index 00000000000..85d957c3914 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs @@ -0,0 +1,182 @@ +namespace EmittedIL.RealInternalSignature + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +/// IL-structural regression locks for dotnet/fsharp#19933. +/// +/// The runtime tests in `Regression_RealsigAugmentationClosure.fs` confirm the +/// programs no longer crash, but a runtime-only test is a weak guard: it would pass +/// even if the IL silently regressed (under `--realsig-` the closure can be a module +/// sibling and still work because the member is IL `assembly`). These tests pin the +/// fix's actual property under `--realsig+`: the synthesized closure is nested INSIDE +/// the declaring type (`Sample/C/@N`), NOT a sibling in the module class +/// (`Sample/@N`). +/// +/// Each assertion below FAILS on the pre-fix/shipped compiler (which emits the module +/// sibling) and PASSES on the fixed compiler — so it genuinely guards the fix. +module Regression_RealsigAugmentationClosure_StructuralAssertions = + + /// Compile under --realsig+ --optimize+, assert the closure nests under the type + /// (present) and is not a module sibling (absent), then run. + let private assertNestedAndRun (present: string list) (absent: string list) source = + let result = + FSharp source + |> withRealInternalSignature true + |> asExe + |> withOptimize + |> ignoreWarnings + |> compile + |> shouldSucceed + result |> verifyILPresent present + result |> verifyILNotPresent absent + result |> run |> shouldSucceed |> ignore + + [] + let ``Non-generic augmentation closure nests under the type`` () = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + member _.Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C().Run() = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] + + [] + let ``Generic augmentation closure nests under the generic type and threads the typar`` () = + """module Sample +type Holder<'T>() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type Holder<'T> with + member _.Run() = + let rec h n = if n = 0 then Holder<'T>.Secret() else h (n - 1) + h 5 +[] +let main _ = + Holder.Set 41 + if Holder().Run() = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/Holder`1/h@" ] [ "Sample/h@" ] + + [] + let ``Property-getter augmentation closure nests under the type`` () = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + member _.Prop = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C().Prop = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] + + [] + let ``Indexer augmentation closure nests under the type`` () = + """module Sample +type C() = + [] + static member private Secret(i) = i + 1 +type C with + member _.Item + with get (i: int) = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(i)) + h 1 0 +[] +let main _ = if C().[41] = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] + + [] + let ``Operator augmentation closure nests under the type`` () = + """module Sample +type C(v: int) = + member _.V = v + [] + static member private Secret(x: int) = x + 1 +type C with + static member (+) (a: C, b: C) = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(a.V)) + C(h b.V 0) +[] +let main _ = if (C(41) + C(1)).V = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] + + [] + let ``Static-method augmentation closure nests under the type`` () = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + static member Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C.Run() = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] + + [] + let ``Seq state-machine augmentation closure nests under the type`` () = + """module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + member _.Run() = seq { yield C.Secret() } +[] +let main _ = + C.Set 41 + if (C().Run() |> Seq.head) = 42 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/C/Run@" ] [ "Sample/Run@" ] + + [] + let ``Two-type augmentations with same closure name nest under their own types`` () = + """module Sample +type Alpha<'T>() = + [] + static member private S() = 1 +type Beta<'U>() = + [] + static member private S() = 2 +type Alpha<'T> with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Alpha<'T>.S()) + h 5 0 +type Beta<'U> with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Beta<'U>.S()) + h 5 0 +[] +let main _ = + if Alpha().Run() = 5 && Beta().Run() = 10 then 0 else 1 +""" + |> assertNestedAndRun [ "Sample/Alpha`1/h@"; "Sample/Beta`1/'h@" ] [ "Sample/h@" ] diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index b29e2601b82..35233249add 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -269,6 +269,7 @@ + From af467f5976259c3dcf5bfcb5930cd4d622af1b20 Mon Sep 17 00:00:00 2001 From: T-Gro Date: Wed, 17 Jun 2026 10:47:13 +0200 Subject: [PATCH 6/6] Consolidate #19933 tests: MemberData matrix + .bsl baseline (test-quality review) A 5-model test-quality review found the prior tests duplicated source across two files, used a vacuous --realsig- matrix, and reinvented framework helpers. - Merge runtime + structural files into one MemberData-driven Theory: each shape is asserted structurally (closure nests under the declaring type, not a module sibling) AND run. 23 shapes as data rows + 1 --realsig- smoke (defense-in-depth only; the fix is gated on realsig+, so realsig- IL is identical before/after). - Add 3 shapes the review found uncovered and confirmed crash-on-shipped/fixed-on-PR: override member, secondary constructor, struct augmentation. - Harden the two-type and async absent-fragments against the closure-name disambiguator ('h@N-1'). - Add an AugmentationClosureNesting.fs FileInlineData baseline (Realsig=Both) in Inlining.fs (idiomatic EmittedIL lock) snapshotting both the realsig+ nesting (closure nested in C, member IL private) and the realsig- lowering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Inlining/AugmentationClosureNesting.fs | 14 + ...Nesting.fs.RealInternalSignatureOff.il.bsl | 190 ++++++ ...eNesting.fs.RealInternalSignatureOn.il.bsl | 248 ++++++++ .../EmittedIL/Inlining/Inlining.fs | 9 + .../Regression_RealsigAugmentationClosure.fs | 543 +++++++++--------- ...ugmentationClosure_StructuralAssertions.fs | 182 ------ .../FSharp.Compiler.ComponentTests.fsproj | 1 - 7 files changed, 724 insertions(+), 463 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOff.il.bsl create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOn.il.bsl delete mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs new file mode 100644 index 00000000000..a1479c7df12 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs @@ -0,0 +1,14 @@ +module Sample +type C() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type C with + member _.Run() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 +[] +let main _ = + C.Set 41 + if C().Run() = 42 then 0 else 1 diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOff.il.bsl new file mode 100644 index 00000000000..4664e389e84 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOff.il.bsl @@ -0,0 +1,190 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly assembly +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32, + int32, + int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ) + + + + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.exe + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed Sample + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class auto ansi serializable nested public C + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field static assembly int32 backing + .field static assembly int32 init@2 + .method public specialname rtspecialname instance void .ctor() cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: callvirt instance void [runtime]System.Object::.ctor() + IL_0006: ldarg.0 + IL_0007: pop + IL_0008: ret + } + + .method public static void Set(int32 v) cil managed + { + + .maxstack 8 + IL_0000: nop + IL_0001: volatile. + IL_0003: ldsfld int32 Sample/C::init@2 + IL_0008: ldc.i4.1 + IL_0009: bge.s IL_0014 + + IL_000b: call void [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::FailStaticInit() + IL_0010: nop + IL_0011: nop + IL_0012: br.s IL_0015 + + IL_0014: nop + IL_0015: ldarg.0 + IL_0016: stsfld int32 Sample/C::backing + IL_001b: ret + } + + .method assembly static int32 Secret() cil managed + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoCompilerInliningAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: nop + IL_0001: volatile. + IL_0003: ldsfld int32 Sample/C::init@2 + IL_0008: ldc.i4.1 + IL_0009: bge.s IL_0014 + + IL_000b: call void [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::FailStaticInit() + IL_0010: nop + IL_0011: nop + IL_0012: br.s IL_0015 + + IL_0014: nop + IL_0015: ldsfld int32 Sample/C::backing + IL_001a: ldc.i4.1 + IL_001b: add + IL_001c: ret + } + + .method public hidebysig instance int32 Run() cil managed + { + + .maxstack 8 + IL_0000: ldc.i4.5 + IL_0001: call int32 Sample::h@9(int32) + IL_0006: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 ''.$Sample::init@ + IL_0006: ldsfld int32 ''.$Sample::init@ + IL_000b: pop + IL_000c: ret + } + + } + + .method public static int32 main(string[] _arg1) cil managed + { + .entrypoint + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 ''.$Sample::init@ + IL_0006: ldsfld int32 ''.$Sample::init@ + IL_000b: pop + IL_000c: ldc.i4.s 41 + IL_000e: call void Sample/C::Set(int32) + IL_0013: nop + IL_0014: nop + IL_0015: newobj instance void Sample/C::.ctor() + IL_001a: callvirt instance int32 Sample/C::Run() + IL_001f: ldc.i4.s 42 + IL_0021: bne.un.s IL_0025 + + IL_0023: ldc.i4.0 + IL_0024: ret + + IL_0025: ldc.i4.1 + IL_0026: ret + } + + .method assembly static int32 h@9(int32 n) cil managed + { + + .maxstack 8 + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: brtrue.s IL_000a + + IL_0004: call int32 Sample/C::Secret() + IL_0009: ret + + IL_000a: ldarg.0 + IL_000b: ldc.i4.1 + IL_000c: sub + IL_000d: starg.s n + IL_000f: br.s IL_0000 + } + +} + +.class private abstract auto ansi sealed ''.$Sample + extends [runtime]System.Object +{ + .field static assembly int32 init@ + .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 Sample/C::backing + IL_0006: ldc.i4.1 + IL_0007: volatile. + IL_0009: stsfld int32 Sample/C::init@2 + IL_000e: ret + } + +} + + + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOn.il.bsl new file mode 100644 index 00000000000..b4e2969a6d7 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/AugmentationClosureNesting.fs.RealInternalSignatureOn.il.bsl @@ -0,0 +1,248 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly assembly +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32, + int32, + int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ) + + + + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.exe + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed Sample + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class auto ansi serializable nested public C + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .class auto ansi serializable sealed nested assembly beforefieldinit h@9 + extends class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 + { + .field static assembly initonly class Sample/C/h@9 @_instance + .method assembly specialname rtspecialname instance void .ctor() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::.ctor() + IL_0006: ret + } + + .method public strict virtual instance int32 Invoke(int32 n) cil managed + { + + .maxstack 8 + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: brtrue.s IL_000a + + IL_0004: call int32 Sample/C::Secret() + IL_0009: ret + + IL_000a: ldarg.1 + IL_000b: ldc.i4.1 + IL_000c: sub + IL_000d: starg.s n + IL_000f: br.s IL_0000 + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 10 + IL_0000: newobj instance void Sample/C/h@9::.ctor() + IL_0005: stsfld class Sample/C/h@9 Sample/C/h@9::@_instance + IL_000a: ret + } + + } + + .field static assembly int32 backing + .field static assembly int32 init@2 + .method public specialname rtspecialname instance void .ctor() cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: callvirt instance void [runtime]System.Object::.ctor() + IL_0006: ldarg.0 + IL_0007: pop + IL_0008: ret + } + + .method public static void Set(int32 v) cil managed + { + + .maxstack 8 + IL_0000: nop + IL_0001: volatile. + IL_0003: ldsfld int32 Sample/C::init@2 + IL_0008: ldc.i4.1 + IL_0009: bge.s IL_0014 + + IL_000b: call void [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::FailStaticInit() + IL_0010: nop + IL_0011: nop + IL_0012: br.s IL_0015 + + IL_0014: nop + IL_0015: ldarg.0 + IL_0016: stsfld int32 Sample/C::backing + IL_001b: ret + } + + .method private static int32 Secret() cil managed + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoCompilerInliningAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: nop + IL_0001: volatile. + IL_0003: ldsfld int32 Sample/C::init@2 + IL_0008: ldc.i4.1 + IL_0009: bge.s IL_0014 + + IL_000b: call void [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::FailStaticInit() + IL_0010: nop + IL_0011: nop + IL_0012: br.s IL_0015 + + IL_0014: nop + IL_0015: ldsfld int32 Sample/C::backing + IL_001a: ldc.i4.1 + IL_001b: add + IL_001c: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 ''.$Sample::init@ + IL_0006: ldsfld int32 ''.$Sample::init@ + IL_000b: pop + IL_000c: ret + } + + .method assembly static void staticInitialization@() cil managed + { + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 Sample/C::backing + IL_0006: ldc.i4.1 + IL_0007: volatile. + IL_0009: stsfld int32 Sample/C::init@2 + IL_000e: ret + } + + .method public hidebysig instance int32 Run() cil managed + { + + .maxstack 4 + .locals init (class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 V_0) + IL_0000: ldsfld class Sample/C/h@9 Sample/C/h@9::@_instance + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: ldc.i4.5 + IL_0008: tail. + IL_000a: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::Invoke(!0) + IL_000f: ret + } + + } + + .method public static int32 main(string[] _arg1) cil managed + { + .entrypoint + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 ''.$Sample::init@ + IL_0006: ldsfld int32 ''.$Sample::init@ + IL_000b: pop + IL_000c: ldc.i4.s 41 + IL_000e: call void Sample/C::Set(int32) + IL_0013: nop + IL_0014: nop + IL_0015: newobj instance void Sample/C::.ctor() + IL_001a: callvirt instance int32 Sample/C::Run() + IL_001f: ldc.i4.s 42 + IL_0021: bne.un.s IL_0025 + + IL_0023: ldc.i4.0 + IL_0024: ret + + IL_0025: ldc.i4.1 + IL_0026: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: stsfld int32 ''.$Sample::init@ + IL_0006: ldsfld int32 ''.$Sample::init@ + IL_000b: pop + IL_000c: ret + } + + .method assembly static void staticInitialization@() cil managed + { + + .maxstack 8 + IL_0000: call void Sample/C::staticInitialization@() + IL_0005: ret + } + +} + +.class private abstract auto ansi sealed ''.$Sample + extends [runtime]System.Object +{ + .field static assembly int32 init@ + .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 8 + IL_0000: call void Sample::staticInitialization@() + IL_0005: ret + } + +} + + + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs index 3358122f743..29e8a9b40bb 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Inlining.fs @@ -52,6 +52,15 @@ module Inlining = |> getCompilation |> verifyCompilation + // Baseline lock for dotnet/fsharp#19933: a closure inside a `type C with member ...` + // augmentation must nest inside C (realsig+) so it can reach C's IL-private members. + // Realsig=Both snapshots both the realsig+ nesting and the realsig- lowering. + [] + let ``AugmentationClosureNesting_fs`` compilation = + compilation + |> getCompilation + |> verifyCompilation + // SOURCE=Match02.fs SCFLAGS="-a --optimize+" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd Match02.dll" # Match02.fs [] let ``Match02_fs`` compilation = diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs index 8d4631a5c23..bb64707f05c 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure.fs @@ -1,92 +1,87 @@ namespace EmittedIL.RealInternalSignature open Xunit -open FSharp.Test open FSharp.Test.Compiler -/// Runtime regression tests for dotnet/fsharp#19933. +/// Regression tests for dotnet/fsharp#19933. /// /// A closure synthesized inside a member declared in an *intrinsic augmentation* -/// (`type C with member ...`) used to be emitted as a sibling of `C` in the -/// enclosing module class rather than nested inside `C`. Under `--realsig+` a -/// source-`private` member of `C` compiles to IL `private` (type-scoped), so the -/// sibling closure could not reach it and the CLR raised `MethodAccessException` -/// at first invocation. Members declared in the type's own body were always nested -/// correctly; the fix makes augmentation members consistent with them. +/// (`type C with member ...`) used to be emitted as a sibling of `C` in the enclosing +/// module class instead of nested inside `C`. Under `--realsig+` a source-`private` +/// member of `C` is IL `private` (type-scoped), so the sibling closure could not reach +/// it and the CLR raised `MethodAccessException` at first call. Members declared in the +/// type's own body were always nested correctly; the fix makes augmentation members +/// consistent with them. These programs are legal F#; only IL placement was wrong. /// -/// These programs are legal F# (the type checker accepts private access from any -/// lexical position within the declaring type, including inner lambdas / `task` / -/// `seq` / quotations). The bug was purely in IL closure placement; the fix does -/// not change access semantics — the private member stays IL `private`. +/// THE FIX IS GATED ON `--realsig+`. Under `--realsig-` the member is IL `assembly` and +/// the closure reaches it regardless (and the inner-rec is often lambda-lifted to a module +/// static), so the emitted IL is identical before and after the fix — a `--realsig-` +/// runtime test cannot distinguish the buggy compiler from the fixed one. The matrix below +/// therefore runs under `--realsig+` only; one `--realsig-` smoke is kept for +/// defense-in-depth, and both realsig modes of the canonical shape are snapshotted by a +/// `.bsl` baseline (`AugmentationClosureNesting.fs`, hosted in `Inlining.fs`). /// -/// IMPORTANT: every private member here is marked `[]` (or -/// reads non-inlinable state). A trivial private body is inlined by the optimizer -/// before codegen, which removes the call site and HIDES the bug — such a test -/// would pass even on the buggy compiler and guard nothing. The IL-nesting shape -/// is additionally locked by `Regression_RealsigAugmentationClosure_StructuralAssertions.fs`. +/// Each case compiles `--realsig+ --optimize+`, asserts the closure nests INSIDE the +/// declaring type (`/@N`, present) and is NOT a module/namespace sibling +/// (`/@N`, absent), then runs it. present/absent are raw IL substrings: +/// keep the trailing `@` but omit the `@N` digits so the fragment survives line-number +/// churn. Each assertion FAILS on the pre-fix compiler (sibling) and PASSES on the fix. /// -/// Each test runs under both realsig settings so a regression in either path is -/// caught at runtime. +/// Every private member is `[]`: a trivial private body is inlined +/// before codegen, erasing the call site and hiding the bug. module Regression_RealsigAugmentationClosure = - let private compileOptimized realsig source = - FSharp source - |> withRealInternalSignature realsig - |> asExe - |> withOptimize - |> ignoreWarnings + /// Shared skeleton for the shapes that differ only in the augmentation member body: + /// a non-generic `C` with a static private `Secret`, augmented with `memberBody`, then + /// invoked by `invoke` (which must yield 42). Triple-quoted-string concatenation (not + /// interpolation) keeps embedded `seq { }` braces literal. + let private header = + "module Sample\n" + + "type C() =\n" + + " static let mutable backing = 0\n" + + " static member Set v = backing <- v\n" + + " []\n" + + " static member private Secret() = backing + 1\n" + + "type C with\n" - let private compileRunSucceeds realsig source = - source |> compileOptimized realsig |> compileExeAndRun |> shouldSucceed |> ignore + let private cWith (memberBody: string) (invoke: string) = + header + + memberBody + + "\n[]\nlet main _ =\n C.Set 41\n if " + + invoke + + " = 42 then 0 else 1\n" - /// Canonical #19933 shape: bare inner `let rec` in an augmentation member of a - /// generic type, calling a type-private static. - [] - let ``Augmentation inner-rec calls type-private static of generic type`` (realsig: bool) = - """module Sample -type Holder<'T>() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type Holder<'T> with - member _.Run() = - let rec h n = if n = 0 then Holder<'T>.Secret() else h (n - 1) - h 5 -[] -let main _ = - Holder.Set 41 - if Holder().Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig + /// (name, source, present [nested], absent [sibling]). + let shapeCases = + [ + // ---- shapes sharing the C/Secret/EntryPoint skeleton (cWith) ---- + "inner-rec (canonical)", + cWith " member _.Run() =\n let rec h n = if n = 0 then C.Secret() else h (n - 1)\n h 5" "C().Run()", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Same shape on a non-generic type. - [] - let ``Augmentation inner-rec calls type-private static of non-generic type`` (realsig: bool) = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - member _.Run() = - let rec h n = if n = 0 then C.Secret() else h (n - 1) - h 5 -[] -let main _ = - C.Set 41 - if C().Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig + ; "property getter", + cWith " member _.Prop =\n let rec h n = if n = 0 then C.Secret() else h (n - 1)\n h 5" "C().Prop", + [| "Sample/C/h@" |], [| "Sample/h@" |] + + ; "static method", + cWith " static member Run() =\n let rec h n = if n = 0 then C.Secret() else h (n - 1)\n h 5" "C.Run()", + [| "Sample/C/h@" |], [| "Sample/h@" |] + + ; "nested closures", + cWith " member _.Run() =\n let rec h n =\n let inner () = C.Secret()\n if n = 0 then inner () else h (n - 1)\n h 5" "C().Run()", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Type-private *instance* method accessed from an inner-rec in an augmentation. - /// `[]` is essential — without it the trivial instance getter - /// inlines to an `assembly` field read and the test passes even on the buggy - /// compiler. - [] - let ``Augmentation inner-rec calls type-private instance method`` (realsig: bool) = - """module Sample + ; "mutual inner-rec", + cWith " member _.Run() =\n let rec ev n = if n = 0 then C.Secret() else od (n - 1)\n and od n = if n = 0 then C.Secret() else ev (n - 1)\n ev 5" "C().Run()", + [| "Sample/C/ev@"; "Sample/C/od@" |], [| "Sample/ev@"; "Sample/od@" |] + + ; "seq state machine", + cWith " member _.Run() = seq { yield C.Secret() }" "(C().Run() |> Seq.head)", + [| "Sample/C/Run@" |], [| "Sample/Run@" |] + + // ---- shapes with genuinely different skeletons ---- + ; "instance private method", + """module Sample type C() = let mutable backing = 0 member _.Set v = backing <- v @@ -100,33 +95,48 @@ type C with let main _ = let c = C() in c.Set 41 if c.Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Augmentation property getter with an inner-rec calling a type-private member. - [] - let ``Augmentation property getter inner-rec calls type-private`` (realsig: bool) = - """module Sample -type C() = + ; "generic type inner-rec", + """module Sample +type Holder<'T>() = static let mutable backing = 0 static member Set v = backing <- v [] static member private Secret() = backing + 1 -type C with - member _.Prop = - let rec h n = if n = 0 then C.Secret() else h (n - 1) +type Holder<'T> with + member _.Run() = + let rec h n = if n = 0 then Holder<'T>.Secret() else h (n - 1) h 5 [] let main _ = - C.Set 41 - if C().Prop = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig + Holder.Set 41 + if Holder().Run() = 42 then 0 else 1 +""", + [| "Sample/Holder`1/h@" |], [| "Sample/h@" |] + + ; "generic method threads typars", + """module Sample +type Holder<'T>() = + static let mutable backing = 0 + static member Set v = backing <- v + [] + static member private Secret() = backing + 1 +type Holder<'T> with + member _.M<'U>(u: 'U) = + let rec h n (acc: 'U) = if n = 0 then (acc, Holder<'T>.Secret()) else h (n - 1) acc + h 5 u +[] +let main _ = + Holder.Set 41 + let (_, s) = Holder().M("x") + if s = 42 then 0 else 1 +""", + [| "Sample/Holder`1/h@" |], [| "Sample/h@" |] - /// Augmentation property setter with an inner-rec calling a type-private member. - [] - let ``Augmentation property setter inner-rec calls type-private`` (realsig: bool) = - """module Sample + ; "property setter", + """module Sample type C() = static let mutable result = 0 static member Get() = result @@ -143,13 +153,11 @@ let main _ = let c = C() c.Prop <- 41 if C.Get() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Augmentation indexed property (`Item with get(i)`). - [] - let ``Augmentation indexer inner-rec calls type-private`` (realsig: bool) = - """module Sample + ; "indexer", + """module Sample type C() = [] static member private Secret(i) = i + 1 @@ -160,33 +168,11 @@ type C with h 1 0 [] let main _ = if C().[41] = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig - - /// Augmentation STATIC method (not instance) with an inner-rec calling a private. - [] - let ``Augmentation static method inner-rec calls type-private`` (realsig: bool) = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - static member Run() = - let rec h n = if n = 0 then C.Secret() else h (n - 1) - h 5 -[] -let main _ = - C.Set 41 - if C.Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Operator (`static member (+)`) declared in an augmentation. - [] - let ``Augmentation operator inner-rec calls type-private`` (realsig: bool) = - """module Sample + ; "operator", + """module Sample type C(v: int) = member _.V = v [] @@ -197,13 +183,11 @@ type C with C(h b.V 0) [] let main _ = if (C(41) + C(1)).V = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// `task { }` state machine in an augmentation referencing a type-private member. - [] - let ``Augmentation task computation expression calls type-private member`` (realsig: bool) = - """module Sample + ; "task state machine", + """module Sample open System.Threading.Tasks type C() = static let mutable backing = 0 @@ -216,13 +200,11 @@ type C with let main _ = C.Set 41 if (C().Run()).Result = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/C/Run@" |], [| "Sample/Run@"; "Sample/'Run@" |] - /// `async { }` variant. - [] - let ``Augmentation async computation expression calls type-private member`` (realsig: bool) = - """module Sample + ; "async", + """module Sample type C() = static let mutable backing = 0 static member Set v = backing <- v @@ -234,122 +216,33 @@ type C with let main _ = C.Set 41 if (C().Run() |> Async.RunSynchronously) = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/C/Run@" |], [| "Sample/Run@"; "Sample/'Run@" |] - /// `seq { }` state machine in an augmentation. - [] - let ``Augmentation seq computation expression calls type-private member`` (realsig: bool) = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - member _.Run() = seq { yield C.Secret() } -[] -let main _ = - C.Set 41 - if (C().Run() |> Seq.head) = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig - - /// Mutual recursion (`let rec ... and ...`) in an augmentation member. - [] - let ``Augmentation mutual inner-rec calls type-private member`` (realsig: bool) = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - member _.Run() = - let rec ev n = if n = 0 then C.Secret() else od (n - 1) - and od n = if n = 0 then C.Secret() else ev (n - 1) - ev 5 -[] -let main _ = - C.Set 41 - if C().Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig - - /// Nested closures (a lambda inside the inner-rec) touching a type-private member. - [] - let ``Augmentation nested closures call type-private member`` (realsig: bool) = - """module Sample + ; "quotation splice", + """module Sample +open Microsoft.FSharp.Quotations type C() = static let mutable backing = 0 static member Set v = backing <- v [] static member private Secret() = backing + 1 type C with - member _.Run() = - let rec h n = - let inner () = C.Secret() - if n = 0 then inner () else h (n - 1) - h 5 -[] -let main _ = - C.Set 41 - if C().Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig - - /// Generic class with a generic augmentation method whose closure captures both - /// the class typar 'T and the method typar 'U and calls a type-private member. - [] - let ``Augmentation generic method threads typars and calls type-private`` (realsig: bool) = - """module Sample -type Holder<'T>() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type Holder<'T> with - member _.M<'U>(u: 'U) = - let rec h n (acc: 'U) = if n = 0 then (acc, Holder<'T>.Secret()) else h (n - 1) acc - h 5 u -[] -let main _ = - Holder.Set 41 - let (_, s) = Holder().M("x") - if s = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig - - /// Two augmentation members of DIFFERENT generic types in the same module whose - /// inner-recs share the local name `h` — verifies no IL type-name collision after - /// re-homing the closures under their respective types. - [] - let ``Augmentations on two types with same closure name do not collide`` (realsig: bool) = - """module Sample -type Alpha<'T>() = - [] - static member private S() = 1 -type Beta<'U>() = - [] - static member private S() = 2 -type Alpha<'T> with - member _.Run() = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Alpha<'T>.S()) - h 5 0 -type Beta<'U> with - member _.Run() = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Beta<'U>.S()) - h 5 0 + member _.MakeQ() : Expr = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret()) + let v = h 3 0 + <@ %%(Expr.Value v) : int @> [] let main _ = - if Alpha().Run() = 5 && Beta().Run() = 10 then 0 else 1 -""" - |> compileRunSucceeds realsig + C.Set 9 + match C().MakeQ() with + | Patterns.Value(o, _) -> if unbox o = 30 then 0 else 1 + | _ -> 2 +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Record-type augmentation. - [] - let ``Augmentation on record type inner-rec calls type-private`` (realsig: bool) = - """module Sample + ; "record augmentation", + """module Sample type R = { X: int } with [] static member private Secret(v) = v + 1 @@ -359,13 +252,11 @@ type R with h 1 0 [] let main _ = if { X = 41 }.Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/R/h@" |], [| "Sample/h@" |] - /// DU-type augmentation. - [] - let ``Augmentation on DU type inner-rec calls type-private`` (realsig: bool) = - """module Sample + ; "DU augmentation", + """module Sample type D = | A of int [] @@ -377,37 +268,62 @@ type D with h 1 0 [] let main _ = if (A 41).Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Sample/D/h@" |], [| "Sample/h@" |] - /// Quotation splice whose value is computed by an inner-rec in the augmentation. - [] - let ``Augmentation quotation splice driven by inner-rec calls type-private`` (realsig: bool) = - """module Sample -open Microsoft.FSharp.Quotations + ; "override member", + """module Sample +[] +type B() = + abstract M : unit -> int type C() = - static let mutable backing = 0 - static member Set v = backing <- v + inherit B() [] - static member private Secret() = backing + 1 + static member private Secret() = 42 type C with - member _.MakeQ() : Expr = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret()) - let v = h 3 0 - <@ %%(Expr.Value v) : int @> + override _.M() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + h 5 [] -let main _ = - C.Set 9 - match C().MakeQ() with - | Patterns.Value(o, _) -> if unbox o = 30 then 0 else 1 - | _ -> 2 -""" - |> compileRunSucceeds realsig +let main _ = if (C() :> B).M() = 42 then 0 else 1 +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] - /// Type declared inside a namespace (not a module), augmentation in same namespace. - [] - let ``Augmentation inner-rec on namespace-scoped type calls type-private`` (realsig: bool) = - """namespace MyNs + ; "secondary constructor", + """module Sample +type C(x: int) = + member _.X = x + [] + static member private Secret() = 42 +type C with + new() = + let rec h n = if n = 0 then C.Secret() else h (n - 1) + C(h 5) +[] +let main _ = if C().X = 42 then 0 else 1 +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] + + ; "struct augmentation", + """module Sample +[] +type C = + val X: int + new(x) = { X = x } + [] + static member private Secret(v) = v + 1 +type C with + member this.Run() = + let captured = this.X + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(captured)) + h 1 0 +[] +let main _ = if C(41).Run() = 42 then 0 else 1 +""", + [| "Sample/C/h@" |], [| "Sample/h@" |] + + ; "namespace-scoped type", + """namespace MyNs type C() = static let mutable backing = 0 static member Set v = backing <- v @@ -422,13 +338,11 @@ module Main = let main _ = C.Set 41 if C().Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "MyNs.C/h@" |], [| "MyNs.h@" |] - /// Type declared in a nested module, augmentation in the same nested module. - [] - let ``Augmentation inner-rec on nested-module type calls type-private`` (realsig: bool) = - """module Top + ; "nested-module type", + """module Top module Inner = type C() = static let mutable backing = 0 @@ -443,5 +357,74 @@ module Inner = let main _ = Inner.C.Set 41 if Inner.C().Run() = 42 then 0 else 1 -""" - |> compileRunSucceeds realsig +""", + [| "Top/Inner/C/h@" |], [| "Top/Inner/h@" |] + + // Two augmentations sharing the local closure name `h`: each must nest under its + // OWN type and NEITHER may remain a module sibling. The shipped compiler emits the + // second sibling with a disambiguator (`'h@N-1'`), so the absent list covers both + // the plain and the quoted spellings rather than pinning Beta's mangled name. + ; "two types, same closure name", + """module Sample +type Alpha<'T>() = + [] + static member private S() = 1 +type Beta<'U>() = + [] + static member private S() = 2 +type Alpha<'T> with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Alpha<'T>.S()) + h 5 0 +type Beta<'U> with + member _.Run() = + let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Beta<'U>.S()) + h 5 0 +[] +let main _ = + if Alpha().Run() = 5 && Beta().Run() = 10 then 0 else 1 +""", + [| "Sample/Alpha`1/h@" |], [| "Sample/h@"; "Sample/'h@" |] + ] + |> List.map (fun (name: string, src: string, nested: string[], sibling: string[]) -> + [| box name; box src; box nested; box sibling |]) + + /// Compile `--realsig+ --optimize+`, assert the closure nests under its declaring type + /// (present) and is not a module/namespace sibling (absent), then run. The IL check sits + /// between `compile` and `run`, so this cannot reuse `compileExeAndRun`. + [] + let ``Augmentation closure nests under its declaring type`` + (name: string) + (source: string) + (nested: string[]) + (sibling: string[]) + = + ignore name + + let result = + FSharp source + |> withRealInternalSignature true + |> asExe + |> withOptimize + |> ignoreWarnings + |> compile + |> shouldSucceed + + result |> verifyILPresent (List.ofArray nested) + result |> verifyILNotPresent (List.ofArray sibling) + result |> run |> shouldSucceed |> ignore + + /// Defense-in-depth only: under `--realsig-` the fix is a no-op (the legacy path emits + /// the same IL before and after), so this does NOT guard #19933 — it guards against a + /// future un-gating or a shared-path regression breaking the legacy path. + [] + let ``Canonical augmentation closure still compiles and runs under realsig-`` () = + cWith " member _.Run() =\n let rec h n = if n = 0 then C.Secret() else h (n - 1)\n h 5" "C().Run()" + |> FSharp + |> withRealInternalSignature false + |> asExe + |> withOptimize + |> ignoreWarnings + |> compileAndRun + |> shouldSucceed + |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs deleted file mode 100644 index 85d957c3914..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RealsigAugmentationClosure_StructuralAssertions.fs +++ /dev/null @@ -1,182 +0,0 @@ -namespace EmittedIL.RealInternalSignature - -open Xunit -open FSharp.Test -open FSharp.Test.Compiler - -/// IL-structural regression locks for dotnet/fsharp#19933. -/// -/// The runtime tests in `Regression_RealsigAugmentationClosure.fs` confirm the -/// programs no longer crash, but a runtime-only test is a weak guard: it would pass -/// even if the IL silently regressed (under `--realsig-` the closure can be a module -/// sibling and still work because the member is IL `assembly`). These tests pin the -/// fix's actual property under `--realsig+`: the synthesized closure is nested INSIDE -/// the declaring type (`Sample/C/@N`), NOT a sibling in the module class -/// (`Sample/@N`). -/// -/// Each assertion below FAILS on the pre-fix/shipped compiler (which emits the module -/// sibling) and PASSES on the fixed compiler — so it genuinely guards the fix. -module Regression_RealsigAugmentationClosure_StructuralAssertions = - - /// Compile under --realsig+ --optimize+, assert the closure nests under the type - /// (present) and is not a module sibling (absent), then run. - let private assertNestedAndRun (present: string list) (absent: string list) source = - let result = - FSharp source - |> withRealInternalSignature true - |> asExe - |> withOptimize - |> ignoreWarnings - |> compile - |> shouldSucceed - result |> verifyILPresent present - result |> verifyILNotPresent absent - result |> run |> shouldSucceed |> ignore - - [] - let ``Non-generic augmentation closure nests under the type`` () = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - member _.Run() = - let rec h n = if n = 0 then C.Secret() else h (n - 1) - h 5 -[] -let main _ = - C.Set 41 - if C().Run() = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] - - [] - let ``Generic augmentation closure nests under the generic type and threads the typar`` () = - """module Sample -type Holder<'T>() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type Holder<'T> with - member _.Run() = - let rec h n = if n = 0 then Holder<'T>.Secret() else h (n - 1) - h 5 -[] -let main _ = - Holder.Set 41 - if Holder().Run() = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/Holder`1/h@" ] [ "Sample/h@" ] - - [] - let ``Property-getter augmentation closure nests under the type`` () = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - member _.Prop = - let rec h n = if n = 0 then C.Secret() else h (n - 1) - h 5 -[] -let main _ = - C.Set 41 - if C().Prop = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] - - [] - let ``Indexer augmentation closure nests under the type`` () = - """module Sample -type C() = - [] - static member private Secret(i) = i + 1 -type C with - member _.Item - with get (i: int) = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(i)) - h 1 0 -[] -let main _ = if C().[41] = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] - - [] - let ``Operator augmentation closure nests under the type`` () = - """module Sample -type C(v: int) = - member _.V = v - [] - static member private Secret(x: int) = x + 1 -type C with - static member (+) (a: C, b: C) = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + C.Secret(a.V)) - C(h b.V 0) -[] -let main _ = if (C(41) + C(1)).V = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] - - [] - let ``Static-method augmentation closure nests under the type`` () = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - static member Run() = - let rec h n = if n = 0 then C.Secret() else h (n - 1) - h 5 -[] -let main _ = - C.Set 41 - if C.Run() = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/C/h@" ] [ "Sample/h@" ] - - [] - let ``Seq state-machine augmentation closure nests under the type`` () = - """module Sample -type C() = - static let mutable backing = 0 - static member Set v = backing <- v - [] - static member private Secret() = backing + 1 -type C with - member _.Run() = seq { yield C.Secret() } -[] -let main _ = - C.Set 41 - if (C().Run() |> Seq.head) = 42 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/C/Run@" ] [ "Sample/Run@" ] - - [] - let ``Two-type augmentations with same closure name nest under their own types`` () = - """module Sample -type Alpha<'T>() = - [] - static member private S() = 1 -type Beta<'U>() = - [] - static member private S() = 2 -type Alpha<'T> with - member _.Run() = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Alpha<'T>.S()) - h 5 0 -type Beta<'U> with - member _.Run() = - let rec h n acc = if n = 0 then acc else h (n - 1) (acc + Beta<'U>.S()) - h 5 0 -[] -let main _ = - if Alpha().Run() = 5 && Beta().Run() = 10 then 0 else 1 -""" - |> assertNestedAndRun [ "Sample/Alpha`1/h@"; "Sample/Beta`1/'h@" ] [ "Sample/h@" ] diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 35233249add..b29e2601b82 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -269,7 +269,6 @@ -