fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Null value returned from trait call

Open gusty opened this issue 3 years ago • 1 comments

There are some cases where an invalid value is returned from a trait call.

Repro steps

  1. Step A

try in fsi

type A = A with
    static member ($) (A, a: float  ) = 0.0
    static member ($) (A, a: decimal) = 0M
    static member ($) (A, a: 't     ) = 0

let inline call x = ($) A x

send it

  1. Step B

Now try this call 42.

Expected behavior

val it : float = 0.0

Actual behavior

val it : obj = <null>

Known workarounds

I haven't found one so far.

  • Operating system: Windows
  • FSI: F# Interactive version 11.4.2.0 for F# 5.0
  • Editing Tools: Visual Studio 2019

I didn't try in older versions, but I'm under the impression that this is a regression.

gusty avatar Nov 13 '21 08:11 gusty

And here's a repro to send to FSI in one shot

type A = A with
    static member ($) (A, a: float  ) = 0.0
    static member ($) (A, a: decimal) = 0M
    static member ($) (A, a: 't     ) = 0

let inline call x = ($) A x


call 42.  + 1.1

Which results in

type A =
  | A
  with
    static member ( $ ) : A:A * a:'t -> int + 2 overloads
  end
val inline call :
  x: ^a ->  ^_arg3
    when (A or  ^a) : (static member ( $ ) : A *  ^a ->  ^_arg3)
val it : float = nan

when the expeced result should be 1.1

gusty avatar Nov 13 '21 08:11 gusty

@gusty I have tried today with on a script and on fsi and seems to be fixed now :) FYI @vzarytovskii Screenshot 2022-09-19 at 14 57 33

edgarfgp avatar Sep 19 '22 12:09 edgarfgp

Yes, it works for me as well. I wonder what was the fixing PR.

gusty avatar Sep 19 '22 14:09 gusty

Yes, it works for me as well. I wonder what was the fixing PR.

It is likely that the work on the static abstract interface methods

vzarytovskii avatar Sep 19 '22 15:09 vzarytovskii

Hmm, I don't think so. I'm still on F# 6 and it seems fixed.

gusty avatar Sep 19 '22 16:09 gusty

Hmm, I don't think so. I'm still on F# 6 and it seems fixed.

Only feature itself is tied to language version, some changes and fixes are not.

Thought if you're on 6.x.x, then it's not it yes

vzarytovskii avatar Sep 19 '22 16:09 vzarytovskii

Might be worth adding a regression test 😀

edgarfgp avatar Sep 19 '22 16:09 edgarfgp

I just tried it again in both F#6 and F#7 my second repro works well.

But the original repro (the one in the issue step by step) still fails in both, surprisingly in different ways:

  • In F#6 I get the original failure:
> type A = A with
-     static member ($) (A, a: float  ) = 0.0
-     static member ($) (A, a: decimal) = 0M
-     static member ($) (A, a: 't     ) = 0
-
- let inline call x = ($) A x
- ;;
type A =
  | A
  static member ($) : A: A * a: 't -> int + 2 overloads
val inline call:
  x:  ^a ->  ^_arg3 when (A or  ^a) : (static member ($) : A *  ^a ->  ^_arg3)

> call 42. ;;
val it: obj = <null>
  • In F#7 I get a value restriction:
> type A = A with
    static member ($) (A, a: float  ) = 0.0
    static member ($) (A, a: decimal) = 0M
    static member ($) (A, a: 't     ) = 0

let inline call x = ($) A x;;
type A =
  | A
  static member ($) : A: A * a: 't -> int + 2 overloads
val inline call:
  x: ^a -> '_arg3 when (A or ^a) : (static member ($) : A * ^a -> '_arg3)

> call 42. ;;

  call 42. ;;
  ^^^^^^^^

stdin(7,1): error FS0030: Value restriction. The value 'it' has been inferred to have generic type
    val it: '_a    
Either define 'it' as a simple data term, make it a function with explicit arguments or, if you do not intend for it to be generic, add a type annotation.

@edgarfgp I think you tried sending all the code to fsi at once. Try it in steps as described in the issue.

gusty avatar Sep 20 '22 05:09 gusty

@gusty You are right I can reproduce it by sending line by line . But works when sending all at once, seems to be a FSI bug/limitation

Note: I have tried with dotnet fsi --multiemit and does NOT make a difference

edgarfgp avatar Sep 21 '22 08:09 edgarfgp

@edgarfgp note that this difference between sending all at once is not that uncommon: type inference uses "last minute" information to specialize generic functions. When you create the function and immediately after you use it, the "use" is taken into account. If you send both fragments separately type inference is independent.

gusty avatar Sep 21 '22 09:09 gusty

Confirmed to be reproducing in F# 8 Microsoft (R) F# Interactive version 12.8.0.0 for F# 8.0 with the value restriction variant described above https://github.com/dotnet/fsharp/issues/12386#issuecomment-1251868590

gusty avatar Sep 18 '23 06:09 gusty