Skip to content
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

Conversation

Youssef1313
Copy link
Member

@Youssef1313 Youssef1313 commented May 20, 2021

Draft for now as it's currently not working 😕

image

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.

image

@333fred @AlekseyTs Would you be able to help here?


Test plan: #52745.

@Youssef1313 Youssef1313 requested a review from a team as a code owner May 20, 2021 08:26
@Youssef1313 Youssef1313 marked this pull request as draft May 20, 2021 08:26
@Youssef1313 Youssef1313 reopened this May 20, 2021
@AlekseyTs AlekseyTs added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label May 20, 2021
@AlekseyTs
Copy link
Contributor

@Youssef1313

Would you be able to help here?

Please add relevant tests

@AlekseyTs
Copy link
Contributor

AlekseyTs commented May 20, 2021

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

@Youssef1313
Copy link
Member Author

@aleksey Thanks! Indeed it looks like the Roslyn Deployment wasn't using the updated compiler. The test passed. 🎉

@Youssef1313 Youssef1313 marked this pull request as ready for review May 20, 2021 16:12
public MyAttribute(string s, [CallerArgumentExpression(""s"")] string x = """") => Console.WriteLine($""'{s}', '{x}'"");
}

[My(""Hello"")]
Copy link
Contributor

@AlekseyTs AlekseyTs May 20, 2021

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)
Copy link
Contributor

@AlekseyTs AlekseyTs May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getArgumentIndex

"getCallerArgumentArgumentIndex"? #Closed

Debug.Assert(parameter.ContainingSymbol is MethodSymbol);
var methodSymbol = (MethodSymbol)parameter.ContainingSymbol;
var callerArgumentParameter = methodSymbol.Parameters[parameter.CallerArgumentExpressionParameterIndex];
return constructorArgumentNamesOpt.IndexOf(callerArgumentParameter.Name);
Copy link
Contributor

@AlekseyTs AlekseyTs May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return constructorArgumentNamesOpt.IndexOf(callerArgumentParameter.Name);

It looks like this code path isn't tested. Especially it is interesting what the behavior is for the situation when the corresponding argument isn't actually named, but others are. #Closed

Copy link
Contributor

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)
Copy link
Contributor

@AlekseyTs AlekseyTs May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetDefaultValueArgument

We have two call sites and only one test scenario. It feels like one call site doesn't have test coverage #Closed

Copy link
Member Author

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.

Copy link
Contributor

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?

Copy link
Member Author

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);
Copy link
Contributor

@AlekseyTs AlekseyTs May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructorArgumentNamesOpt.IndexOf(callerArgumentParameter.Name)

We probably should take advantage of GetMatchingNamedConstructorArgumentIndex helper rather than inventing a new way of mapping parameters to named arguments. #Closed

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

@Youssef1313 Youssef1313 May 24, 2021

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.

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 2)

@Youssef1313
Copy link
Member Author

@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);
Copy link
Contributor

@AlekseyTs AlekseyTs May 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetMatchingNamedConstructorArgumentIndex

Do we still need this helper and the constructorArgumentNamesOpt if we now have argumentsToParams map? #Closed

@@ -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)
Copy link
Contributor

@AlekseyTs AlekseyTs May 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax.ArgumentList.Arguments.Count

This looks suspicious. Isn't this going to include property/field initializers? Do we have tests for scenarios like that? #Closed

Copy link
Member Author

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlekseyTs Fixed. Thanks!

@AlekseyTs
Copy link
Contributor

@Youssef1313 It looks like there are legitimate test failures.

@Youssef1313
Copy link
Member Author

Youssef1313 commented May 27, 2021

@AlekseyTs When I quickly looked at it, I couldn't figure out what's going wrong. The line that sets defaultValue is setting it to the expected value correctly. I'll try to debug more deeply. Fixed

";

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.
Copy link
Contributor

@AlekseyTs AlekseyTs May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PROTOTYPE(caller-expr): This is inconsistent with the behavior of invocations, which doesn't show a lang version error.

It feels like this isn't just a language version error. Are we certain the right default value is supplied for the parameter when the language version is "Preview"? #Closed

Copy link
Member Author

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.

Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

@Youssef1313 Youssef1313 May 28, 2021

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?

image

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.

Copy link
Member Author

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.

https://onlinegdb.com/vponLAJ2g

Copy link
Contributor

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.

Copy link
Member Author

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);
Copy link
Contributor

@AlekseyTs AlekseyTs May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateCompilation

Default language version is a "moving target". If the intent to use C# 9, the test should be explicit about that. #Closed


class MyAttribute : Attribute
{
public MyAttribute(int a = 1, [CallerArgumentExpression(""a"")] int expr_a = 0)
Copy link
Contributor

@AlekseyTs AlekseyTs May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CallerArgumentExpression(""a"")]

Should we cover a scenario whan an attribute like that is applied in metadata? #Closed

Copy link
Member Author

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?

Copy link
Contributor

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.

Copy link
Member Author

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!

@AlekseyTs
Copy link
Contributor

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.
Copy link
Contributor

@AlekseyTs AlekseyTs May 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// PROTOTYPE(caller-expr): This is inconsistent with the behavior of invocations, which doesn't show a lang version error.

Is the PROTOTYPE comment still relevant? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted this outdated comment.

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 16)

[My(1+2)]
class Program
{
}";
Copy link
Contributor

@AlekseyTs AlekseyTs May 28, 2021

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

Copy link
Contributor

@AlekseyTs AlekseyTs left a 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.

@AlekseyTs
Copy link
Contributor

@dotnet/roslyn-compiler For the second review.

@AlekseyTs
Copy link
Contributor

@dotnet/roslyn-compiler For the second review.

@333fred 333fred self-assigned this Jun 1, 2021
Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 18)

@333fred
Copy link
Member

333fred commented Jun 1, 2021

I'll trigger a new pipeline when #53776 is merged into the branch, which should hopefully correct the integration failures.

@333fred
Copy link
Member

333fred commented Jun 1, 2021

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@333fred 333fred enabled auto-merge (squash) June 1, 2021 18:14
@333fred 333fred merged commit 3ff9727 into dotnet:features/caller-argument-expression Jun 1, 2021
@Youssef1313 Youssef1313 deleted the attribute-caller-arg-expr branch June 1, 2021 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee. Feature - CallerArgumentExpressionAttribute
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants