Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No compile-time error when static abstract member accessed on type parameter whose resolved type has no concrete implementation for said member #17139

Open
brianrourkeboll opened this issue May 11, 2024 · 2 comments
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Bug Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Milestone

Comments

@brianrourkeboll
Copy link
Contributor

brianrourkeboll commented May 11, 2024

There is no compile-time error when:

  1. You invoke a static abstract member on a type parameter that is constrained to be an interface with the given static abstract member (IWSAM).
  2. The type variable for that type parameter is automatically constrained to be the interface type itself (as opposed to a concrete type that has a concrete implementation of the member being invoked).

A System.Runtime.AmbiguousImplementationException is raised at runtime instead. This is raised even when the interface in question has only one static abstract member (or member of any kind).

Repro steps

#nowarn "3535"

type IFace =
    static abstract P1 : int

type T =
    interface IFace with
        static member P1 = 1

let inline p1<'T & #IFace> = 'T.P1

let _ = p1 // 'T is resolved to be IFace itself.

image

net8.0

Unhandled exception. System.Runtime.AmbiguousImplementationException: Could not call method 'Program+IFace.get_P1()' on interface 'Program+IFace' with type 'Program+IFace' from assembly '' because there are multiple incompatible interface methods overriding this method.
   at <StartupCode$ConsoleApp1>.$Program.main@() in D:\src\ConsoleApp1\ConsoleApp1\Program.fs:line 12

net9.0

Unhandled exception. System.Runtime.AmbiguousImplementationException: Ambiguous implementation found.
   at <StartupCode$ConsoleApp1>.$Program.main@() in D:\src\ConsoleApp1\ConsoleApp1\Program.fs:line 12

Expected behavior

Resolution of the type variable should fail at compile-time in the absence of further type information when the type variable has a constraint involving static abstract members — although ideally it would only fail when an abstract (as opposed to virtual) member was actually being accessed, which is not visible through a constraint like 'T & #IFace alone.

Actual behavior

A type variable like 'T & #IFace resolves to the interface type IFace itself, and a System.Runtime.AmbiguousImplementationException is raised at runtime.

Known workarounds

Always manually ensure that all type variables are resolved to types with concrete implementations for all static abstract methods that are invoked on them.

Related information

.NET 8/9 preview.

This problem is similar in spirit to #14012, #16299, etc., although it might be a bit trickier to fix.

@github-actions github-actions bot added this to the Backlog milestone May 11, 2024
@brianrourkeboll brianrourkeboll changed the title No compile-time error when static abstract method called on type parameter whose resolved type is non-concrete No compile-time error when static abstract member accessed on type parameter whose resolved type has no concrete implementation for said member May 11, 2024
@vzarytovskii
Copy link
Member

vzarytovskii commented May 12, 2024

I honestly don't think we can resolve it easily for general case, since it's a constrained runtime call by design, and it would be hard to distinguish such cases in compile time.

We might though solve this specific one - for type functions, when type inferred is the interface.

@vzarytovskii vzarytovskii added the Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code. label May 12, 2024
@brianrourkeboll
Copy link
Contributor Author

brianrourkeboll commented May 12, 2024

I honestly don't think we can resolve it easily for general case, since it's a constrained runtime call by design, and it would be hard to distinguish such cases in compile time.

Yeah I agree, especially since presumably the mechanism that resolves #IFaceIFace for an IWSAM is the same one that resolves, e.g., #('T seq)'T seq for a regular interface, which is definitely relied upon.

We might though solve this specific one - for type functions, when type inferred is the interface.

Unfortunately, the same thing happens when it's a regular function, too:

#nowarn "3535"

type IFace =
    static abstract P1 : int

type T =
    interface IFace with
        static member P1 = 1

let f<'T & #IFace> () = 'T.P1 + 'T.P1

f () // 'T is resolved to IFace.

On the other hand, this problem probably shouldn't affect most uses of IWSAMs in the wild, since a type parameter constrained by a (recursively) generic IWSAM (like anything from System.Numerics) will not silently resolve to the interface itself:

open System.Numerics

let g<'T & #INumber<'T>> () = 'T.Zero + 'T.One

let _ = g ()
// error FS0071: Type constraint mismatch when applying the default type 'INumber<'a>'
// for a type inference variable. The types ''a' and 'INumber<'a>' cannot be unified.
// Consider adding further type constraints

@abonie abonie added Area-Compiler-Checking Type checking, attributes and all aspects of logic checking and removed Needs-Triage labels May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Bug Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Projects
Status: New
Development

No branches or pull requests

3 participants