-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Feature: CallerArgumentExpressionAttribute #51952
Feature: CallerArgumentExpressionAttribute #51952
Conversation
src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs
Outdated
Show resolved
Hide resolved
50a83c5
to
cb0ab7d
Compare
0cd335f
to
28f460a
Compare
Ready for initial review. |
@Youssef1313 I've created a feature branch for this work. Feel free to put your open questions as prototype comments in the source: the syntax is |
src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs
Outdated
Show resolved
Hide resolved
@@ -603,6 +619,30 @@ internal override CSharpAttributeData EarlyDecodeWellKnownAttribute(ref EarlyDec | |||
{ | |||
arguments.GetOrCreateData<ParameterEarlyWellKnownAttributeData>().HasCallerMemberNameAttribute = true; | |||
} | |||
else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerArgumentExpressionAttribute)) |
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.
As the language exists today we can do this as an early attribute, but if we do implement the nameof proposal (allowing nameof(parameter)
in parameters/attributes on methods) I don't believe it will end well (this will cause a loop or fail to bind entirely). Please add a prototype comment to consider moving binding of the actual attribute data into regular attribute binding.
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.
@333fred To make sure I understand this correctly, this is about nameof
binding being done after we are trying to get the parameter name?
This also affects existing code, for example:
roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs
Lines 898 to 912 in 8408750
if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.ConditionalAttribute)) | |
{ | |
boundAttribute = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, out hasAnyDiagnostics); | |
if (!boundAttribute.HasErrors) | |
{ | |
string name = boundAttribute.GetConstructorArgument<string>(0, SpecialType.System_String); | |
arguments.GetOrCreateData<CommonTypeEarlyWellKnownAttributeData>().AddConditionalSymbol(name); | |
if (!hasAnyDiagnostics) | |
{ | |
return boundAttribute; | |
} | |
} | |
return null; | |
} |
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.
To make sure I understand this correctly, this is about nameof binding being done after we are trying to get the parameter name?
I'm not sure I understand the question. My concern here is:
- We're attempting to bind the parameter
- The parameter has this attribute, and uses nameof on a parameter in this method (even itself!)
- Attempting to bind the nameof argument causes us to bind the method, which causes us to bind the nameof, which causes us to bind the method, which... etc.
Today, the parameter is not in scope for that nameof
, so our standard early attribute handling will work correctly without a chance for a loop. I'm not certain it will work correctly in the face of an updated nameof
, however, so to do the right thing we'd want to move binding of the attribute argument to the standard binding location, which isn't susceptible to this problem.
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 think I got the general idea here. I think (most likely) this will require fixes in other existing areas first (e.g, the ConditionalAttribute above), so I'll probably follow the same work and add tests.
@@ -419,6 +424,11 @@ public void NullableWarnings() | |||
ErrorCode.WRN_AnalyzerReferencesFramework, | |||
ErrorCode.WRN_UnreadRecordParameter, | |||
ErrorCode.WRN_DoNotCompareFunctionPointers, | |||
ErrorCode.WRN_CallerArgumentExpressionParamForUnconsumedLocation, |
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.
ErrorCode.WRN_CallerArgumentExpressionParamForUnconsumedLocation, [](start = 20, length = 65)
It looks like this test requires unnecessary maintenance. Perhap the check for the range of error codes at the beginning of the loop should be adjusted instead? Based on the comment only "Nullable-unrelated warnings in the C# 8 range should be added to this array." #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.
@AlekseyTs I guess you meant this to be a "general" feedback on this test, rather than this PR specifically. Is that right?
Should I target main
for that change in a separate PR, or file an issue for discussion?
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 guess you meant this to be a "general" feedback on this test, rather than this PR specifically. Is that right?
It is specific to this PR because there are changes here.
Should I target main for that change in a separate PR, or file an issue for discussion?
I guess either way is fine as long as we either fix it in this PR, or make sure it will be fixed before merging the feature into main.
In reply to: 607929347 [](ancestors = 607929347)
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.
Sorry I initially misunderstood your comment. Fixed in 418ec88.
Will give that a look 👀 |
Done with review pass (commit 39) #Closed |
roslyn/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs Lines 110 to 115 in 7becc52
If I understand this method correctly, it looks like both flags are needed. One flag specifies whether we can get the attribute data or not, and the other specifies the actual data. Both |
I believe that is not the issue I am referring to. See https://github.com/dotnet/roslyn/pull/51952/files#r600068668 #Closed |
Thanks. Does b99bb7a address that? |
@@ -596,6 +599,14 @@ private bool HasCallerMemberNameAttribute | |||
} | |||
} | |||
|
|||
private bool HasCallerArgumentExpressionAttribute |
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.
HasCallerArgumentExpressionAttribute [](start = 21, length = 36)
This property feels unnecessary. Also, it feels like we should be able to avoid looking for the attribute twice. First in this property and then to extract parameter name from it. #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.
@AlekseyTs It feels much more simple if I didn't follow the existing pattern for other caller info, i.e, not even touch PackedFlags. I attempted to do that in e8023b1. Let me know if there are any concerns with the approach I took.
@@ -656,6 +658,44 @@ internal override bool IsCallerMemberName | |||
} | |||
} | |||
|
|||
private int? _cachedCallerArgumentExpressionParameterIndex; |
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.
private int? _cachedCallerArgumentExpressionParameterIndex; [](start = 8, length = 59)
Please place the field declaration at the beginning of the class declaration, next to other fields. Also, we use "lazy" prefix for fields with delayed initialization. #Closed
bool isCallerArgumentExpression = !HasCallerLineNumberAttribute | ||
&& !HasCallerFilePathAttribute | ||
&& !HasCallerMemberNameAttribute | ||
&& info.HasValue |
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.
&& info.HasValue [](start = 20, length = 16)
Is this check cheap? Consider doing it first. #Closed
{ | ||
if (_cachedCallerArgumentExpressionParameterIndex.HasValue) | ||
{ | ||
return _cachedCallerArgumentExpressionParameterIndex.Value; |
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.
Value [](start = 73, length = 5)
GetValueOrDefault? It avoids another HasValue
check. #Closed
{ | ||
if (parameters[i].Name == parameterName) | ||
{ | ||
_cachedCallerArgumentExpressionParameterIndex = i; |
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.
_cachedCallerArgumentExpressionParameterIndex = i; [](start = 28, length = 50)
This assignment is not guaranteed to be atomic, therefore, the implementation of this property is not thread safe. That is why we don't use Nullable<T>
for "lazy" members. For example, we use ThreeState
instead of bool?
. It looks like we can use a simple int
field with initial value (-2), for example, to indicate uninitialized state. #Closed
} | ||
} | ||
|
||
_cachedCallerArgumentExpressionParameterIndex = -1; |
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.
_cachedCallerArgumentExpressionParameterIndex = -1; [](start = 16, length = 51)
Same comment #Closed
|
||
private const int WellKnownAttributeDataOffset = 0; | ||
private const int WellKnownAttributeCompletionFlagOffset = 8; | ||
private const int RefKindOffset = 16; | ||
private const int FlowAnalysisAnnotationsOffset = 20; | ||
|
||
private const int RefKindMask = 0x3; | ||
private const int WellKnownAttributeDataMask = 0xFF; | ||
|
||
private const int WellKnownAttributeDataMask = (0x1 << 8) - 1; |
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.
(0x1 << 8) - 1 [](start = 59, length = 14)
Consider reverting back to 0xFF literal. #Closed
Done with review pass (commit 43) #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 44)
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 44)
@333fred Please review the latest revision. Please squash commits during merging. |
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 44)
Thanks @Youssef1313! |
Thanks a lot for review @AlekseyTs and @333fred. |
Proposal: dotnet/csharplang#287
Test plan: #52745
TODO:
Add a warning if the parameter name specified in the attribute is incorrect.Done.Open questions:
Do we need to support VB?
Do we need to add a warning for pre-C# 10 using this feature?
What should happen for the following case:
What should happen for the following case, should this have a warning?