-
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
Handle CallerArgumentExpression in attributes #53535
Changes from 13 commits
2b111de
91d65f2
c5c8b12
2a6ceb7
a434aa5
446bfd1
d945e3c
0e19e35
0bce268
413c81a
68c8078
146b609
6131eb4
c05caed
4b3d333
a8ba7d1
d103a43
2194bc9
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 |
---|---|---|
|
@@ -241,7 +241,7 @@ private CSharpAttributeData GetAttribute(BoundAttribute boundAttribute, BindingD | |
else | ||
{ | ||
constructorArguments = GetRewrittenAttributeConstructorArguments(out constructorArgumentsSourceIndices, attributeConstructor, | ||
constructorArgsArray, boundAttribute.ConstructorArgumentNamesOpt, (AttributeSyntax)boundAttribute.Syntax, diagnostics, ref hasErrors); | ||
constructorArgsArray, boundAttribute.ConstructorArgumentNamesOpt, (AttributeSyntax)boundAttribute.Syntax, boundAttribute.ConstructorArgumentsToParamsOpt, diagnostics, ref hasErrors); | ||
} | ||
|
||
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); | ||
|
@@ -593,6 +593,7 @@ private ImmutableArray<TypedConstant> GetRewrittenAttributeConstructorArguments( | |
ImmutableArray<TypedConstant> constructorArgsArray, | ||
ImmutableArray<string> constructorArgumentNamesOpt, | ||
AttributeSyntax syntax, | ||
ImmutableArray<int> argumentsToParams, | ||
BindingDiagnosticBag diagnostics, | ||
ref bool hasErrors) | ||
{ | ||
|
@@ -610,9 +611,6 @@ private ImmutableArray<TypedConstant> GetRewrittenAttributeConstructorArguments( | |
Debug.Assert(!hasNamedCtorArguments || | ||
constructorArgumentNamesOpt.Length == argumentsCount); | ||
|
||
// index of the first named constructor argument | ||
int firstNamedArgIndex = -1; | ||
|
||
ImmutableArray<ParameterSymbol> parameters = attributeConstructor.Parameters; | ||
int parameterCount = parameters.Length; | ||
|
||
|
@@ -650,28 +648,20 @@ private ImmutableArray<TypedConstant> GetRewrittenAttributeConstructorArguments( | |
} | ||
else | ||
{ | ||
// named constructor argument | ||
|
||
// Store the index of the first named constructor argument | ||
if (firstNamedArgIndex == -1) | ||
{ | ||
firstNamedArgIndex = argsConsumedCount; | ||
} | ||
|
||
// Current parameter must either have a matching named argument or a default value | ||
// For the former case, argsConsumedCount must be incremented to note that we have | ||
// consumed a named argument. For the latter case, argsConsumedCount stays same. | ||
int matchingArgumentIndex; | ||
reorderedArgument = GetMatchingNamedOrOptionalConstructorArgument(out matchingArgumentIndex, constructorArgsArray, | ||
constructorArgumentNamesOpt, parameter, firstNamedArgIndex, argumentsCount, ref argsConsumedCount, syntax, diagnostics); | ||
reorderedArgument = getMatchingNamedOrOptionalConstructorArgument(out matchingArgumentIndex, constructorArgsArray, | ||
parameter, argumentsCount, ref argsConsumedCount, syntax, argumentsToParams, diagnostics); | ||
|
||
sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount); | ||
sourceIndices[i] = matchingArgumentIndex; | ||
} | ||
} | ||
else | ||
{ | ||
reorderedArgument = GetDefaultValueArgument(parameter, syntax, diagnostics); | ||
reorderedArgument = GetDefaultValueArgument(parameter, syntax, argumentsToParams, argumentsCount, diagnostics); | ||
sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount); | ||
} | ||
|
||
|
@@ -697,6 +687,36 @@ private ImmutableArray<TypedConstant> GetRewrittenAttributeConstructorArguments( | |
|
||
constructorArgumentsSourceIndices = sourceIndices != null ? sourceIndices.AsImmutableOrNull() : default(ImmutableArray<int>); | ||
return reorderedArguments.AsImmutableOrNull(); | ||
|
||
// Local functions | ||
TypedConstant getMatchingNamedOrOptionalConstructorArgument( | ||
out int matchingArgumentIndex, | ||
ImmutableArray<TypedConstant> constructorArgsArray, | ||
ParameterSymbol parameter, | ||
int argumentsCount, | ||
ref int argsConsumedCount, | ||
AttributeSyntax syntax, | ||
ImmutableArray<int> argumentsToParams, | ||
BindingDiagnosticBag diagnostics) | ||
{ | ||
matchingArgumentIndex = argumentsToParams.IsDefaultOrEmpty | ||
? parameter.Ordinal | ||
: argumentsToParams.IndexOf(parameter.Ordinal); | ||
|
||
if (matchingArgumentIndex > -1) | ||
{ | ||
// found a matching named argument | ||
Debug.Assert(matchingArgumentIndex < argumentsCount); | ||
|
||
// increment argsConsumedCount | ||
argsConsumedCount++; | ||
return constructorArgsArray[matchingArgumentIndex]; | ||
} | ||
else | ||
{ | ||
return GetDefaultValueArgument(parameter, syntax, argumentsToParams, argumentsCount, diagnostics); | ||
} | ||
} | ||
} | ||
|
||
private static int[] CreateSourceIndicesArray(int paramIndex, int parameterCount) | ||
|
@@ -718,64 +738,7 @@ private static int[] CreateSourceIndicesArray(int paramIndex, int parameterCount | |
return sourceIndices; | ||
} | ||
|
||
private TypedConstant GetMatchingNamedOrOptionalConstructorArgument( | ||
out int matchingArgumentIndex, | ||
ImmutableArray<TypedConstant> constructorArgsArray, | ||
ImmutableArray<string> constructorArgumentNamesOpt, | ||
ParameterSymbol parameter, | ||
int startIndex, | ||
int argumentsCount, | ||
ref int argsConsumedCount, | ||
AttributeSyntax syntax, | ||
BindingDiagnosticBag diagnostics) | ||
{ | ||
int index = GetMatchingNamedConstructorArgumentIndex(parameter.Name, constructorArgumentNamesOpt, startIndex, argumentsCount); | ||
|
||
if (index < argumentsCount) | ||
{ | ||
// found a matching named argument | ||
Debug.Assert(index >= startIndex); | ||
|
||
// increment argsConsumedCount | ||
argsConsumedCount++; | ||
matchingArgumentIndex = index; | ||
return constructorArgsArray[index]; | ||
} | ||
else | ||
{ | ||
matchingArgumentIndex = -1; | ||
return GetDefaultValueArgument(parameter, syntax, diagnostics); | ||
} | ||
} | ||
|
||
private static int GetMatchingNamedConstructorArgumentIndex(string parameterName, ImmutableArray<string> argumentNamesOpt, int startIndex, int argumentsCount) | ||
{ | ||
RoslynDebug.Assert(parameterName != null); | ||
Debug.Assert(startIndex >= 0 && startIndex < argumentsCount); | ||
|
||
if (parameterName.IsEmpty() || !argumentNamesOpt.Any()) | ||
{ | ||
return argumentsCount; | ||
} | ||
|
||
// get the matching named (constructor) argument | ||
int argIndex = startIndex; | ||
while (argIndex < argumentsCount) | ||
{ | ||
var name = argumentNamesOpt[argIndex]; | ||
|
||
if (string.Equals(name, parameterName, StringComparison.Ordinal)) | ||
{ | ||
break; | ||
} | ||
|
||
argIndex++; | ||
} | ||
|
||
return argIndex; | ||
} | ||
|
||
private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, AttributeSyntax syntax, BindingDiagnosticBag diagnostics) | ||
private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, AttributeSyntax syntax, ImmutableArray<int> argumentsToParams, int argumentsCount, BindingDiagnosticBag diagnostics) | ||
{ | ||
var parameterType = parameter.Type; | ||
ConstantValue? defaultConstantValue = parameter.IsOptional ? parameter.ExplicitDefaultConstantValue : ConstantValue.NotAvailable; | ||
|
@@ -803,22 +766,31 @@ private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, Attribu | |
else | ||
{ | ||
// Boxing or identity conversion: | ||
parameterType = Compilation.GetSpecialType(SpecialType.System_Int32); | ||
parameterType = GetSpecialType(SpecialType.System_Int32, diagnostics, syntax); | ||
defaultValue = line; | ||
} | ||
} | ||
else if (!IsEarlyAttributeBinder && parameter.IsCallerFilePath) | ||
{ | ||
parameterType = Compilation.GetSpecialType(SpecialType.System_String); | ||
parameterType = GetSpecialType(SpecialType.System_String, diagnostics, syntax); | ||
kind = TypedConstantKind.Primitive; | ||
defaultValue = syntax.SyntaxTree.GetDisplayPath(syntax.Name.Span, Compilation.Options.SourceReferenceResolver); | ||
} | ||
else if (!IsEarlyAttributeBinder && parameter.IsCallerMemberName && (object)((ContextualAttributeBinder)this).AttributedMember != null) | ||
{ | ||
parameterType = Compilation.GetSpecialType(SpecialType.System_String); | ||
parameterType = GetSpecialType(SpecialType.System_String, diagnostics, syntax); | ||
kind = TypedConstantKind.Primitive; | ||
defaultValue = ((ContextualAttributeBinder)this).AttributedMember.GetMemberCallerName(); | ||
} | ||
else if (!IsEarlyAttributeBinder && syntax.ArgumentList is not null && | ||
getCallerArgumentArgumentIndex(parameter, argumentsToParams) is int argumentIndex && argumentIndex > -1 && argumentIndex < argumentsCount) | ||
{ | ||
Debug.Assert(argumentsCount <= syntax.ArgumentList.Arguments.Count); | ||
CheckFeatureAvailability(syntax.ArgumentList, MessageID.IDS_FeatureCallerArgumentExpression, diagnostics); | ||
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. |
||
parameterType = GetSpecialType(SpecialType.System_String, diagnostics, syntax); | ||
kind = TypedConstantKind.Primitive; | ||
defaultValue = syntax.ArgumentList.Arguments[argumentIndex].Expression.ToString(); | ||
} | ||
else if (defaultConstantValue == ConstantValue.NotAvailable) | ||
{ | ||
// There is no constant value given for the parameter in source/metadata. | ||
|
@@ -874,6 +846,13 @@ private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, Attribu | |
{ | ||
return new TypedConstant(parameterType, kind, defaultValue); | ||
} | ||
|
||
static int getCallerArgumentArgumentIndex(ParameterSymbol parameter, ImmutableArray<int> argumentsToParams) | ||
{ | ||
return argumentsToParams.IsDefault | ||
? parameter.CallerArgumentExpressionParameterIndex | ||
: argumentsToParams.IndexOf(parameter.CallerArgumentExpressionParameterIndex); | ||
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. Suppose 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. @AlekseyTs I'm not sure what exactly we want to test here. Extra argument or unmatched named argument would produce an error elsewhere, so I assume such a test shouldn't be affected by the logic here? Please correct me. I'm certainly open to add more tests, but I'm not getting which scenario I should try to hit in the test. 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.
I'll try to explain why I am asking for more testing here. The logic here as it stands right now feels fragile to me. In particular, if 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. @AlekseyTs Thanks a ton! Yes I wrote the code under the assumption that the array won't contain -1. |
||
} | ||
} | ||
|
||
private static TypedConstant GetParamArrayArgument(ParameterSymbol parameter, ImmutableArray<TypedConstant> constructorArgsArray, | ||
|
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 feels like we should be able to completely get rid of this field in favor of using
ConstructorArgumentsToParamsOpt
. However, it is probably better to follow up on that separately.