Skip to content

Type inference breaks when class is not sealed #4171

Open
@gusty

Description

@gusty

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

//[<Sealed>]
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<Monad>, Unchecked.defaultof<'``Monad<'T>``>) x
    static member Return (_:Interface<'a>) = fun (_:'a) -> Unchecked.defaultof<Interface<'a>> : 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<Monad>, 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<string, option<_>> = ReaderT result >>= result
let test2 : ReaderT<string, Id<_>>     = ReaderT result >>= result
let test3 : ReaderT<string, seq<_>>    = 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

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'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-Compiler-SRTPbugs in SRTP inference, resolution, witness passing, code genBugImpact-Medium(Internal MS Team use only) Describes an issue with moderate impact on existing code.

    Type

    Projects

    Status

    In Progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions