Skip to content

Cannot call method with a generic interface constraint, when concrete type implements the interface with multiple generic instantiations #20103

Description

@jwosty

I ran into a situation that the type checker doesn't like:

type I<'a> = interface end

type IServices =
    abstract member Register<'a, 'b when 'a :> I<'b>> : ctor: (IServices -> 'a) -> unit

type Foo(services: IServices) =
    interface I<int>
    interface I<string>

let register (services: IServices) =
    services.Register<Foo, int> Foo
    services.Register<Foo, string> Foo

Expected behavior

Compiles successfully.

Actual behavior

You get a compile error:

Restore complete (0.2s)
  MailboxGenericsProblem net10.0 failed with 1 error(s) (1.0s)
    C:\Code\FSharpSandbox\GenericsProblem\Program.fs(11,5): error FS0001: This expression was expected to have type    'string'    but here has type    'int'

Build failed with 1 error(s) in 1.5s

Known workarounds

Interestingly, reordering the generic parameters on the method declaration satisfies the type checker:

type I<'a> = interface end

type IServices =
    abstract member Register<'a, 'b when 'b :> I<'a>> : ctor: (IServices -> 'b) -> unit

type Foo(services: IServices) =
    interface I<int>
    interface I<string>

let register (services: IServices) =
    services.Register<int, Foo> Foo
    services.Register<string, Foo> Foo

Presumably this is due to the ordering and the eagerness in how the type checker unifies.

However, I suspect this workaround may not always possible, especially if you were to run into this in the wild with a C# library you don't control (though this hasn't happened to me yet).

More details

My real-world use case is of course a bit more complex: a flexible strongly-typed actor-like system where actors can accept multiple types of messages by implementing a generic interface, you can have a "weak" / "opaque" reference to an actor, etc.

I could also see this plausibly showing up in a service locator pattern.

Related information

  • Windows 11
  • .NET 5, 6, 7, 8, 9, 10

I found some similar issues:

but most of these are related to issues on the declaration side with multiple constraints, and sometimes type inference, rather than the caller side with specific concrete types with full annotations. I can see how the type checker would struggle with the other usages, but hopefully this particular situation is more plausible to deal with.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status
    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions