-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add standard implicit array-to-Span conversions #73577
Add standard implicit array-to-Span conversions #73577
Conversation
4412b6b
to
4be5ec4
Compare
8b98a9f
to
67afe70
Compare
67afe70
to
a9556fa
Compare
@@ -244,6 +244,7 @@ private static void AssertTrivialConversion(ConversionKind kind) | |||
case ConversionKind.InterpolatedString: | |||
case ConversionKind.InterpolatedStringHandler: | |||
case ConversionKind.InlineArray: | |||
case ConversionKind.ImplicitSpan: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is actually true? Won't we need to store the method symbol of the conversion on the conversion object? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't seem necessary, we can find the method during lowering. It's the same thing InlineArray conversion does. I think the difference between trivial conversions like ImplicitSpan/InlineArray and complex conversions that need a method symbol like user-defined conversions is that the latter need to do a more complex lookup to find the method symbol whereas the former just obtain a well-known member.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a matter of when we can find the method, the question is the impact to our public API; namely, will we expose the method symbol we look up in Conversion.MethodSymbol
or not. I'm currently a bit torn; given that I think we will likely spec which methods the compiler calls for each conversion, it seems like we should include that method in the conversion. On the other hand, it may be that we need to have multiple methods (Span<string>
-> ReadOnlySpan<object>
, for example, will probably need to go to ReadOnlySpan<string>
in between), and that dual method becomes more complicated to represent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the question is the impact to our public API
I see, that sounds like a question for API review.
The docs of Conversion.MethodSymbol
currently state that it's set only for user-defined and method group conversions. And indeed, for example, InlineArray conversion also does not expose the MethodSymbol.
There are other places that seem to rely on the fact that Conversion.MethodSymbol
is set only for user-defined and method group conversions, e.g., Binder.ReportDiagnosticsIfObsolete
- we report obsolete diagnostics only for user-defined methods and not built-in conversions.
src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs
Outdated
Show resolved
Hide resolved
The .Syntax one yes, in Conversion_Array_Span_Implicit_SemanticModel, the two above no, thanks. In reply to: 2127872044 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:8 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
You're right, this test is not very interesting then, I will remove it; thanks. In reply to: 2128039093 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:2429 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
I don't know what would be the advantage of disallowing user-defined conversions; but I guess the advantage of allowing them is more flexibility for users. Anyway it probably should not matter as users usually do not define their own Span types? In reply to: 2128021447 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:2185 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
Yes.
I wouldn't say it's inconsistent - if the new standard implicit span conversion isn't found, we also proceed to searching for other implicit conversions, even user-defined.
They likely could, but I don't know if they should. Do you think that would be better? It also does not look like we have any precedent for this, at least the implementation of other conversions seems to always fall through to other conversions if the one in question does not match.
Adding test _SemanticModel_02. In reply to: 2127973330 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:1584 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
Note: I have moved these
Yes. I would like to define them from type, but I guess that's impossible if we want to have them gated by LangVersion. Is that correct? Also they need to access well-known types (Span/ROS) which also feels like something that needs Compilation, i.e., can be done only for conversions from expressions. (I guess we could use SpecialType instead but then Span/ROS would need to come from the corlib, and wouldn't that make netfx unsupported since there Span/ROS comes from System.Memory.dll?)
Yes, I will do that, thanks.
Looks like it won't work because as you said, that would require the conversion to be from type, but it's currently from expression. Unless we made the span conversion a type conversion but one that requires a compilation. For example, at least when determining better conversion target, it seems we could access the compilation fine. But I assume there are other scenarios that could break. I don't actually fully understand yet why type conversions in general don't have access to compilation and what would break (it's on my list to investigate). Or we can improve the betterness rules to handle this. In reply to: 2128004491 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:1907 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
That sounds correct to me. In reply to: 2128975997 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:1907 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
It seems inconsistent to specify and implement the implicit conversions from arrays to spans but rely on an unspecified set of user-defined conversions for any additional explicit conversions from arrays to spans.
Are the other cases where the compiler specifies and implements the implicit conversions strictly between pairs of special types (where the API is presumably well-defined) or are there cases that involve well-known types (where the API is more flexible)? And if the former, are there user-defined conversions for those pairs of types? My sense is we should explicitly specify (and implement) the implicit and explicit conversions between arrays and spans, and not fallback to any additional user-defined conversions on those types. This seems like something that should be decided by language design. Please consider adding a PROTOTYPE comment to determine whether explicit conversions should be specified and implemented directly. In reply to: 2128891824 Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:1584 in 937ef86. [](commit_id = 937ef86, deletion_comment = False) |
Consider also testing: var a = new string[] { "a" };
a.M();
static class C
{
public static void M(this object[] x) => ...
public static void M(this ReadOnlySpan<object> x) => ...
}
``` #Closed
---
Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:2453 in 05ffb16. [](commit_id = 05ffb167e6c02d81950cafc006bc2660477a44fd, deletion_comment = False) |
Consider also testing: var a = new string[] { "a" };
C.M(a);
...
static class C
{
public static void M(object[] x) => ...
public static void M(ReadOnlySpan<object> x) => ...
}
``` #Closed
---
Refers to: src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs:2330 in 05ffb16. [](commit_id = 05ffb167e6c02d81950cafc006bc2660477a44fd, deletion_comment = False) |
@333fred for the second review, thanks |
@@ -244,6 +244,7 @@ private static void AssertTrivialConversion(ConversionKind kind) | |||
case ConversionKind.InterpolatedString: | |||
case ConversionKind.InterpolatedStringHandler: | |||
case ConversionKind.InlineArray: | |||
case ConversionKind.ImplicitSpan: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a matter of when we can find the method, the question is the impact to our public API; namely, will we expose the method symbol we look up in Conversion.MethodSymbol
or not. I'm currently a bit torn; given that I think we will likely spec which methods the compiler calls for each conversion, it seems like we should include that method in the conversion. On the other hand, it may be that we need to have multiple methods (Span<string>
-> ReadOnlySpan<object>
, for example, will probably need to go to ReadOnlySpan<string>
in between), and that dual method becomes more complicated to represent.
} | ||
|
||
[Fact] | ||
public void Conversion_Array_Span_Implicit_NullableAnalysis() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm honestly confused why any of these work without warning. Shouldn't this all be an invariant scenario? Or does it fall out of array covariance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, nullability warnings are a bit weird here because user-defined conversions are considered as well. So even if a span conversion would not exist (and hence produce a warning), an UDC might exist and the warning isn't produced. In a follow-up PR, UDCs will be ignored, so this should be cleared up. There's already a PROTOTYPE comment for ignoring UDCs.
Can we ignore the nullability tests here and return to them in the follow up PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just leave a quick prototype comment above one of these tests to revisit.
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.ReadOnlySpan<string>").WithLocation(9, 47), | ||
// (10,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan<object>'. | ||
// ReadOnlySpan<object> M6(string?[] arg) => arg; | ||
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.ReadOnlySpan<object>").WithLocation(10, 47), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to the warning on M7 here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, investigating this again now it seems wrong. It happens because we check that the conversion exists with ConversionsBase.IncludeNullability=true
- and we find one - an implicit user defined conversion - but we started with implicit span conversion. Anyway, as above, this will be resolved when UDCs are ignored.
} | ||
|
||
[Fact] | ||
public void Conversion_Array_Span_Implicit_NullableAnalysis_NestedArrays() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm similarly confused here. Shouldn't we have warnings on M3
? And shouldn't both versions have warnings on M5
?
} | ||
|
||
[Fact] | ||
public void Conversion_Array_ReadOnlySpan_Implicit_NullableAnalysis_NestedArrays() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confused again. I'd have expected warnings on M3
and M7
in both scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in both scenarios
C# 12 scenario isn't changed, so that might be a pre-existing bug. Compare also with _NestedNullability tests - looks like nested arrays have relaxed nullability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've investigated more and there are pre-existing inconsistencies which I think are out of scope for this feature:
- HasExplicitArrayConversion does not consider nullability of element types
- HasImplicitConversionFromArray does consider nullability of element types
} | ||
|
||
[Theory, MemberData(nameof(LangVersions))] | ||
public void Conversion_Array_Span_Implicit_NullableAnalysis_NestedNullability(LanguageVersion langVersion) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now this one works as I expected.
} | ||
|
||
[Fact] | ||
public void Conversion_Array_Span_Implicit_Out() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there variants of this with ref
returns?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like ref assignments / ref returns cannot have conversions. But I can add a test to demonstrate that.
c24c886
into
dotnet:features/FirstClassSpan
Test plan: #73445
This adds only the first two conversions:
The others will be similar, so it will be easier to add them once these are fleshed out. Plus some of the other conversions will need new well-known members and I didn't want to blow up this PR unnecessarily with that.