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

Null-conditional assignment 1 #75821

Merged

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Nov 8, 2024

Relates to test plan #75554

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Nov 8, 2024
@RikkiGibson
Copy link
Contributor Author

@CyrusNajmabadi ready for a high-level review pass.

// We should consume suppression '!'s which are in the middle of the 'whenNotNull', but not at the end.
// For example, 'a?.b!.c' should be a cond-access whose RHS is '.b!.c',
// while 'a?.b!' should be a suppression-expr containing a cond-access 'a?.b'.
using var beforeSuppressionsResetPoint = GetDisposableResetPoint(resetOnDispose: false);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using a reset point lets us unify the "check" and "act" steps of deciding to include the '!'s we found in the current cond-access node or not. This lets us avoid having to write a "dry-run" version of the assignment operator parsing code, which advances the token stream and potentially merges tokens found in it.

@RikkiGibson
Copy link
Contributor Author

@cston @jjonescz This is ready for review

rs?.RF = ref i; // 1

RS? nrs = rs; // 2
nrs?.RF = ref i; // 3
Copy link
Member

Choose a reason for hiding this comment

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

nrs?.RF = ref i; // 3

Consider testing nrs?.RF = i; as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's an interesting thought, as the assignment itself is fine. Left side doesn't need to be a variable in this case. It just doesn't work because a ref struct can't be nullable.


static void M(C c)
{
c?.E += () => { Console.Write(1); };
Copy link
Member

Choose a reason for hiding this comment

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

c?.E +=

Consider testing event remove as well: c?.E -= ...

}

[Fact]
public void TypeParameter_03()
Copy link
Member

Choose a reason for hiding this comment

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

TypeParameter_03

Consider testing type parameter as the receiver:

interface I
{
    int P { get; set; }
}
static void F1<T>(T t) where T : I
{
    t?.P = GetValue(1);
}
static void F2<T>(T? t) where T : struct, I
{
    t?.P = GetValue(2);
}

[Fact]
public void Increment_LeftMemberAccess()
{
// Increment/decrement of a conditional access is not supported.
Copy link
Member

@cston cston Nov 19, 2024

Choose a reason for hiding this comment

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

is not supported

Are we testing that a?.b++ and --a?.b are reported as errors?

public void Increment_LeftMemberAccess()
{
// Increment/decrement of a conditional access is not supported.
string source = "a?.b++";
Copy link
Member

@cston cston Nov 19, 2024

Choose a reason for hiding this comment

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

a?.b++

Consider adding a parsing test for --a?[b]

[Fact]
public void Parentheses_Assignment_LHS_01()
{
UsingExpression("(c?.F) = 1");
Copy link
Member

Choose a reason for hiding this comment

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

UsingExpression

Consider testing all the remaining tests with TestOptions.Regular13 as well.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps pass LanguageVersion as a parameter in each case.

[Theory]
[CombinatorialData]
public void Parentheses_Assignment_LHS_01(
    [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)]
    LanguageVersion languageVersion)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, probably the best way is by a Theory/TheoryData which is shared between all the tests.

[InlineData(SyntaxKind.PercentEqualsToken)]
[InlineData(SyntaxKind.EqualsToken)]
[InlineData(SyntaxKind.QuestionQuestionEqualsToken)]
public void VariousAssignmentKinds_LeftMemberAccess(SyntaxKind kind)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't feel like setting up the combinatorial data for all the syntax kinds and language versions here so just kept the local functions approach.

[InlineData(SyntaxKind.PercentEqualsToken)]
[InlineData(SyntaxKind.EqualsToken)]
[InlineData(SyntaxKind.QuestionQuestionEqualsToken)]
public void LangVersion_03(SyntaxKind kind)
Copy link
Contributor Author

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 if a new assignment kind is added later, it will be gated behind its own LangVersion check, which is at least as strict as this one.

@@ -10944,6 +10944,10 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess

var conditionalAccessBinder = new BinderWithConditionalReceiver(this, receiver);
var access = conditionalAccessBinder.BindValue(node.WhenNotNull, diagnostics, BindValueKind.RValue);
if (access.Syntax is AssignmentExpressionSyntax)
{
CheckFeatureAvailability(node.WhenNotNull, MessageID.IDS_FeatureNullConditionalAssignment, diagnostics);
Copy link
Member

Choose a reason for hiding this comment

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

Consider placing the diagnostics just on the =. It's generally nicer than a big squiggle on something large.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea!

@RikkiGibson RikkiGibson merged commit 7c84cba into dotnet:features/null-conditional-assignment Nov 20, 2024
28 checks passed
@RikkiGibson RikkiGibson deleted the nca-1 branch November 20, 2024 20:01
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.

6 participants