Weird dynamic invocation error on trait-call
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 131
Description
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, 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, 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>`` = 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>`` = 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, source, Unchecked.defaultof<'``Functor<'Bitraversable<'T2,'U2>>``>)
type Bisequence =
static member inline Bisequence (x: Result<'``Functor<'Error>``, '``Functor<'T>``>, _impl: Bisequence) : '``Functor>`` = 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>`` = 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, 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 list = Bitraverse.Invoke id id (Left ["Bad"])
// this gives a better error
// let _failureBad: Either 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 .$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
Contributor guide
Assessment
This issue has not been assessed yet.