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

Report diagnostic for field and value identifiers in property accessors where the meaning would change as contextual keywords #73570

Merged
merged 16 commits into from
Jun 5, 2024

Conversation

cston
Copy link
Member

@cston cston commented May 18, 2024

Report a diagnostic for field and value in contexts where the identifiers will be treated as keywords in a later language version and where the meaning will change as a result:

  • use of field in a property or event accessor
  • use of value in a property or indexer set or init accessor, or event accessor, and when the identifier does not refer to the implicit parameter

Relates to test plan #57012

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels May 18, 2024
@cston cston changed the title Report diagnostic when meaning of 'field' or 'value' will change as a keyword Report diagnostic for field and value identifiers in property accessors where the meaning would change as contextual keywords May 18, 2024
@cston cston force-pushed the keyword-diagnostic branch 3 times, most recently from 96ec041 to 80b4999 Compare May 18, 2024 19:41
…rs where the meaning would change as contextual keywords

[Theory]
[CombinatorialData]
public void Event_01(
Copy link
Member Author

@cston cston May 21, 2024

Choose a reason for hiding this comment

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

Event_01

Should field and value be considered keywords in event accessors? #Closed

Copy link
Member

Choose a reason for hiding this comment

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

I think value should be a keyword as that parameter exists in the add and remove accessors today (see SynthesizedEventAccessorValueParameterSymbol), like it does in a property setter (SynthesizedPropertyAccessorValueParameterSymbol).
But I don't think field will take any particular meaning in events with the C#Next feature, so I don't think it should be a keyword.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

this matches my takeaway from last LDM on this topic.

@cston cston marked this pull request as ready for review May 21, 2024 21:29
@cston cston requested a review from a team as a code owner May 21, 2024 21:29
@jcouv jcouv self-assigned this May 24, 2024
default:
// If we reach here, add the unhandled identifier case to the
// switch cases above (and test in FieldAndValueKeywordTests).
Debug.Assert(false, $"Unhandled identifier: parent {syntax.Kind()}, token {token}");
Copy link
Member

@jcouv jcouv May 24, 2024

Choose a reason for hiding this comment

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

I don't think we should need to update this method when new syntax nodes are added to C# 14, 15, ... (we'll produce an error already when the LangVer is too old) #Closed

// (14,21): info CS9248: 'value' is a contextual keyword, with a specific meaning, starting in language version preview. Use '@value' to avoid a breaking change when compiling with language version preview or later.
// void G1(object value) { }
Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "object value").WithArguments("value", "preview").WithLocation(14, 21));
}
Copy link
Member

@jcouv jcouv May 24, 2024

Choose a reason for hiding this comment

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

nit: Consider adding a test for attribute scenarios, even though they are being kept out-of-scope for this PR:

  • [MyAttribute(nameof(value))]
  • attribute target scenario: [field: MyAttribute] and [@field: MyAttribute], on accessors #Closed

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 2)

@jcouv jcouv requested a review from AlekseyTs May 24, 2024 23:03
break;
}
break;
default:
Copy link
Member

Choose a reason for hiding this comment

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

TBH i find hte approach taken in this pr extremely surprising. It's not how i would have expected this to be done.

Instead, i would expect us to treat his how we treat query-keywords or the await-keyword. Specifically, the parser should enter into a new contextual parsing state when we enter into these specific contexts.

Within that appropriate context, we then have the following behavior:

  1. in c# 13, the word becomes a keyword, and the new semantics take effect.
  2. prior to c# 12, when eating an identifier, if we have one of these keywords, we convert it to an identifier, and report the diagnostics about the potential future issues.

I think we also need this for incremental parsing to work properly. If we reinterpret the statements from an accessor into a different context (or vice-versa), then we need to reparse since field/value will change from keywords to identifiers.

--

TLDR: as a contextual keyword, this should work the same way as async/await and query-keywords.

Copy link
Member

Choose a reason for hiding this comment

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

