diff --git a/proposals/first-class-span-types.md b/proposals/first-class-span-types.md index 17d1246de3..de1832774f 100644 --- a/proposals/first-class-span-types.md +++ b/proposals/first-class-span-types.md @@ -75,6 +75,24 @@ We also add _implicit span conversion_ to the list of acceptable implicit conver > - The name of `Mₑ` is *identifier* > - `Mₑ` is accessible and applicable when applied to the arguments as a static method as shown above > - An implicit identity, reference ~~or boxing~~ **, boxing, or span** conversion exists from *expr* to the type of the first parameter of `Mₑ`. +> **Span conversion is not considered when overload resolution is performed for a method group conversion.** + +Note that implicit span conversion is not considered for extension receiver in method group conversions +which makes the following code continue working as opposed to resulting in a compile-time error +`CS1113: Extension method 'E.M(Span, int)' defined on value type 'Span' cannot be used to create delegates`: + +```cs +using System; +using System.Collections.Generic; +Action a = new int[0].M; // binds to M(IEnumerable, int) +static class E +{ + public static void M(this Span s, T x) => Console.Write(1); + public static void M(this IEnumerable e, T x) => Console.Write(2); +} +``` + +There's [an open question](#delegate-extension-receiver-break) whether this break should be avoided or not. #### Variance @@ -144,7 +162,8 @@ The compiler expects to use the following helpers or equivalents to implement th | ReadOnlySpan to ReadOnlySpan | `static ReadOnlySpan.CastUp(ReadOnlySpan)` | | string to ReadOnlySpan | `static ReadOnlySpan MemoryExtensions.AsSpan(string)` | -#### Overload resolution +#### Better conversion from expression +[betterness-rule]: #better-conversion-from-expression *Better conversion from expression* ([§12.6.4.5][better-conversion-from-expression]) is updated to prefer implicit span conversions. This is based on [collection expressions overload resolution changes][ce-or]. @@ -205,6 +224,34 @@ static class C > For example, if .NET 9 BCL introduces such overloads, users that upgrade to `net9.0` TFM but stay on lower LangVersion > will get ambiguity errors for existing code, unless BCL also applies > [the new `OverloadResolutionPriorityAttribute`][overload-resolution-priority]. +> See also [an open question](#unrestricted-betterness-rule) below. + +#### Better conversion target + +*Better conversion target* ([§12.6.4.7][better-conversion-target]) is updated to consider implicit span conversions. + +> Given two types `T₁` and `T₂`, `T₁` is a *better conversion target* than `T₂` if one of the following holds: +> +> - An implicit conversion from `T₁` to `T₂` exists and no implicit conversion from `T₂` to `T₁` exists +> **(the implicit span conversion is considered in this bullet point, even though it's a conversion from expression)** +> - [...] + +This change is needed to avoid ambiguities in existing code that would arise +because we are now ignoring user-defined Span conversions which were considered in the "better conversion target" rule, +whereas "implicit Span conversion" would not be considered as it is a "conversion from expression", not a "conversion from type". + +```cs +using System; +C.M(null); // used to print 1, would be ambiguous without this rule +static class C +{ + public static void M(object[] x) => Console.Write(1); + public static void M(ReadOnlySpan x) => Console.Write(2); +} +``` + +This rule is not needed if the span conversion is a conversion from type. +See [an open question][conversion-from-type] below. ### Type inference @@ -360,27 +407,6 @@ namespace N2 } ``` -#### Extension invocations as delegates - -In the following code snippet, `Enumerable.Contains` was chosen previously, -but `MemoryExtensions.Contains` will be chosen with this feature -(thanks to the betterness rule, otherwise this would become an ambiguity). -However, it will result in this error: - -``` -error CS1113: Extension method 'MemoryExtensions.Contains(Span, int)' defined on value type 'Span' cannot be used to create delegates -``` - -```cs -using System; -using System.Collections.Generic; -using System.Linq; - -var a = new[] { 1, 2 }; -var l = new List { 1, 2, 3, 4 }; -l.RemoveAll(a.Contains); // works today, error tomorrow -``` - ## Open questions ### Delegate signature matching (answered) @@ -418,12 +444,60 @@ without needing to create wrappers. We don't have precedent in the language for We will not allow variance in delegate conversions here. `D1 d1 = M1;` and `D2 d2 = M2;` will not compile. We could reconsider at a later point if use cases are discovered. +### Unrestricted betterness rule + +Should we make [the betterness rule][betterness-rule] unconditional on LangVersion? +That would allow API authors to add new Span APIs where IEnumerable equivalents exist +without breaking users on older LangVersions and without needing to use the `OverloadResolutionPriorityAttribute`. +However, that would mean users could get different behavior after updating the toolset (without changing LangVersion or TargetFramework): +- Compiler could choose different overloads (technically a breaking change, but hopefully those overloads would have equivalent behavior). +- Other breaks could arise, unknown at this time. + +### Delegate extension receiver break + +Should we break existing code like the following (real code found in runtime)? +LDM recently allowed breaks related to new Span overloads (https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks). + +```cs +using System; +using System.Collections.Generic; +using System.Linq; + +var list = new List { 1, 2, 3, 4 }; +var toRemove = new int[] { 2, 3 }; +list.RemoveAll(toRemove.Contains); // error CS1113: Extension method 'MemoryExtensions.Contains(Span, int)' defined on value type 'Span' cannot be used to create delegates +``` + +### Conversion from type vs. from expression +[conversion-from-type]: #conversion-from-type-vs-from-expression + +We now think it would be better if the span conversion would be a conversion from type: + +- Nothing about span conversions cares what form the expression takes; it can be a local variable, a `new[]`, a collection expression, a field, a `stackalloc`, etc. +- We have to go through everywhere in the spec that doesn't accept conversions from expression and check if they need updating to accept span conversions. + Like [better conversion target](#better-conversion-target) above. + +Note that it is not possible to define a user-defined operator between types for which a non-user-defined conversion exists ([§10.5.2 Permitted user-defined conversions][permitted-udcs]). +Hence, we would need to make an exception, so BCL can keep defining the existing Span conversion operators even when they switch to C# 13 +(to avoid binary breaking changes and also because we use these operators in codegen of the new standard span conversion). + +In Roslyn, type conversions do not have access to Compilation which is needed to access well-known type Span. +We see a couple of ways of solving this concern: + +1. We make the new conversions only applicable to the case where Span comes from the corelib, which would significantly simplify this space for us. + This would mean the rules don't exist downlevel; on the one hand, they already partly have issues downlevel + (covariance won't exist downlevel because the helper API doesn't exist). + On the other hand, that could affect partners abilities to take them up. +2. We couple type conversions to a Compilation or look at other ways of providing the well-known type to it. This will take a bit of investigation. + ## Alternatives Keep things as they are. [standard-explicit-conversions]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/conversions.md#1043-standard-explicit-conversions +[permitted-udcs]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/conversions.md#1052-permitted-user-defined-conversions [better-conversion-from-expression]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/expressions.md#12645-better-conversion-from-expression +[better-conversion-target]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/expressions.md#12647-better-conversion-target [is-type-operator]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/expressions.md#1212121-the-is-type-operator [ce-or]: https://github.com/dotnet/csharplang/blob/566a4812682ccece4ae4483d640a489287fa9c76/proposals/csharp-12.0/collection-expressions.md#overload-resolution