Skip to content

Commit

Permalink
Add handling of params collections for natural delegate types (#71334)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlekseyTs authored Jan 6, 2024
1 parent 07fc1db commit 0e16ec0
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 13 deletions.
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,10 @@ private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpr
{
hasErrors = true;
}
else if (destination is AnonymousTypeManager.AnonymousDelegatePublicSymbol { CheckParamsCollectionsFeatureAvailability: true })
{
MessageID.IDS_ParamsCollections.CheckFeatureAvailability(diagnostics, syntax);
}

Debug.Assert(conversion.UnderlyingConversions.IsDefault);
conversion.MarkUnderlyingConversionsChecked();
Expand Down
13 changes: 7 additions & 6 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10051,14 +10051,14 @@ bool satisfiesConstraintChecks(MethodSymbol method)
var parameterDefaultValues = parameters.Any(p => p.HasExplicitDefaultValue) ?
parameters.SelectAsArray(p => p.ExplicitDefaultConstantValue) :
default;
// PROTOTYPE(ParamsCollections): Adjust
var hasParamsArray = parameters is [.., { IsParams: true } p] && p.Type.IsSZArray();

var hasParams = OverloadResolution.IsValidParams(this, methodSymbol);

Debug.Assert(ContainingMemberOrLambda is { });
Debug.Assert(parameterRefKinds.IsDefault || parameterRefKinds.Length == parameterTypes.Length);
Debug.Assert(parameterDefaultValues.IsDefault || parameterDefaultValues.Length == parameterTypes.Length);
Debug.Assert(returnType.Type is { }); // Expecting System.Void rather than null return type.
Debug.Assert(!hasParamsArray || parameterTypes.Length != 0);
Debug.Assert(!hasParams || parameterTypes.Length != 0);

bool returnsVoid = returnType.Type.IsVoidType();
var typeArguments = returnsVoid ? parameterTypes : parameterTypes.Add(returnType);
Expand All @@ -10076,7 +10076,7 @@ bool satisfiesConstraintChecks(MethodSymbol method)
}

// Use System.Action<...> or System.Func<...> if possible.
if (!hasParamsArray &&
if (!hasParams &&
returnRefKind == RefKind.None &&
parameterDefaultValues.IsDefault &&
(parameterRefKinds.IsDefault || parameterRefKinds.All(refKind => refKind == RefKind.None)) &&
Expand Down Expand Up @@ -10116,13 +10116,14 @@ bool satisfiesConstraintChecks(MethodSymbol method)
parameterRefKinds.IsDefault ? RefKind.None : parameterRefKinds[i],
parameterScopes.IsDefault ? ScopedKind.None : parameterScopes[i],
parameterDefaultValues.IsDefault ? null : parameterDefaultValues[i],
isParams: hasParamsArray && i == parameterTypes.Length - 1,
isParams: hasParams && i == parameterTypes.Length - 1,
hasUnscopedRefAttribute: parameterHasUnscopedRefAttributes.IsDefault ? false : parameterHasUnscopedRefAttributes[i]));
}
fieldsBuilder.Add(new AnonymousTypeField(name: "", location, returnType, returnRefKind, ScopedKind.None));

var typeDescr = new AnonymousTypeDescriptor(fieldsBuilder.ToImmutableAndFree(), location);
return Compilation.AnonymousTypeManager.ConstructAnonymousDelegateSymbol(typeDescr);
return Compilation.AnonymousTypeManager.ConstructAnonymousDelegateSymbol(typeDescr,
checkParamsCollectionsFeatureAvailability: hasParams && !parameters[^1].Type.IsSZArray() && Compilation.SourceModule != methodSymbol.ContainingModule);

