diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 7aac36943c81f..2437070b52e69 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1703,16 +1703,13 @@ private static bool IsPropertyAssignedThroughBackingField(BoundExpression receiv propertySymbol = propertySymbol.OriginalDefinition; } - // PROTOTYPE(semi-auto-props): TODO: Support assigning semi auto prop from constructors. - return propertySymbol is SourcePropertySymbolBase sourceProperty && - // PROTOTYPE(semi-auto-props): Consider `public int P { get => field; set; }` - sourceProperty.GetMethod is SourcePropertyAccessorSymbol { IsEquivalentToBackingFieldAccess: true } && + IsConstructorOrField(fromMember, isStatic: sourceProperty.IsStatic) && + TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.ConsiderEverything2) && + (sourceProperty.IsStatic || receiver.Kind == BoundKind.ThisReference) && // To be assigned through backing field, either SetMethod is null, or it's equivalent to backing field write sourceProperty.SetMethod is null or SourcePropertyAccessorSymbol { IsEquivalentToBackingFieldAccess: true } && - TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.ConsiderEverything2) && - IsConstructorOrField(fromMember, isStatic: sourceProperty.IsStatic) && - (sourceProperty.IsStatic || receiver.Kind == BoundKind.ThisReference); + (sourceProperty.GetMethod is SourcePropertyAccessorSymbol { IsEquivalentToBackingFieldAccess: true } || sourceProperty.FieldKeywordBackingField is not null); } private static bool IsConstructorOrField(Symbol member, bool isStatic) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 86f01459b7973..b559e9837cfef 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -2581,7 +2581,19 @@ internal DiagnosticBag AdditionalCodegenWarnings } } + /// + /// A bag in which circular struct diagnostics should be reported. + /// + internal BindingDiagnosticBag CircularStructDiagnostics + { + get + { + return _circularStructDiagnostics; + } + } + private readonly DiagnosticBag _additionalCodegenWarnings = new DiagnosticBag(); + private readonly BindingDiagnosticBag _circularStructDiagnostics = new BindingDiagnosticBag(new DiagnosticBag(), new ConcurrentSet()); internal DeclarationTable Declarations { diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index ced70dac3561b..314e1f74b39df 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -202,6 +202,9 @@ public static void CompileMethodBodies( new LocalizableResourceString(messageResourceName, CodeAnalysisResources.ResourceManager, typeof(CodeAnalysisResources))); } + addCircularStructDiagnostics(compilation.SourceModule.GlobalNamespace); + methodCompiler.WaitForWorkers(); + diagnostics.AddRange(compilation.CircularStructDiagnostics, allowMismatchInDependencyAccumulation: true); diagnostics.AddRange(compilation.AdditionalCodegenWarnings); // we can get unused field warnings only if compiling whole compilation. @@ -214,6 +217,45 @@ public static void CompileMethodBodies( moduleBeingBuiltOpt.SetPEEntryPoint(entryPoint, diagnostics.DiagnosticBag); } } + + void addCircularStructDiagnostics(NamespaceOrTypeSymbol symbol) + { + if (symbol is SourceMemberContainerTypeSymbol sourceMemberContainerTypeSymbol && PassesFilter(filterOpt, symbol)) + { + _ = sourceMemberContainerTypeSymbol.KnownCircularStruct; + } + + foreach (var member in symbol.GetMembersUnordered()) + { + if (member is NamespaceOrTypeSymbol namespaceOrTypeSymbol) + { + if (compilation.Options.ConcurrentBuild) + { + Task worker = addCircularStructDiagnosticsAsAsync(namespaceOrTypeSymbol); + methodCompiler._compilerTasks.Push(worker); + } + else + { + addCircularStructDiagnostics(namespaceOrTypeSymbol); + } + } + } + } + + Task addCircularStructDiagnosticsAsAsync(NamespaceOrTypeSymbol symbol) + { + return Task.Run(UICultureUtilities.WithCurrentUICulture(() => + { + try + { + addCircularStructDiagnostics(symbol); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + }), methodCompiler._cancellationToken); + } } // Returns the MethodSymbol for the assembly entrypoint. If the user has a Task returning main, @@ -471,6 +513,43 @@ private void CompileNamedType(NamedTypeSymbol containingType) } var members = containingType.GetMembers(); + + // We first compile the accessors that contain 'field' keyword to avoid extra bindings for 'field' keyword when compiling other members. + for (int memberOrdinal = 0; memberOrdinal < members.Length; memberOrdinal++) + { + var member = members[memberOrdinal]; + + //When a filter is supplied, limit the compilation of members passing the filter. + if (member is not SourcePropertyAccessorSymbol { ContainsFieldKeyword: true } accessor || + !PassesFilter(_filterOpt, member)) + { + continue; + } + + Debug.Assert(member.Kind == SymbolKind.Method); + Binder.ProcessedFieldInitializers processedInitializers = default; + CompileMethod(accessor, memberOrdinal, ref processedInitializers, synthesizedSubmissionFields, compilationState); + } + + // After compiling accessors containing 'field' keyword, we mark the backing field of the corresponding property as calculated. + foreach (var member in members) + { + if (member is SourcePropertySymbolBase property) + { + // PROTOTYPE(semi-auto-props): This can be optimized by checking for field keyword syntactically first. + // If we don't have field keyword, we can safely ignore the filter check. + // This will require more tests. + var getMethod = property.GetMethod; + var setMethod = property.SetMethod; + if ((getMethod is null || PassesFilter(_filterOpt, getMethod)) && + (setMethod is null || PassesFilter(_filterOpt, setMethod))) + { + property.MarkBackingFieldAsCalculated(_diagnostics); + } + } + } + + // Then we compile everything, excluding the accessors that contain field keyword we already compiled in the loop above. for (int memberOrdinal = 0; memberOrdinal < members.Length; memberOrdinal++) { var member = members[memberOrdinal]; @@ -490,6 +569,22 @@ private void CompileNamedType(NamedTypeSymbol containingType) case SymbolKind.Method: { MethodSymbol method = (MethodSymbol)member; + if (method.MethodKind is MethodKind.PropertyGet or MethodKind.PropertySet) + { + // The loop for compiling accessors was written with these valid assumptions. + // If this requirement has changed, the loop above may need to be modified (e.g, if partial accessors was allowed in future) + Debug.Assert(!method.IsScriptConstructor); + Debug.Assert((object)method != scriptEntryPoint); + Debug.Assert(!IsFieldLikeEventAccessor(method)); + Debug.Assert(!method.IsPartialDefinition()); + } + + if (member is SourcePropertyAccessorSymbol { ContainsFieldKeyword: true }) + { + // We already compiled these accessors in the loop above. + continue; + } + if (method.IsScriptConstructor) { Debug.Assert(scriptCtorOrdinal == -1); @@ -574,23 +669,6 @@ private void CompileNamedType(NamedTypeSymbol containingType) } } - foreach (var member in members) - { - if (member is SourcePropertySymbolBase property) - { - // PROTOTYPE(semi-auto-props): This can be optimized by checking for field keyword syntactically first. - // If we don't have field keyword, we can safely ignore the filter check. - // This will require more tests. - var getMethod = property.GetMethod; - var setMethod = property.SetMethod; - if ((getMethod is null || PassesFilter(_filterOpt, getMethod)) && - (setMethod is null || PassesFilter(_filterOpt, setMethod))) - { - property.MarkBackingFieldAsCalculated(_diagnostics); - } - } - } - Debug.Assert(containingType.IsScriptClass == (scriptCtorOrdinal >= 0)); // process additional anonymous type members diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 59f0379650692..6a3c5dd0faa48 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -571,7 +571,8 @@ protected void VisitLvalue(BoundExpression node) if (Binder.IsPropertyAssignedThroughBackingField(access, _symbol)) { - var backingField = (access.PropertySymbol as SourcePropertySymbolBase)?.BackingField; + var property = access.PropertySymbol as SourcePropertySymbolBase; + var backingField = property?.BackingField ?? property?.FieldKeywordBackingField; if (backingField != null) { VisitFieldAccessInternal(access.ReceiverOpt, backingField); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index addf1fb8a4cb1..9d2b7db6e2d94 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -1038,14 +1038,13 @@ protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundE case BoundKind.PropertyAccess: { var propAccess = (BoundPropertyAccess)expr; - // PROTOTYPE(semi-auto-props): The call to IsPropertyAssignedThroughBackingField isn't sensible. // We get here for both property read and write. // This applies to ALL IsPropertyAssignedThroughBackingField calls in this file. if (Binder.IsPropertyAssignedThroughBackingField(propAccess, this.CurrentSymbol)) // PROTOTYPE(semi-auto-props): Revise this method call is the behavior we want and add unit tests.. { - var propSymbol = propAccess.PropertySymbol; - member = (propSymbol as SourcePropertySymbolBase)?.BackingField; + var propSymbol = propAccess.PropertySymbol as SourcePropertySymbolBase; + member = propSymbol?.BackingField ?? propSymbol?.FieldKeywordBackingField; if (member is null) { return false; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs index ac63c36de73d3..fd9acc2ddd414 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs @@ -215,12 +215,15 @@ private FieldSymbol GetActualField(Symbol member, NamedTypeSymbol type) return (!eventSymbol.HasAssociatedField || ShouldIgnoreStructField(eventSymbol, eventSymbol.Type)) ? null : eventSymbol.AssociatedField.AsMember(type); case SymbolKind.Property: // PROTOTYPE(semi-auto-props): Review other event associated field callers and see if we have to do anything special for properties. + // Backing field for semi auto props are not included in GetMembers. - // PROTOTYPE(semi-auto-props): This condition is always false after modifiying BackingField implementation. Figure out a - // test that needs this. - if (member is SourcePropertySymbol { BackingField: SynthesizedBackingFieldSymbol { IsCreatedForFieldKeyword: true } backingField }) + if (member is SourcePropertySymbol property) { - return backingField; + // PROTOTYPE(semi-auto-props): Add definite assignment tests involving initializer, including error scenarios where a FieldKeywordBackingField is still created. + if (property.FieldKeywordBackingField is { } backingField) + { + return backingField; + } } return null; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index 6a6d4099ffc1b..ec662e09fa14c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -301,11 +301,11 @@ private BoundExpression MakePropertyAssignment( if (setMethod is null) { var autoProp = (SourcePropertySymbolBase)property.OriginalDefinition; - Debug.Assert(autoProp.IsAutoPropertyWithGetAccessor, + Debug.Assert(autoProp.GetMethod is SourcePropertyAccessorSymbol { IsEquivalentToBackingFieldAccess: true } || autoProp.FieldKeywordBackingField is not null, "only autoproperties can be assignable without having setters"); Debug.Assert(property.Equals(autoProp, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); - var backingField = autoProp.BackingField; + var backingField = autoProp.BackingField ?? autoProp.FieldKeywordBackingField; Debug.Assert(backingField is not null); return _factory.AssignmentExpression( diff --git a/src/Compilers/CSharp/Portable/Symbols/BaseTypeAnalysis.cs b/src/Compilers/CSharp/Portable/Symbols/BaseTypeAnalysis.cs index 5918d1d848ee6..9e6ff467240af 100644 --- a/src/Compilers/CSharp/Portable/Symbols/BaseTypeAnalysis.cs +++ b/src/Compilers/CSharp/Portable/Symbols/BaseTypeAnalysis.cs @@ -92,9 +92,27 @@ private static void StructDependsClosure(NamedTypeSymbol type, HashSet p { foreach (var member in type.GetMembersUnordered()) { - var field = member as FieldSymbol; - var fieldType = field?.NonPointerType(); - if (fieldType is null || fieldType.TypeKind != TypeKind.Struct || field.IsStatic) + if (member.IsStatic) + { + continue; + } + + FieldSymbol field; + if (member is SourcePropertySymbolBase { FieldKeywordBackingField: { } fieldKeywordField }) + { + field = fieldKeywordField; + } + else if (member.Kind == SymbolKind.Field) + { + field = (FieldSymbol)member; + } + else + { + continue; + } + + var fieldType = field.NonPointerType(); + if (fieldType is null || fieldType.TypeKind != TypeKind.Struct) { continue; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 65cd6833c686c..81985dabfefe0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1617,8 +1617,6 @@ protected void AfterMembersChecks(BindingDiagnosticBag diagnostics) CheckTypeParameterNameConflicts(diagnostics); CheckAccessorNameConflicts(diagnostics); - bool unused = KnownCircularStruct; - CheckSequentialOnPartialType(diagnostics); CheckForProtectedInStaticClass(diagnostics); CheckForUnmatchedOperators(diagnostics); @@ -2133,11 +2131,12 @@ internal override bool KnownCircularStruct else { var diagnostics = BindingDiagnosticBag.GetInstance(); + Debug.Assert(diagnostics.DiagnosticBag is not null); var value = (int)CheckStructCircularity(diagnostics).ToThreeState(); if (Interlocked.CompareExchange(ref _lazyKnownCircularStruct, value, (int)ThreeState.Unknown) == (int)ThreeState.Unknown) { - AddDeclarationDiagnostics(diagnostics); + DeclaringCompilation.CircularStructDiagnostics.AddRange(diagnostics); } Debug.Assert(value == _lazyKnownCircularStruct); @@ -2163,16 +2162,22 @@ private bool HasStructCircularity(BindingDiagnosticBag diagnostics) { foreach (var member in valuesByName) { - if (member.Kind != SymbolKind.Field) + FieldSymbol field; + if (member is SourcePropertySymbolBase { IsStatic: false, FieldKeywordBackingField: { } fieldKeywordField }) { - // NOTE: don't have to check field-like events, because they can't have struct types. - continue; + field = fieldKeywordField; } - var field = (FieldSymbol)member; - if (field.IsStatic) + else if (member.Kind == SymbolKind.Field && !member.IsStatic) { + field = (FieldSymbol)member; + } + else + { + // NOTE: don't have to check field-like events, because they can't have struct types. continue; } + + Debug.Assert(!field.IsStatic); var type = field.NonPointerType(); if (((object)type != null) && (type.TypeKind == TypeKind.Struct) && diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs index 9ebf8fa0ec377..1fbb6f138471f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs @@ -459,6 +459,7 @@ internal Accessibility LocalAccessibility /// This is only calculated from syntax, so we don't know if it /// will bind to something or will create a backing field. /// + // PROTOTYPE(semi-auto-props): Rename to ContainsFieldKeywordSyntactically or similar. internal bool ContainsFieldKeyword => _containsFieldKeyword; /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PropertyFieldKeywordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PropertyFieldKeywordTests.cs index 94c64833f8462..3a7c2877e996e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PropertyFieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PropertyFieldKeywordTests.cs @@ -502,6 +502,270 @@ .property instance int32 P() Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } + [Fact] + public void TestPropertyNotAssignedInStructConstructor() + { + var comp = CreateCompilation(@" +public struct C +{ + public static void Main() + { + var x = new C(); + x.P = 10; + x = new C(); + } + + public C() + { + System.Console.WriteLine(""In C..ctor: "" + P); + } + + public int P { get => field; set => field = value; } +} +", options: TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings)); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: @"In C..ctor: 0 +In C..ctor: 0").VerifyIL("C..ctor", @" +{ + // Code size 37 (0x25) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ldstr ""In C..ctor: "" + IL_000c: ldarg.0 + IL_000d: call ""int C.P.get"" + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: call ""string int.ToString()"" + IL_001a: call ""string string.Concat(string, string)"" + IL_001f: call ""void System.Console.WriteLine(string)"" + IL_0024: ret +} +").VerifyDiagnostics( + // (13,51): 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. + // System.Console.WriteLine("In C..ctor: " + P); + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P").WithLocation(13, 51) + ); + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void TestPropertyIsReadThenAssignedInStructConstructor() + { + var comp = CreateCompilation(@" +public struct C +{ + public static void Main() + { + var x = new C(); + x.P = 10; + x = new C(); + } + + public C() + { + System.Console.WriteLine(""In C..ctor before assignment: "" + P); + P = 5; + System.Console.WriteLine(""In C..ctor after assignment: "" + P); + } + + public int P { get => field; set => field = value; } +} +", options: TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings)); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: @"In C..ctor before assignment: 0 +In C..ctor after assignment: 5 +In C..ctor before assignment: 0 +In C..ctor after assignment: 5").VerifyIL("C..ctor", @" +{ + // Code size 73 (0x49) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ldstr ""In C..ctor before assignment: "" + IL_000c: ldarg.0 + IL_000d: call ""int C.P.get"" + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: call ""string int.ToString()"" + IL_001a: call ""string string.Concat(string, string)"" + IL_001f: call ""void System.Console.WriteLine(string)"" + IL_0024: ldarg.0 + IL_0025: ldc.i4.5 + IL_0026: call ""void C.P.set"" + IL_002b: ldstr ""In C..ctor after assignment: "" + IL_0030: ldarg.0 + IL_0031: call ""int C.P.get"" + IL_0036: stloc.0 + IL_0037: ldloca.s V_0 + IL_0039: call ""string int.ToString()"" + IL_003e: call ""string string.Concat(string, string)"" + IL_0043: call ""void System.Console.WriteLine(string)"" + IL_0048: ret +} +").VerifyDiagnostics( + // (13,69): 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. + // System.Console.WriteLine("In C..ctor before assignment: " + P); + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P").WithLocation(13, 69) + ); + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] // PROTOTYPE(semi-auto-props): Add test with semi-colon setter when mixed scenarios are supported. + public void TestPropertyIsReadThenAssignedInStructConstructor_ReadOnlyProperty() + { + var comp = CreateCompilation(@" +public struct C +{ + public static void Main() + { + var x = new C(); + System.Console.Write(x.P); + } + + public C() + { + System.Console.WriteLine(""In C..ctor before assignment: "" + P); + P = 5; + System.Console.WriteLine(""In C..ctor after assignment: "" + P); + } + + public int P { get => field; } +} +", options: TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings)); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: @"In C..ctor before assignment: 0 +In C..ctor after assignment: 5 +5").VerifyIL("C..ctor", @" +{ + // Code size 73 (0x49) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ldstr ""In C..ctor before assignment: "" + IL_000c: ldarg.0 + IL_000d: call ""int C.P.get"" + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: call ""string int.ToString()"" + IL_001a: call ""string string.Concat(string, string)"" + IL_001f: call ""void System.Console.WriteLine(string)"" + IL_0024: ldarg.0 + IL_0025: ldc.i4.5 + IL_0026: stfld ""int C.

