Skip to content

MethodAccessException for type-private member call from inner-rec in a type C with member ... augmentation under --realsig+ #19933

Description

@T-Gro

Closures generated for an inner let rec inside a method defined in a
type C with member ... augmentation are emitted as a sibling of C inside
the module class, not as a nested type of C. Under --realsig+, a source
private member of C becomes IL private (type-scoped), so the sibling
closure cannot reach it and the CLR raises MethodAccessException at first
invocation. Under --realsig- the same private compiles to IL assembly,
which is reachable from the sibling, so the bug is invisible.

Repro

moduleMtypeHolder<'T>()=static let mutablebacking=0static memberInit v = backing <- v
static memberprivateSecretMethod()= backing +1typeHolder<'T>withmember_.Run()=let rech n =if n =0then Holder<'T>.SecretMethod()else h (n -1)
h 5[<EntryPoint>]letmain _ =
Holder<int>.Init 41if Holder<int>().Run()=42then0else1
fsc --realsig+ --optimize+ -r:FSharp.Core.dll repro.fs
dotnet repro.dll
Unhandled exception. System.MethodAccessException:
Attempt by method 'M+h@8<T>.Invoke(Int32)' to access method
'M+Holder`1<T>.SecretMethod()' failed.

IL shape (--realsig+, shortened)

.class public sealed M // module class
.class nested public Holder`1<T>
.method private static int32 SecretMethod() // IL private (type-scoped)
.method public instance int32 Run()
.class nested assembly h@8<T> // SIBLING of Holder, not nested in it
.method public virtual instance int32 Invoke(int32 n)
call int32 class M/Holder`1<!T>::SecretMethod() // crosses IL private boundary

Moving Run() from the augmentation into the primary type body emits
h@8<T> as class nested ... Holder1/h@8(nested insideHolder`) and the
program runs cleanly.

Reproduces on (all --realsig+; --realsig- clean)

net8.0 (sdk 8.0.422), net9.0 (sdk 9.0.301, 9.0.315),
net10.0 (sdk 10.0.101, 10.0.109, 10.0.204, 10.0.300).

Notes

Not specific to TLR. Independent of --optimize. The closure-class
placement uses the module's cloc rather than the augmented type's cloc
in IlxGen.fs. Tangentially related to #19882 — that PR's TLR private-ref
guard prevents the inner-rec from being lifted to a module static under
--realsig+, so the fallback path (this closure) is what trips here.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions