Open Tarmil opened 5 days ago
The motivating example with extending "allows ref struct" is by itself a reason to resolve this - I agree.
Even though we might add syntactical support for "allows ref struc" some time in the future, having a forward-compatible handling of yet-unknown constraints is good., especially in order to avoid syncing challenges with C# or runtime.
It of course follows that the constraints would have to be copied over and still checked, not just ignored. Which is tricky for "allows ref struct" since F# will not be able to enforce it.
(e.g. prevent creation of a 'T array)
Is this issue related?
// old code
type Dictionary<'Key, 'Value when 'Key: not null> with // FS0957 ... the type parameter 'Key requires a constraint of the form 'Key: not null...
member t.TryGetValueSafe(k: 'Key) =
let (b, v) = t.TryGetValue(k)
if b then ValueSome v else ValueNone
type Dictionary<'Key, 'Value when 'Key: not null> with... // same error
Whatever is causing these things making constraints optional would be a possible temporary workaround, much better would be to express these type constraints in F#.
"not null" constraint can be expressed, this is the syntax for doing a type augmentation for a Dictionary:
module TypeAugmentDictionary =
open System.Collections.Generic
type Dictionary<'TKey,'TValue when 'TKey:not null> with
member x.WhatEver() = x.Count
Even when optional in a possibly new syntax, the constraints should still be typechecked - e.g. it should not be alowed to declare a local of TKey
type and assign null to it, because this is not a support operation.
I think it makes sense to think about constraints and anti-constraints ("allows ref struct") separately in the context of a possible design.
In the new proposal, if the constraints are syntactically optional in a type extension:
.ToArray
extension)If the idea is the latter, it will enable members of an augmentation to further restrict the reach compared to the type being augmented. Either by adding a regular constraints, or by dropping an anti-constraint.
It of course follows that the constraints would have to be copied over and still checked, not just ignored. Which is tricky for "allows ref struct" since F# will not be able to enforce it.
(e.g. prevent creation of a 'T array)
For an actual constraint, like eg 'T : null
, then yes definitely. For an anti-constraint such as allows ref struct
, I think we can do without. The question of what would happen if you pass a ref struct is kind of moot, since right now you can't have ref struct type parameters at all in F#. And if/when that is implemented, then the anti-constraint can be implicitly added to the augmentation as a non-breaking change.
For a point of comparison, see what happens with a function definition, where constraints are also optional.
let f (s: seq<'T>) = ()
/// val f: seq<'T> -> unit (without the "allows ref struct" constraint)
let g (s: seq<'T>) = Seq.exists isNull s
/// val g: s: 'T seq -> bool when 'T: null
To me it would make sense that augmentations work similarly in these two cases.
If you would do a type augmentation for Seq<>, and the generic code would not be typechecked for meeting "allows ref struct" criteria, what would happen if someone tries to invoke that extension member e.g. on Seq<Span<char>>.NewMember...
?
We cannot do without unless the member is checked separately, and then the member would not be offered for ref structs.
"not null" constraint can be expressed, this is the syntax for doing a type augmentation for a Dictionary:
module TypeAugmentDictionary = open System.Collections.Generic type Dictionary<'TKey,'TValue when 'TKey:not null> with
OK so overall not relevant to this thread. On further analysis: with netstandard2.0
you can't have the not null
constraint here, with dotnet9
you must have it, and multitargeting isn't possible with a single definition. Not a big deal to worry about since netstandard2.0
is on the way to obsolescence.
I propose we make it optional to repeat the constraints on type parameters when writing an extension on a generic type.
The existing way of approaching this problem in F# is to repeat all constraints. However this is not always possible:
This is actually causing a regression in F# 9, as mentioned here. The problem is that .NET 9 introduces a new constraint
allows ref struct
which cannot (yet) be expressed in F#. So types that have this constraint currently cannot be extended. This is especially problematic because this constraint has been added to existing standard library types such asIEnumerable<T>
.Pros and Cons
The advantages of making this adjustment to F# are
allows ref struct
regression mentioned above, and avoiding similar future regressions.The disadvantages of making this adjustment to F# are
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: N/A
Affidavit (please submit!)
Please tick these items by placing a cross in the box:
Please tick all that apply:
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.