-
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
Handle CallerArgumentExpression in attributes #53535
Conversation
Please add relevant tests |
For the attempt to try behavior in VS, make sure the updated version of the compiler is used to build the scenario. If you are using deployment project from Roslyn solution, that is good for testing IDE behavior, but usually doesn't use updated compiler when builds out of process. In reply to: 845157436 |
@aleksey Thanks! Indeed it looks like the Roslyn Deployment wasn't using the updated compiler. The test passed. 🎉 |
public MyAttribute(string s, [CallerArgumentExpression(""s"")] string x = """") => Console.WriteLine($""'{s}', '{x}'""); | ||
} | ||
|
||
[My(""Hello"")] |
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.
Consider also testing what do we get from GetAttributes API on the calss. #Closed
@@ -874,6 +881,19 @@ private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, Attribu | |||
{ | |||
return new TypedConstant(parameterType, kind, defaultValue); | |||
} | |||
|
|||
static int getArgumentIndex(ParameterSymbol parameter, ImmutableArray<string> constructorArgumentNamesOpt) |
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.
Debug.Assert(parameter.ContainingSymbol is MethodSymbol); | ||
var methodSymbol = (MethodSymbol)parameter.ContainingSymbol; | ||
var callerArgumentParameter = methodSymbol.Parameters[parameter.CallerArgumentExpressionParameterIndex]; | ||
return constructorArgumentNamesOpt.IndexOf(callerArgumentParameter.Name); |
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.
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.
Also when there is an out of order named argument used at the position of the target parameter.
@@ -775,7 +775,7 @@ private static int GetMatchingNamedConstructorArgumentIndex(string parameterName | |||
return argIndex; | |||
} | |||
|
|||
private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, AttributeSyntax syntax, BindingDiagnosticBag diagnostics) | |||
private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, AttributeSyntax syntax, ImmutableArray<string> constructorArgumentNamesOpt, int argumentsCount, BindingDiagnosticBag diagnostics) |
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.
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.
@AlekseyTs I added few more tests. But seems I'm still hitting one call site only.
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 added few more tests. But seems I'm still hitting one call site only.
Is this still the case or were you able to cover both code paths?
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.
@AlekseyTs It's not the case now.
Debug.Assert(parameter.ContainingSymbol is MethodSymbol); | ||
var methodSymbol = (MethodSymbol)parameter.ContainingSymbol; | ||
var callerArgumentParameter = methodSymbol.Parameters[parameter.CallerArgumentExpressionParameterIndex]; | ||
return constructorArgumentNamesOpt.IndexOf(callerArgumentParameter.Name); |
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.
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.
@AlekseyTs GetMatchingNamedConstructorArgumentIndex
needs to take the parameter name, which is mostly what the helper I wrote does. Also GetMatchingNamedConstructorArgumentIndex
doesn't handle the case when no named arguments exist.
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.
GetMatchingNamedConstructorArgumentIndex needs to take the parameter name, which is mostly what the helper I wrote does
I didn't mean to say the helper is all what we need, but for the same portion of the task I prefer to reuse it.
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 found that my initial approach is problematic and hard to fix it to pass the newly added test cases. So I used a totally new approach.
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 found that my initial approach is problematic and hard to fix it to pass the newly added test cases. So I used a totally new approach.
I still think we should unify what we do here with what GetMatchingNamedConstructorArgumentIndex
, unless there is a good reason to diverge.
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.
@AlekseyTs I'm confused by this comment vs. #53535 (comment)
The other comment suggests to completely get rid of GetMatchingNamedConstructorArgumentIndex
, which I did locally and all compiler tests passed.
I went with the other comment as GetMatchingNamedConstructorArgumentIndex
didn't seem necessary.
Done with review pass (commit 2) |
@AlekseyTs This is ready for another look. |
@@ -727,6 +728,7 @@ private static int[] CreateSourceIndicesArray(int paramIndex, int parameterCount | |||
int argumentsCount, | |||
ref int argsConsumedCount, | |||
AttributeSyntax syntax, | |||
ImmutableArray<int> argumentsToParams, | |||
BindingDiagnosticBag diagnostics) | |||
{ | |||
int index = GetMatchingNamedConstructorArgumentIndex(parameter.Name, constructorArgumentNamesOpt, startIndex, argumentsCount); |
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.
@@ -819,6 +821,14 @@ private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, Attribu | |||
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 < syntax.ArgumentList.Arguments.Count) |
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.
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.
this going to include property/field initializers
Indeed, but it's probably that getCallerArgumentArgumentIndex
won't return the index of a property/field initializer. But I'll give a deeper look and add more tests.
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.
@AlekseyTs Fixed. Thanks!
@Youssef1313 It looks like there are legitimate test failures. |
@AlekseyTs |
"; | ||
|
||
var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe); | ||
// PROTOTYPE(caller-expr): This is inconsistent with the behavior of invocations, which doesn't show a lang version error. |
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.
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.
@AlekseyTs Since this is an error, nothing should be supplied. But since the error is extra, and only the warning should remain, the default value should be whatever CallerMemberName
says.
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.
Since this is an error, nothing should be supplied.
It is an error only when the target language version is C# 9. The error is supposed to go away when the target language version is Preview
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.
@AlekseyTs That is how it is currently. But it shouldn't be an error in any version.
Per your recommendation on the first PR, we check language version when there is "actual binding" for it, which is not the case here.
Let me know if I'm misunderstanding something.
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 me know if I'm misunderstanding something.
I agree with you, the error shouldn't be there. I just find the PROTOTYPE comment somewhat incomplete. I believe the problem can be observed through a value used for the parameter in a success scenario. That raises severity of the issue in my opinion.
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.
@AlekseyTs This brings another question. Is the existing behavior for CallerMemberName is correct?
It doesn't seem to work when the attribute is placed on classes. Is that by design, or a bug?
Assuming it's by design, I've fixed the CallerArgumentExpression case in a8ba7d1.
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.
@AlekseyTs Note that Mono compiler have a different behavior for this CallerMemberName
case.
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 doesn't seem to work when the attribute is placed on classes. Is that by design, or a bug?
Hard to tell, I can see this both ways. It would be interesting to know what is the behavior of the native compiler (the last version of the compiler not based on Roslyn codebase). In any case, this feels like an ortogonal issue to what we are trying to do here, feel free to open a separate issue. I think the behavior around default value shouldn't change for this scenario.
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 doesn't seem to work when the attribute is placed on classes. Is that by design, or a bug?
Hard to tell, I can see this both ways. It would be interesting to know what is the behavior of the native compiler (the last version of the compiler not based on Roslyn codebase). In any case, this feels like an ortogonal issue to what we are trying to do here, feel free to open a separate issue. I think the behavior around default value shouldn't change for this scenario.
➡️ #53757
} | ||
"; | ||
|
||
var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe); |
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.
|
||
class MyAttribute : Attribute | ||
{ | ||
public MyAttribute(int a = 1, [CallerArgumentExpression(""a"")] int expr_a = 0) |
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.
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.
@AlekseyTs The attribute definition is an error in this case, so how would I emit it as a metadata reference?
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 attribute definition is an error in this case, so how would I emit it as a metadata reference?
The simplest way would be to test with IL source. See CreateCompilationWithIL
helper for example.
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.
@AlekseyTs Added the test, not sure if the current behavior is what you'd expect or not. Can you please take a look? Thanks!
Done with review pass (commit 14) |
"; | ||
|
||
var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular9); | ||
// PROTOTYPE(caller-expr): This is inconsistent with the behavior of invocations, which doesn't show a lang version error. |
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.
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.
Deleted this outdated comment.
Done with review pass (commit 16) |
[My(1+2)] | ||
class Program | ||
{ | ||
}"; |
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.
We probably should use this code, execute it and observe behavior at runtime:
[My(1+2)]
class Program
{
static void Main()
{
typeof(Program).GetCustomAttribute(typeof(MyAttribute));
}
}
#Closed
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.
LGTM (commit 17), with a test adjustment suggestion.
@dotnet/roslyn-compiler For the second review. |
@dotnet/roslyn-compiler For the second review. |
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.
LGTM (commit 18)
I'll trigger a new pipeline when #53776 is merged into the branch, which should hopefully correct the integration failures. |
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
Draft for now as it's currently not working 😕
The code I wrote seems to be getting the default value for correctly as shown in the below screenshot. So no idea what's going on.
@333fred @AlekseyTs Would you be able to help here?
Test plan: #52745.