Skip to content

Clarifying status of SRTP constraints on class types #10959

@dsyme

Description

@dsyme

@TIHan and I spent a while looking at the status of SRTP constraints on class type parameters.

F# is intended to only support a very limited version of this, namely

type C< ^T when ^T : (static member Foo: unit -> unit)>() =
    member inline x.Method1() = ...
    ...
    member inline x.Property1 = ...
    ...

Many cases of this are properly enforced, e.g. checking there are no non-inline members, or interface implementations etc. However there is another case that we should statically disallow (constructors) and there is also a case where this interacts badly with witness passing.

Problem 1 – "witness not available at runtime".

Here the problem is that a constructor attempts to make an SRTP call on ^T, e.g.

type C< ^T when ^T : (static member Foo: unit -> unit)>() =
    let x = (^T : (static member Foo: unit -> unit)())
    member inline x.P = 1

type B1() = 
  static member Foo() = ()

let cb1 = C<B1>()

We should fail if ^T is used anywhere in the constructor (which can’t be inlined). As an aside, note that record types with constrained parameters and a fully inline Create method are allowed.

type C< ^T when ^T : (static member Foo: unit -> unit)> =

    { F: unit -> unit }
    member inline x.M() = x.F()
    
    static member inline Create () : C< ^T > =  { F = f< ^T > }

type B1() = 
  static member Foo() = printfn "hello B1"

type B2() = 
  static member Foo() = printfn "hello B2"

let cb1 = C<B1>.Create()
let cb2 = C<B2>.Create()

cb1.M()
cb2.M()  

Problem 2 – "non-generation of witness".

In this case, one of the "inline" methods has code that requires a witness e.g. a quotation or indeed any use of the witness per RFC FS-1043 #6805. We believe this code can be fixed by removing let typs = typars |> List.skip numParentTypars in GetTraitWitnessInfosOfTypars

let inline f< ^T when ^T : (static member Foo: unit -> unit)>() = (^T : (static member Foo: unit -> unit)())

type C< ^T when ^T : (static member Foo: unit -> unit)>() =

    member inline x.M() = <@ f< ^T >()  @>

type B1() = 
  static member Foo() = printfn "hello B1"

type B2() = 
  static member Foo() = printfn "hello B2"

let cb1 = C<B1>()
let cb2 = C<B2>()

cb1.M()
cb2.M() 

We should be careful that inline properties are still ok even even if problem 2 is fixed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions