Skip to content

Commit

Permalink
Implement remaining span conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
jjonescz committed Jul 17, 2024
1 parent 991adeb commit f7577ec
Show file tree
Hide file tree
Showing 6 changed files with 3,419 additions and 418 deletions.
200 changes: 186 additions & 14 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -487,51 +487,223 @@ void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syn
}
else if (conversion.IsSpan)
{
Debug.Assert(source.Type is not null);
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.
// NOTE: We cannot use well-known members because per the spec
// the Span types involved in the Span conversions can be any that match the type name.
if (TryFindImplicitOperatorFromArray(destination.OriginalDefinition) is { } method)

// Span<T>.op_Implicit(T[]) or ReadOnlySpan<T>.op_Implicit(T[])
if (source.Type is ArrayTypeSymbol)
{
diagnostics.ReportUseSite(method, syntax);
reportUseSiteOrMissing(
TryFindImplicitOperatorFromArray(destination.OriginalDefinition),
destination.OriginalDefinition,
WellKnownMemberNames.ImplicitConversionName,
syntax,
diagnostics);
}
else

// ReadOnlySpan<T> Span<T>.op_Implicit(Span<T>)
if (source.Type.OriginalDefinition.IsSpan())
{
Error(diagnostics,
ErrorCode.ERR_MissingPredefinedMember,
Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan());
reportUseSiteOrMissing(
TryFindImplicitOperatorFromSpan(source.Type.OriginalDefinition, destination.OriginalDefinition),
source.Type.OriginalDefinition,
WellKnownMemberNames.ImplicitConversionName,
syntax,
destination.OriginalDefinition,
WellKnownMemberNames.ImplicitConversionName);
diagnostics);
}

// ReadOnlySpan<T> ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>)
if (source.Type.OriginalDefinition.IsSpan() || source.Type.OriginalDefinition.IsReadOnlySpan())
{
Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan());
if (NeedsSpanCastUp(source.Type, destination))
{
// If converting Span<TDerived> -> ROS<TDerived> -> ROS<T>,
// the source of the CastUp is the return type of the op_Implicit (i.e., the ROS<TDerived>)
// which has the same original definition as the destination ROS<T>.
TypeSymbol sourceForCastUp = source.Type.OriginalDefinition.IsSpan()
? destination.OriginalDefinition
: source.Type.OriginalDefinition;

MethodSymbol? castUpMethod = TryFindCastUpMethod(sourceForCastUp.OriginalDefinition, destination.OriginalDefinition);
reportUseSiteOrMissing(
castUpMethod,
destination.OriginalDefinition,
WellKnownMemberNames.CastUpMethodName,
syntax,
diagnostics);
castUpMethod?
.AsMember((NamedTypeSymbol)destination)
.Construct([((NamedTypeSymbol)source.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]])
.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(Compilation, Conversions, includeNullability: false, syntax.Location, diagnostics));
}
}

// ReadOnlySpan<char> MemoryExtensions.AsSpan(string)
if (source.Type.IsStringType())
{
reportUseSiteOrMissing(
TryFindAsSpanCharMethod(Compilation, destination),
WellKnownMemberNames.MemoryExtensionsTypeFullName,
WellKnownMemberNames.AsSpanMethodName,
syntax,
diagnostics);
}
}
}

static void reportUseSiteOrMissing(MethodSymbol? method, object containingType, string methodName, SyntaxNode syntax, BindingDiagnosticBag diagnostics)
{
if (method is not null)
{
diagnostics.ReportUseSite(method, syntax);
}
else
{
Error(diagnostics,
ErrorCode.ERR_MissingPredefinedMember,
syntax,
containingType,
methodName);
}
}
}

// {type}.op_Implicit(T[])
internal static MethodSymbol? TryFindImplicitOperatorFromArray(TypeSymbol type)
{
Debug.Assert(type.IsSpan() || type.IsReadOnlySpan());
Debug.Assert(type.IsDefinition);

return TryFindSingleMember(type, WellKnownMemberNames.ImplicitConversionName,
static (method) => method is
return TryFindImplicitOperator(type, 0, static (_, parameterType) =>
parameterType is ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol });
}

