未验证 提交 68279b00 编写于 作者: J Julien Couvreur 提交者: GitHub

Disallow record with ambiguous constructors (#49975)

上级 a06b2245
......@@ -4121,6 +4121,7 @@ internal static bool IsUserDefinedRecordCopyConstructor(MethodSymbol constructor
{
return constructor.ContainingType is SourceNamedTypeSymbol sourceType &&
sourceType.IsRecord &&
constructor is not SynthesizedRecordConstructor &&
SynthesizedRecordCopyCtor.HasCopyConstructorSignature(constructor);
}
......
......@@ -6591,6 +6591,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_UnreadRecordParameter_Title" xml:space="preserve">
<value>Parameter is unread. Did you forget to use it to initialize the property with that name?</value>
</data>
<data name="ERR_RecordAmbigCtor" xml:space="preserve">
<value>The primary constructor conflicts with the synthesized copy constructor.</value>
</data>
<data name="WRN_DoNotCompareFunctionPointers" xml:space="preserve">
<value>Comparison of function pointers might yield an unexpected result, since pointers to the same function may be distinct.</value>
</data>
......
......@@ -1927,6 +1927,7 @@ internal enum ErrorCode
ERR_BadFieldTypeInRecord = 8908,
WRN_DoNotCompareFunctionPointers = 8909,
ERR_RecordAmbigCtor = 8910,
#endregion diagnostics introduced for C# 9.0
......
......@@ -3051,6 +3051,7 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde
CSharpCompilation compilation = this.DeclaringCompilation;
// Positional record
bool primaryAndCopyCtorAmbiguity = false;
if (!(paramList is null))
{
Debug.Assert(builder.RecordDeclarationWithParameters is object);
......@@ -3063,9 +3064,11 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde
var existingOrAddedMembers = addProperties(ctor.Parameters);
addDeconstruct(ctor, existingOrAddedMembers);
}
primaryAndCopyCtorAmbiguity = ctor.ParameterCount == 1 && ctor.Parameters[0].Type.Equals(this, TypeCompareKind.AllIgnoreOptions);
}
addCopyCtor();
addCopyCtor(primaryAndCopyCtorAmbiguity);
addCloneMethod();
PropertySymbol equalityContract = addEqualityContract();
......@@ -3097,7 +3100,7 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde
SynthesizedRecordConstructor addCtor(RecordDeclarationSyntax declWithParameters)
{
Debug.Assert(declWithParameters.ParameterList is object);
var ctor = new SynthesizedRecordConstructor(this, declWithParameters, diagnostics);
var ctor = new SynthesizedRecordConstructor(this, declWithParameters);
members.Add(ctor);
return ctor;
}
......@@ -3146,7 +3149,7 @@ void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray<PropertySy
}
}
void addCopyCtor()
void addCopyCtor(bool primaryAndCopyCtorAmbiguity)
{
var targetMethod = new SignatureOnlyMethodSymbol(
WellKnownMemberNames.InstanceConstructorName,
......@@ -3168,7 +3171,13 @@ void addCopyCtor()
if (!memberSignatures.TryGetValue(targetMethod, out Symbol? existingConstructor))
{
members.Add(new SynthesizedRecordCopyCtor(this, memberOffset: members.Count));
var copyCtor = new SynthesizedRecordCopyCtor(this, memberOffset: members.Count);
members.Add(copyCtor);
if (primaryAndCopyCtorAmbiguity)
{
diagnostics.Add(ErrorCode.ERR_RecordAmbigCtor, copyCtor.Locations[0]);
}
}
else
{
......@@ -3540,7 +3549,8 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder<Symbol> members,
{
case MethodKind.Constructor:
// Ignore the record copy constructor
if (!IsRecord || !SynthesizedRecordCopyCtor.HasCopyConstructorSignature(method))
if (!IsRecord ||
!(SynthesizedRecordCopyCtor.HasCopyConstructorSignature(method) && method is not SynthesizedRecordConstructor))
{
hasInstanceConstructor = true;
hasParameterlessInstanceConstructor = hasParameterlessInstanceConstructor || method.ParameterCount == 0;
......
......@@ -11,8 +11,7 @@ internal sealed class SynthesizedRecordConstructor : SourceConstructorSymbolBase
{
public SynthesizedRecordConstructor(
SourceMemberContainerTypeSymbol containingType,
RecordDeclarationSyntax syntax,
DiagnosticBag diagnostics) :
RecordDeclarationSyntax syntax) :
base(containingType, syntax.Identifier.GetLocation(), syntax, isIterator: false)
{
this.MakeFlags(MethodKind.Constructor, containingType.IsAbstract ? DeclarationModifiers.Protected : DeclarationModifiers.Public, returnsVoid: true, isExtensionMethod: false);
......
......@@ -777,6 +777,11 @@
<target state="translated">{0}: U přístupových objektů se modifikátor readonly může použít jenom v případě, že vlastnost nebo indexer má přístupový objekt get i set.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">Přiřazení odkazu {1} k {0} nelze provést, protože {1} má užší řídicí obor než {0}.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">{0}: "readonly" kann für Accessoren nur verwendet werden, wenn die Eigenschaft oder der Indexer sowohl einen get- als auch einen set-Accessor aufweist.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">ref-assign von "{1}" zu "{0}" ist nicht möglich, weil "{1}" einen geringeren Escapebereich als "{0}" aufweist.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">"{0}": "readonly" solo se puede usar en los descriptores de acceso si la propiedad o el indexador tienen un descriptor de acceso get y set</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">No se puede asignar referencia "{1}" a "{0}" porque "{1}" tiene un ámbito de escape más limitado que "{0}".</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}' : 'readonly' peut uniquement être utilisé sur des accesseurs si la propriété ou l'indexeur a un accesseur get et un accesseur set</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">Impossible d'effectuer une assignation par référence de '{1}' vers '{0}', car '{1}' a une portée de sortie plus limitée que '{0}'.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}': 'readonly' può essere usato su funzioni di accesso solo se la proprietà o l'indicizzatore include entrambi le funzioni di accesso get e set</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">Non è possibile assegnare '{1}' a '{0}' come ref perché l'ambito di escape di '{1}' è ridotto rispetto a quello di '{0}'.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}': 'readonly' は、プロパティまたはインデクサーが get および set の両方のアクセサーを含む場合にのみ、アクセサーで使用できます</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">'{1}' を '{0}' に ref 割り当てすることはできません。'{1}' のエスケープ スコープが '{0}' より狭いためです。</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}': 속성 또는 인덱서에 get 접근자와 set 접근자가 둘 다 있는 경우에만 접근자에 'readonly'를 사용할 수 있습니다.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">'{1}'을(를) '{0}'에 참조 할당할 수 없습니다. '{1}'이(가) '{0}'보다 이스케이프 범위가 좁기 때문입니다.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'„{0}”: modyfikatora „readonly” można użyć dla metod dostępu tylko wtedy, gdy właściwość lub indeksator mają metody dostępu get i set</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">Nie można przypisać odwołania elementu „{1}” do elementu „{0}”, ponieważ element „{1}” ma węższy zakres wyjścia niż element „{0}”.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}': 'readonly' somente pode ser usado em acessadores quando a propriedade ou o indexador tem um acessador get e um set</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">Não é possível atribuir ref '{1}' a '{0}' porque '{1}' tem um escopo de escape mais limitado que '{0}'.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'"{0}": readonly можно использовать для методов доступа, только если свойство или индексатор имеет оба метода доступа, get и set.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">Не удается присвоить по ссылке "{1}" для "{0}", так как escape-область у "{1}" уже, чем у "{0}".</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}': 'readonly' erişimcilerde yalnızca özellik veya dizin oluşturucusu hem alma hem ayarlama erişimcisine sahipse kullanılabilir</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">'{1}', '{0}' öğesinden daha dar bir kaçış kapsamı içerdiğinden '{0}' öğesine '{1}' ref ataması yapılamıyor.</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">“{0}”: 仅当属性或索引器同时具有 get 访问器和 set 访问器时,才能对访问器使用 "readonly"</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">无法将“{1}”重新赋值为“{0}”,因为“{1}”具有比“{0}”更窄的转义范围。</target>
......
......@@ -777,6 +777,11 @@
<target state="translated">'{0}': 只有在屬性或索引子同時具有 get 和 set 存取子時,才能在存取子上使用 'readonly'</target>
<note />
</trans-unit>
<trans-unit id="ERR_RecordAmbigCtor">
<source>The primary constructor conflicts with the synthesized copy constructor.</source>
<target state="new">The primary constructor conflicts with the synthesized copy constructor.</target>
<note />
</trans-unit>
<trans-unit id="ERR_RefAssignNarrower">
<source>Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'.</source>
<target state="translated">不能將 '{1}' 參考指派至 '{0}',因為 '{1}' 的逸出範圍比 '{0}' 還要窄。</target>
......
......@@ -229,6 +229,168 @@ class E
comp.VerifyDiagnostics();
}
[Fact, WorkItem(49628, "https://github.com/dotnet/roslyn/issues/49628")]
public void AmbigCtor()
{
var src = @"
record R(R x);
#nullable enable
record R2(R2? x) { }
record R3([System.Diagnostics.CodeAnalysis.NotNull] R3 x);
";
var comp = CreateCompilation(new[] { src, NotNullAttributeDefinition });
comp.VerifyEmitDiagnostics(
// (2,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R(R x);
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R").WithLocation(2, 8),
// (5,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R2(R2? x) { }
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R2").WithLocation(5, 8),
// (7,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R3([System.Diagnostics.CodeAnalysis.NotNull] R3 x);
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R3").WithLocation(7, 8)
);
var r = comp.GlobalNamespace.GetTypeMember("R");
Assert.Equal(new[] { "R..ctor(R x)", "R..ctor(R original)" }, r.GetMembers(".ctor").ToTestDisplayStrings());
}
[Fact, WorkItem(49628, "https://github.com/dotnet/roslyn/issues/49628")]
public void AmbigCtor_Generic()
{
var src = @"
record R<T>(R<T> x);
#nullable enable
record R2<T>(R2<T?> x) { }
";
var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics(
// (2,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R<T>(R<T> x);
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R").WithLocation(2, 8),
// (5,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R2<T>(R2<T?> x) { }
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R2").WithLocation(5, 8)
);
}
[Fact, WorkItem(49628, "https://github.com/dotnet/roslyn/issues/49628")]
public void AmbigCtor_WithExplicitCopyCtor()
{
var src = @"
record R(R x)
{
public R(R x) => throw null;
}
";
var comp = CreateCompilation(new[] { src, NotNullAttributeDefinition });
comp.VerifyEmitDiagnostics(
// (4,12): error CS0111: Type 'R' already defines a member called 'R' with the same parameter types
// public R(R x) => throw null;
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "R").WithArguments("R", "R").WithLocation(4, 12)
);
var r = comp.GlobalNamespace.GetTypeMember("R");
Assert.Equal(new[] { "R..ctor(R x)", "R..ctor(R x)" }, r.GetMembers(".ctor").ToTestDisplayStrings());
}
[Fact, WorkItem(49628, "https://github.com/dotnet/roslyn/issues/49628")]
public void AmbigCtor_WithBase()
{
var src = @"
record Base;
record R(R x) : Base; // 1
record Derived(Derived y) : R(y) // 2
{
public Derived(Derived y) : base(y) => throw null; // 3, 4, 5
}
record Derived2(Derived2 y) : R(y); // 6, 7, 8
record R2(R2 x) : Base
{
public R2(R2 x) => throw null; // 9, 10
}
record R3(R3 x) : Base
{
public R3(R3 x) : base(x) => throw null; // 11
}
";
var comp = CreateCompilation(new[] { src, NotNullAttributeDefinition });
comp.VerifyEmitDiagnostics(
// (4,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R(R x) : Base; // 1
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R").WithLocation(4, 8),
// (6,30): error CS0121: The call is ambiguous between the following methods or properties: 'R.R(R)' and 'R.R(R)'
// record Derived(Derived y) : R(y) // 2
Diagnostic(ErrorCode.ERR_AmbigCall, "(y)").WithArguments("R.R(R)", "R.R(R)").WithLocation(6, 30),
// (8,12): error CS0111: Type 'Derived' already defines a member called 'Derived' with the same parameter types
// public Derived(Derived y) : base(y) => throw null; // 3, 4, 5
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Derived").WithArguments("Derived", "Derived").WithLocation(8, 12),
// (8,33): error CS0121: The call is ambiguous between the following methods or properties: 'R.R(R)' and 'R.R(R)'
// public Derived(Derived y) : base(y) => throw null; // 3, 4, 5
Diagnostic(ErrorCode.ERR_AmbigCall, "base").WithArguments("R.R(R)", "R.R(R)").WithLocation(8, 33),
// (8,33): error CS8868: A copy constructor in a record must call a copy constructor of the base, or a parameterless object constructor if the record inherits from object.
// public Derived(Derived y) : base(y) => throw null; // 3, 4, 5
Diagnostic(ErrorCode.ERR_CopyConstructorMustInvokeBaseCopyConstructor, "base").WithLocation(8, 33),
// (11,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record Derived2(Derived2 y) : R(y); // 6, 7, 8
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "Derived2").WithLocation(11, 8),
// (11,8): error CS8867: No accessible copy constructor found in base type 'R'.
// record Derived2(Derived2 y) : R(y); // 6, 7, 8
Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "Derived2").WithArguments("R").WithLocation(11, 8),
// (11,32): error CS0121: The call is ambiguous between the following methods or properties: 'R.R(R)' and 'R.R(R)'
// record Derived2(Derived2 y) : R(y); // 6, 7, 8
Diagnostic(ErrorCode.ERR_AmbigCall, "(y)").WithArguments("R.R(R)", "R.R(R)").WithLocation(11, 32),
// (15,12): error CS0111: Type 'R2' already defines a member called 'R2' with the same parameter types
// public R2(R2 x) => throw null; // 9, 10
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "R2").WithArguments("R2", "R2").WithLocation(15, 12),
// (15,12): error CS8868: A copy constructor in a record must call a copy constructor of the base, or a parameterless object constructor if the record inherits from object.
// public R2(R2 x) => throw null; // 9, 10
Diagnostic(ErrorCode.ERR_CopyConstructorMustInvokeBaseCopyConstructor, "R2").WithLocation(15, 12),
// (20,12): error CS0111: Type 'R3' already defines a member called 'R3' with the same parameter types
// public R3(R3 x) : base(x) => throw null; // 11
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "R3").WithArguments("R3", "R3").WithLocation(20, 12)
);
}
[Fact, WorkItem(49628, "https://github.com/dotnet/roslyn/issues/49628")]
public void AmbigCtor_WithFieldInitializer()
{
var src = @"
record R(R X)
{
public R X { get; init; } = X;
}
";
var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics(
// (2,8): error CS8909: The primary constructor conflicts with the synthesized copy constructor.
// record R(R X)
Diagnostic(ErrorCode.ERR_RecordAmbigCtor, "R").WithLocation(2, 8)
);
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
var parameterSyntax = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Single();
var parameter = model.GetDeclaredSymbol(parameterSyntax)!;
Assert.Equal("R X", parameter.ToTestDisplayString());
Assert.Equal(SymbolKind.Parameter, parameter.Kind);
Assert.Equal("R..ctor(R X)", parameter.ContainingSymbol.ToTestDisplayString());
var initializerSyntax = tree.GetRoot().DescendantNodes().OfType<EqualsValueClauseSyntax>().Single();
var initializer = model.GetSymbolInfo(initializerSyntax.Value).Symbol!;
Assert.Equal("R X", initializer.ToTestDisplayString());
Assert.Equal(SymbolKind.Parameter, initializer.Kind);
Assert.Equal("R..ctor(R X)", initializer.ContainingSymbol.ToTestDisplayString());
}
[Fact, WorkItem(46123, "https://github.com/dotnet/roslyn/issues/46123")]
public void IncompletePositionalRecord()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册