dotnet / dotnet/fsharp

Type inference breaks when class is not sealed

Open
#4,171 2 comments 0 reactions 0 assignees View on GitHub
Area-Compiler-SRTP Bug Impact-Medium
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

There are cases when using SRTP with overloads where a class that is not sealed is not resolved, when in fact the compiler has a good candidate.

#### Repro steps

Try this script

```
//[]
type Id<'t>(v:'t) = member __.getValue = v

type Interface<'t> = abstract member getValue : 't

type Monad =
static member inline InvokeReturn (x:'T) : '``Monad<'T>`` =
let inline call (mthd : ^M, output : ^R) = ((^M or ^R) : (static member Return: _ -> _) output)
call (Unchecked.defaultof, Unchecked.defaultof<'``Monad<'T>``>) x
static member Return (_:Interface<'a>) = fun (_:'a) -> Unchecked.defaultof> : Interface<'a>
static member Return (_:seq<'a> ) = fun x -> Seq.singleton x : seq<'a>
static member Return (_:option<'a> ) = fun x -> Some x : option<'a>
static member Return (_:Id<'a> ) = fun x -> Id x : Id<'a>

static member inline InvokeBind (source : '``Monad<'T>``) (binder : 'T -> '``Monad<'U>``) : '``Monad<'U>`` =
let inline call (mthd : 'M, input : 'I, _output : 'R, f) = ((^M or ^I or ^R) : (static member Bind: _*_ -> _) input, f)
call (Unchecked.defaultof, source, Unchecked.defaultof<'``Monad<'U>``>, binder)
static member Bind (source : Interface<'T>, f : 'T -> Interface<'U>) = f source.getValue : Interface<'U>
static member Bind (source : seq<'T> , f : 'T -> seq<'U> ) = Seq.collect f source : seq<'U>
static member Bind (source : Id<'T> , f : 'T -> Id<'U> ) = f source.getValue : Id<'U>
static member Bind (source :option<'T> , f : 'T -> _ ) = Option.bind f source : option<'U>

let inline result (x:'T) = Monad.InvokeReturn x :'``Monad<'T>``
let inline (>>=) (x:'``Monad<'T>``) (f:'T->'``Monad<'U>``) = Monad.InvokeBind x f :'``Monad<'U>``

type ReaderT<'R,'``monad<'T>``> = ReaderT of ('R -> '``monad<'T>``)
let runReaderT (ReaderT x) = x : 'R -> '``Monad<'T>``
type ReaderT<'R,'``monad<'T>``> with
static member inline Return _ = fun (x : 'T) -> ReaderT (fun _ -> result x) : ReaderT<'R, '``Monad<'T>``>
static member inline Bind (ReaderT (m:_->'``Monad<'T>``), f:'T->_) = ReaderT (fun r -> m r >>= (fun a -> runReaderT (f a) r)) : ReaderT<'R, '``Monad<'U>``>

let test1 : ReaderT> = ReaderT result >>= result
let test2 : ReaderT> = ReaderT result >>= result
let test3 : ReaderT> = ReaderT result >>= result
```

#### Expected behavior

Compile fine, inferring ``test1``, ``test2``, ``test3``

#### Actual behavior

Only ``test1`` is inferred, since it's an ``option`` type (sealed).
Then we get this error:

error FS0332: Could not resolve the ambiguity inherent in the use of the operator 'Return' at or near this program point. Consider using type annotations to resolve the ambiguity.

Which doesn't add any useful information.

#### Known workarounds

Uncomment the first line and ``test2`` will be inferred.
However for ``test3`` there's no workaround without fixing the compiler.

#### Related information

I submitted a [very easy fix for this issue](https://github.com/Microsoft/visualfsharp/pull/4170)

Basically we don't fail, because:

- If type inference comes with a candidate (other than the default inferred ie. obj) we use it.
- Even in the case where this is really an error, this error message is not telling us anything interesting, in turn it "hides" the real error message, which tell us that a default type ``obj`` was used and it doesn't match this (very useful to debug) list of candidate methods.

Another option would be to check if the candidate was inferred using a default type, but again, that error message is more useful than just 'Could not resolve the ambiguity'.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.