// ReadOnlySpan<T> Span<T>.op_Implicit(Span<T>)
internal static MethodSymbol? TryFindImplicitOperatorFromSpan(TypeSymbol spanType, TypeSymbol readonlySpanType)
{
Debug.Assert(spanType.IsSpan() && readonlySpanType.IsReadOnlySpan());
Debug.Assert(spanType.IsDefinition && readonlySpanType.IsDefinition);

return TryFindImplicitOperator(spanType, readonlySpanType,
parameterPredicate: static (_, parameterType) => parameterType.IsSpan(),
returnTypePredicate: static (readonlySpanType, returnType) => readonlySpanType.Equals(returnType.OriginalDefinition, TypeCompareKind.ConsiderEverything));
}

private static MethodSymbol? TryFindImplicitOperator<TArg>(TypeSymbol type, TArg arg,
Func<TArg, TypeSymbol, bool> parameterPredicate,
Func<TArg, TypeSymbol, bool>? returnTypePredicate = null)
{
return TryFindSingleMember(type, WellKnownMemberNames.ImplicitConversionName, (parameterPredicate, returnTypePredicate, arg),
static (arg, method) => method is
{
ParameterCount: 1,
Arity: 0,
IsStatic: true,
DeclaredAccessibility: Accessibility.Public,
Parameters: [{ Type: ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol } }]
});
Parameters: [{ Type: { } parameterType }],
} && arg.parameterPredicate(arg.arg, parameterType) &&
arg.returnTypePredicate?.Invoke(arg.arg, method.ReturnType) != false);
}

internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert(source.OriginalDefinition.IsSpan() || source.OriginalDefinition.IsReadOnlySpan());
Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan());
Debug.Assert(!source.IsDefinition && !destination.IsDefinition);

var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;

var sameElementTypes = sourceElementType.Equals(destinationElementType, TypeCompareKind.AllIgnoreOptions);

if (source.OriginalDefinition.IsReadOnlySpan())
{
Debug.Assert(!sameElementTypes);
}
else
{
Debug.Assert(source.OriginalDefinition.IsSpan());
}

return !sameElementTypes;
}

