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

Field-backed properties: definite assignment #75116

Merged
merged 19 commits into from
Sep 23, 2024

Conversation

cston
Copy link
Member

@cston cston commented Sep 13, 2024

See proposal.

Similar to changes started in earlier features/semi-auto-props branch in #58832, #60601.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Sep 13, 2024
@cston cston marked this pull request as ready for review September 19, 2024 20:11
@cston cston requested a review from a team as a code owner September 19, 2024 20:11
@RikkiGibson RikkiGibson self-assigned this Sep 19, 2024
propertyAccess.ReceiverOpt,
propertyAccess.InitialBindingReceiverIsSubjectToCloning,
propertyAccess.PropertySymbol,
useAsLvalue.Value ? AccessorKind.Set : AccessorKind.Get, // PROTOTYPE: What about compound assignment, increment?
Copy link
Contributor

@RikkiGibson RikkiGibson Sep 19, 2024

Choose a reason for hiding this comment

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

Consider extracting and reusing logic from 'GetIndexerAccessorKind' in order to determine this. #Resolved

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.

Per offline discussion I am wondering if a somewhat smaller change would be adequate to address the scenario. It looks like the existing behavior with auto properties in struct constructors is:

  • property get: auto-default all unassigned fields, even if getter is auto-implemented.
  • property set: don't auto-default anything, if the set accessor is auto-implemented or absent.

So, shouldn't a relatively small change to logic in definite assignment be adequate here (check if setter specifically is auto-implemented rather than whole property)? There would be no need to adjust handling of property reads, compound assignments or increment/decrement.

@cston
Copy link
Member Author

cston commented Sep 20, 2024

... It looks like the existing behavior with auto properties in struct constructors is:

  • property get: auto-default all unassigned fields, even if getter is auto-implemented.
  • property set: don't auto-default anything, if the set accessor is auto-implemented or absent.

Calling get with an auto property today will cause that one backing field to be initialized before the call but does not affect other fields that have yet to be assigned.

For instance, with the following, y = P1 results in initialization of P1 but not F1. See sharplab.io.

struct S1
{
    public int F1;
    public int P1 { get; }
    public S1(int x, out int y)
    {
        y = P1;
        F1 = x;
    }
}

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.

Sorry, I still need more time to finish review, but had a few more comments.

src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml Outdated Show resolved Hide resolved
@@ -1073,6 +1074,7 @@ static IEnumerable<Symbol> getAllMembersToBeDefaulted(Symbol requiredMember)
}
}

// PROTOTYPE: Is checking IsAutoProperty sufficient or should we check the appropriate accessors are missing or auto-implemented?
Copy link
Contributor

Choose a reason for hiding this comment

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

I will try and resolve this comment in my upcoming PR. I think we should just check for a BackingField and return it here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved comment to test plan #57012.

@@ -1964,6 +1966,8 @@ protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundE
var propAccess = (BoundPropertyAccess)expr;
var propSymbol = propAccess.PropertySymbol;
member = propSymbol;
// PROTOTYPE: Why don't we call Binder.AccessingAutoPropertyFromConstructor
Copy link
Contributor

@RikkiGibson RikkiGibson Sep 20, 2024

Choose a reason for hiding this comment

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

I will also try to resolve this comment. I think I know why--nullable prefers to rewrite field initializers to be assigning to the property, for example, and then when a field is visited for constructor checks, we work back to the property and use the property's slot. Perhaps definite assignment works the opposite way, deciding when a property access is "morally" an access of the backing field, and treating it as the latter instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved comment to test plan #57012.

{
if (_found is { })
{
return node;
Copy link
Contributor

@RikkiGibson RikkiGibson Sep 23, 2024

Choose a reason for hiding this comment

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

Is this return value used? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the return value is not used, and in fact most of the visit methods return null. I'll change this to return null;.

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, nothing blocking.

{
public int F1;
public int P1 { get; }
public C1(int i) { P1 += i; }
Copy link
Contributor

@RikkiGibson RikkiGibson Sep 23, 2024

Choose a reason for hiding this comment

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

It seems like this and the other scenarios in this test would be more illustrative if F1 were explicitly assigned after the P1 += i; statement. That would show whether the property access is causing auto-default of the whole instance.

Same for the ++, --, ??= tests below. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, that makes sense.

@@ -2194,7 +2194,7 @@
<!-- Whether receiver will need to be cloned during emit (only valid before lowering) -->
<Field Name="InitialBindingReceiverIsSubjectToCloning" Type="ThreeState"/>
<Field Name="PropertySymbol" Type="PropertySymbol"/>
<!-- The property accessor referenced if the property access should use the backing field directly instead. -->
<!-- The property accessor kind, set for properties with synthesized backing field only. -->
<Field Name="UseBackingField" Type="AccessorKind" />
Copy link
Contributor

@RikkiGibson RikkiGibson Sep 23, 2024

Choose a reason for hiding this comment

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

I'm not 100% happy with this field name, but I don't have any suggestions for a better one. It feels like this name is saying "this property access should be replaced with a use of the backing field". #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed to BoundPropertyAccess.AutoPropertyAccessorKind.

@@ -2195,7 +2195,7 @@
<Field Name="InitialBindingReceiverIsSubjectToCloning" Type="ThreeState"/>
<Field Name="PropertySymbol" Type="PropertySymbol"/>
<!-- The property accessor kind, set for properties with synthesized backing field only. -->
Copy link
Contributor

@RikkiGibson RikkiGibson Sep 23, 2024

Choose a reason for hiding this comment

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

is it also the case that this will only have a non-Unknown value when auto accessors are being used? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

The value will be set to something other than AccessorKind.Unknown for classic auto properties and for properties that use field. Properties that use field may include an auto-implemented accessor.

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.

3 participants