未验证 提交 a646747f 编写于 作者: A AlekseyTs 提交者: GitHub

Put record parameters in scope withing instance initializers (#44906)

* Put record parameters in scope withing instance initializers

https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-01.md
Also fixes #44879.

* Follow-up on merge

* Share closure across initializers and the base call.
上级 4be66fea
......@@ -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<SynthesizedRecordConstructor>().SingleOrDefault() is SynthesizedRecordConstructor recordCtor)
{
binder = new InMethodBinder(recordCtor, binder);
}
return new LocalScopeBinder(binder).WithAdditionalFlagsAndContainingMemberOrLambda(suppressBinderFlagsFieldInitializer ? BinderFlags.None : BinderFlags.FieldInitializer, fieldSymbol);
}
/// <summary>
/// In script C#, some field initializers are assignments to fields and others are global
/// statements. There are no restrictions on accessing instance members.
......
......@@ -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)
{
......
......@@ -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)
{
......
......@@ -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)
{
......
......@@ -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);
}
/// <summary>
/// Given a program, calls LookupNames at each character position and verifies the results.
///
......
......@@ -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.<P2>k__BackingField""
IL_000d: stfld ""object C.<P2>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.<X>k__BackingField""
IL_0007: ldarg.0
IL_0008: ldarg.2
IL_0009: stfld ""int C.<Y>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<IdentifierNameSyntax>().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<int> 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<IdentifierNameSyntax>().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<IdentifierNameSyntax>().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<IdentifierNameSyntax>().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<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<IdentifierNameSyntax>().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<int> X)
{
Console.WriteLine(X());
}
public Base() {}
}
record C(int X) : Base(() => 100 + X++)
{
Func<int> Y = () => 200 + X++;
Func<int> 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
");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册