Skip to content

MethodImpl attribute is silently dropped on property accessors (and leaks into metadata as a real custom attribute) #20288

Description

@T-Gro

When [<MethodImpl(...)>] is applied to a property accessor, the compiler does not translate it into the method's IL implementation flags. Instead it emits it as an ordinary custom attribute, which has no effect on the runtime or on downstream tools.

MethodImplAttribute is a pseudo-custom-attribute: it must be encoded in the method's implflags, and it should never appear in metadata as a real custom attribute.

The failure is silent. The code compiles without a warning, and the attribute looks like it was applied.

Repro

module P
open System.Runtime.CompilerServices

[<AbstractClass; Sealed>]
type A =
    static member P1 with [<MethodImpl(MethodImplOptions.AggressiveInlining)>] get () = 1

[<AbstractClass; Sealed>]
type B =
    static member P2 with [<MethodImpl(MethodImplOptions.NoInlining)>] set (v: int) = ignore v

[<AbstractClass; Sealed>]
type C =
    [<MethodImpl(MethodImplOptions.AggressiveInlining)>]
    static member M1() = 1

Actual

Inspecting the emitted assembly:

A    get_P1   implFlags=IL                   customAttrs=["MethodImplAttribute"]
B    set_P2   implFlags=IL                   customAttrs=["MethodImplAttribute"]
C    M1       implFlags=AggressiveInlining   customAttrs=[]

The accessors get_P1 and set_P2 keep implFlags=IL, so the requested option is lost, and MethodImplAttribute is emitted as a custom attribute. The plain method C.M1 is correct.

Expected

get_P1 and set_P2 behave like C.M1: the option appears in implflags and no MethodImplAttribute custom attribute is emitted.

Notes

This is not specific to NoInlining. It applies to any MethodImplOptions carried by an accessor, including AggressiveInlining, Synchronized and PreserveSig, and it affects both getters and setters.

The cause looks like ComputeMethodImplAttribs running on an attribute list that has already had the accessor-applied attributes partitioned out, in IlxGen.fs (around the attrsAppliedToGetterOrSetter handling).

[<MethodImpl(...)>] on a property member (rather than inside the accessor) is not a workaround either, since that is rejected with error FS0842: This attribute cannot be applied to property, event, return value. Valid targets are: constructor, method. So a plain method is currently the only reliable way to request a method impl option.

Why it matters

This was found while making Array2D trim- and AOT-clean. A trimming feature switch needs MethodImplOptions.NoInlining so that ILLink and ILC still have a real call to substitute. Written as a property getter (the shape every BCL feature switch uses, such as RuntimeFeature.IsDynamicCodeSupported), the attribute silently disappeared, and the only reason the substitution still worked was an unrelated compiler internal. There is no way to assert the intended contract from the emitted metadata, because the flag is simply not there.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions