-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
[semi-auto-props]: Require overriding all accessors #61114
Changes from 1 commit
712aa77
09e36a8
10326e6
5d61c97
e507fcb
3ed97f6
786c0b8
20bcc1f
7bf8e2c
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 |
---|---|---|
|
@@ -185,7 +185,7 @@ protected SourcePropertySymbolBase( | |
Debug.Assert(!IsIndexer); | ||
// PROTOTYPE(semi-auto-props): Make sure that TestSemiAutoPropertyWithInitializer (when enabled back) is affected by this. | ||
// That is, if we removed "hasInitializer", the test should fail, or any other test should get affected. | ||
GetOrCreateBackingField(isCreatedForFieldKeyword: hasInitializer && !isAutoProperty, isEarlyConstructed: true); | ||
GetOrCreateBackingField(isCreatedForFieldKeyword: hasInitializer && !isAutoProperty, isEarlyConstructed: true, diagnostics); | ||
} | ||
|
||
if (hasGetAccessor) | ||
|
@@ -198,7 +198,7 @@ protected SourcePropertySymbolBase( | |
} | ||
} | ||
|
||
private SynthesizedBackingFieldSymbol? GetOrCreateBackingField(bool isCreatedForFieldKeyword, bool isEarlyConstructed) | ||
private SynthesizedBackingFieldSymbol? GetOrCreateBackingField(bool isCreatedForFieldKeyword, bool isEarlyConstructed, BindingDiagnosticBag diagnostics) | ||
{ | ||
Debug.Assert(!IsIndexer); | ||
if (_lazyBackingFieldSymbol == _lazyBackingFieldSymbolSentinel) | ||
|
@@ -210,15 +210,24 @@ protected SourcePropertySymbolBase( | |
hasInitializer: (_propertyFlags & Flags.HasInitializer) != 0, | ||
isCreatedForFieldKeyword: isCreatedForFieldKeyword, | ||
isEarlyConstructed: isEarlyConstructed); | ||
Interlocked.CompareExchange(ref _lazyBackingFieldSymbol, backingField, _lazyBackingFieldSymbolSentinel); | ||
if (Interlocked.CompareExchange(ref _lazyBackingFieldSymbol, backingField, _lazyBackingFieldSymbolSentinel) == _lazyBackingFieldSymbolSentinel && | ||
isCreatedForFieldKeyword) | ||
{ | ||
// semi auto property should override all accessors. | ||
if ((!HasSetAccessor && !this.IsReadOnly) || | ||
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. 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. @AlekseyTs Yup I was going to do that. I added the IsOverride assert in another PR just to be sure my assumption is correct :) |
||
(!HasGetAccessor && !this.IsWriteOnly)) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_AutoPropertyMustOverrideSet, Location, this); | ||
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. Some questions here:
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.
We prefer pure refactoring changes (a rename is a pure refactoring) kept separate from behavior changes.
Consider adding a dedicated (new) error. Something like: "Properties using 'field' keyword must override all accessors of the overridden property."
I think other comments already provided feedback on the implementation strategy.
333fred marked this conversation as resolved.
Show resolved
Hide resolved
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.
Reporting any diagnostics here conditionally, based on whether the symbol is created now or by another call, is really a bad idea. The caller of this API is method binding, it is not responsible to propagate this diagnostics to a "user". The diagnostics can be discarded. In fact, 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. Indeed. Calling |
||
} | ||
} | ||
} | ||
|
||
return (SynthesizedBackingFieldSymbol?)_lazyBackingFieldSymbol; | ||
} | ||
|
||
internal SynthesizedBackingFieldSymbol? GetOrCreateBackingFieldForFieldKeyword() | ||
internal SynthesizedBackingFieldSymbol? GetOrCreateBackingFieldForFieldKeyword(BindingDiagnosticBag diagnostics) | ||
{ | ||
return GetOrCreateBackingField(isCreatedForFieldKeyword: true, isEarlyConstructed: false); | ||
return GetOrCreateBackingField(isCreatedForFieldKeyword: true, isEarlyConstructed: false, diagnostics); | ||
} | ||
|
||
private void EnsureSignatureGuarded(BindingDiagnosticBag diagnostics) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,13 +39,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.Semantics | |
// both expression body and block body. We should confirm that SemanticModel doesn't bind an expression body in presence of a block body. | ||
|
||
// PROTOTYPE(semi-auto-props): Add ENC tests. | ||
|
||
// PROTOTYPE(semi-auto-props): Add a test where a virtual `get; set;` auto property is overridden by a sealed `get => 0;`. | ||
// A synthesized sealed accessor is expected to be produced. | ||
|
||
// PROTOTYPE(semi-auto-props): Add a test where a virtual property is `public virtual int P6 { get => 0; set { } }`, and | ||
// it's overridden by `public override int P6 { get => field; }` | ||
// The assignment of the property in constructor should use the base setter. | ||
public class PropertyFieldKeywordTests : CompilingTestBase | ||
{ | ||
/// <summary> | ||
|
@@ -87,6 +80,63 @@ private void VerifyTypeIL(CSharpCompilation compilation, string typeName, string | |
CompileAndVerify(compilation).VerifyTypeIL(typeName, expected); | ||
} | ||
|
||
[Fact] | ||
public void TestVirtualPropertyOverride() | ||
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. 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. |
||
{ | ||
var comp = CreateCompilation(@" | ||
public class Base | ||
{ | ||
public virtual int P1 { get; set; } | ||
public virtual int P2 { get => 0; set { } } | ||
} | ||
public class Derived1 : Base | ||
{ | ||
public override int P1 { get => field; } | ||
public override int P2 { get => field; } | ||
} | ||
public class Derived2 : Base | ||
{ | ||
public override int P1 { set => _ = field; } | ||
public override int P2 { set => _ = field; } | ||
} | ||
public class Derived3 : Base | ||
{ | ||
// PROTOTYPE(semi-auto-props): | ||
// This should produce ERR_AutoPropertyMustOverrideSet ""Auto-implemented properties must override all accessors of the overridden property."" | ||
// instead of ERR_AutoPropertyMustHaveGetAccessor, unless https://github.com/dotnet/csharplang/issues/6089 is accepted. | ||
public override int P1 { set; } | ||
} | ||
public class Derived4 : Base | ||
{ | ||
public override int P1 { get => field; set => field = value; } | ||
} | ||
"); | ||
var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); | ||
comp.TestOnlyCompilationData = accessorBindingData; | ||
comp.VerifyDiagnostics( | ||
// (10,25): error CS8080: Auto-implemented properties must override all accessors of the overridden property. | ||
// public override int P1 { get => field; } // PROTOTYPE(semi-auto-props): This should error | ||
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. 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 is an outdated PROTOTYPE comment. I had it written when I wrote the test but before fixing implementation. Removing. |
||
Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithArguments("Derived1.P1").WithLocation(10, 25), | ||
// (11,25): error CS8080: Auto-implemented properties must override all accessors of the overridden property. | ||
// public override int P2 { get => field; } // PROTOTYPE(semi-auto-props): This should error | ||
Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P2").WithArguments("Derived1.P2").WithLocation(11, 25), | ||
// (16,25): error CS8080: Auto-implemented properties must override all accessors of the overridden property. | ||
// public override int P1 { set => _ = field; } // PROTOTYPE(semi-auto-props): This should error | ||
Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithArguments("Derived2.P1").WithLocation(16, 25), | ||
// (17,25): error CS8080: Auto-implemented properties must override all accessors of the overridden property. | ||
// public override int P2 { set => _ = field; } // PROTOTYPE(semi-auto-props): This should error | ||
Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P2").WithArguments("Derived2.P2").WithLocation(17, 25), | ||
// (25,30): error CS8051: Auto-implemented properties must have get accessors. | ||
// public override int P1 { set; } | ||
Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithArguments("Derived3.P1.set").WithLocation(25, 30) | ||
); | ||
Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); | ||
} | ||
|
||
[Fact] | ||
public void TestInInterface() | ||
{ | ||
|
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.
I extremely dislike the idea of bundling any diadnostic reporting with this API. I think there was already an attempt to do this in another PR. #Closed