From eb352ebf52e11e29b648d402437a133dc63cb0e0 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:10:11 -0700 Subject: [PATCH] Differentiate l-value and r-value property use in ctor --- .../Portable/Binder/Binder.ValueChecks.cs | 2 +- .../Portable/Binder/Binder_Statements.cs | 12 +- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 5 +- .../FlowAnalysis/DefiniteAssignment.cs | 24 +- .../FlowAnalysis/LocalDataFlowPass.cs | 6 +- .../Portable/FlowAnalysis/NullableWalker.cs | 19 +- .../Source/SourcePropertySymbolBase.cs | 19 +- .../CSharp/Test/Emit3/FieldKeywordTests.cs | 364 ++++++++++++++++-- 8 files changed, 367 insertions(+), 84 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index a23da4551f2f3..e34aca22a85c6 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1694,7 +1694,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV if (setMethod is null) { var containing = this.ContainingMemberOrLambda; - if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing) + if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing, useAsLvalue: true) && !isAllowedDespiteReadonly(receiver)) { Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index aa058e8d7678d..6fd8e4184d588 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1749,15 +1749,13 @@ private DiagnosticInfo GetBadEventUsageDiagnosticInfo(EventSymbol eventSymbol) new CSDiagnosticInfo(ErrorCode.ERR_BadEventUsageNoField, leastOverridden); } - internal static bool AccessingAutoPropertyFromConstructor(BoundPropertyAccess propertyAccess, Symbol fromMember) + internal static bool AccessingAutoPropertyFromConstructor(BoundPropertyAccess propertyAccess, Symbol fromMember, bool useAsLvalue = false) { - return AccessingAutoPropertyFromConstructor(propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, fromMember); + return AccessingAutoPropertyFromConstructor(propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, fromMember, useAsLvalue); } - // PROTOTYPE: Is this method used only when writing to the backing field, or also when reading? - // If it's used for reading as well, then we need to check that the getter is auto-implemented in - // that case, rather than checking CanAssignBackingFieldDirectlyInConstructor. - private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember) + // PROTOTYPE: Is useAsLvalue set correctly in all callers? + private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember, bool useAsLvalue) { if (!propertySymbol.IsDefinition && propertySymbol.ContainingType.Equals(propertySymbol.ContainingType.OriginalDefinition, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) { @@ -1768,7 +1766,7 @@ private static bool AccessingAutoPropertyFromConstructor(BoundExpression receive var propertyIsStatic = propertySymbol.IsStatic; return (object)sourceProperty != null && - sourceProperty.CanAssignBackingFieldDirectlyInConstructor && + sourceProperty.CanUseBackingFieldDirectlyInConstructor(useAsLvalue) && TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.AllIgnoreOptions) && IsConstructorOrField(fromMember, isStatic: propertyIsStatic) && (propertyIsStatic || receiver.Kind == BoundKind.ThisReference); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 9d4bef8a46fe0..4cef407ae733d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -570,7 +570,7 @@ protected void VisitLvalue(BoundExpression node) { var access = (BoundPropertyAccess)node; - if (Binder.AccessingAutoPropertyFromConstructor(access, _symbol)) + if (Binder.AccessingAutoPropertyFromConstructor(access, _symbol, useAsLvalue: true)) { var backingField = (access.PropertySymbol as SourcePropertySymbolBase)?.BackingField; if (backingField != null) @@ -2026,6 +2026,7 @@ protected virtual void PropertySetter(BoundExpression node, BoundExpression rece VisitReceiverAfterCall(receiver, setter); } + // PROTOTYPE: Test all uses of this method. // returns false if expression is not a property access // or if the property has a backing field // and accessed in a corresponding constructor @@ -2036,7 +2037,7 @@ private bool RegularPropertyAccess(BoundExpression expr) return false; } - return !Binder.AccessingAutoPropertyFromConstructor((BoundPropertyAccess)expr, _symbol); + return !Binder.AccessingAutoPropertyFromConstructor((BoundPropertyAccess)expr, _symbol, useAsLvalue: true); } public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index 8136382090229..092172077f908 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -479,8 +479,8 @@ protected virtual void ReportUnassignedOutParameter(ParameterSymbol parameter, S if (HasInitializer(field)) continue; - if (!isAssigned(thisSlot, field) && - !(field.AssociatedSymbol is { } associatedProperty && isAssigned(thisSlot, associatedProperty))) + int fieldSlot = VariableSlot(field, thisSlot); + if (fieldSlot == -1 || !this.State.IsAssigned(fieldSlot)) { Symbol associatedPropertyOrEvent = field.AssociatedSymbol; bool hasAssociatedProperty = associatedPropertyOrEvent?.Kind == SymbolKind.Property; @@ -533,12 +533,6 @@ protected virtual void ReportUnassignedOutParameter(ParameterSymbol parameter, S Diagnostics.Add(ErrorCode.ERR_ParamUnassigned, location, parameter.Name); } } - - bool isAssigned(int thisSlot, Symbol symbol) - { - int slot = VariableSlot(symbol, thisSlot); - return slot != -1 && this.State.IsAssigned(slot); - } } /// @@ -1060,7 +1054,7 @@ protected override void Normalize(ref LocalState state) } } - protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression receiver, out Symbol member) + protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression receiver, out Symbol member, bool useAsLvalue) { receiver = null; member = null; @@ -1099,7 +1093,7 @@ protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundE { var propAccess = (BoundPropertyAccess)expr; - if (Binder.AccessingAutoPropertyFromConstructor(propAccess, this.CurrentSymbol)) + if (Binder.AccessingAutoPropertyFromConstructor(propAccess, this.CurrentSymbol, useAsLvalue)) { var propSymbol = propAccess.PropertySymbol; member = (propSymbol as SourcePropertySymbolBase)?.BackingField; @@ -1327,13 +1321,7 @@ void addDiagnosticForStructField(int fieldSlot, FieldSymbol fieldSymbol) if (containingSlot == thisSlot) { // should we handle nested fields here? https://github.com/dotnet/roslyn/issues/59890 - var implicitlyInitializedField = fieldIdentifier.Symbol switch - { - FieldSymbol f => f, - SourcePropertySymbolBase { BackingField: { } backingField } => backingField, // PROTOTYPE: Temporary work around. - var s => throw ExceptionUtilities.UnexpectedValue(s), - }; - AddImplicitlyInitializedField(implicitlyInitializedField); + AddImplicitlyInitializedField((FieldSymbol)fieldIdentifier.Symbol); if (fieldSymbol.RefKind != RefKind.None) { @@ -1640,7 +1628,7 @@ protected virtual void AssignImpl(BoundNode node, BoundExpression value, bool is case BoundKind.PropertyAccess: { var expression = (BoundExpression)node; - int slot = MakeSlot(expression); + int slot = MakeSlot(expression, useAsLvalue: true); SetSlotState(slot, written); if (written) NoteWrite(expression, value, read); break; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs index fec058592d072..c512a0b9cfb37 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs @@ -201,14 +201,14 @@ private int DescendThroughTupleRestFields(ref Symbol symbol, int containingSlot, return containingSlot; } - protected abstract bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression? receiver, [NotNullWhen(true)] out Symbol? member); + protected abstract bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression? receiver, [NotNullWhen(true)] out Symbol? member, bool useAsLvalue = false); /// /// Return the slot for a variable, or -1 if it is not tracked (because, for example, it is an empty struct). /// /// /// - protected virtual int MakeSlot(BoundExpression node) + protected virtual int MakeSlot(BoundExpression node, bool useAsLvalue = false) { switch (node.Kind) { @@ -224,7 +224,7 @@ protected virtual int MakeSlot(BoundExpression node) case BoundKind.FieldAccess: case BoundKind.EventAccess: case BoundKind.PropertyAccess: - if (TryGetReceiverAndMember(node, out BoundExpression? receiver, out Symbol? member)) + if (TryGetReceiverAndMember(node, out BoundExpression? receiver, out Symbol? member, useAsLvalue)) { Debug.Assert((receiver is null) != member.RequiresInstanceReceiver()); return MakeMemberSlot(receiver, member); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index db4df5596a579..0c8ea87d6b01a 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -423,7 +423,7 @@ private void SetAnalyzedNullability(BoundExpression? expr, VisitResult result, b private bool _expressionIsRead = true; /// - /// Used to allow to substitute the correct slot for a when + /// Used to allow to substitute the correct slot for a when /// it's encountered. /// private int _lastConditionalAccessSlot = -1; @@ -1927,7 +1927,7 @@ private NullableFlowState GetDefaultState(ref LocalState state, int slot) } } - protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression? receiver, [NotNullWhen(true)] out Symbol? member) + protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression? receiver, [NotNullWhen(true)] out Symbol? member, bool useAsLvalue) { receiver = null; member = null; @@ -1968,6 +1968,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 + // to match the DefiniteAssignment implementation? if (propSymbol.IsStatic) { return true; @@ -1985,7 +1987,7 @@ receiver is object && receiver.Type is object; } - protected override int MakeSlot(BoundExpression node) + protected override int MakeSlot(BoundExpression node, bool useAsLvalue = false) { int result = makeSlot(node); #if DEBUG @@ -9754,15 +9756,12 @@ private void VisitThisOrBaseReference(BoundExpression node) Debug.Assert(!IsConditionalState); var left = node.Left; - // PROTOTYPE: We cannot simply use the associated property if the field is a backing field - // if the property uses `field`, because the relationship between property and field may - // be something other than { get => field; set { field = value; } }. switch (left) { // when binding initializers, we treat assignments to auto-properties or field-like events as direct assignments to the underlying field. // in order to track member state based on these initializers, we need to see the assignment in terms of the associated member - case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: PropertySymbol property }, /*PROTOTYPE: Temporary work around*/Syntax: not FieldExpressionSyntax } fieldAccess: - left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, LookupResultKind.Viable, property.Type, fieldAccess.HasErrors); + case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: PropertySymbol autoProperty }, /*PROTOTYPE: Temporary work around*/Syntax: not FieldExpressionSyntax } fieldAccess: + left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, autoProperty, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); break; case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: EventSymbol @event } } fieldAccess: left = new BoundEventAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, @event, isUsableAsField: true, LookupResultKind.Viable, @event.Type, fieldAccess.HasErrors); @@ -9842,10 +9841,6 @@ private bool IsPropertyOutputMoreStrictThanInput(PropertySymbol property) /// private void AdjustSetValue(BoundExpression left, ref TypeWithState rightState) { - // PROTOTYPE: AdjustSetValue() is called from VisitAssignmentOperator() above, and in - // that method, we may have replaced a BoundFieldAccess with a BoundPropertyAccess, - // but the underlying field may have different attributes than the property. Are we incorrectly - // interpreting the nullable state of the resulting assignment? var property = left switch { BoundPropertyAccess propAccess => propAccess.PropertySymbol, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 62992d971597c..d1d4307cac02f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -668,10 +668,23 @@ internal bool HasAutoPropertySet /// /// True if the property has a synthesized backing field, and - /// either no setter or the setter is auto-implemented. + /// either no accessor or the accessor is auto-implemented. /// - internal bool CanAssignBackingFieldDirectlyInConstructor - => BackingField is { } && (SetMethod is null || HasAutoPropertySet); + internal bool CanUseBackingFieldDirectlyInConstructor(bool useAsLvalue) + { + if (BackingField is null) + { + return false; + } + if (useAsLvalue) + { + return SetMethod is null || HasAutoPropertySet; + } + else + { + return GetMethod is null || HasAutoPropertyGet; + } + } private bool IsSetOnEitherPart(Flags flags) { diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index 24f9c4a84005d..1187e1d34a6c4 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -1648,6 +1648,11 @@ .maxstack 1 """); } + // PROTOTYPE: Test uses of l-value other than assignment: +=, ++, ??=. + // PROTOTYPE: Why aren't we testing { get; set { field = value; } }? + // PROTOTYPE: Add similar tests for nullability. Or perhaps just test that we're not inferring the + // nullability of the property from the nullability of the field when both (or even one?) of the + // accessors are manually-implemented. [Theory] [CombinatorialData] public void ConstructorAssignment_02([CombinatorialValues("class", "struct", "ref struct")] string typeKind, bool useInit) @@ -1661,42 +1666,49 @@ public void ConstructorAssignment_02([CombinatorialValues("class", "struct", "re public int F1; public int P1 { get; } public C1(int i) { P1 = i; } + public C1(int x, out int y) { y = P1; F1 = x; } } {{typeKind}} C2 { public int F2; public int P2 { get => field; } public C2(int i) { P2 = i; } + public C2(int x, out int y) { y = P2; F2 = x; } } {{typeKind}} C3 { public int F3; public int P3 { get => field; {{setter}}; } public C3(int i) { P3 = i; } + public C3(int x, out int y) { y = P3; F3 = x; } } {{typeKind}} C4 { public int F4; public int P4 { get => field; {{setter}} { } } public C4(int i) { P4 = i; } + public C4(int x, out int y) { y = P4; F4 = x; } } {{typeKind}} C5 { public int F5; public int P5 { get => default; {{setter}}; } public C5(int i) { P5 = i; } + public C5(int x, out int y) { y = P5; F5 = x; } } {{typeKind}} C6 { public int F6; public int P6 { get; {{setter}}; } public C6(int i) { P6 = i; } + public C6(int x, out int y) { y = P6; F6 = x; } } {{typeKind}} C7 { public int F7; public int P7 { get; {{setter}} { } } public C7(int i) { P7 = i; } + public C7(int x, out int y) { y = P7; F7 = x; } } {{typeKind}} C8 { @@ -1709,6 +1721,7 @@ public void ConstructorAssignment_02([CombinatorialValues("class", "struct", "re public int F9; public int P9 { get { return field; } {{setter}} { field = value; } } public C9(int i) { P9 = i; } + public C9(int x, out int y) { y = P9; F9 = x; } } class Program { @@ -1735,7 +1748,7 @@ static void Main() verifier.VerifyDiagnostics(); if (typeKind == "class") { - verifier.VerifyIL("C1..ctor", $$""" + verifier.VerifyIL("C1..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1747,7 +1760,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C2..ctor", $$""" + verifier.VerifyIL("C2..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1759,7 +1772,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C3..ctor", $$""" + verifier.VerifyIL("C3..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1771,7 +1784,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C4..ctor", $$""" + verifier.VerifyIL("C4..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1783,7 +1796,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C5..ctor", $$""" + verifier.VerifyIL("C5..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1795,7 +1808,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C6..ctor", $$""" + verifier.VerifyIL("C6..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1807,7 +1820,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C7..ctor", $$""" + verifier.VerifyIL("C7..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1819,7 +1832,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C8..ctor", $$""" + verifier.VerifyIL("C8..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1831,7 +1844,7 @@ .maxstack 2 IL_000d: ret } """); - verifier.VerifyIL("C9..ctor", $$""" + verifier.VerifyIL("C9..ctor(int)", $$""" { // Code size 14 (0xe) .maxstack 2 @@ -1846,7 +1859,7 @@ .maxstack 2 } else { - verifier.VerifyIL("C1..ctor", $$""" + verifier.VerifyIL("C1..ctor(int)", $$""" { // Code size 15 (0xf) .maxstack 2 @@ -1859,7 +1872,7 @@ .maxstack 2 IL_000e: ret } """); - verifier.VerifyIL("C2..ctor", $$""" + verifier.VerifyIL("C2..ctor(int)", $$""" { // Code size 15 (0xf) .maxstack 2 @@ -1872,7 +1885,7 @@ .maxstack 2 IL_000e: ret } """); - verifier.VerifyIL("C3..ctor", $$""" + verifier.VerifyIL("C3..ctor(int)", $$""" { // Code size 15 (0xf) .maxstack 2 @@ -1885,7 +1898,7 @@ .maxstack 2 IL_000e: ret } """); - verifier.VerifyIL("C4..ctor", $$""" + verifier.VerifyIL("C4..ctor(int)", $$""" { // Code size 22 (0x16) .maxstack 2 @@ -1901,7 +1914,7 @@ .maxstack 2 IL_0015: ret } """); - verifier.VerifyIL("C5..ctor", $$""" + verifier.VerifyIL("C5..ctor(int)", $$""" { // Code size 15 (0xf) .maxstack 2 @@ -1914,7 +1927,7 @@ .maxstack 2 IL_000e: ret } """); - verifier.VerifyIL("C6..ctor", $$""" + verifier.VerifyIL("C6..ctor(int)", $$""" { // Code size 15 (0xf) .maxstack 2 @@ -1927,7 +1940,7 @@ .maxstack 2 IL_000e: ret } """); - verifier.VerifyIL("C7..ctor", $$""" + verifier.VerifyIL("C7..ctor(int)", $$""" { // Code size 22 (0x16) .maxstack 2 @@ -1943,7 +1956,7 @@ .maxstack 2 IL_0015: ret } """); - verifier.VerifyIL("C8..ctor", $$""" + verifier.VerifyIL("C8..ctor(int)", $$""" { // Code size 22 (0x16) .maxstack 2 @@ -1959,7 +1972,7 @@ .maxstack 2 IL_0015: ret } """); - verifier.VerifyIL("C9..ctor", $$""" + verifier.VerifyIL("C9..ctor(int)", $$""" { // Code size 22 (0x16) .maxstack 2 @@ -1976,6 +1989,293 @@ .maxstack 2 } """); } + if (typeKind == "class") + { + verifier.VerifyIL("C1..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C1.P1.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C1.F1" + IL_0015: ret + } + """); + verifier.VerifyIL("C2..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C2.P2.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C2.F2" + IL_0015: ret + } + """); + verifier.VerifyIL("C3..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C3.P3.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C3.F3" + IL_0015: ret + } + """); + verifier.VerifyIL("C4..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C4.P4.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C4.F4" + IL_0015: ret + } + """); + verifier.VerifyIL("C5..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C5.P5.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C5.F5" + IL_0015: ret + } + """); + verifier.VerifyIL("C6..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C6.P6.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C6.F6" + IL_0015: ret + } + """); + verifier.VerifyIL("C7..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C7.P7.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C7.F7" + IL_0015: ret + } + """); + verifier.VerifyIL("C9..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C9.P9.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C9.F9" + IL_0015: ret + } + """); + } + else + { + verifier.VerifyIL("C1..ctor(int, out int)", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C1.k__BackingField" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: call "readonly int C1.P1.get" + IL_000e: stind.i4 + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: stfld "int C1.F1" + IL_0016: ret + } + """); + verifier.VerifyIL("C2..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C2.F2" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C2.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C2.P2.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C2.F2" + IL_001d: ret + } + """); + verifier.VerifyIL("C3..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C3.F3" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C3.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C3.P3.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C3.F3" + IL_001d: ret + } + """); + verifier.VerifyIL("C4..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C4.F4" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C4.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C4.P4.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C4.F4" + IL_001d: ret + } + """); + verifier.VerifyIL("C5..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C5.F5" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C5.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C5.P5.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C5.F5" + IL_001d: ret + } + """); + verifier.VerifyIL("C6..ctor(int, out int)", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C6.k__BackingField" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: call "readonly int C6.P6.get" + IL_000e: stind.i4 + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: stfld "int C6.F6" + IL_0016: ret + } + """); + // PROTOTYPE: What if C7.P7.get is virtual? Is the virtual method still + // referring to C7 at that point, so we can still assume the get is from C7? + verifier.VerifyIL("C7..ctor(int, out int)", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C7.k__BackingField" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: call "readonly int C7.P7.get" + IL_000e: stind.i4 + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: stfld "int C7.F7" + IL_0016: ret + } + """); + verifier.VerifyIL("C9..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C9.F9" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C9.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C9.P9.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C9.F9" + IL_001d: ret + } + """); + } } [Fact] @@ -2279,12 +2579,9 @@ static void Main() // (5,33): warning CS9018: Auto-implemented property 'P1' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. // public S1(int unused) { _ = P1; } Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P1").WithArguments("P1").WithLocation(5, 33), - // (10,12): warning CS9021: Control is returned to caller before auto-implemented property 'S2.P2' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S2").WithArguments("S2.P2").WithLocation(10, 12), - // (10,33): warning CS9018: Auto-implemented property 'P2' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // (10,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P2").WithArguments("P2").WithLocation(10, 33), + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P2").WithLocation(10, 33), // (15,12): warning CS9021: Control is returned to caller before auto-implemented property 'S3.P3' is explicitly assigned, causing a preceding implicit assignment of 'default'. // public S3(int unused) { _ = P3; } Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S3").WithArguments("S3.P3").WithLocation(15, 12), @@ -2297,15 +2594,12 @@ static void Main() // (25,12): warning CS9021: Control is returned to caller before auto-implemented property 'S5.P5' is explicitly assigned, causing a preceding implicit assignment of 'default'. // public S5(int unused) { _ = P5; } Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S5").WithArguments("S5.P5").WithLocation(25, 12), - // (25,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // (25,33): warning CS9018: Auto-implemented property 'P5' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. // public S5(int unused) { _ = P5; } - Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P5").WithLocation(25, 33), - // (30,12): warning CS9021: Control is returned to caller before auto-implemented property 'S6.P6' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S6(int unused) { _ = P6; } - Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S6").WithArguments("S6.P6").WithLocation(30, 12), - // (30,33): warning CS9018: Auto-implemented property 'P6' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P5").WithArguments("P5").WithLocation(25, 33), + // (30,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. // public S6(int unused) { _ = P6; } - Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P6").WithArguments("P6").WithLocation(30, 33)); + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P6").WithLocation(30, 33)); } else { @@ -2448,15 +2742,9 @@ static void Main() // (10,16): warning CS0649: Field 'S2.F2' is never assigned to, and will always have its default value 0 // public int F2; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F2").WithArguments("S2.F2", "0").WithLocation(10, 16), - // (12,12): warning CS9021: Control is returned to caller before auto-implemented property 'S2.P2' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S2").WithArguments("S2.P2").WithLocation(12, 12), - // (12,12): warning CS9022: Control is returned to caller before field 'S2.F2' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S2").WithArguments("S2.F2").WithLocation(12, 12), - // (12,33): warning CS9018: Auto-implemented property 'P2' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // (12,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P2").WithArguments("P2").WithLocation(12, 33)); + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P2").WithLocation(12, 33)); } else {