Handle CallerArgumentExpression in attributes#53535
Handle CallerArgumentExpression in attributes#53535333fred merged 18 commits intodotnet:features/caller-argument-expressionfrom
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.
Consider also testing what do we get from GetAttributes API on the calss. #Closed
| return new TypedConstant(parameterType, kind, defaultValue); | ||
| } | ||
|
|
||
| static int getArgumentIndex(ParameterSymbol parameter, ImmutableArray<string> constructorArgumentNamesOpt) |
| 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.
There was a problem hiding this comment.
Also when there is an out of order named argument used at the position of the target parameter.
| } | ||
|
|
||
| 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.
@AlekseyTs I added few more tests. But seems I'm still hitting one call site only.
There was a problem hiding this comment.
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?
| 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.
@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.
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.
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.
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.
@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. |
| ImmutableArray<int> argumentsToParams, | ||
| BindingDiagnosticBag diagnostics) | ||
| { | ||
| int index = GetMatchingNamedConstructorArgumentIndex(parameter.Name, constructorArgumentNamesOpt, startIndex, argumentsCount); |
| 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.
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.
|
@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.
There was a problem hiding this comment.
@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.
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.
@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.
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.
@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.
@AlekseyTs Note that Mono compiler have a different behavior for this CallerMemberName case.
There was a problem hiding this comment.
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.
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); |
|
|
||
| class MyAttribute : Attribute | ||
| { | ||
| public MyAttribute(int a = 1, [CallerArgumentExpression(""a"")] int expr_a = 0) |
There was a problem hiding this comment.
@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.
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.
@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.
Deleted this outdated comment.
|
Done with review pass (commit 16) |
| [My(1+2)] | ||
| class Program | ||
| { | ||
| }"; |
There was a problem hiding this comment.
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
AlekseyTs
left a comment
There was a problem hiding this comment.
LGTM (commit 17), with a test adjustment suggestion.
|
@dotnet/roslyn-compiler For the second review. |
|
@dotnet/roslyn-compiler For the second review. |
|
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.