-
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
Detect unused record parameters #48895
Conversation
aef2643
to
0f631f6
Compare
src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs
Outdated
Show resolved
Hide resolved
@jcouv we just fixed a build break in master. You may want to merge master into your PR branch to get a reasonable result in CI. #Closed |
@@ -6579,4 +6579,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |||
<data name="ERR_FunctionPointersCannotBeCalledWithNamedArguments" xml:space="preserve"> | |||
<value>A function pointer cannot be called with named arguments.</value> | |||
</data> | |||
<data name="WRN_UnusedRecordParameter" xml:space="preserve"> | |||
<value>Parameter '{0}' is unread. Did you forget to use it to initialize the property with that name?</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.
Did you forget to use it to initialize the property with that name? [](start = 38, length = 67)
I am not sure the second sentence is necessary. #ByDesign
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's not necessary, but I think it's useful to guide users to the most likely solution. It helps for the primary scenario motivating this change.
In reply to: 512796721 [](ancestors = 512796721)
@@ -560,6 +579,12 @@ private void NoteCaptured(Symbol variable) | |||
_usedVariables.Add(local); | |||
} | |||
|
|||
if (variable is ParameterSymbol parameter && CurrentSymbol is SynthesizedRecordConstructor) |
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.
variable is ParameterSymbol parameter && CurrentSymbol is SynthesizedRecordConstructor [](start = 16, length = 86)
Where do we check relationship between CurrentSymbol
and parameter
? #Closed
@@ -1878,6 +1903,10 @@ public override BoundNode VisitParameter(BoundParameter node) | |||
{ | |||
CheckAssigned(node.ParameterSymbol, node.Syntax); | |||
} | |||
else if (CurrentSymbol is SynthesizedRecordConstructor) | |||
{ | |||
NoteRead(node.ParameterSymbol); |
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.
node.ParameterSymbol [](start = 25, length = 20)
How do we know parameter and CurrentSymbol are related? #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.
In lambda scenarios, CurrentSymbol is the lambda, not the primary constructor.
I'll add tests, but I believe that's the behavior we want (ie. reading the record parameter in a lambda doesn't count).
In reply to: 512809160 [](ancestors = 512809160)
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.
but I believe that's the behavior we want (ie. reading the record parameter in a lambda doesn't count).
I don't know why would this be the behavior we want. Nevertheless, it would be good to clearly specify in the PR description what we are trying to achieve in this PR.
In reply to: 512902651 [](ancestors = 512902651,512809160)
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.
Thinking more about this, maybe the usage inside the lambda should count as well. Made the change. Thanks
In reply to: 512907636 [](ancestors = 512907636,512902651,512809160)
@@ -560,6 +579,12 @@ private void NoteCaptured(Symbol variable) | |||
_usedVariables.Add(local); | |||
} | |||
|
|||
if (variable is ParameterSymbol parameter && CurrentSymbol is SynthesizedRecordConstructor) |
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.
CurrentSymbol is SynthesizedRecordConstructor [](start = 57, length = 45)
It looks like a parameter can be used in a lambda. Is this condition going to work for this scenario? #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.
@@ -1878,6 +1903,10 @@ public override BoundNode VisitParameter(BoundParameter node) | |||
{ | |||
CheckAssigned(node.ParameterSymbol, node.Syntax); | |||
} | |||
else if (CurrentSymbol is SynthesizedRecordConstructor) | |||
{ | |||
NoteRead(node.ParameterSymbol); |
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.
NoteRead [](start = 16, length = 8)
I would prefer us to not call a method that isn't meant to be called. It is fine to have it to do a little bit of extra work when we call it anyway, it is another thing to call it in a new place when we only interested in that additional extra work and the method does plenty of other things. If the goal is to reuse code, it can be extracted into a helper that can be called from here and from NoteRead
#Closed
// (2,18): warning CS8907: Parameter 'O1' is unread. Did you forget to use it to initialize the property with that name? | ||
// record C1(object O1, object O2, object O3) | ||
Diagnostic(ErrorCode.WRN_UnusedRecordParameter, "O1").WithArguments("O1").WithLocation(2, 18), | ||
// (2,40): warning CS8907: Parameter 'O3' is unread. Did you forget to use it to initialize the property with that 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.
warning CS8907: Parameter 'O3' is unread. Did you forget to use it to initialize the property with that name? [](start = 31, length = 109)
This warning feels unexpected to me. The parameter is used. #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.
I should have updated the PR title, but didn't want to mess email threading. The main intent for this warning is that people are surprised when they write record C(int X) { public int X { get; init; } }
and the property is not initialized. So I think it makes sense to focus on reads.
If one does record C(int X) : Base(X) { public int X { get; init; } };
, I think a warning would also be desirable for that reason, but I can see that's debatable.
Started a thread with LDM to clarify expectations.
In reply to: 512822355 [](ancestors = 512822355)
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.
Clarified with LDM. We'll warn on unread parameters. Didn't get feedback on phrasing of warning, but I'm open to proposal (let me know).
In reply to: 512891377 [](ancestors = 512891377,512822355)
{ | ||
public object O1 { get; init; } | ||
public object O2 { get; init; } = M(O2); | ||
public object O3 { get; init; } = M(O3 = 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.
M(O3 = null) [](start = 38, length = 12)
It would be good to test scenarios when parameter is passed as ref
, in
, out
. #Closed
@@ -415,6 +416,7 @@ public void NullableWarnings() | |||
ErrorCode.WRN_RecordNamedDisallowed, | |||
ErrorCode.WRN_RecordEqualsWithoutGetHashCode, | |||
ErrorCode.WRN_AnalyzerReferencesFramework, | |||
ErrorCode.WRN_UnusedRecordParameter, |
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_UnusedRecordParameter, [](start = 20, length = 36)
It looks like the warning is in the C#9 range. Why do we need to add it to this array? #ByDesign
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 test catches any warnings in the range 8600 to 9000. The new warning is 8907...
I could adjust the range instead, but figured it is useful to remind us to think about whether the new diagnostics should be considered "nullability" diagnostics.
In reply to: 512825729 [](ancestors = 512825729)
Done with review pass (iteration 5) #Closed |
@dotnet/roslyn-compiler for review. Thanks |
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 (iteration 7)
Taking a look |
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, thanks!
comp.VerifyDiagnostics( | ||
// (11,17): warning CS8907: Parameter 'P1' is unread. Did you forget to use it to initialize the property with that name? | ||
// record B(object P1, object P2, object P3, object P4, object P5, object P6) : A | ||
Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "P1").WithArguments("P1").WithLocation(11, 17), |
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 occurs to me that it's not really possible for the user to assign to the base property here. A scenario where we change a base nominal property to a positional parameter in a derived type is going to be unpleasant until we add the ability to include a constructor body for a primary constructor.
Fixes #48584
FYI @Youssef1313 since relates to a previous PR
Spec update: dotnet/csharplang#4077