// ReadOnlySpan<T> ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) where TDerived : class
internal static MethodSymbol? TryFindCastUpMethod(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert(source.IsReadOnlySpan() && destination.IsReadOnlySpan());
Debug.Assert(source.IsDefinition && destination.IsDefinition);

return TryFindSingleMember(destination, WellKnownMemberNames.CastUpMethodName, (source, destination),
static (arg, method) => method is
{
ParameterCount: 1,
Arity: 1,
IsStatic: true,
DeclaredAccessibility: Accessibility.Public,
Parameters: [{ } parameter],
TypeArgumentsWithAnnotations: [{ } typeArgument],
} &&
// parameter type is the source ReadOnlySpan<>
arg.source.Equals(parameter.Type.OriginalDefinition, TypeCompareKind.ConsiderEverything) &&
// return type is the destination ReadOnlySpan<>
arg.destination.Equals(method.ReturnType.OriginalDefinition, TypeCompareKind.ConsiderEverything) &&
// TDerived : class
typeArgument.Type.IsReferenceType &&
// parameter type argument is TDerived
((NamedTypeSymbol)parameter.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type.Equals(typeArgument.Type, TypeCompareKind.ConsiderEverything) &&
// return type argument is T
((NamedTypeSymbol)method.ReturnType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type.Equals(((NamedTypeSymbol)arg.destination).TypeParameters[0], TypeCompareKind.ConsiderEverything));
}

// ReadOnlySpan<char> MemoryExtensions.AsSpan(string)
internal static MethodSymbol? TryFindAsSpanCharMethod(CSharpCompilation compilation, TypeSymbol readOnlySpanType)
{
Debug.Assert(readOnlySpanType.IsReadOnlySpan());
Debug.Assert(!readOnlySpanType.IsDefinition);
Debug.Assert(((NamedTypeSymbol)readOnlySpanType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].SpecialType is SpecialType.System_Char);

MethodSymbol? result = null;
foreach (var memoryExtensionsType in compilation.GetTypesByMetadataName(WellKnownMemberNames.MemoryExtensionsTypeFullName))
{
if (TryFindSingleMember(memoryExtensionsType.GetSymbol<NamedTypeSymbol>(), WellKnownMemberNames.AsSpanMethodName, 0,
static (_, method) => method is
{
ParameterCount: 1,
Arity: 0,
IsStatic: true,
DeclaredAccessibility: Accessibility.Public,
Parameters: [{ Type.SpecialType: SpecialType.System_String }]
}) is { } method &&
method.ReturnType.Equals(readOnlySpanType, TypeCompareKind.ConsiderEverything))
{
if (result is not null)
{
// Ambiguous member found.
return null;
}

result = method;
}
}

return result;
}

private static MethodSymbol? TryFindSingleMember(TypeSymbol type, string name, Func<MethodSymbol, bool> predicate)
private static MethodSymbol? TryFindSingleMember<TArg>(TypeSymbol type, string name, TArg arg, Func<TArg, MethodSymbol, bool> predicate)
{
var members = type.GetMembers(name);
MethodSymbol? result = null;
foreach (var member in members)
{
if (member is MethodSymbol method && predicate(method))
if (member is MethodSymbol method && predicate(arg, method))
{
if (result is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3953,7 +3953,7 @@ private bool IsFeatureFirstClassSpanEnabled

private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (!IsFeatureFirstClassSpanEnabled)
if (source is null || !IsFeatureFirstClassSpanEnabled)
{
return false;
}
Expand All @@ -3975,6 +3975,26 @@ private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio
return hasCovariantConversion(elementType, spanElementType, ref useSiteInfo);
}
}
// SPEC: From `System.Span<Ti>` to `System.ReadOnlySpan<Ui>`, provided that `Ti` is covariance-convertible to `Ui`.
// SPEC: From `System.ReadOnlySpan<Ti>` to `System.ReadOnlySpan<Ui>`, provided that `Ti` is covariance-convertible to `Ui`.
else if (source.OriginalDefinition.IsSpan() || source.OriginalDefinition.IsReadOnlySpan())
{
if (destination.OriginalDefinition.IsReadOnlySpan())
{
var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return hasCovariantConversion(sourceElementType, destinationElementType, ref useSiteInfo);
}
}
// SPEC: From `string` to `System.ReadOnlySpan<char>`.
else if (source.IsStringType())
{
if (destination.OriginalDefinition.IsReadOnlySpan())
{
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return spanElementType.SpecialType is SpecialType.System_Char;
}
}

return false;

Expand Down Expand Up @@ -4019,7 +4039,8 @@ private bool IgnoreUserDefinedSpanConversions(TypeSymbol? source, TypeSymbol? ta
{
return source is not null && target is not null &&
IsFeatureFirstClassSpanEnabled &&
(ignoreUserDefinedSpanConversionsInOneDirection(source, target) ||
(ignoreUserDefinedSpanConversionsInAnyDirection(source, target) ||
ignoreUserDefinedSpanConversionsInOneDirection(source, target) ||
ignoreUserDefinedSpanConversionsInOneDirection(target, source));

static bool ignoreUserDefinedSpanConversionsInOneDirection(TypeSymbol a, TypeSymbol b)
Expand All @@ -4032,11 +4053,21 @@ static bool ignoreUserDefinedSpanConversionsInOneDirection(TypeSymbol a, TypeSym
return true;
}

// PROTOTYPE: - any combination of `System.Span<T>`/`System.ReadOnlySpan<T>`
// PROTOTYPE: - `string` and `System.ReadOnlySpan<char>`
// SPEC: - `string` and `System.ReadOnlySpan<char>`
if (a.IsStringType() && b.IsReadOnlySpanChar())
{
return true;
}

return false;
}

static bool ignoreUserDefinedSpanConversionsInAnyDirection(TypeSymbol a, TypeSymbol b)
{
// SPEC: - any combination of `System.Span<T>`/`System.ReadOnlySpan<T>`
return (a.OriginalDefinition.IsSpan() || a.OriginalDefinition.IsReadOnlySpan()) &&
(b.OriginalDefinition.IsSpan() || b.OriginalDefinition.IsReadOnlySpan());
}
}
}
}
Loading

0 comments on commit f7577ec

Please sign in to comment.