diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs index 6e12c568cdf27184d122312ad6220d8ac991d18e..d83d2b1618af18a03498aec83f2409df276bef33 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs @@ -106,15 +106,13 @@ internal struct ProcessedFieldInitializers } Binder parentBinder = binderFactory.GetBinder(initializerNode); - Debug.Assert((parentBinder.ContainingMemberOrLambda is TypeSymbol containing && TypeSymbol.Equals(containing, fieldSymbol.ContainingType, TypeCompareKind.ConsiderEverything2)) || //should be the binder for the type - fieldSymbol.ContainingType.IsImplicitClass); //however, we also allow fields in namespaces to help support script scenarios if (firstDebugImports == null) { firstDebugImports = parentBinder.ImportChain; } - parentBinder = new LocalScopeBinder(parentBinder).WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.FieldInitializer, fieldSymbol); + parentBinder = parentBinder.GetFieldInitializerBinder(fieldSymbol); BoundFieldEqualsValue boundInitializer = BindFieldInitializer(parentBinder, fieldSymbol, initializerNode, diagnostics); boundInitializers.Add(boundInitializer); @@ -145,6 +143,21 @@ internal struct ProcessedFieldInitializers } } + internal Binder GetFieldInitializerBinder(FieldSymbol fieldSymbol, bool suppressBinderFlagsFieldInitializer = false) + { + Debug.Assert((ContainingMemberOrLambda is TypeSymbol containing && TypeSymbol.Equals(containing, fieldSymbol.ContainingType, TypeCompareKind.ConsiderEverything2)) || //should be the binder for the type + fieldSymbol.ContainingType.IsImplicitClass); //however, we also allow fields in namespaces to help support script scenarios + + Binder binder = this; + + if (!fieldSymbol.IsStatic && fieldSymbol.ContainingType.GetMembersUnordered().OfType().SingleOrDefault() is SynthesizedRecordConstructor recordCtor) + { + binder = new InMethodBinder(recordCtor, binder); + } + + return new LocalScopeBinder(binder).WithAdditionalFlagsAndContainingMemberOrLambda(suppressBinderFlagsFieldInitializer ? BinderFlags.None : BinderFlags.FieldInitializer, fieldSymbol); + } + /// /// In script C#, some field initializers are assignments to fields and others are global /// statements. There are no restrictions on accessing instance members. diff --git a/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs b/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs index cdf44ced9794ebeb08224676d584b65400e34bf2..48bd938480ade78e1ca51a34a7a92c378ab2b2f8 100644 --- a/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs @@ -395,7 +395,7 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no public override void VisitRecordDeclaration(RecordDeclarationSyntax node) { - Debug.Assert(((RecordDeclarationSyntax)node).ParameterList is object); + Debug.Assert(node.ParameterList is object); if (node.BaseWithArguments is SimpleBaseTypeSyntax baseWithArguments) { diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index a4be9216679707f3d5d2b99e04c07eb70d1dcdc5..e31435850de7ab0609d427cec059861db53e4c6f 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -1261,15 +1261,8 @@ private FieldSymbol GetDeclaredFieldSymbol(VariableDeclaratorSyntax variableDecl private Binder GetFieldOrPropertyInitializerBinder(FieldSymbol symbol, Binder outer, EqualsValueClauseSyntax initializer) { - BinderFlags flags = BinderFlags.None; - // NOTE: checking for a containing script class is sufficient, but the regular C# test is quick and easy. - if (this.IsRegularCSharp || !symbol.ContainingType.IsScriptClass) - { - flags |= BinderFlags.FieldInitializer; - } - - outer = new LocalScopeBinder(outer).WithAdditionalFlagsAndContainingMemberOrLambda(flags, symbol); + outer = outer.GetFieldInitializerBinder(symbol, suppressBinderFlagsFieldInitializer: !this.IsRegularCSharp && symbol.ContainingType.IsScriptClass); if (initializer != null) { diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 4ba6cd58d9a92bf1160109b4e49e99b1917b1dbd..306b8eade9aaef0d88f0811258973cdafb05e932 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -999,7 +999,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum analyzedInitializers = InitializerRewriter.RewriteConstructor(processedInitializers.BoundInitializers, methodSymbol); processedInitializers.HasErrors = processedInitializers.HasErrors || analyzedInitializers.HasAnyErrors; - if (body != null && ((methodSymbol.ContainingType.IsStructType() && !methodSymbol.IsImplicitConstructor) || _emitTestCoverageData)) + if (body != null && ((methodSymbol.ContainingType.IsStructType() && !methodSymbol.IsImplicitConstructor) || methodSymbol is SynthesizedRecordConstructor || _emitTestCoverageData)) { if (_emitTestCoverageData && methodSymbol.IsImplicitConstructor) { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs index 79d7004134b11ae7e6824fd83f1704afe0010c16..b3c627cf22cc42c4ba137961369d0d21dbcc4fff 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LookupPositionTests.cs @@ -2043,6 +2043,116 @@ interface C : Base(X) TestLookupNames(text, expectedNames); } + [Fact] + public void RecordInitializers_01() + { + var text = @" +record C(int X) +`{ + int Z `= X + 1`; +`} +"; + var expectedNames = MakeExpectedSymbols( + Add( // Global + "System", + "Microsoft", + "C"), + Add( // Members + "System.Boolean C.Equals(C? )", + "System.Boolean C.Equals(System.Object? )", + "System.Boolean System.Object.Equals(System.Object obj)", + "System.Boolean System.Object.Equals(System.Object objA, System.Object objB)", + "System.Boolean System.Object.ReferenceEquals(System.Object objA, System.Object objB)", + "System.Int32 C.GetHashCode()", + "System.Int32 C.X { get; init; }", + "System.Int32 C.Z", + "System.Int32 System.Object.GetHashCode()", + "System.Object System.Object.MemberwiseClone()", + "System.String System.Object.ToString()", + "System.Type C.EqualityContract { get; }", + "System.Type System.Object.GetType()", + "void System.Object.Finalize()"), + Combine( + Remove("System.Int32 C.X { get; init; }"), + Add("System.Int32 X") + ), + Combine(s_pop, s_pop), + s_pop + ); + + TestLookupNames(text, expectedNames); + } + + [Fact] + public void RecordInitializers_02() + { + var text = @" +record C(int X) +`{ + static int Z = X + 1; +`} +"; + var expectedNames = MakeExpectedSymbols( + Add( // Global + "System", + "Microsoft", + "C"), + Add( // Members + "System.Boolean C.Equals(C? )", + "System.Boolean C.Equals(System.Object? )", + "System.Boolean System.Object.Equals(System.Object obj)", + "System.Boolean System.Object.Equals(System.Object objA, System.Object objB)", + "System.Boolean System.Object.ReferenceEquals(System.Object objA, System.Object objB)", + "System.Int32 C.GetHashCode()", + "System.Int32 C.X { get; init; }", + "System.Int32 C.Z", + "System.Int32 System.Object.GetHashCode()", + "System.Object System.Object.MemberwiseClone()", + "System.String System.Object.ToString()", + "System.Type C.EqualityContract { get; }", + "System.Type System.Object.GetType()", + "void System.Object.Finalize()"), + s_pop + ); + + TestLookupNames(text, expectedNames); + } + + [Fact] + public void RecordInitializers_03() + { + var text = @" +record C(int X) +`{ + const int Z = X + 1; +`} +"; + var expectedNames = MakeExpectedSymbols( + Add( // Global + "System", + "Microsoft", + "C"), + Add( // Members + "System.Boolean C.Equals(C? )", + "System.Boolean C.Equals(System.Object? )", + "System.Boolean System.Object.Equals(System.Object obj)", + "System.Boolean System.Object.Equals(System.Object objA, System.Object objB)", + "System.Boolean System.Object.ReferenceEquals(System.Object objA, System.Object objB)", + "System.Int32 C.GetHashCode()", + "System.Int32 C.X { get; init; }", + "System.Int32 C.Z", + "System.Int32 System.Object.GetHashCode()", + "System.Object System.Object.MemberwiseClone()", + "System.String System.Object.ToString()", + "System.Type C.EqualityContract { get; }", + "System.Type System.Object.GetType()", + "void System.Object.Finalize()"), + s_pop + ); + + TestLookupNames(text, expectedNames); + } + /// /// Given a program, calls LookupNames at each character position and verifies the results. /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 7f266a637636ee815c459a4cbeae1b30bdfe5c44..9b28f05a20096553615b627d9fffacfab83dc507 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -343,15 +343,7 @@ record C1(object O1) public object O1 { get; } = O1; public object O2 { get; } = O1; }"); - // PROTOTYPE: primary ctor parameters not currently in scope - comp.VerifyDiagnostics( - // (4,33): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C1.O1' - // public object O1 { get; } = O1; - Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "O1").WithArguments("C1.O1").WithLocation(4, 33), - // (5,33): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C1.O1' - // public object O2 { get; } = O1; - Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "O1").WithArguments("C1.O1").WithLocation(5, 33) - ); + comp.VerifyDiagnostics(); } [Fact] @@ -393,7 +385,8 @@ public void RecordProperties_10() comp.VerifyDiagnostics( // (1,17): error CS0102: The type 'C' already contains a definition for 'P' // record C(object P) - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(1, 17)); + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(1, 17) + ); } [Fact] @@ -3465,6 +3458,22 @@ public void Inheritance_18() "ref System.Object C.P5 { get; }", }; AssertEx.Equal(expectedMembers, actualMembers); + + var verifier = CompileAndVerify(source); + + verifier.VerifyIL("C..ctor(C)", @" +{ + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call ""object..ctor()"" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: ldfld ""object C.k__BackingField"" + IL_000d: stfld ""object C.k__BackingField"" + IL_0012: ret +} +"); } [Fact] @@ -5007,6 +5016,110 @@ interface C : Base(X) Assert.DoesNotContain("X", model.LookupNames(x.SpanStart)); } + [Fact] + public void BaseArguments_15() + { + var src = @" +using System; + +record Base +{ + public Base(int X, int Y) + { + Console.WriteLine(X); + Console.WriteLine(Y); + } + + public Base() {} +} + +partial record C +{ +} + +partial record C(int X, int Y) : Base(X, Y) +{ + int Z = 123; + public static void Main() + { + var c = new C(1, 2); + Console.WriteLine(c.Z); + } +} + +partial record C +{ +} +"; + var verifier = CompileAndVerify(src, expectedOutput: @" +1 +2 +123"); + verifier.VerifyIL("C..ctor(int, int)", @" + +{ + // Code size 31 (0x1f) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld ""int C.k__BackingField"" + IL_0007: ldarg.0 + IL_0008: ldarg.2 + IL_0009: stfld ""int C.k__BackingField"" + IL_000e: ldarg.0 + IL_000f: ldc.i4.s 123 + IL_0011: stfld ""int C.Z"" + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: ldarg.2 + IL_0019: call ""Base..ctor(int, int)"" + IL_001e: ret +} +"); + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").ElementAt(1); + Assert.Equal("Base(X, Y)", x.Parent!.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Parameter, symbol!.Kind); + Assert.Equal("System.Int32 X", symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X, System.Int32 Y)", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Same(symbol.ContainingSymbol, model.GetEnclosingSymbol(x.SpanStart)); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void BaseArguments_16() + { + var src = @" +using System; + +record Base +{ + public Base(Func X) + { + Console.WriteLine(X()); + } + + public Base() {} +} + +record C(int X) : Base(() => X) +{ + public static void Main() + { + var c = new C(1); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"1"); + } + [Fact(Skip = "record struct")] public void Equality_01() { @@ -6176,5 +6289,172 @@ .maxstack 2 IL_0007: ret }"); } + + [Fact] + public void Initializers_01() + { + var src = @" +using System; + +record C(int X) +{ + int Z = X + 1; + + public static void Main() + { + var c = new C(1); + Console.WriteLine(c.Z); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"2"); + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("= X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Parameter, symbol!.Kind); + Assert.Equal("System.Int32 X", symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X)", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void Initializers_02() + { + var src = @" +record C(int X) +{ + static int Z = X + 1; +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,20): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C.X' + // static int Z = X + 1; + Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "X").WithArguments("C.X").WithLocation(4, 20) + ); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("= X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Property, symbol!.Kind); + Assert.Equal("System.Int32 C.X { get; init; }", symbol.ToTestDisplayString()); + Assert.Equal("C", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void Initializers_03() + { + var src = @" +record C(int X) +{ + const int Z = X + 1; +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (4,19): error CS0236: A field initializer cannot reference the non-static field, method, or property 'C.X' + // const int Z = X + 1; + Diagnostic(ErrorCode.ERR_FieldInitRefNonstatic, "X").WithArguments("C.X").WithLocation(4, 19) + ); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("= X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Property, symbol!.Kind); + Assert.Equal("System.Int32 C.X { get; init; }", symbol.ToTestDisplayString()); + Assert.Equal("C", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void Initializers_04() + { + var src = @" +using System; + +record C(int X) +{ + Func Z = () => X + 1; + + public static void Main() + { + var c = new C(1); + Console.WriteLine(c.Z()); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"2"); + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var x = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "X").First(); + Assert.Equal("() => X + 1", x.Parent!.Parent!.ToString()); + + var symbol = model.GetSymbolInfo(x).Symbol; + Assert.Equal(SymbolKind.Parameter, symbol!.Kind); + Assert.Equal("System.Int32 X", symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X)", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("lambda expression", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); + Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); + Assert.Contains("X", model.LookupNames(x.SpanStart)); + } + + [Fact] + public void Initializers_05() + { + var src = @" +using System; + +record Base +{ + public Base(Func X) + { + Console.WriteLine(X()); + } + + public Base() {} +} + +record C(int X) : Base(() => 100 + X++) +{ + Func Y = () => 200 + X++; + Func Z = () => 300 + X++; + + public static void Main() + { + var c = new C(1); + Console.WriteLine(c.Y()); + Console.WriteLine(c.Z()); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @" +101 +202 +303 +"); + } } }