Note: i was able to get us additional bits on NodeFlags. So we can store this info there as well for incremental parsing purposes. A

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. prior to c# 12, when eating an identifier, if we have one of these keywords, we convert it to an identifier, and report the diagnostics about the potential future issues.

The context from the parser might help, but the diagnostic may need to be reported in binding since we only want a diagnostic when the identifier would bind differently as a keyword.

Copy link
Member

Choose a reason for hiding this comment

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

my approach supports that. these would be identifiers whose contextual kind would either be 'nothing' (which means it's a legal usage of field/value as an identifier), or it has a contextual kind of SyntaxKind.FieldKeyword or SyntaxKind.ValueKeyword. IN that case, in binding you report a warning saying this will change meaning.

basically, the parser has the core logic for figuring out if it's an identifier or keyword. if it's a true identifier, great, you get an identifier and everything works as normal. If it's contextual keyword, then in C# 13 you get a keyword and the new semantics. In C# 12 you get an identifier, marked as being a contextual keyword, and the binder warns you.

Note: i don't actually see why it as to be the binder that needs to care. Sicne C# 13 will have these rules set up at the syntactic level, it feels fine for the parser to warn here for me. But i'm fine with it beign the binder if you prefer that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think what Chuck is getting at is that, for example, there's no problem with using identifier 'value' to reference the setter value parameter. The parsing will technically change from C# 12 to 13, but it will still refer to the same thing in its keyword form.

So in order to know whether to give diagnostics, compiler needs to know whether 'value' is referring to that parameter, or referring to something else such as a 'value' local declared in a nested function.

FWIW, in the case I just described, I feel ok with saying you did something really weird and you don't get to have this warning. It's the .01% case.

Maybe we should only warn for use of identifier 'field' in property accessors and just do it in the parser.

Copy link
Member

Choose a reason for hiding this comment

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

my approach would work for this afaict. the node indicates the information needed by the binder to warn.

}
else
{
comp.VerifyEmitDiagnostics(
Copy link
Member Author

@cston cston May 28, 2024

Choose a reason for hiding this comment

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

VerifyEmitDiagnostics

The diagnostics here are incorrect. No diagnostics should be reported because [A(nameof(field))] is not within the scope of the accessor. #Resolved

}
else
{
comp.VerifyEmitDiagnostics(
Copy link
Member Author

@cston cston May 28, 2024

Choose a reason for hiding this comment

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

VerifyEmitDiagnostics

The diagnostics here are incorrect. No diagnostics should be reported because [A(nameof(value))] is not within the scope of the accessor. #Resolved

Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "int value").WithArguments("value", "preview").WithLocation(21, 40),
// (28,23): info CS9248: 'value' is a contextual keyword, with a specific meaning, starting in language version preview. Use '@value' to avoid a breaking change when compiling with language version preview or later.
// [A(nameof(value))] void F3() { }
Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "value").WithArguments("value", "preview").WithLocation(28, 23));
Copy link
Member Author

@cston cston May 28, 2024

Choose a reason for hiding this comment

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

Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "value").WithArguments("value", "preview").WithLocation(28, 23)

This diagnostic should not be reported because nameof(value) binds to the accessor parameter. #Resolved

Copy link
Member

@jcouv jcouv 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 (iteration 12)

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 15)

@AlekseyTs AlekseyTs self-assigned this Jun 4, 2024
@@ -220,6 +220,11 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno
CheckFeatureAvailability(_syntax.AwaitKeyword, MessageID.IDS_FeatureAsyncStreams, diagnostics);
}

if (_syntax is ForEachStatementSyntax forEachStatement)
{
ReportFieldOrValueContextualKeywordConflictIfAny(forEachStatement, forEachStatement.Identifier, diagnostics);
Copy link
Member

Choose a reason for hiding this comment

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

nit: have you considered placing this check in the switch below that already differentiates regular foreach from deconstruction foreach? (line 277)

Copy link
Member

@jcouv jcouv 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 (iteration 16)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants