From c8889b1e940a0e824a4391011b109cce018314db Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 25 May 2023 17:47:37 -0700 Subject: [PATCH 1/5] Don't give duplicate member error for explicit implementations of different file-local interfaces with same source name --- .../Source/SourceMemberContainerSymbol.cs | 19 +- .../Symbols/Source/FileModifierTests.cs | 476 ++++++++++++++++++ 2 files changed, 492 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index d038c2ece3ebf..8186377348e7f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1948,10 +1948,23 @@ private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics) { if (symbol.Kind != SymbolKind.Field || !symbol.IsImplicitlyDeclared) { - // The type '{0}' already contains a definition for '{1}' - if (Locations.Length == 1 || IsPartial) + var explicitImplementations = symbol.GetExplicitInterfaceImplementations(); + var lastExplicitImplementations = lastSym.GetExplicitInterfaceImplementations(); + + // Source symbols can only explicitly implement at most 1 member + Debug.Assert(explicitImplementations.Length is 0 or 1 && explicitImplementations.Length == lastExplicitImplementations.Length); + + // Sometimes two non-overloadable members can have exactly the same source name, but be explicit implementations of different members. + // For example, when explicitly implementing members of file-local interfaces. + // We don't consider such members to be duplicates. + if (explicitImplementations.Length == 0 + || explicitImplementations[0].Equals(lastExplicitImplementations[0], TypeCompareKind.AllIgnoreOptions)) { - diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, symbol.GetFirstLocation(), this, symbol.Name); + // The type '{0}' already contains a definition for '{1}' + if (Locations.Length == 1 || IsPartial) + { + diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, symbol.GetFirstLocation(), this, symbol.Name); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index 96928b63c0555..ff6098d60023e 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -2404,6 +2404,67 @@ void I.F() { } // 2, 3 Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "I").WithArguments("C", "I.F()").WithLocation(5, 19)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void InterfaceImplementation_06() + { + // Ensure that appropriate error is given for duplicate implementations which have a type difference which is insignificant to the runtime. + var source1 = """ + file interface FI + { + public T Prop { get; } + } + + internal class C : FI + { + object FI.Prop { get; } + dynamic FI.Prop { get; } + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs") }, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // F1.cs(6,16): error CS8646: 'FI.Prop' is explicitly implemented more than once. + // internal class C : FI + Diagnostic(ErrorCode.ERR_DuplicateExplicitImpl, "C").WithArguments("FI.Prop").WithLocation(6, 16), + // F1.cs(9,13): error CS0540: 'C.FI.Prop': containing type does not implement interface 'FI' + // dynamic FI.Prop { get; } + Diagnostic(ErrorCode.ERR_ClassDoesntImplementInterface, "FI").WithArguments("C.FI.Prop", "FI").WithLocation(9, 13) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void InterfaceImplementation_07() + { + // Ensure that appropriate error is given for duplicate implementations which have a type difference which is insignificant to the runtime. + var source1 = """ + using System; + + file interface FI + { + public T Prop { get; } + } + + internal class C : FI, FI + { + nint FI.Prop { get; } + IntPtr FI.Prop { get; } + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs") }, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // F1.cs(8,16): error CS8646: 'FI.Prop' is explicitly implemented more than once. + // internal class C : FI, FI + Diagnostic(ErrorCode.ERR_DuplicateExplicitImpl, "C").WithArguments("FI.Prop").WithLocation(8, 16), + // F1.cs(8,30): error CS0528: 'FI' is already listed in interface list + // internal class C : FI, FI + Diagnostic(ErrorCode.ERR_DuplicateInterfaceInBaseList, "FI").WithArguments("FI").WithLocation(8, 30), + // F1.cs(11,23): error CS0102: The type 'C' already contains a definition for 'FI.Prop' + // IntPtr FI.Prop { get; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Prop").WithArguments("C", "FI.Prop").WithLocation(11, 23) + ); + } + [Fact] public void TypeArguments_01() { @@ -4022,4 +4083,419 @@ partial file class C { } var ex = Assert.Throws(() => CreateCompilation(new[] { tree, tree })); Assert.Equal("trees[1]", ex.ParamName); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_01() + { + var source0 = """ + var c = new C(); + c.Use1(); + c.Use2(); + """; + + var source1 = """ + using System; + + file interface FI + { + void M(); + } + + partial class C : FI + { + void FI.M() { Console.Write(1); } + + public void Use1() { ((FI)this).M(); } + } + """; + + var source2 = """ + using System; + + file interface FI + { + void M(); + } + + partial class C : FI + { + void FI.M() { Console.Write(2); } + + public void Use2() { ((FI)this).M(); } + } + """; + + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_02() + { + var source1 = """ + file interface FI + { + void M(); + } + + partial class C : FI + { + void FI.M() => throw null!; + } + """; + + // Explicit implementation of 'FI.M()' in 'source1' does not implement 'FI.M()' in 'source2'. + var source2 = """ + file interface FI + { + void M(); + } + + partial class C : FI + { + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }); + comp.VerifyDiagnostics( + // F2.cs(6,19): error CS0535: 'C' does not implement interface member 'FI.M()' + // partial class C : FI + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "FI").WithArguments("C", "FI.M()").WithLocation(6, 19)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_03() + { + var source1 = """ + using System.Collections.Generic; + + file interface IFoo + { + IReadOnlyDictionary Bar { get; } + } + + internal partial class Foo : IFoo + { + private readonly Dictionary _bar = new() { { 1, "one" }, { 2, "two" } }; + IReadOnlyDictionary IFoo.Bar => _bar; + } + """; + + var source2 = """ + using System.Collections.Generic; + + file interface IFoo + { + IReadOnlyDictionary Bar { get; } + } + + internal partial class Foo : IFoo + { + IReadOnlyDictionary IFoo.Bar => _bar; + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_04() + { + var source1 = """ + file interface IFoo + { + int Bar { get; } + } + + internal partial class Foo : IFoo + { + } + """; + + var source2 = """ + file interface IFoo + { + int Bar { get; } + } + + internal partial class Foo : IFoo + { + int IFoo.Bar => 1; + int IFoo.Bar => 2; + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }); + comp.VerifyDiagnostics( + // F1.cs(6,24): error CS8646: 'IFoo.Bar' is explicitly implemented more than once. + // internal partial class Foo : IFoo + Diagnostic(ErrorCode.ERR_DuplicateExplicitImpl, "Foo").WithArguments("IFoo.Bar").WithLocation(6, 24), + // F1.cs(6,30): error CS0535: 'Foo' does not implement interface member 'IFoo.Bar' + // internal partial class Foo : IFoo + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "IFoo").WithArguments("Foo", "IFoo.Bar").WithLocation(6, 30), + // F2.cs(9,14): error CS0102: The type 'Foo' already contains a definition for 'IFoo.Bar' + // int IFoo.Bar => 2; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Bar").WithArguments("Foo", "IFoo.Bar").WithLocation(9, 14)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_05() + { + var source0 = """ + var c = new C(); + c.Use1(); + c.Use2(); + """; + + var source1 = """ + using System; + + file interface FI + { + int Bar { get; } + } + + internal partial class C : FI + { + int FI.Bar => 1; + public void Use1() => Console.Write(((FI)this).Bar); + } + """; + + var source2 = """ + using System; + + file interface FI + { + int Bar { get; } + } + + internal partial class C : FI + { + int FI.Bar => 2; + public void Use2() => Console.Write(((FI)this).Bar); + } + """; + + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_06() + { + var source0 = """ + var c = new C(); + c.Use1(); + c.Use2(); + """; + + var source1 = """ + using System; + + file interface FI + { + event Action E; + } + + internal partial class C : FI + { + event Action FI.E { add { Console.Write(1); } remove { } } + public void Use1() => ((FI)this).E += () => { }; + } + """; + + var source2 = """ + using System; + + file interface FI + { + event Action E; + } + + internal partial class C : FI + { + event Action FI.E { add { Console.Write(2); } remove { } } + public void Use2() => ((FI)this).E += () => { }; + } + """; + + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_07() + { + var source0 = """ + var c = new C(); + c.Use1(); + c.Use2(); + """; + + var source1 = """ + using System; + + file interface FI + { + int this[int i] { get; } + } + + internal partial class C : FI + { + int FI.this[int i] => 1; + public void Use1() => Console.Write(((FI)this)[0]); + } + """; + + var source2 = """ + using System; + + file interface FI + { + int this[int i] { get; } + } + + internal partial class C : FI + { + int FI.this[int i] => 2; + public void Use2() => Console.Write(((FI)this)[0]); + } + """; + + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_08() + { + // Test explicit implementation of a file interface operator in a partial type multiple times across files. + // File types can't be used in signatures of non-file types, so this scenario isn't allowed currently, + // but we'd like to make sure that redundant/invalid duplicate member name diagnostics aren't given here. + var source1 = """ + file interface FI + { + static abstract int operator +(FI fi, int i); + } + + internal partial class C : FI + { + static int FI.operator +(FI fi, int i) => throw null!; // 1 + } + """; + + var source2 = """ + file interface FI + { + static abstract int operator +(FI fi, int i); + } + + internal partial class C : FI + { + static int FI.operator +(FI fi, int i) => throw null!; // 2 + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // F2.cs(8,28): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. + // static int FI.operator +(FI fi, int i) => throw null!; // 2 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(8, 28), + // F1.cs(8,28): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. + // static int FI.operator +(FI fi, int i) => throw null!; // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(8, 28) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_09() + { + // Similar to PartialExplicitImplementation_08, except only one of the files contains duplicate operator implementations. + var source1 = """ + file interface FI + { + static abstract int operator +(FI fi, int i); + } + + internal partial class C : FI // 1, 2 + { + } + """; + + var source2 = """ + file interface FI + { + static abstract int operator +(FI fi, int i); + } + + internal partial class C : FI + { + static int FI.operator +(FI fi, int i) => throw null!; // 3 + static int FI.operator +(FI fi, int i) => throw null!; // 4, 5 + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // F1.cs(6,24): error CS8646: 'FI.operator +(FI, int)' is explicitly implemented more than once. + // internal partial class C : FI // 1, 2 + Diagnostic(ErrorCode.ERR_DuplicateExplicitImpl, "C").WithArguments("FI.operator +(FI, int)").WithLocation(6, 24), + // F1.cs(6,28): error CS0535: 'C' does not implement interface member 'FI.operator +(FI, int)' + // internal partial class C : FI // 1, 2 + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "FI").WithArguments("C", "FI.operator +(FI, int)").WithLocation(6, 28), + // F2.cs(8,28): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. + // static int FI.operator +(FI fi, int i) => throw null!; // 3 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(8, 28), + // F2.cs(9,28): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. + // static int FI.operator +(FI fi, int i) => throw null!; // 4, 5 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(9, 28), + // F2.cs(9,28): error CS0111: Type 'C' already defines a member called 'FI.op_Addition' with the same parameter types + // static int FI.operator +(FI fi, int i) => throw null!; // 4, 5 + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "+").WithArguments("FI.op_Addition", "C").WithLocation(9, 28) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_10() + { + // Test explicit implementation of a file interface operator in a partial type. + // In another file, implement a member in a type with the same source name, but with member name using the metadata name of the same operator. + // File types can't be used in signatures of non-file types, so this scenario isn't allowed currently, + // but we'd like to make sure that redundant/invalid duplicate member name diagnostics aren't given here. + var source1 = """ + file interface FI + { + static abstract int operator +(FI fi, int i); + } + + internal partial class C : FI + { + static int FI.operator +(FI fi, int i) => throw null!; + } + """; + + var source2 = """ + file interface FI + { + static abstract int op_Addition(FI fi, int i); + } + + internal partial class C : FI + { + static int FI.op_Addition(FI fi, int i) => throw null!; + } + """; + + var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // F2.cs(8,19): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. + // static int FI.op_Addition(FI fi, int i) => throw null!; + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "op_Addition").WithArguments("FI", "C").WithLocation(8, 19), + // F1.cs(8,28): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. + // static int FI.operator +(FI fi, int i) => throw null!; + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(8, 28) + ); + } } From 988783ee9a838fdc0e68403a52710767c7861d74 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Sat, 27 May 2023 11:03:49 -0700 Subject: [PATCH 2/5] Skip verification --- .../Test/Symbol/Symbols/Source/FileModifierTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index ff6098d60023e..8ec228573152d 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -4125,7 +4125,7 @@ partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4278,7 +4278,7 @@ internal partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4321,7 +4321,7 @@ internal partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4364,7 +4364,7 @@ internal partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); verifier.VerifyDiagnostics(); } From d4fea2f686f3e1f41ace2fcc2073797d4abfac0c Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 2 Jun 2023 14:32:00 -0700 Subject: [PATCH 3/5] Include file prefix in explicit interface implementation name --- .../SymbolDisplayVisitor.Types.cs | 11 +++- .../Source/SourceMemberContainerSymbol.cs | 19 +----- .../Symbols/Source/FileModifierTests.cs | 64 ++++++++++++++++--- .../SymbolDisplayCompilerInternalOptions.cs | 7 ++ .../SymbolDisplay/SymbolDisplayFormat.cs | 2 +- 5 files changed, 74 insertions(+), 29 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index b04e2982195f7..77264489f00bd 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -179,14 +179,21 @@ public override void VisitDynamicType(IDynamicTypeSymbol symbol) public override void VisitNamedType(INamedTypeSymbol symbol) { + if ((format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeFileLocalTypesPrefix) != 0 + && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { } internalSymbol1 } + && internalSymbol1.GetFileLocalTypeMetadataNamePrefix() is { } fileLocalNamePrefix) + { + builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, fileLocalNamePrefix)); + } + VisitNamedTypeWithoutNullability(symbol); AddNullableAnnotations(symbol); if ((format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes) != 0 - && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { AssociatedFileIdentifier: { } identifier } internalSymbol }) + && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { AssociatedFileIdentifier: { } identifier } internalSymbol2 }) { var fileDescription = identifier.DisplayFilePath is { Length: not 0 } path ? path - : internalSymbol.GetFirstLocationOrNone().SourceTree is { } tree ? $"" + : internalSymbol2.GetFirstLocationOrNone().SourceTree is { } tree ? $"" : ""; builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "@")); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 8186377348e7f..d038c2ece3ebf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1948,23 +1948,10 @@ private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics) { if (symbol.Kind != SymbolKind.Field || !symbol.IsImplicitlyDeclared) { - var explicitImplementations = symbol.GetExplicitInterfaceImplementations(); - var lastExplicitImplementations = lastSym.GetExplicitInterfaceImplementations(); - - // Source symbols can only explicitly implement at most 1 member - Debug.Assert(explicitImplementations.Length is 0 or 1 && explicitImplementations.Length == lastExplicitImplementations.Length); - - // Sometimes two non-overloadable members can have exactly the same source name, but be explicit implementations of different members. - // For example, when explicitly implementing members of file-local interfaces. - // We don't consider such members to be duplicates. - if (explicitImplementations.Length == 0 - || explicitImplementations[0].Equals(lastExplicitImplementations[0], TypeCompareKind.AllIgnoreOptions)) + // The type '{0}' already contains a definition for '{1}' + if (Locations.Length == 1 || IsPartial) { - // The type '{0}' already contains a definition for '{1}' - if (Locations.Length == 1 || IsPartial) - { - diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, symbol.GetFirstLocation(), this, symbol.Name); - } + diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, symbol.GetFirstLocation(), this, symbol.Name); } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index 8ec228573152d..2b963211188e1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -2459,9 +2459,9 @@ internal class C : FI, FI // F1.cs(8,30): error CS0528: 'FI' is already listed in interface list // internal class C : FI, FI Diagnostic(ErrorCode.ERR_DuplicateInterfaceInBaseList, "FI").WithArguments("FI").WithLocation(8, 30), - // F1.cs(11,23): error CS0102: The type 'C' already contains a definition for 'FI.Prop' + // F1.cs(11,23): error CS0102: The type 'C' already contains a definition for 'F2A62B10769F2595F65CAD631A41E2B54F5D1B3601B00884A41306FA9AD9BACDB__FI.Prop' // IntPtr FI.Prop { get; } - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Prop").WithArguments("C", "FI.Prop").WithLocation(11, 23) + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Prop").WithArguments("C", "F2A62B10769F2595F65CAD631A41E2B54F5D1B3601B00884A41306FA9AD9BACDB__FI.Prop").WithLocation(11, 23) ); } @@ -4125,7 +4125,7 @@ partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4234,9 +4234,9 @@ internal partial class Foo : IFoo // F1.cs(6,30): error CS0535: 'Foo' does not implement interface member 'IFoo.Bar' // internal partial class Foo : IFoo Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "IFoo").WithArguments("Foo", "IFoo.Bar").WithLocation(6, 30), - // F2.cs(9,14): error CS0102: The type 'Foo' already contains a definition for 'IFoo.Bar' + // F2.cs(9,14): error CS0102: The type 'Foo' already contains a definition for 'F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__IFoo.Bar' // int IFoo.Bar => 2; - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Bar").WithArguments("Foo", "IFoo.Bar").WithLocation(9, 14)); + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Bar").WithArguments("Foo", "F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__IFoo.Bar").WithLocation(9, 14)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] @@ -4278,7 +4278,7 @@ internal partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4321,7 +4321,7 @@ internal partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4364,7 +4364,7 @@ internal partial class C : FI } """; - var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, verify: Verification.Skipped, expectedOutput: "12"); + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); verifier.VerifyDiagnostics(); } @@ -4451,9 +4451,9 @@ internal partial class C : FI // F2.cs(9,28): error CS9051: File-local type 'FI' cannot be used in a member signature in non-file-local type 'C'. // static int FI.operator +(FI fi, int i) => throw null!; // 4, 5 Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(9, 28), - // F2.cs(9,28): error CS0111: Type 'C' already defines a member called 'FI.op_Addition' with the same parameter types + // F2.cs(9,28): error CS0111: Type 'C' already defines a member called 'F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__FI.op_Addition' with the same parameter types // static int FI.operator +(FI fi, int i) => throw null!; // 4, 5 - Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "+").WithArguments("FI.op_Addition", "C").WithLocation(9, 28) + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "+").WithArguments("F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__FI.op_Addition", "C").WithLocation(9, 28) ); } @@ -4498,4 +4498,48 @@ internal partial class C : FI Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "+").WithArguments("FI", "C").WithLocation(8, 28) ); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")] + public void PartialExplicitImplementation_11() + { + var source0 = """ + var c = new C(); + c.Use1(); + c.Use2(); + + interface I + { + void M(); + } + """; + + var source1 = """ + using System; + + file interface FI { } + + partial class C : I + { + void I.M() { Console.Write(1); } + + public void Use1() { ((I)this).M(); } + } + """; + + var source2 = """ + using System; + + file interface FI { } + + partial class C : I + { + void I.M() { Console.Write(2); } + + public void Use2() { ((I)this).M(); } + } + """; + + var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12"); + verifier.VerifyDiagnostics(); + } } diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs index eea505665d8d8..b7ecd7c4f9f6b 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs @@ -73,5 +73,12 @@ internal enum SymbolDisplayCompilerInternalOptions /// (i.e., not as part of a method, delegate, or indexer). /// ExcludeParameterNameIfStandalone = 1 << 9, + + /// + /// Display `<File>F<sha256-hex-string>_MyType` instead of `MyType`. + /// Differs from because it guarantees that + /// the prefix will be unique for all files which are permitted to declare file-local types. + /// + IncludeFileLocalTypesPrefix = 1 << 10, } } diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs index 7ff86cd4e5360..fef71e511e6ce 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs @@ -269,7 +269,7 @@ public class SymbolDisplayFormat typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers, - compilerInternalOptions: SymbolDisplayCompilerInternalOptions.ReverseArrayRankSpecifiers); + compilerInternalOptions: SymbolDisplayCompilerInternalOptions.ReverseArrayRankSpecifiers | SymbolDisplayCompilerInternalOptions.IncludeFileLocalTypesPrefix); /// /// Determines how the global namespace is displayed. From 8627cf2e6ab9cc918390f1e8dab11044bf86b678 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 12 Jun 2023 15:13:33 -0700 Subject: [PATCH 4/5] Address feedback --- .../Symbol/Symbols/Source/FileModifierTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index 2b963211188e1..b8a6a1c77731b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -4169,29 +4169,29 @@ public void PartialExplicitImplementation_03() var source1 = """ using System.Collections.Generic; - file interface IFoo + file interface I { - IReadOnlyDictionary Bar { get; } + IReadOnlyDictionary P { get; } } - internal partial class Foo : IFoo + internal partial class C : I { - private readonly Dictionary _bar = new() { { 1, "one" }, { 2, "two" } }; - IReadOnlyDictionary IFoo.Bar => _bar; + private readonly Dictionary _p = new() { { 1, "one" }, { 2, "two" } }; + IReadOnlyDictionary I.P => _p; } """; var source2 = """ using System.Collections.Generic; - file interface IFoo + file interface I { - IReadOnlyDictionary Bar { get; } + IReadOnlyDictionary P { get; } } - internal partial class Foo : IFoo + internal partial class C : I { - IReadOnlyDictionary IFoo.Bar => _bar; + IReadOnlyDictionary I.P => _p; } """; From aba4821725d9191c6356de6bf47a1f48daf07511 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 12 Jun 2023 15:39:09 -0700 Subject: [PATCH 5/5] More --- .../Symbols/Source/FileModifierTests.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index b8a6a1c77731b..83372d2464dfc 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -4203,40 +4203,40 @@ internal partial class C : I public void PartialExplicitImplementation_04() { var source1 = """ - file interface IFoo + file interface I { - int Bar { get; } + int P { get; } } - internal partial class Foo : IFoo + internal partial class C : I { } """; var source2 = """ - file interface IFoo + file interface I { - int Bar { get; } + int P { get; } } - internal partial class Foo : IFoo + internal partial class C : I { - int IFoo.Bar => 1; - int IFoo.Bar => 2; + int I.P => 1; + int I.P => 2; } """; var comp = CreateCompilation(new[] { (source1, "F1.cs"), (source2, "F2.cs") }); comp.VerifyDiagnostics( - // F1.cs(6,24): error CS8646: 'IFoo.Bar' is explicitly implemented more than once. - // internal partial class Foo : IFoo - Diagnostic(ErrorCode.ERR_DuplicateExplicitImpl, "Foo").WithArguments("IFoo.Bar").WithLocation(6, 24), - // F1.cs(6,30): error CS0535: 'Foo' does not implement interface member 'IFoo.Bar' - // internal partial class Foo : IFoo - Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "IFoo").WithArguments("Foo", "IFoo.Bar").WithLocation(6, 30), - // F2.cs(9,14): error CS0102: The type 'Foo' already contains a definition for 'F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__IFoo.Bar' - // int IFoo.Bar => 2; - Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Bar").WithArguments("Foo", "F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__IFoo.Bar").WithLocation(9, 14)); + // F1.cs(6,24): error CS8646: 'I.P' is explicitly implemented more than once. + // internal partial class C : I + Diagnostic(ErrorCode.ERR_DuplicateExplicitImpl, "C").WithArguments("I.P").WithLocation(6, 24), + // F1.cs(6,28): error CS0535: 'C' does not implement interface member 'I.P' + // internal partial class C : I + Diagnostic(ErrorCode.ERR_UnimplementedInterfaceMember, "I").WithArguments("C", "I.P").WithLocation(6, 28), + // F2.cs(9,11): error CS0102: The type 'C' already contains a definition for 'F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__I.P' + // int I.P => 2; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__I.P").WithLocation(9, 11)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68219")]