k__BackingField"" + IL_002b: ldstr ""In C..ctor after assignment: "" + IL_0030: ldarg.0 + IL_0031: call ""int C.P.get"" + IL_0036: stloc.0 + IL_0037: ldloca.s V_0 + IL_0039: call ""string int.ToString()"" + IL_003e: call ""string string.Concat(string, string)"" + IL_0043: call ""void System.Console.WriteLine(string)"" + IL_0048: ret +} +").VerifyDiagnostics( + // (12,69): 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. + // System.Console.WriteLine("In C..ctor before assignment: " + P); + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P").WithLocation(12, 69) + ); + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void TestPropertyIsAssignedInStructConstructor_PropertyHasSetter() + { + var comp = CreateCompilation(@" +public struct C +{ + public static void Main() + { + var x = new C(); + System.Console.Write(x.P + "" ""); // 5 + x.P = 10; + System.Console.Write(x.P + "" ""); // 10 + x = new C(); + System.Console.Write(x.P); // 5 + } + + public C() + { + P = 5; + } + + public int P { get => field; set => field = value; } +} +", options: TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings)); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: "5 10 5").VerifyIL("C..ctor", @" +{ + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ldarg.0 + IL_0008: ldc.i4.5 + IL_0009: call ""void C.P.set"" + IL_000e: ret +} +").VerifyDiagnostics( + // (16,9): 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. + // P = 5; + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P").WithLocation(16, 9) + ); + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void TestPropertyIsAssignedInStructConstructor_ReadOnlyProperty() + { + var comp = CreateCompilation(@" +public struct C +{ + public static void Main() + { + var x = new C(); + System.Console.Write(x.P); + } + + public C() + { + P = 5; + } + + public int P { get => field; } +} +", options: TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings)); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: "5").VerifyIL("C..ctor", @" + { + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.5 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ret + } +").VerifyDiagnostics(); + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + [Fact] public void TestFieldInLocalFunction() { @@ -578,7 +842,12 @@ public MyAttribute(int i) { } "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - CompileAndVerify(comp, expectedOutput: "10"); + CompileAndVerify(comp, expectedOutput: "10").VerifyDiagnostics( + // Looks like an incorrect diagnostic. Tracked by https://github.com/dotnet/roslyn/issues/60645 + // (10,23): warning CS0219: The variable 'field' is assigned but its value is never used + // const int field = 5; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "field").WithArguments("field").WithLocation(10, 23) + ); VerifyTypeIL(comp, "C", @" .class public auto ansi beforefieldinit C extends [mscorlib]System.Object @@ -731,7 +1000,7 @@ public int P "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - CompileAndVerify(comp, expectedOutput: "5"); + CompileAndVerify(comp, expectedOutput: "5").VerifyDiagnostics(); VerifyTypeIL(comp, "C", @" .class public auto ansi beforefieldinit C extends [mscorlib]System.Object @@ -819,7 +1088,7 @@ public int P "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - CompileAndVerify(comp, expectedOutput: "5"); + CompileAndVerify(comp, expectedOutput: "5").VerifyDiagnostics(); VerifyTypeIL(comp, "C", @" .class public auto ansi beforefieldinit C extends [mscorlib]System.Object @@ -1048,7 +1317,7 @@ public int P "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - CompileAndVerify(comp, expectedOutput: "5"); + CompileAndVerify(comp, expectedOutput: "5").VerifyDiagnostics(); VerifyTypeIL(comp, "C", @" .class public auto ansi beforefieldinit C extends [mscorlib]System.Object @@ -1200,7 +1469,7 @@ public int P "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - CompileAndVerify(comp, expectedOutput: "5"); + CompileAndVerify(comp, expectedOutput: "5").VerifyDiagnostics(); VerifyTypeIL(comp, "C", @" .class public auto ansi beforefieldinit C extends [mscorlib]System.Object @@ -1269,7 +1538,9 @@ public void TestNameOfField_FieldIsMember(string member) public class C {{ +#pragma warning disable CS0649 // Field 'C.field' is never assigned to, and will always have its default value 0 {member} +#pragma warning restore CS0649 public string P {{ @@ -1282,11 +1553,11 @@ public string P "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - CompileAndVerify(comp, expectedOutput: "field"); + CompileAndVerify(comp, expectedOutput: "field").VerifyDiagnostics(); Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } - [Fact(Skip = "PROTOTYPE(semi-auto-props): Assigning in constructor is not yet supported.")] + [Fact] public void TestFieldOnlyGetter() { var comp = CreateCompilation(@" @@ -1309,10 +1580,9 @@ public static void Main() comp.TestOnlyCompilationData = accessorBindingData; Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); - - CompileAndVerify(comp, expectedOutput: "5"); + CompileAndVerify(comp, expectedOutput: "5").VerifyDiagnostics(); VerifyTypeIL(comp, "C", @" - .class public auto ansi beforefieldinit C +.class public auto ansi beforefieldinit C extends [mscorlib]System.Object { // Fields @@ -1373,137 +1643,443 @@ .property instance int32 P() Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } - // PROTOTYPE(semi-auto-props): All success scenarios should be executed, expected runtime behavior should be observed. - // This is waiting until we support assigning to readonly properties in constructor. [Fact] - public void TestExpressionBodiedProperty() + public void TestAssigningFromConstructorNoAccessors() { var comp = CreateCompilation(@" public class C { - public int P => field; + public C() + { + P = 5; + } + + public int P { } } "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); - VerifyTypeIL(comp, "C", @" -.class public auto ansi beforefieldinit C - extends [mscorlib]System.Object -{ - // Fields - .field private initonly int32 '

k__BackingField' - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - // Methods - .method public hidebysig specialname - instance int32 get_P () cil managed - { - // Method begins at RVA 0x2050 - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: ldfld int32 C::'

k__BackingField' - IL_0006: ret - } // end of method C::get_P - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - // Method begins at RVA 0x2058 - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: call instance void [mscorlib]System.Object::.ctor() - IL_0006: ret - } // end of method C::.ctor - // Properties - .property instance int32 P() - { - .get instance int32 C::get_P() - } -} // end of class C -"); + comp.VerifyDiagnostics( + // (6,9): error CS0200: Property or indexer 'C.P' cannot be assigned to -- it is read only + // P = 5; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P").WithArguments("C.P").WithLocation(6, 9), + // (9,16): error CS0548: 'C.P': property or indexer must have at least one accessor + // public int P { } + Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P").WithArguments("C.P").WithLocation(9, 16) + ); Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } - [Fact(Skip = "PROTOTYPE(semi-auto-props): Mixing semicolon-only with field not yet working.")] - public void TestSimpleCase() + [Theory] + [InlineData("get;", false)] + [InlineData("get; set;", true)] + // [InlineData("set;")] PROTOTYPE(semi-auto-props): Not yet supported. + [InlineData("get => field;", false)] + // [InlineData("get => field; set;")] PROTOTYPE(semi-auto-props): Not yet supported + public void TestAssigningFromConstructorThroughBackingField(string accessors, bool callsSynthesizedSetter) { - var comp = CreateCompilation(@" + var comp = CreateCompilation($@" public class C -{ - public string P { get; set => field = value; } -} -"); +{{ + public C() + {{ + P = 5; + }} + + public int P {{ {accessors} }} + + public static void Main() + {{ + System.Console.WriteLine(new C().P); + }} +}} +", options: TestOptions.DebugExe); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - // PROTOTYPE(semi-auto-props): Should be empty or non-empty? Current behavior is unknown since mixed scenarios not yet supported. - Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); - VerifyTypeIL(comp, "C", @" -.class public auto ansi beforefieldinit C - extends [mscorlib]System.Object + string expectedCtorIL; + if (callsSynthesizedSetter) + { + + expectedCtorIL = @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: nop + IL_0007: nop + IL_0008: ldarg.0 + IL_0009: ldc.i4.5 + IL_000a: call ""void C.P.set"" + IL_000f: nop + IL_0010: ret +} +"; + } + else + { + expectedCtorIL = @" +{ + // Code size 16 (0x10) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: nop + IL_0007: nop + IL_0008: ldarg.0 + IL_0009: ldc.i4.5 + IL_000a: stfld ""int C.

k__BackingField"" + IL_000f: ret +} +"; + } + + CompileAndVerify(comp, expectedOutput: "5").VerifyDiagnostics().VerifyIL("C.P.get", @" { - // Fields - .field private string '

k__BackingField' - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - // Methods - .method public hidebysig specialname - instance string get_P () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - // Method begins at RVA 0x2050 - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: ldfld string C::'

k__BackingField' - IL_0006: ret - } // end of method C::get_P - .method public hidebysig specialname - instance void set_P ( - string 'value' - ) cil managed - { - // Method begins at RVA 0x2058 - // Code size 8 (0x8) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: stfld string C::'

k__BackingField' - IL_0007: ret - } // end of method C::set_P - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - // Method begins at RVA 0x2061 - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: call instance void [mscorlib]System.Object::.ctor() - IL_0006: ret - } // end of method C::.ctor - // Properties - .property instance string P() - { - .get instance string C::get_P() - .set instance void C::set_P(string) - } -} // end of class C -"); + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.

k__BackingField"" + IL_0006: ret +} +").VerifyIL("C..ctor", expectedCtorIL); Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } - [Fact(Skip = "PROTOTYPE(semi-auto-props): Produces error CS8050: Only auto-implemented properties can have initializers.")] - public void TestSemiAutoPropertyWithInitializer() + [Theory] + [InlineData("class")] + [InlineData("struct")] + public void TestAssigningFromConstructorThroughSetterWithFieldKeyword_NoGetter(string type) { - var comp = CreateCompilation(@" -public class C -{ - public string P { get => field; set => field = value; } = ""Hello""; + var comp = CreateCompilation($@" +public {type} C +{{ + public C() + {{ + P = 5; + }} + + public int P {{ set => field = value * 2; }} +}} +"); + string ctorExpectedIL; + if (type == "struct") + { + ctorExpectedIL = @" +{ + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ldarg.0 + IL_0008: ldc.i4.5 + IL_0009: call ""void C.P.set"" + IL_000e: ret +} +"; + } + else + { + ctorExpectedIL = @" +{ + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldc.i4.5 + IL_0008: call ""void C.P.set"" + IL_000d: ret +} +"; + } + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp).VerifyDiagnostics().VerifyIL("C.P.set", @" +{ + // Code size 10 (0xa) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldc.i4.2 + IL_0003: mul + IL_0004: stfld ""int C.

k__BackingField"" + IL_0009: ret +} +").VerifyIL("C..ctor", ctorExpectedIL); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory] + //[InlineData("get; set => field = value * 2;")] PROTOTYPE(semi-auto-props): Not yet supported. + [InlineData("get => field; set => field = value * 2;")] + public void TestAssigningFromConstructorThroughSetterWithFieldKeyword_HasGetter(string accessors) + { + var comp = CreateCompilation($@" +public class C +{{ + public C() + {{ + P = 5; + }} + + public int P {{ {accessors} }} + + public static void Main() + {{ + System.Console.WriteLine(new C().P); + }} +}} +", options: TestOptions.DebugExe); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: "10").VerifyDiagnostics().VerifyIL("C.P.get", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.

