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

Detect unused record parameters #48895

Merged
merged 7 commits into from
Oct 31, 2020
Merged

Detect unused record parameters #48895

merged 7 commits into from
Oct 31, 2020

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Oct 24, 2020

Fixes #48584
FYI @Youssef1313 since relates to a previous PR

Spec update: dotnet/csharplang#4077

@jcouv jcouv marked this pull request as ready for review October 24, 2020 22:28
@jcouv jcouv requested a review from a team as a code owner October 24, 2020 22:28
@RikkiGibson RikkiGibson self-assigned this Oct 26, 2020
@RikkiGibson
Copy link
Contributor

RikkiGibson commented Oct 26, 2020

@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

@jcouv jcouv requested a review from RikkiGibson October 26, 2020 23:41
@@ -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>
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 27, 2020

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

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

@AlekseyTs AlekseyTs Oct 27, 2020

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

@AlekseyTs AlekseyTs Oct 27, 2020

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

Copy link
Member Author

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)

Copy link
Contributor

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)

Copy link
Member Author

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

@AlekseyTs AlekseyTs Oct 27, 2020

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Added tests for lambdas. Thanks for the scenario!


In reply to: 512814777 [](ancestors = 512814777)

@@ -1878,6 +1903,10 @@ public override BoundNode VisitParameter(BoundParameter node)
{
CheckAssigned(node.ParameterSymbol, node.Syntax);
}
else if (CurrentSymbol is SynthesizedRecordConstructor)
{
NoteRead(node.ParameterSymbol);
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 27, 2020

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

@AlekseyTs AlekseyTs Oct 27, 2020

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

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 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)

Copy link
Member Author

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

@AlekseyTs AlekseyTs Oct 27, 2020

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

@AlekseyTs AlekseyTs Oct 27, 2020

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

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 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)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Oct 27, 2020

Done with review pass (iteration 5) #Closed

@jcouv
Copy link
Member Author

jcouv commented Oct 30, 2020

@dotnet/roslyn-compiler for review. Thanks

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 (iteration 7)

@RikkiGibson
Copy link
Contributor

Taking a look

Copy link
Contributor

@RikkiGibson RikkiGibson left a 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),
Copy link
Contributor

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.

@jcouv jcouv merged commit 0fdb64e into dotnet:master Oct 31, 2020
@ghost ghost added this to the Next milestone Oct 31, 2020
@jcouv jcouv deleted the unused-parameter branch October 31, 2020 02:59
@allisonchou allisonchou modified the milestones: Next, 16.9.P2 Nov 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

C# 9 Records: Explicit property declaration breaks primary constructor
4 participants