static bool checkConstraints(CSharpCompilation compilation, ConversionsBase conversions, NamedTypeSymbol delegateType, ImmutableArray<TypeWithAnnotations> typeArguments)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public AnonymousTypeField(
bool isParams = false,
bool hasUnscopedRefAttribute = false)
{
Debug.Assert(!isParams || !typeWithAnnotations.Type.IsTypeParameter());
this.Name = name;
this.Location = location;
this.TypeWithAnnotations = typeWithAnnotations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ private NamedTypeSymbol ConstructAnonymousDelegateImplementationSymbol(Anonymous
// Replace `T` with `T[]` for params array.
if (fields is [.., { IsParams: true } lastParam, _])
{
var index = nTypeArguments - 1;
Debug.Assert(lastParam.Type.IsSZArray());

var index = fields.Length - 2;
// T minus `NullabilityAnnotation.Ignored`
var original = TypeWithAnnotations.Create(genericFieldTypes[index].Type);
// T[]
Expand Down Expand Up @@ -328,7 +330,8 @@ static bool isValidTypeArgument(bool useUpdatedEscapeRules, AnonymousTypeField f
return hasDefaultScope(useUpdatedEscapeRules, field) &&
field.Type is { } type &&
!type.IsPointerOrFunctionPointer() &&
!type.IsRestrictedType();
!type.IsRestrictedType() &&
(!field.IsParams || field.Type.IsSZArray()); // [params T collection] is not recognized as a valid params parameter definition
}

static SynthesizedDelegateKey getTemplateKey(AnonymousTypeDescriptor typeDescr, ImmutableArray<TypeParameterSymbol> typeParameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public NamedTypeSymbol ConstructAnonymousTypeSymbol(AnonymousTypeDescriptor type
return new AnonymousTypePublicSymbol(this, typeDescr);
}

public NamedTypeSymbol ConstructAnonymousDelegateSymbol(AnonymousTypeDescriptor typeDescr)
public NamedTypeSymbol ConstructAnonymousDelegateSymbol(AnonymousTypeDescriptor typeDescr, bool checkParamsCollectionsFeatureAvailability)
{
return new AnonymousDelegatePublicSymbol(this, typeDescr);
return new AnonymousDelegatePublicSymbol(this, typeDescr, checkParamsCollectionsFeatureAvailability);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@ internal sealed class AnonymousDelegatePublicSymbol : AnonymousTypeOrDelegatePub
{
private ImmutableArray<Symbol> _lazyMembers;

internal AnonymousDelegatePublicSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr) :
/// <summary>
/// This member does not participate in equality because it is not reflecting any semantic aspect of the symbol.
/// It is only used to determine if we need to check for <see cref="MessageID.IDS_ParamsCollections"/>
/// feature availability, which happens if this field is set to 'true'.
/// If in the process of merging equivalent types, the one with 'false' wins over the one with 'true',
/// that is fine, because that means that the feature availability check is performed on a
/// method declared in this compilation.
/// </summary>
internal readonly bool CheckParamsCollectionsFeatureAvailability;

internal AnonymousDelegatePublicSymbol(AnonymousTypeManager manager, AnonymousTypeDescriptor typeDescr, bool checkParamsCollectionsFeatureAvailability) :
base(manager, typeDescr)
{
CheckParamsCollectionsFeatureAvailability = checkParamsCollectionsFeatureAvailability;
}

internal override NamedTypeSymbol MapToImplementationSymbol()
Expand All @@ -30,7 +41,7 @@ internal override AnonymousTypeOrDelegatePublicSymbol SubstituteTypes(AbstractTy
{
var typeDescr = TypeDescriptor.SubstituteTypes(map, out bool changed);
return changed ?
new AnonymousDelegatePublicSymbol(Manager, typeDescr) :
new AnonymousDelegatePublicSymbol(Manager, typeDescr, CheckParamsCollectionsFeatureAvailability) :
this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ static SynthesizedDelegateInvokeMethod createInvokeMethod(
// Replace `T` with `T[]` for params array.
if (field.IsParams)
{
Debug.Assert(field.Type.IsSZArray());
Debug.Assert(i == parameterCount - 1);
type = TypeWithAnnotations.Create(ArrayTypeSymbol.CreateSZArray(containingType.ContainingAssembly, type));
}

Expand Down
Loading

0 comments on commit 0e16ec0

Please sign in to comment.