fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Strange behavior in runtime

Open ingted opened this issue 9 months ago • 4 comments

According to this https://github.com/dotnet/fsharp/issues/15135

type MyType<^T when ^T: (member o: int)> (t:^T) =
    member inline this.T = t

Looks good in editor (no syntax error), but results

error FS1113: The value 'T' was marked inline but its implementation makes use of an internal or private function which is not sufficiently accessible

Is normal.

However

type MyType1<^T when ^T: (member o:int)> = {
    t: ^T
}
    with
        member inline this.T = this.t

Provides very similar functionality, but working...

type MyType1<^T when ^T: (member o: int)> =
  { t: ^T }
  member inline T: ^T

> 

Excuse me, is anyone kind to tell what's the difference? Since according to the past investigation,

I found the underlying reason. The CLR that handles generics recognizes a number of [constraints](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters), including type/interface constraints, but not explicit member constraints. The latter is a pure [F# thing](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/constraints). This means (now I am speculating) the compiler has to express it in IL by an interface constraint, and that can be done only if MyType is a "head type", i.e. statically known at compile time. These complications are also the reason why the F# Language Reference says that explicit member constraints are "not intended for common use".

ingted avatar Mar 27 '25 00:03 ingted

Hi @Martin521,

I found an interesting stuff related to you investigation.

type a () =
    member this.o x = x + 1

type A<'T> (t:'T) =
    member val T = t

type MT<^T when ^T: (member o:int -> int)> (v:^T) =
    inherit A<^T> (v)

module o =
    let v = (MT<a> (a()) :> A<a>).T.o 1

In this case, the explicit member constraint successfully has been expressed it in IL by an interface constraint (successfully executed in fsi, SDK 9.0.103), And type ^T is NOT a "head type"... How do you think about this case?

I mean... maybe the following code snipets should be valid for F# itself (not related to CLR)

type MyType<'T when ^T: (member o: int)> (t:^T) =
    member inline this.T = t

or

type MyType<'T when ^T: (member o: int)> (t:^T) =
    member this.T = t

ingted avatar Mar 28 '25 00:03 ingted

If anyone come across here, I got the workaround:


type WithBasedData<'T> (t:'T) =
    member val _base = t

type MyType<^T when ^T: (member o: int)> (t:^T) =
    inherit WithBasedData<'T>(t)
    do
        ()

type MyType<^T when ^T: (member o: int)> with
    member inline this.T = this._base

ingted avatar Apr 23 '25 09:04 ingted

@ingted I missed the comment you made a month ago. I am sorry, but this is now beyond my amateur knowledge of escape analysis and related type checking procedures. But good that you found a workaround.

Martin521 avatar Apr 23 '25 12:04 Martin521

A much quicker workaround:

type O<'T when 'T:(member i:int)>(t:'T) as this =
    [<DefaultValue>]
    val mutable internalState : 'T
    do
        this.internalState <- t

type O<'T when 'T:(member i:int)> with
    member inline this.i2 = this.internalState.i

No inherit required!!

ingted avatar May 01 '25 12:05 ingted