-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Add support for init-only members #43275
Changes from 4 commits
68426e7
4eeaa66
4000849
069290a
f288922
a966422
63fe94a
7cce780
1b54d56
a03f90c
25e02e5
a348704
72243df
1d77553
f1c9883
0cdecfe
9aadc79
526c09f
84bae27
a67b061
7d60226
527f006
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -709,7 +709,8 @@ private bool CheckFieldValueKind(SyntaxNode node, BoundFieldAccess fieldAccess, | |
{ | ||
MethodSymbol containingMethod = (MethodSymbol)containing; | ||
MethodKind desiredMethodKind = fieldIsStatic ? MethodKind.StaticConstructor : MethodKind.Constructor; | ||
canModifyReadonly = containingMethod.MethodKind == desiredMethodKind; | ||
canModifyReadonly = (containingMethod.MethodKind == desiredMethodKind) || | ||
isAssignedFromInitOnlySetterOnThis(fieldAccess.ReceiverOpt); | ||
} | ||
else if (containing.Kind == SymbolKind.Field) | ||
{ | ||
|
@@ -745,6 +746,24 @@ private bool CheckFieldValueKind(SyntaxNode node, BoundFieldAccess fieldAccess, | |
|
||
// for other fields defer to the receiver. | ||
return CheckIsValidReceiverForVariable(node, fieldAccess.ReceiverOpt, valueKind, diagnostics); | ||
|
||
bool isAssignedFromInitOnlySetterOnThis(BoundExpression receiver) | ||
{ | ||
// bad: other.readonlyField = ... | ||
// bad: base.readonlyField = ... | ||
if (!(receiver is BoundThisReference)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It looks like the speclet doesn't have this restriction. Is only says: "Assign readonly fields declared on the same type". However, I think the intention was to have this restriction. Please make sure to reconcile the difference. #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
return false; | ||
} | ||
|
||
var containingMember = ContainingMemberOrLambda; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider inlining this local #Resolved |
||
if (!(containingMember is MethodSymbol method)) | ||
{ | ||
return false; | ||
} | ||
|
||
return method.MethodKind == MethodKind.PropertySet && method.IsInitOnly; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is this check redundant? Doesn't |
||
} | ||
} | ||
|
||
private bool CheckSimpleAssignmentValueKind(SyntaxNode node, BoundAssignmentOperator assignment, BindValueKind valueKind, DiagnosticBag diagnostics) | ||
|
@@ -931,9 +950,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV | |
|
||
// Addendum: Assignment is also allowed for get-only autoprops in their constructor | ||
|
||
BoundExpression receiver; | ||
SyntaxNode propertySyntax; | ||
var propertySymbol = GetPropertySymbol(expr, out receiver, out propertySyntax); | ||
var propertySymbol = GetPropertySymbol(expr, out BoundExpression receiver, out SyntaxNode propertySyntax); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think refactorings like this negatively impact readability of the code. Was there something wrong with the code? Does it violate style, analyzer complained? Was this change necessary to accomplish the task? #Closed |
||
|
||
Debug.Assert((object)propertySymbol != null); | ||
Debug.Assert(propertySyntax != null); | ||
|
@@ -970,7 +987,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV | |
{ | ||
var setMethod = propertySymbol.GetOwnOrInheritedSetMethod(); | ||
|
||
if ((object)setMethod == null) | ||
if (setMethod is null) | ||
{ | ||
var containing = this.ContainingMemberOrLambda; | ||
if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing)) | ||
|
@@ -981,6 +998,13 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV | |
} | ||
else | ||
{ | ||
if (setMethod.IsInitOnly && | ||
!isAllowedInitOnlySet(receiver)) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_AssignmentInitOnly, node, propertySymbol); | ||
return false; | ||
} | ||
|
||
var accessThroughType = this.GetAccessThroughType(receiver); | ||
bool failedThroughTypeCheck; | ||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; | ||
|
@@ -1069,6 +1093,36 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV | |
} | ||
|
||
return true; | ||
|
||
bool isAllowedInitOnlySet(BoundExpression receiver) | ||
{ | ||
// ok: new C() { InitOnlyProperty = ... } | ||
if (receiver is BoundObjectOrCollectionValuePlaceholder) | ||
{ | ||
return true; | ||
} | ||
|
||
// bad: other.InitOnlyProperty = ... | ||
if (!(receiver is BoundThisReference || receiver is BoundBaseReference)) | ||
{ | ||
return false; | ||
} | ||
|
||
var containingMember = ContainingMemberOrLambda; | ||
if (!(containingMember is MethodSymbol method)) | ||
{ | ||
return false; | ||
} | ||
|
||
bool isInitOnlySetter = method.MethodKind == MethodKind.PropertySet && method.IsInitOnly; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is this condition redundant? #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but it follows the letter of the spec, "Inside the init accessor of any property, on this or base" In reply to: 415102012 [](ancestors = 415102012) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In reply to: 415204497 [](ancestors = 415204497,415102012) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename it to In reply to: 415213918 [](ancestors = 415213918,415204497,415102012) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that's necessary. In reply to: 415214044 [](ancestors = 415214044,415213918,415204497,415102012) |
||
if (method.IsConstructor() || isInitOnlySetter) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It looks like this check returns true for a static constructor. The spec doesn't allow assignment in a static constructor. Even though some other error is likely to be reported in this scenario, I think it would be better to use more precise condition that follows the spec "to the letter". Also, please add a test that gets here for a static constructor, even if that would be an error due to reasons unrelated to this feature. #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This thread was marked as resolved. However, I don't see any changes for the corresponding code and no other response. If the context for the thread is not clear, it is about the In reply to: 415103737 [](ancestors = 415103737) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The resolution was the added because assertion above ( In reply to: 416728343 [](ancestors = 416728343,415103737) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Given that the explicit check is simple and even less expensive than In reply to: 416822362 [](ancestors = 416822362,416728343,415103737) |
||
{ | ||
// ok: setting on `this` or `base` from a constructor or init-only setter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
instance constructor? #Closed |
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
|
||
private bool IsBadBaseAccess(SyntaxNode node, BoundExpression receiverOpt, Symbol member, DiagnosticBag diagnostics, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -630,6 +630,9 @@ | |
<data name="ERR_BadMemberFlag" xml:space="preserve"> | ||
<value>The modifier '{0}' is not valid for this item</value> | ||
</data> | ||
<data name="ERR_BadInitAccessor" xml:space="preserve"> | ||
<value>The 'init' accessor is not valid on static members</value> | ||
</data> | ||
<data name="ERR_BadMemberProtection" xml:space="preserve"> | ||
<value>More than one protection modifier</value> | ||
</data> | ||
|
@@ -1592,6 +1595,9 @@ If such a class is used as a base class and if the deriving class defines a dest | |
<data name="ERR_ExplicitPropertyAddingAccessor" xml:space="preserve"> | ||
<value>'{0}' adds an accessor not found in interface member '{1}'</value> | ||
</data> | ||
<data name="ERR_ExplicitPropertyMismatchInitOnly" xml:space="preserve"> | ||
<value>Accessors '{0}' and '{1}' should both be init-only or neither</value> | ||
</data> | ||
<data name="ERR_ExplicitPropertyMissingAccessor" xml:space="preserve"> | ||
<value>Explicit interface implementation '{0}' is missing accessor '{1}'</value> | ||
</data> | ||
|
@@ -2891,7 +2897,7 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep | |
<value>Members of readonly field '{0}' cannot be used as a ref or out value (except in a constructor)</value> | ||
</data> | ||
<data name="ERR_AssgReadonly" xml:space="preserve"> | ||
<value>A readonly field cannot be assigned to (except in a constructor of the class in which the field is defined or a variable initializer))</value> | ||
<value>A readonly field cannot be assigned to (except in a constructor or init-only setter of the class in which the field is defined or a variable initializer))</value> | ||
</data> | ||
<data name="ERR_AssgReadonly2" xml:space="preserve"> | ||
<value>Members of readonly field '{0}' cannot be modified (except in a constructor or a variable initializer)</value> | ||
|
@@ -4918,6 +4924,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="ERR_CantChangeRefReturnOnOverride" xml:space="preserve"> | ||
<value>'{0}' must match by reference return of overridden member '{1}'</value> | ||
</data> | ||
<data name="ERR_CantChangeInitOnlyOnOverride" xml:space="preserve"> | ||
<value>'{0}' must match by init-only of overridden member '{1}'</value> | ||
</data> | ||
<data name="ERR_MustNotHaveRefReturn" xml:space="preserve"> | ||
<value>By-reference returns may only be used in methods that return by reference</value> | ||
</data> | ||
|
@@ -4930,6 +4939,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="ERR_CloseUnimplementedInterfaceMemberWrongRefReturn" xml:space="preserve"> | ||
<value>'{0}' does not implement interface member '{1}'. '{2}' cannot implement '{1}' because it does not have matching return by reference.</value> | ||
</data> | ||
<data name="ERR_CloseUnimplementedInterfaceMemberWrongInitOnly" xml:space="preserve"> | ||
<value>'{0}' does not implement interface member '{1}'. '{2}' cannot implement '{1}' because it does not match by init-only.</value> | ||
</data> | ||
<data name="ERR_BadIteratorReturnRef" xml:space="preserve"> | ||
<value>The body of '{0}' cannot be an iterator block because '{0}' returns by reference</value> | ||
</data> | ||
|
@@ -6049,6 +6061,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="IDS_FeatureRecords" xml:space="preserve"> | ||
<value>records</value> | ||
</data> | ||
<data name="IDS_FeatureInitOnlySetters" xml:space="preserve"> | ||
<value>init-only setters</value> | ||
</data> | ||
<data name="ERR_BadRecordDeclaration" xml:space="preserve"> | ||
<value>Records must have both a 'data' modifier and non-empty parameter list</value> | ||
</data> | ||
|
@@ -6067,4 +6082,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="WRN_GeneratorFailedDuringInitialization_Title" xml:space="preserve"> | ||
<value>Generator failed to initialize.</value> | ||
</data> | ||
<data name="ERR_AssignmentInitOnly" xml:space="preserve"> | ||
<value>Init-only member '{0}' can only be assigned from a constructor, object initialization or 'with' expression of that type.</value> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
"in" vs. "from"? #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is there a concept of "Init-only member"? #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec was written in those terms. We now updated it, I'll update here too. In reply to: 415097040 [](ancestors = 415097040) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The spec also says: "Inside the init accessor of any property, on this or base" #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The full quote:
The spec also doesn't mention 'with' expression. I suggest we drop that part for now and make appropriate changes later when the interaction between the features is specified and being implemented. In reply to: 415097435 [](ancestors = 415097435) |
||
</data> | ||
</root> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
As of yet this isn't an access modifier. #Resolved