Cannot call method with a generic interface constraint, when concrete type implements the interface with multiple generic instantiations
- 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
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