k__BackingField"" + IL_0006: ret +} +").VerifyIL("C..ctor", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: nop + IL_0007: nop + IL_0008: ldarg.0 + IL_0009: ldc.i4.5 + IL_000a: call ""void C.P.set"" + IL_000f: nop + IL_0010: ret +} +"); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory(Skip = "PROTOTYPE(semi-auto-props): Not supported yet.")] + [InlineData("get; set => _ = value;")] + [InlineData("set => _ = value;")] + [InlineData("get => field; set => _ = value;")] + public void TestAssigningFromConstructorThroughSetter_RegularSetter(string accessors) + { + var comp = CreateCompilation($@" +public class C +{{ + public C() + {{ + P = 5; + }} + + public int P {{ {accessors} }} + + public static void Main() + {{ + System.Console.WriteLine(new C().P); + }} +}} +", options: TestOptions.DebugExe); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: "0").VerifyDiagnostics().VerifyIL("C.P.get", @" +").VerifyIL("C..ctor", @" +"); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory] + // [InlineData("get => 0; set;")] PROTOTYPE(semi-auto-props): Not yet supported. + [InlineData("get => 0; set => field = value;")] + public void TestAssigningFromConstructorThroughSetter_RegularGetter_CanAssignInCtor(string accessors) + { + var comp = CreateCompilation($@" +public class C +{{ + public C() + {{ + P = 5; + }} + + public int P {{ {accessors} }} + + public static void Main() + {{ + System.Console.WriteLine(new C().P); + }} +}} +", options: TestOptions.DebugExe); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + CompileAndVerify(comp, expectedOutput: "0").VerifyDiagnostics().VerifyIL("C.P.get", @" +{ + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldc.i4.0 + IL_0001: ret +} +").VerifyIL("C.P.set", @" +{ + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int C.

