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
{