dotnet / dotnet/fsharp

Weird dynamic invocation error on trait-call

Open
#8,690 2 comments 1 reaction 0 assignees View on GitHub
Area-Compiler-SRTP Bug Impact-Low
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

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.