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/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index 96928b63c0555..83372d2464dfc 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 'F2A62B10769F2595F65CAD631A41E2B54F5D1B3601B00884A41306FA9AD9BACDB__FI.Prop' + // IntPtr FI.Prop { get; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Prop").WithArguments("C", "F2A62B10769F2595F65CAD631A41E2B54F5D1B3601B00884A41306FA9AD9BACDB__FI.Prop").WithLocation(11, 23) + ); + } + [Fact] public void TypeArguments_01() { @@ -4022,4 +4083,463 @@ 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 I + { + IReadOnlyDictionary P { get; } + } + + internal partial class C : I + { + private readonly Dictionary _p = new() { { 1, "one" }, { 2, "two" } }; + IReadOnlyDictionary I.P => _p; + } + """; + + var source2 = """ + using System.Collections.Generic; + + file interface I + { + IReadOnlyDictionary P { get; } + } + + internal partial class C : I + { + IReadOnlyDictionary I.P => _p; + } + """; + + 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 I + { + int P { get; } + } + + internal partial class C : I + { + } + """; + + var source2 = """ + file interface I + { + int P { get; } + } + + internal partial class C : I + { + 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: '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")] + 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 '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("F141A34209AF0D3C8CA844A7D9A360C895EB14E557F17D27626C519D9BE96AF4A__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) + ); + } + + [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.