k__BackingField"" + IL_0007: ret +} +").VerifyIL("C..ctor", @" +{ + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: nop + IL_0007: nop + IL_0008: ldarg.0 + IL_0009: ldc.i4.5 + IL_000a: call ""void C.P.set"" + IL_000f: nop + IL_0010: ret +} +"); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + // PROTOTYPE(semi-auto-props): All success scenarios should be executed, expected runtime behavior should be observed. + // This is waiting until we support assigning to readonly properties in constructor. + [Fact] + public void TestExpressionBodiedProperty() + { + var comp = CreateCompilation(@" +public class C +{ + public int P => field; +} +"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + VerifyTypeIL(comp, "C", @" +.class public auto ansi beforefieldinit C + extends [mscorlib]System.Object +{ + // Fields + .field private initonly int32 '

k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Methods + .method public hidebysig specialname + instance int32 get_P () cil managed + { + // Method begins at RVA 0x2050 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 C::'

k__BackingField' + IL_0006: ret + } // end of method C::get_P + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2058 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method C::.ctor + // Properties + .property instance int32 P() + { + .get instance int32 C::get_P() + } +} // end of class C +"); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact(Skip = "PROTOTYPE(semi-auto-props): Mixing semicolon-only with field not yet working.")] + public void TestSimpleCase() + { + var comp = CreateCompilation(@" +public class C +{ + public string P { get; set => field = value; } +} +"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + // PROTOTYPE(semi-auto-props): Should be empty or non-empty? Current behavior is unknown since mixed scenarios not yet supported. + Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + VerifyTypeIL(comp, "C", @" +.class public auto ansi beforefieldinit C + extends [mscorlib]System.Object +{ + // Fields + .field private string '

k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Methods + .method public hidebysig specialname + instance string get_P () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2050 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld string C::'

k__BackingField' + IL_0006: ret + } // end of method C::get_P + .method public hidebysig specialname + instance void set_P ( + string 'value' + ) cil managed + { + // Method begins at RVA 0x2058 + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld string C::'

k__BackingField' + IL_0007: ret + } // end of method C::set_P + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2061 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method C::.ctor + // Properties + .property instance string P() + { + .get instance string C::get_P() + .set instance void C::set_P(string) + } +} // end of class C +"); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact(Skip = "PROTOTYPE(semi-auto-props): Produces error CS8050: Only auto-implemented properties can have initializers.")] + public void TestSemiAutoPropertyWithInitializer() + { + var comp = CreateCompilation(@" +public class C +{ + public string P { get => field; set => field = value; } = ""Hello""; } "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); @@ -1812,7 +2388,7 @@ .property instance string P() Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } - [Fact(Skip = "PROTOTYPE(semi-auto-props): Currently has extra diagnostics")] + [Fact] public void Test_ERR_AutoSetterCantBeReadOnly() { var comp = CreateCompilation(@" @@ -1826,7 +2402,7 @@ public struct S "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; - Assert.Empty(comp.GetTypeByMetadataName("C").GetMembers().OfType()); + Assert.Equal("System.Int32 S.k__BackingField", comp.GetTypeByMetadataName("S").GetMembers().OfType().Single().ToTestDisplayString()); comp.VerifyDiagnostics( // (4,35): error CS8658: Auto-implemented 'set' accessor 'S.P1.set' cannot be marked 'readonly'. // public int P1 { get; readonly set; } // ERR_AutoSetterCantBeReadOnly @@ -1836,9 +2412,13 @@ public struct S Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(6, 51), // (7,42): error CS1604: Cannot assign to 'field' because it is read-only // public int P4 { get; readonly set => field = value; } // No ERR_AutoSetterCantBeReadOnly, but ERR_AssgReadonlyLocal - Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(7, 42) + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(7, 42), + // PROTOTYPE(semi-auto-props): The following diagnostic shouldn't exist. It should go away when mixed scenarios are supported. + // (7,21): error CS0501: 'S.P4.get' must declare a body because it is not marked abstract, extern, or partial + // public int P4 { get; readonly set => field = value; } // No ERR_AutoSetterCantBeReadOnly, but ERR_AssgReadonlyLocal + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("S.P4.get").WithLocation(7, 21) ); - Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + Assert.Equal(1, accessorBindingData.NumberOfPerformedAccessorBinding); } [Fact] @@ -2264,7 +2844,7 @@ public class C } [Fact] - public void AssignReadOnlyOnlyPropertyOutsideConstructor() + public void AssignReadOnlyOnlyPropertyOutsideConstructor_FieldAssignedFirst() { var comp = CreateCompilation(@" class Test @@ -2296,36 +2876,95 @@ int X Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } - [Fact(Skip = "PROTOTYPE(semi-auto-props): Assigning in constructor is not yet supported.")] - public void AssignReadOnlyOnlyPropertyInConstructor() + [Fact] + public void AssignReadOnlyOnlyPropertyOutsideConstructor_FieldAssignedAfterProperty() { var comp = CreateCompilation(@" -using System; - -_ = new Test(); - class Test { - public Test() - { - X = 3; - Console.WriteLine(X); - } - int X { - get { return field; } + get + { + X = 3; + field = 3; + return 0; + } } } "); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; Assert.Empty(comp.GetTypeByMetadataName("Test").GetMembers().OfType()); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "3"); - VerifyTypeIL(comp, "Test", @" -.class private auto ansi beforefieldinit Test - extends [mscorlib]System.Object + comp.VerifyDiagnostics( + // (8,13): error CS0200: Property or indexer 'Test.X' cannot be assigned to -- it is read only + // X = 3; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "X").WithArguments("Test.X").WithLocation(8, 13), + // PROTOTYPE(semi-auto-props): + // Should the generated field not be readonly? + // (9,13): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // field = 3; + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(9, 13) + ); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void AssignReadOnlyOnlyPropertyOutsideConstructor_FieldNotAssigned() + { + var comp = CreateCompilation(@" +class Test +{ + int X + { + get + { + X = 3; + return 0; + } + } +} +"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + Assert.Empty(comp.GetTypeByMetadataName("Test").GetMembers().OfType()); + comp.VerifyDiagnostics( + // (8,13): error CS0200: Property or indexer 'Test.X' cannot be assigned to -- it is read only + // X = 3; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "X").WithArguments("Test.X").WithLocation(8, 13) + ); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void AssignReadOnlyOnlyPropertyInConstructor() + { + var comp = CreateCompilation(@" +using System; + +_ = new Test(); + +class Test +{ + public Test() + { + X = 3; + Console.WriteLine(X); + } + + int X + { + get { return field; } + } +} +"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + Assert.Empty(comp.GetTypeByMetadataName("Test").GetMembers().OfType()); + CompileAndVerify(comp, expectedOutput: "3").VerifyDiagnostics(); + VerifyTypeIL(comp, "Test", @" +.class private auto ansi beforefieldinit Test + extends [mscorlib]System.Object { // Fields .field private initonly int32 'k__BackingField' @@ -2918,9 +3557,39 @@ public class C1 } [Fact] - public void InStruct() + public void FieldLocal_PropertyAssignedInConstructor() { var comp = CreateCompilation(@" +public class C +{ + public C() + { + P = 10; + } + + public int P + { + get + { + int field = 0; + return field; + } + } +}"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + comp.VerifyDiagnostics( + // (6,9): error CS0200: Property or indexer 'C.P' cannot be assigned to -- it is read only + // P = 10; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P").WithArguments("C.P").WithLocation(6, 9) + ); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory, CombinatorialData] + public void InStruct(bool structTreeFirst) + { + var source1 = @" struct S_WithAutoProperty { public int P { get; set; } @@ -2935,7 +3604,9 @@ struct S_WithSemiAutoProperty { public int P { get => field; set => field = value; } } +"; + var source2 = @" class C { void M1() @@ -2956,20 +3627,182 @@ void M3() S_WithSemiAutoProperty s2 = s1; } } -"); +"; + var comp = CreateCompilation(new[] { source1, source2 }); var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); comp.TestOnlyCompilationData = accessorBindingData; Assert.Equal("System.Int32 S_WithAutoProperty.

k__BackingField", comp.GetTypeByMetadataName("S_WithAutoProperty").GetMembers().OfType().Single().ToTestDisplayString()); Assert.Empty(comp.GetTypeByMetadataName("S_WithManualProperty").GetMembers().OfType()); Assert.Empty(comp.GetTypeByMetadataName("S_WithSemiAutoProperty").GetMembers().OfType()); + + if (structTreeFirst) + { + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + } + + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true).Verify( + // (7,33): error CS0165: Use of unassigned local variable 's1' + // S_WithAutoProperty s2 = s1; + Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(7, 33), + // (19,37): error CS0165: Use of unassigned local variable 's1' + // S_WithSemiAutoProperty s2 = s1; + Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(19, 37) + ); + + if (!structTreeFirst) + { + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + } + comp.VerifyDiagnostics( - // PROTOTYPE(semi-auto-props): Do we expect a similar error for semi auto props? - // (22,33): error CS0165: Use of unassigned local variable 's1' + // (7,33): error CS0165: Use of unassigned local variable 's1' // S_WithAutoProperty s2 = s1; - Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(22, 33) + Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(7, 33), + // (19,37): error CS0165: Use of unassigned local variable 's1' + // S_WithSemiAutoProperty s2 = s1; + Diagnostic(ErrorCode.ERR_UseDefViolation, "s1").WithArguments("s1").WithLocation(19, 37) ); - Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + Assert.Equal(structTreeFirst ? 0 : 1, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory, CombinatorialData] + public void InStruct_AssignStructToDefault(bool structTreeFirst) + { + var source1 = @" +struct S_WithAutoProperty +{ + public int P { get; set; } +} + +struct S_WithManualProperty +{ + public int P { get => 0; set => _ = value; } +} + +struct S_WithSemiAutoProperty +{ + public int P { get => field; set => field = value; } +} + +"; + + var source2 = @" +class C +{ + void M1() + { + S_WithAutoProperty s1 = default; + S_WithAutoProperty s2 = s1; + } + + void M2() + { + S_WithManualProperty s1 = default; + S_WithManualProperty s2 = s1; + } + + void M3() + { + S_WithSemiAutoProperty s1 = default; + S_WithSemiAutoProperty s2 = s1; + } +} +"; + var comp = CreateCompilation(new[] { source1, source2 }); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + Assert.Equal("System.Int32 S_WithAutoProperty.

k__BackingField", comp.GetTypeByMetadataName("S_WithAutoProperty").GetMembers().OfType().Single().ToTestDisplayString()); + Assert.Empty(comp.GetTypeByMetadataName("S_WithManualProperty").GetMembers().OfType()); + Assert.Empty(comp.GetTypeByMetadataName("S_WithSemiAutoProperty").GetMembers().OfType()); + + if (structTreeFirst) + { + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + } + + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + + if (!structTreeFirst) + { + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + } + + comp.VerifyDiagnostics(); + + Assert.Equal(structTreeFirst ? 0 : 1, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory, CombinatorialData] + public void InStruct_AssignStructViaConstructor(bool structTreeFirst) + { + var source1 = @" +struct S_WithAutoProperty +{ + public int P { get; set; } + + public S_WithAutoProperty() { } +} + +struct S_WithManualProperty +{ + public int P { get => 0; set => _ = value; } + + public S_WithManualProperty() { } +} + +struct S_WithSemiAutoProperty +{ + public int P { get => field; set => field = value; } + + public S_WithSemiAutoProperty() { } +} +"; + + var source2 = @" +class C +{ + void M1() + { + S_WithAutoProperty s1 = new S_WithAutoProperty(); + S_WithAutoProperty s2 = s1; + } + + void M2() + { + S_WithManualProperty s1 = new S_WithManualProperty(); + S_WithManualProperty s2 = s1; + } + + void M3() + { + S_WithSemiAutoProperty s1 = new S_WithSemiAutoProperty(); + S_WithSemiAutoProperty s2 = s1; + } +} +"; + var comp = CreateCompilation(new[] { source1, source2 }); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + Assert.Equal("System.Int32 S_WithAutoProperty.

k__BackingField", comp.GetTypeByMetadataName("S_WithAutoProperty").GetMembers().OfType().Single().ToTestDisplayString()); + Assert.Empty(comp.GetTypeByMetadataName("S_WithManualProperty").GetMembers().OfType()); + Assert.Empty(comp.GetTypeByMetadataName("S_WithSemiAutoProperty").GetMembers().OfType()); + + if (structTreeFirst) + { + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + } + + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + + if (!structTreeFirst) + { + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + } + + comp.VerifyDiagnostics(); + + Assert.Equal(structTreeFirst ? 0 : 1, accessorBindingData.NumberOfPerformedAccessorBinding); } [Fact] @@ -3016,6 +3849,180 @@ public S(int arg) Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); } + [Theory, CombinatorialData] + public void InStruct_Cycle(bool bindS1First) + { + var source1 = @" +struct S1 +{ + public S1() { } + public S2 P { get => field; } +} +"; + var source2 = @" +struct S2 +{ + public S2() { } + public S1 P { get; } +} +"; + var comp = CreateCompilation(new[] { source1, source2 }); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, bindS1First ? comp.SyntaxTrees[0] : comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true).Verify( + // (5,15): error CS0523: Struct member 'S1.P' of type 'S2' causes a cycle in the struct layout + // public S2 P { get => field; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments(bindS1First ? "S1.P" : "S2.P", bindS1First ? "S2" : "S1").WithLocation(5, 15)); + + Assert.Equal(bindS1First ? 0 : 1, accessorBindingData.NumberOfPerformedAccessorBinding); + + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, bindS1First ? comp.SyntaxTrees[1] : comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify( + // (5,15): error CS0523: Struct member 'S2.P' of type 'S1' causes a cycle in the struct layout + // public S1 P { get => field; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments(bindS1First ? "S2.P" : "S1.P", bindS1First ? "S1" : "S2").WithLocation(5, 15)); + + comp.VerifyDiagnostics( + // (5,15): error CS0523: Struct member 'S1.P' of type 'S2' causes a cycle in the struct layout + // public S2 P { get; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S1.P", "S2").WithLocation(5, 15), + // (5,15): error CS0523: Struct member 'S2.P' of type 'S1' causes a cycle in the struct layout + // public S1 P { get; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S2.P", "S1").WithLocation(5, 15) + ); + Assert.Equal(bindS1First ? 0 : 1, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void InStruct_Cycle2() + { + var source1 = @" +struct S1 +{ + public S1() { } + public S2 P { get => field; } +} +"; + var source2 = @" +struct S2 +{ + public S2() { } + public S1 P { get => field; } +} +"; + var comp = CreateCompilation(new[] { source1, source2 }); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify( + // (5,15): error CS0523: Struct member 'S1.P' of type 'S2' causes a cycle in the struct layout + // public S2 P { get; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S1.P", "S2").WithLocation(5, 15) + ); + + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true).Verify( + // (5,15): error CS0523: Struct member 'S2.P' of type 'S1' causes a cycle in the struct layout + // public S1 P { get; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S2.P", "S1").WithLocation(5, 15) + ); + + comp.VerifyDiagnostics( + // (5,15): error CS0523: Struct member 'S1.P' of type 'S2' causes a cycle in the struct layout + // public S2 P { get; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S1.P", "S2").WithLocation(5, 15), + // (5,15): error CS0523: Struct member 'S2.P' of type 'S1' causes a cycle in the struct layout + // public S1 P { get; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S2.P", "S1").WithLocation(5, 15) + ); + Assert.Equal(1, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void InStruct_NoCycle() + { + var source1 = @" +struct S1 +{ + public S1() { } + + public int P + { + get + { + _ = field; + var x = new S2(); + return field; + } + } +}"; + + var source2 = @" +struct S2 +{ + public S2() { } + + public int P + { + get + { + _ = field; + var x = new S1(); + return field; + } + } +}"; + var comp = CreateCompilation(new[] { source1, source2 }); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[0], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + Assert.Equal(1, accessorBindingData.NumberOfPerformedAccessorBinding); + comp.GetDiagnosticsForSyntaxTree(CompilationStage.Compile, comp.SyntaxTrees[1], filterSpanWithinTree: null, includeEarlierStages: true).Verify(); + Assert.Equal(1, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Theory, CombinatorialData] + public void InStruct_NoCycleBecauseStatic(bool firstIsSemi, bool secondIsSemi) + { + var firstAccessor = firstIsSemi ? "get => field;" : "get;"; + var secondAccessor = secondIsSemi ? "get => field;" : "get;"; + var comp = CreateCompilation($@" +struct S1 +{{ + public S1() {{ }} + public static S2 P {{ {firstAccessor} }} +}} + +struct S2 +{{ + public S2() {{ }} + public static S1 P {{ {secondAccessor} }} +}} +"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + comp.VerifyDiagnostics(); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + + [Fact] + public void InStruct_SelfCycle() + { + var comp = CreateCompilation(@" +struct S +{ + public S() { } + public S P { get => field; } +} +"); + var accessorBindingData = new SourcePropertySymbolBase.AccessorBindingData(); + comp.TestOnlyCompilationData = accessorBindingData; + comp.VerifyDiagnostics( + // (5,14): error CS0523: Struct member 'S.P' of type 'S' causes a cycle in the struct layout + // public S P { get => field; } + Diagnostic(ErrorCode.ERR_StructLayoutCycle, "P").WithArguments("S.P", "S").WithLocation(5, 14) + ); + Assert.Equal(0, accessorBindingData.NumberOfPerformedAccessorBinding); + } + [Theory] [InlineData(SpeculativeBindingOption.BindAsExpression, "never")] [InlineData(SpeculativeBindingOption.BindAsExpression, "always")]