fsharp / fsharp/fslang-suggestions

Clarify status of SRTP constructor constraints

Open
#1,068 4 comments 13 reactions 0 assignees View on GitHub
approved-in-principle area: srtp-and-constraints
Dominant language
No language data
Stars
373
Forks
21
PR merge metrics
No merged PRs in 30d

Description

I propose we clarify status of "SRTP constructor constraints".

The F# language spec defines
* SRTP member constraints and
* .NET default constructor constraints

These are very different things though they look related in syntax. In types:

```fsharp
let inline SomeThing< ^T when ^T : (static member CallMe: unit -> ^T) > () = ...
let SomeThing< 'T when 'T : (new : unit -> ^T) > () = ...
```
The first is an F# SRTP member constraint and the second a "real" .NET constraint emitted as .NET metadata. The first requires `inline`, the second doesn't. The corresponding standard invocation at callsites is as follows:
```fsharp
let inline SomeThing () = (^T : (static member CallMe: unit -> ^T) ())
let SomeThing () = new 'T()
```
These correspond to `AddCxMethodConstraint` and `AddCxTypeMustSupportDefaultCtor` in the implementation.

The problem is that the status of "SRTP constructor constraints" is left unspecified. In types these are actually disallowed:
```fsharp
let inline f< ^a when ^a : (new : int -> ^a) > () = failwith ""

let inline f< ^a when ^a : (new : int -> ^a) > () = failwith ""
----------------------^^^^^^^^^^^^^^^^^^^^^^

stdin(8,23): error FS0700: 'new' constraints must take one argument of type 'unit' and return the constructed type
```

That indicates the intent of F# 2.0 was to disallow these. However in implementations a corresponding check is missing and they can arise:
```fsharp
let inline f () = (^a : (new : unit -> ^a) ());;

// val inline f : unit -> ^a when ^a : (( .ctor ) : -> ^a)
```
Note the printing is different and uses `.ctor` - that's because this is an "SRTP constructor constraint". and not a .NET default constructor constraint. These constraints are also not checked in the same way as .NET default constructor constraints, for example there is no requirement that the return type be the same as the type being constrained, leading to nonsense code like this;
```fsharp
let inline f () = (^a : (new : unit -> ^b) ()), (^b : (new : unit -> ^a) ());;

//val inline f :
// unit -> ^b * ^a
// when ^b : (( .ctor ) : -> ^a) and ^a : (( .ctor ) : -> ^b)
```

We need to clarify the status of these constraints in the F# language - given that they can already arise we should allow them. If they are allowed, then decide how they can be written in types and signatures. Note the syntax `when ^a : (new : unit -> ^a)` is not available because it is used for .NET default constructor constraints

Proposal:

1. The syntax `^a : (new : unit -> ^a)` continues to be reserved for .NET default constructor constraints
2. We find a new syntax e.g `^a : (member ``.ctor`` : unit -> ^a)` is used for SRTP constructor constraints
3. We give a warning to require this syntax at existing implementation calls, so

```fsharp
let inline f () = (^a : (new : unit -> ^a) ());;
```

gives a warning with a recommendation to become

```fsharp
let inline f () = (^a : (member ``.ctor`` : unit -> ^a) ());;
```

No syntax for these constraints is perfect given that the `when ^a : (new : unit -> ^a)` syntax is already taken, and can't really be changed to

## Pros and Cons

The advantages of making this adjustment to F# are to make the language uniform and allow all inferred signatures to be written.

The disadvantages of making this adjustment to F# are none

## Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

Related suggestions: (put links to related suggestions here)

## Affidavit (please submit!)

Please tick this by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [stackoverflow](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:
* [x] This is not a breaking change to the F# language design
* [x] I or my company would be willing to help implement and/or test this

## For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reviewing the F# language specification sections on SRTP member constraints and .NET default constructor constraints, then inspect the implementation paths named AddCxMethodConstraint and AddCxTypeMustSupportDefaultCtor. Done means reaching a language-design decision on whether SRTP constructor constraints are allowed, their signature syntax, and the required handling of existing implementation calls.

Written by the indexing model from the issue text.

Assessment

Tech stack
fsharp
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.