dotnet / dotnet/fsharp

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

Open
#20,103 0 comments 0 reactions 0 assignees View on GitHub
Bug Needs-Triage
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

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

```fsharp
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
interface I

let register (services: IServices) =
services.Register Foo
services.Register 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:

```fsharp
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
interface I

let register (services: IServices) =
services.Register Foo
services.Register 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:
* https://github.com/dotnet/fsharp/issues/12206
* https://github.com/dotnet/fsharp/issues/12814
* https://github.com/dotnet/fsharp/issues/16804
* https://github.com/fsharp/fslang-suggestions/issues/1036

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.

Contributor guide

Open the contributing guide

Research direction

Start by compiling the minimal F# reproduction in the issue and compare it with the reordered generic-parameter workaround. Investigate the type checker’s handling of the generic constraint and unification, then add a regression test showing that both Register calls compile without reordering the parameters.

Written by the indexing model from the issue text.

Assessment

Tech stack
fsharp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.