dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.9k stars 782 forks source link

Weird dynamic invocation error on trait-call #8690

Open gusty opened 4 years ago

gusty commented 4 years ago

By accident I discovered a situation where a dynamic invocation error is thrown at run-time, instead of failing compilation.

Repro steps

type Default1 = obj

type Map =
    static member Map ((x: option<_>, f: 'T->'U), _mthd: Map) = Option.map  f x
    static member Map ((x: list<_>  , f: 'T->'U), _mthd: Map) = List.map    f x : list<'U>

    static member inline Invoke (mapping: 'T->'U) (source: '``Functor<'T>``) : '``Functor<'U>`` = 
        let inline call (mthd: ^M, source: ^I, _output: ^R) = ((^M or ^I or ^R) : (static member Map : (_*_)*_ -> _) (source, mapping), mthd)
        call (Unchecked.defaultof<Map>, source, Unchecked.defaultof<'``Functor<'U>``>)

type Bimap =
    static member Bimap (x: Result<'T2, 'T1>, f: 'T1->'U1, g: 'T2->'U2, _mthd: Bimap) = match x with Ok x -> Ok (g x) | Error x -> Error (f x)
    static member Bimap (x: Choice<'T2, 'T1>, f: 'T1->'U1, g: 'T2->'U2, _mthd: Bimap) = match x with Choice1Of2 x -> Choice1Of2 (g x) | Choice2Of2 x -> Choice2Of2 (f x)

    static member inline Invoke (f: 'T->'U) (g: 'V->'W) (source: '``Bifunctor<'T,'V>``) : '``Bifunctor<'U,'W>`` =
        let inline call (mthd: ^M, source: ^I, _output: ^R) = ((^M or ^I or ^R) : (static member Bimap : _*_*_*_ -> _) source, f, g, mthd)
        call (Unchecked.defaultof<Bimap>, source, Unchecked.defaultof<'``Bifunctor<'U,'W>``>)

type Bitraverse =
    static member inline Bitraverse (x: Result<'T1,'Error1>, f: 'Error1->'``Functor<'Error2>``, g: 'T1->'``Functor<'T2>``, _impl: Bitraverse) : '``Functor<Result<'Error2,'T2>>`` = match x with Ok a         -> Map.Invoke Result<'Error2,'T2>.Ok         (g a) | Error e      -> Map.Invoke Result<'Error2,'T2>.Error      (f e)
    static member inline Bitraverse (x: Choice<'T1,'Error1>, f: 'Error1->'``Functor<'Error2>``, g: 'T1->'``Functor<'T2>``, _impl: Bitraverse) : '``Functor<Choice<'Error2,'T2>>`` = match x with Choice1Of2 a -> Map.Invoke Choice<'Error2,'T2>.Choice1Of2 (g a) | Choice2Of2 e -> Map.Invoke Choice<'Error2,'T2>.Choice2Of2 (f e)    

    static member inline Invoke (f: 'T1->'``Functor<'T2>``) (g: 'U1->'``Functor<'U2>``) (source: '``Bitraversable<'T1,'U1>``) : '``Functor<'Bitraversable<'T2,'U2>>`` =
        let inline call (a: ^a, b: ^b, _: 'r) = ((^a or ^b or ^r) : (static member Bitraverse : _*_*_*_ -> _) b,f,g,a)
        call (Unchecked.defaultof<Bitraverse>, source, Unchecked.defaultof<'``Functor<'Bitraversable<'T2,'U2>>``>)

type Bisequence =
    static member inline Bisequence (x: Result<'``Functor<'Error>``, '``Functor<'T>``>, _impl: Bisequence) : '``Functor<Result<'Error,'T>>`` = match x with Ok a         -> Map.Invoke Result<'Error,'T>.Ok         a | Error e      -> Map.Invoke Result<'Error,'T>.Error      e
    static member inline Bisequence (x: Choice<'``Functor<'Error>``, '``Functor<'T>``>, _impl: Bisequence) : '``Functor<Choice<'Error,'T>>`` = match x with Choice1Of2 a -> Map.Invoke Choice<'Error,'T>.Choice1Of2 a | Choice2Of2 e -> Map.Invoke Choice<'Error,'T>.Choice2Of2 e

    static member inline Invoke (source: '``Bitraversable<'Functor<'T>,'Functor<'U>>``) : '``Functor<'Bitraversable<'T,'U>>`` =
        let inline call (a: ^a, b: ^b, _: 'r) = ((^a or ^b or ^r) : (static member Bisequence : _*_ -> _) b, a)
        call (Unchecked.defaultof<Bisequence>, source, Unchecked.defaultof<'``Functor<'Bitraversable<'T,'U>>``>)

    static member inline InvokeOnInstance (source: '``Bitraversable<'Functor<'T>,'Functor<'U>>``) : '``Functor<'Bitraversable<'T,'U>>`` =
        (^``Bitraversable<'Functor<'T>,'Functor<'U>>`` : (static member Bisequence : _ -> _) source)

type Bitraverse with
    static member inline Bitraverse (x: '``Bitraversable<'T1,'U1>``, f: 'T1->'``Functor<'T2>``, g: 'U1->'``Functor<'U2>``, _impl: Default1) =
        printfn "Using Default"
        Bimap.Invoke f g x |> Bisequence.InvokeOnInstance : '``Functor<'Bitraversable<'T2,'U2>>``

type Either<'left,'right> = Left of 'left | Right of 'right with
    static member inline Bisequence x = match x with Right a -> Map.Invoke Either<'Left,'Right>.Right a | Left e -> Map.Invoke Either<'Left,'Right>.Left e
    static member inline Bimap (x, f, g) = match x with Right a -> Right (f a) | Left e -> Left (g e)

let _failureBad: Either<string, int> list = Bitraverse.Invoke id id (Left ["Bad"])

// this gives a better error
// let _failureBad: Either<string, int> list = Bimap.Invoke id id (Left ["Bad"]) |> Bisequence.InvokeOnInstance

Expected behavior

Either to fail compilation, or succeed but without run-time errors. I understand that maybe type inference is not able to solve in some cases like this, but if that's the case, it should fail at compiler-time.

Also, I don't understand why there's a dynamic invocation involved in these static calls.

Actual behavior

Compiles but fails at run-time with

> 
Using Default
> System.NotSupportedException: Dynamic invocation of Bisequence is not supported
   at <StartupCode$FSI_0005>.$FSI_0005.main@() in C:\Users\gmpl2\AppData\Local\Temp\~vs7011.fsx:line 51
Stopped due to error

Known workarounds

There are many workarounds, just change a bit the code, for instance invoking the Bimap member directly on instances, avoiding the or constraint.

Related information

F# Interactive version 10.7.0.0 for F# 4.7

gusty commented 4 years ago

I just found a better minimal repro:

type T = T with
    static member inline Mtd (f, x: 'FT) = ((^FT or ^FU) : (static member Mtd : _*_ -> _) f, x)
    static member inline Mtd (_, _:'t when 't: struct) = ()

let inline invoke x : 'FV =
    let inline call (mthd : ^M, i1: ^I1, _output: ^R) = ((^M or ^R) : (static member Mtd : _*_ -> _) (), i1)
    call (T, x, Unchecked.defaultof<'FV>)

invoke [|1|]
dsyme commented 4 years ago

I've added this to https://github.com/dotnet/fsharp/issues/8690 as any investigation should be in the context of that branch work