-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Switch implicit span to conversion from type #74232
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -486,20 +486,56 @@ void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syn | |
} | ||
else if (conversion.IsSpan) | ||
{ | ||
Debug.Assert(destination.OriginalDefinition.IsSpan() || destination.OriginalDefinition.IsReadOnlySpan()); | ||
|
||
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureFirstClassSpan, diagnostics); | ||
|
||
// PROTOTYPE: Check runtime APIs used for other span conversions once they are implemented. | ||
if (destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)) | ||
// NOTE: Span conversions do not use well-known types because they are "conversions from type" and hence don't have access to Compilation. | ||
if (TryFindImplicitOperatorFromArray(destination.OriginalDefinition) is { } method) | ||
{ | ||
_ = GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__op_Implicit_Array, diagnostics, syntax: syntax); | ||
diagnostics.ReportUseSite(method, syntax); | ||
} | ||
else | ||
{ | ||
Debug.Assert(destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions)); | ||
_ = GetWellKnownTypeMember(WellKnownMember.System_Span_T__op_Implicit_Array, diagnostics, syntax: syntax); | ||
Error(diagnostics, | ||
ErrorCode.ERR_MissingPredefinedMember, | ||
syntax, | ||
destination.OriginalDefinition, | ||
WellKnownMemberNames.ImplicitConversionName); | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal static MethodSymbol? TryFindImplicitOperatorFromArray(TypeSymbol type) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be worth extracting this as a |
||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
var members = type.GetMembers(WellKnownMemberNames.ImplicitConversionName); | ||
MethodSymbol? result = null; | ||
foreach (var member in members) | ||
{ | ||
if (member is MethodSymbol | ||
{ | ||
ParameterCount: 1, | ||
Arity: 0, | ||
IsStatic: true, | ||
DeclaredAccessibility: Accessibility.Public, | ||
Parameters: [{ Type: ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol } }] | ||
} method) | ||
{ | ||
if (result is not null) | ||
{ | ||
// Ambiguous method found. | ||
return null; | ||
} | ||
|
||
result = method; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static void CheckInlineArrayTypeIsSupported(SyntaxNode syntax, TypeSymbol inlineArrayType, TypeSymbol elementType, BindingDiagnosticBag diagnostics) | ||
{ | ||
if (elementType.IsPointerOrFunctionPointer() || elementType.IsRestrictedType()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -602,7 +602,6 @@ private static bool IsStandardImplicitConversionFromExpression(ConversionKind ki | |
case ConversionKind.StackAllocToPointerType: | ||
case ConversionKind.StackAllocToSpanType: | ||
case ConversionKind.InlineArray: | ||
case ConversionKind.ImplicitSpan: | ||
return true; | ||
default: | ||
return false; | ||
|
@@ -624,6 +623,7 @@ private static bool IsStandardImplicitConversionFromType(ConversionKind kind) | |
case ConversionKind.ImplicitPointer: | ||
case ConversionKind.ImplicitPointerToVoid: | ||
case ConversionKind.ImplicitTuple: | ||
case ConversionKind.ImplicitSpan: | ||
return true; | ||
default: | ||
return false; | ||
|
@@ -743,6 +743,11 @@ Conversion classifyConversion(TypeSymbol source, TypeSymbol destination, ref Com | |
return tupleConversion; | ||
} | ||
|
||
if (HasImplicitSpanConversion(source, destination, ref useSiteInfo)) | ||
{ | ||
return Conversion.ImplicitSpan; | ||
} | ||
|
||
return Conversion.NoConversion; | ||
} | ||
} | ||
|
@@ -912,6 +917,10 @@ private Conversion DeriveStandardExplicitFromOppositeStandardImplicitConversion( | |
|
||
break; | ||
|
||
case ConversionKind.ImplicitSpan: | ||
impliedExplicitConversion = Conversion.NoConversion; | ||
break; | ||
|
||
default: | ||
throw ExceptionUtilities.UnexpectedValue(oppositeConversion.Kind); | ||
} | ||
|
@@ -1021,11 +1030,6 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi | |
return Conversion.ImplicitDynamic; | ||
} | ||
|
||
if (HasImplicitSpanConversion(source, destination, ref useSiteInfo)) | ||
{ | ||
return Conversion.ImplicitSpan; | ||
} | ||
|
||
// The following conversions only exist for certain form of expressions, | ||
// if we have no expression none if them is applicable. | ||
if (sourceExpression == null) | ||
|
@@ -1909,8 +1913,7 @@ public Conversion ConvertExtensionMethodThisArg(TypeSymbol parameterType, TypeSy | |
public Conversion ClassifyImplicitExtensionMethodThisArgConversion(BoundExpression sourceExpressionOpt, TypeSymbol sourceType, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
{ | ||
Debug.Assert(sourceExpressionOpt is null || Compilation is not null); | ||
// PROTOTYPE: Revert `TypeSymbol.Equals(...)` to `(object)sourceExpressionOpt.Type == sourceType` when implicit span is a "conversion from type". | ||
Debug.Assert(sourceExpressionOpt == null || TypeSymbol.Equals(sourceExpressionOpt.Type, sourceType, TypeCompareKind.ConsiderEverything)); | ||
Debug.Assert(sourceExpressionOpt == null || (object)sourceExpressionOpt.Type == sourceType); | ||
Debug.Assert((object)destination != null); | ||
|
||
if ((object)sourceType != null) | ||
|
@@ -3931,8 +3934,8 @@ private static bool IsIntegerTypeSupportingPointerConversions(TypeSymbol type) | |
#nullable enable | ||
private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
{ | ||
// PROTOTYPE: Is it fine that this conversion does not exists when Compilation is null? | ||
if (Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) != true) | ||
// Note: when Compilation is null, we assume latest LangVersion. | ||
if (Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) == false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little worried about this. Do we have an understanding of when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So far I've been able to hit only one scenario with Compilation being null - from /// <param name="compilation">The compilation in which constraints should be checked.
/// Should not be null, but if it is null we treat constraints as we would in the latest
/// language version.</param> Maybe other scenarios (not just reduced extension methods) should behave similarly? But there might be no more scenarios, I have investigated callsites for a while and haven't found any that I could hit.
If they end up in the binder, they will get a language version error. Otherwise, they will get different behavior - like the ReduceExtensionMethod example mentioned above - the call will consider the implicit span conversion even in older lang version. |
||
{ | ||
return false; | ||
} | ||
|
@@ -3941,14 +3944,14 @@ private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio | |
if (source is ArrayTypeSymbol { IsSZArray: true, ElementTypeWithAnnotations: { } elementType }) | ||
{ | ||
// SPEC: ...to `System.Span<Ei>`. | ||
if (destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions)) | ||
if (destination.OriginalDefinition.IsSpan()) | ||
{ | ||
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; | ||
return hasIdentityConversion(elementType, spanElementType); | ||
} | ||
|
||
// SPEC: ...to `System.ReadOnlySpan<Ui>`, provided that `Ei` is covariance-convertible to `Ui`. | ||
if (destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)) | ||
if (destination.OriginalDefinition.IsReadOnlySpan()) | ||
{ | ||
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; | ||
return hasCovariantConversion(elementType, spanElementType, ref useSiteInfo); | ||
|
@@ -3975,8 +3978,8 @@ bool hasIdentityConversion(TypeWithAnnotations source, TypeWithAnnotations desti | |
/// </remarks> | ||
private bool HasExplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
{ | ||
// PROTOTYPE: Is it fine that this conversion does not exists when Compilation is null? | ||
if (Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) != true) | ||
// Note: when Compilation is null, we assume latest LangVersion. | ||
if (Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) == false) | ||
{ | ||
return false; | ||
} | ||
|
@@ -3985,8 +3988,7 @@ private bool HasExplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio | |
// to `System.Span<Ui>` or `System.ReadOnlySpan<Ui>` | ||
// provided an explicit reference conversion exists from `Ti` to `Ui`. | ||
if (source is ArrayTypeSymbol { IsSZArray: true, ElementTypeWithAnnotations: { } elementType } && | ||
(destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions) || | ||
destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions))) | ||
(destination.OriginalDefinition.IsSpan() || destination.OriginalDefinition.IsReadOnlySpan())) | ||
{ | ||
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; | ||
return HasIdentityOrReferenceConversion(elementType.Type, spanElementType.Type, ref useSiteInfo) && | ||
|
@@ -4003,18 +4005,17 @@ private bool IgnoreUserDefinedSpanConversions(TypeSymbol? source, TypeSymbol? ta | |
return false; | ||
} | ||
|
||
// PROTOTYPE: Is it fine that this check is not performed when Compilation is null? | ||
return Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) == true && | ||
(ignoreUserDefinedSpanConversionsInOneDirection(Compilation, source, target) || | ||
ignoreUserDefinedSpanConversionsInOneDirection(Compilation, target, source)); | ||
// Note: when Compilation is null, we assume latest LangVersion. | ||
return Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) != false && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check is repeated several times, with the same comment. Consider creating a private |
||
(ignoreUserDefinedSpanConversionsInOneDirection(source, target) || | ||
ignoreUserDefinedSpanConversionsInOneDirection(target, source)); | ||
|
||
static bool ignoreUserDefinedSpanConversionsInOneDirection(CSharpCompilation compilation, TypeSymbol a, TypeSymbol b) | ||
static bool ignoreUserDefinedSpanConversionsInOneDirection(TypeSymbol a, TypeSymbol b) | ||
{ | ||
// SPEC: User-defined conversions are not considered when converting between | ||
// SPEC: - any single-dimensional `array_type` and `System.Span<T>`/`System.ReadOnlySpan<T>` | ||
if (a is ArrayTypeSymbol { IsSZArray: true } && | ||
(b.OriginalDefinition.Equals(compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions) || | ||
b.OriginalDefinition.Equals(compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions))) | ||
(b.OriginalDefinition.IsSpan() || b.OriginalDefinition.IsReadOnlySpan())) | ||
{ | ||
return true; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -619,28 +619,17 @@ private BoundExpression MakeConversionNodeCore( | |
{ | ||
var spanType = (NamedTypeSymbol)rewrittenType; | ||
|
||
WellKnownMember member; | ||
if (spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)) | ||
{ | ||
member = WellKnownMember.System_ReadOnlySpan_T__op_Implicit_Array; | ||
} | ||
else | ||
{ | ||
Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions)); | ||
member = WellKnownMember.System_Span_T__op_Implicit_Array; | ||
} | ||
|
||
if (!TryGetWellKnownTypeMember(rewrittenOperand.Syntax, member, out MethodSymbol? symbol)) | ||
if (Binder.TryFindImplicitOperatorFromArray(spanType.OriginalDefinition) is not { } methodDefinition) | ||
{ | ||
throw ExceptionUtilities.Unreachable(); | ||
} | ||
else | ||
{ | ||
MethodSymbol method = symbol.AsMember(spanType); | ||
MethodSymbol method = methodDefinition.AsMember(spanType); | ||
|
||
rewrittenOperand = _factory.Convert(method.ParameterTypesWithAnnotations[0].Type, rewrittenOperand); | ||
|
||
if (member == WellKnownMember.System_ReadOnlySpan_T__op_Implicit_Array) | ||
if (_compilation.IsReadOnlySpanType(spanType)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was my intent - I even have a test for it: |
||
{ | ||
return new BoundReadOnlySpanFromArray(syntax, rewrittenOperand, method, spanType) { WasCompilerGenerated = true }; | ||
} | ||
|
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.
Not quite how I'd word this comment; the lack of a
Compilation
inConversionsBase
is just an implementation detail. The reason is that we explicitly specified that we match(ReadOnly)Span
by type name, so we need to use that same(ReadOnly)Span
to look up APIs in. #Resolved