From 5b2db9e0192b3d6857b163cd9a3940269825ee4b Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Wed, 25 May 2022 18:12:31 -0700 Subject: [PATCH 01/13] File types emit --- .../CSharp/Portable/Binder/Binder_Symbols.cs | 3 +- .../Emitter/Model/NamedTypeReference.cs | 10 + .../Emitter/Model/NamedTypeSymbolAdapter.cs | 10 + .../SymbolDisplayVisitor.Types.cs | 10 +- .../Portable/Symbols/NamedTypeSymbol.cs | 4 +- .../Source/SourceMemberContainerSymbol.cs | 2 +- .../Symbols/Synthesized/GeneratedNameKind.cs | 1 + .../Portable/Symbols/TypeSymbolExtensions.cs | 59 ++ .../Symbols/Source/FileModifierTests.cs | 511 ++++++++++++++++-- .../Portable/CodeGen/CompilationTestData.cs | 2 +- .../CodeGen/PrivateImplementationDetails.cs | 2 + src/Compilers/Core/Portable/Emit/ErrorType.cs | 10 + .../Portable/Emit/NoPia/CommonEmbeddedType.cs | 10 + .../MetadataReader/MetadataHelpers.cs | 6 +- .../Core/Portable/PEWriter/MetadataWriter.cs | 2 +- .../Core/Portable/PEWriter/RootModuleType.cs | 5 + .../Portable/PEWriter/TypeNameSerializer.cs | 8 + src/Compilers/Core/Portable/PEWriter/Types.cs | 10 +- .../SymbolDisplay/SymbolDisplayFormat.cs | 3 +- .../Portable/Binding/Binder_Symbols.vb | 2 +- .../Portable/Emit/NamedTypeReference.vb | 6 + .../Portable/Emit/NamedTypeSymbolAdapter.vb | 6 + .../Portable/Symbols/NamedTypeSymbol.vb | 2 +- .../NamespaceTypeDefinitionNoBase.cs | 4 + 24 files changed, 621 insertions(+), 67 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index 1331731c757b1..9c977c755319c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -2493,7 +2493,8 @@ protected AssemblySymbol GetForwardedToAssembly(string name, int arity, ref Name // NOTE: This won't work if the type isn't using CLS-style generic naming (i.e. `arity), but this code is // only intended to improve diagnostic messages, so false negatives in corner cases aren't a big deal. - var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity); + // File types can't be forwarded, so we won't attempt to determine a file identifier to attach to the metadata name. + var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity, associatedFileIdentifier: null); var fullMetadataName = MetadataHelpers.BuildQualifiedName(qualifierOpt?.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat), metadataName); var result = GetForwardedToAssembly(fullMetadataName, diagnostics, location); if ((object)result != null) diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs index a4eeac4890e69..4d5b6f4b2c18b 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeReference.cs @@ -40,6 +40,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return UnderlyingNamedType.AssociatedFileIdentifier(); + } + } +#nullable disable + string Cci.INamedEntity.Name { get diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs index 377645bb42318..ea7196e6596e2 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs @@ -756,6 +756,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return AdaptedNamedTypeSymbol.AssociatedFileIdentifier(); + } + } +#nullable disable + string Cci.INamedEntity.Name { get diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 32e89cb58cbb6..17ff888ea1055 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -182,13 +182,11 @@ public override void VisitNamedType(INamedTypeSymbol symbol) AddNullableAnnotations(symbol); if ((format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes) != 0 - // PROTOTYPE(ft): public API? - && symbol.GetSymbol() is SourceMemberContainerTypeSymbol { IsFile: true } fileType) + && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } internalSymbol }) { - var tree = symbol.DeclaringSyntaxReferences[0].SyntaxTree; - var fileDescription = tree.FilePath is { Length: not 0 } path - ? Path.GetFileNameWithoutExtension(path) - : $""; + var fileDescription = tree.GetDisplayFileName() is { Length: not 0 } path + ? path + : $""; builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "@")); builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, fileDescription)); diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 19639974753a0..f677ef23cacfa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -478,6 +478,7 @@ public virtual bool IsImplicitClass /// public abstract override string Name { get; } +#nullable enable /// /// Return the name including the metadata arity suffix. /// @@ -485,7 +486,7 @@ public override string MetadataName { get { - return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity) : Name; + return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, this.AssociatedFileIdentifier()) : Name; } } @@ -498,6 +499,7 @@ internal abstract bool MangleName // Intentionally no default implementation to force consideration of appropriate implementation for each new subclass get; } +#nullable disable /// /// Collection of names of members declared within this type. May return duplicates. diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 84a4499cd0b71..75619164470b4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -932,7 +932,7 @@ internal override bool MangleName { get { - return Arity > 0; + return Arity > 0 || IsFile; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs index 66a294e654bad..1404bc8a80496 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs @@ -30,6 +30,7 @@ internal enum GeneratedNameKind ReusableHoistedLocalField = '7', LambdaCacheField = '9', FixedBufferField = 'e', + FileType = 'F', AnonymousType = 'f', TransparentIdentifier = 'h', AnonymousTypeField = 'i', diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 54ef93be6dd1b..b255ed17ade78 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -1361,6 +1362,64 @@ public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) return foundType is not null; } + internal static string? AssociatedFileIdentifier(this NamedTypeSymbol type) + { + if (type is not SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree }) + { + return null; + } + var ordinal = type.DeclaringCompilation.GetSyntaxTreeOrdinal(tree); + + var pooledBuilder = PooledStringBuilder.GetInstance(); + var sb = pooledBuilder.Builder; + sb.Append('<'); + AppendFileName(tree, sb); + sb.Append('>'); + sb.Append((char)GeneratedNameKind.FileType); + sb.Append(ordinal); + sb.Append("__"); + return pooledBuilder.ToStringAndFree(); + } + + internal static string GetDisplayFileName(this SyntaxTree tree) + { + var pooledBuilder = PooledStringBuilder.GetInstance(); + var sb = pooledBuilder.Builder; + AppendFileName(tree, sb); + return pooledBuilder.ToStringAndFree(); + } + + private static void AppendFileName(SyntaxTree tree, StringBuilder sb) + { + // note: a "file path" for a syntax tree can be basically any string. + // since methods like Path.GetFileNameWithoutExtension throw when invalid path characters are found, + // we implement more of a heuristic here. + var originalFilePath = tree.FilePath; + var fileNameStartIndex = Math.Max(originalFilePath.LastIndexOf('/'), originalFilePath.LastIndexOf('\\')) + 1; + var fileNameEndIndex = originalFilePath.LastIndexOf('.'); + if (fileNameEndIndex < fileNameStartIndex) + { + fileNameEndIndex = originalFilePath.Length; + } + + for (int i = fileNameStartIndex; i < fileNameEndIndex; i++) + { + var ch = originalFilePath[i]; + switch (ch) + { + case '_': + case >= 'a' and <= 'z': + case >= 'A' and <= 'Z': + case >= '0' and <= '9': + sb.Append(ch); + break; + default: + sb.Append('_'); + break; + } + } + } + public static bool IsPointerType(this TypeSymbol type) { return type is PointerTypeSymbol; diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index b5e473aee9b02..b7ccb77fcb505 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -3,9 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Linq; +using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests; @@ -189,9 +192,194 @@ static void Main() } """; - var verifier = CompileAndVerify(source, expectedOutput: "1"); + var verifier = CompileAndVerify(source, expectedOutput: "1", symbolValidator: symbolValidator); verifier.VerifyDiagnostics(); - // PROTOTYPE(ft): check metadata names + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("C"); + Assert.Equal("<>F0__C", symbol.MetadataName); + + // The qualified name here is based on `SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes`. + // We don't actually look up based on the mangled name of the type. + // This is similar to how generic types work (lookup based on 'C' instead of 'C`1'). + verifier.VerifyIL("C@.M", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: call ""void System.Console.Write(int)"" + IL_0006: ret +}"); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__C", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__C"); + Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void FileEnum_01() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + class Program + { + static void Main() + { + Console.Write(E.E2); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "E2", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("E"); + Assert.Equal("<>F0__E", symbol.MetadataName); + + verifier.VerifyIL("Program.Main", @" +{ + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: box ""E"" + IL_0006: call ""void System.Console.Write(object)"" + IL_000b: ret +}"); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__E", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__E"); + Assert.Equal(new[] { "value__", "E1", "E2", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void FileEnum_02() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + file class Attr : Attribute + { + public Attr(E e) { } + } + + [Attr(E.E2)] + class Program + { + static void Main() + { + var data = typeof(Program).GetCustomAttributesData(); + Console.Write(data[0].ConstructorArguments[0]); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "(<>F0__E)1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("E"); + Assert.Equal("<>F0__E", symbol.MetadataName); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__E", "<>F0__Attr", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__E"); + Assert.Equal(new[] { "value__", "E1", "E2", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void FileEnum_03() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + class Attr : Attribute + { + public Attr(E e) { } // 1 + } + + [Attr(E.E2)] + class Program + { + static void Main() + { + var data = typeof(Program).GetCustomAttributesData(); + Console.Write(data[0].ConstructorArguments[0]); + } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (10,12): error CS9300: File type 'E' cannot be used in a member signature in non-file type 'Attr'. + // public Attr(E e) { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeDisallowedInSignature, "Attr").WithArguments("E", "Attr").WithLocation(10, 12)); + } + + [Fact] + public void FileEnum_04() + { + var source = """ + using System; + + file enum E + { + E1, E2 + } + + class Attr : Attribute + { + public Attr(object obj) { } + } + + [Attr(E.E2)] + class Program + { + static void Main() + { + var data = typeof(Program).GetCustomAttributesData(); + Console.Write(data[0].ConstructorArguments[0]); + } + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "(<>F0__E)1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("E"); + Assert.Equal("<>F0__E", symbol.MetadataName); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F0__E", "Attr", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F0__E"); + Assert.Equal(new[] { "value__", "E1", "E2", ".ctor" }, classC.MemberNames); + } } [Fact] @@ -226,11 +414,242 @@ static void Main() Diagnostic(ErrorCode.ERR_NameNotInContext, "C").WithArguments("C").WithLocation(5, 9)); } + [Fact] + public void Generic_01() + { + var source = """ + using System; + + C.M(1); + + file class C + { + public static void M(T t) { Console.Write(t); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/MyFile.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C@MyFile.M(T)", @" +{ + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: box ""T"" + IL_0006: call ""void System.Console.Write(object)"" + IL_000b: ret +} +"); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("F0__C`1", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + + // note: the arity is "unmangled" out of the name when loading metadata symbols. + // however, since file types aren't meant to be referenced outside the declaring file, we don't go to the effort of "unmangling" the file identifier out of the name. + var classC = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C`1", classC.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); + } + } + + [Fact] + public void BadFileNames_01() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/My<>File.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@My__File", c.ToTestDisplayString()); + Assert.Equal("F0__C", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + var expectedSymbol = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C", expectedSymbol.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, expectedSymbol.MemberNames); + } + } + + [Fact] + public void BadFileNames_02() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/MyGeneratedFile.g.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@MyGeneratedFile_g", c.ToTestDisplayString()); + Assert.Equal("F0__C", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + var expectedSymbol = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C", expectedSymbol.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, expectedSymbol.MemberNames); + } + } + + // Data based on Lexer.ScanIdentifier_FastPath, excluding '/' and '\'. [Theory] - [InlineData("file", "file")] - [InlineData("file", "")] - [InlineData("", "file")] - public void Duplication_01(string firstFileModifier, string secondFileModifier) + [InlineData('&')] + [InlineData('\0')] + [InlineData(' ')] + [InlineData('\r')] + [InlineData('\n')] + [InlineData('\t')] + [InlineData('!')] + [InlineData('%')] + [InlineData('(')] + [InlineData(')')] + [InlineData('*')] + [InlineData('+')] + [InlineData(',')] + [InlineData('-')] + [InlineData('.')] + [InlineData(':')] + [InlineData(';')] + [InlineData('<')] + [InlineData('=')] + [InlineData('>')] + [InlineData('?')] + [InlineData('[')] + [InlineData(']')] + [InlineData('^')] + [InlineData('{')] + [InlineData('|')] + [InlineData('}')] + [InlineData('~')] + [InlineData('"')] + [InlineData('\'')] + public void BadFileNames_03(char badChar) + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: $"path/to/My{badChar}File.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@My_File", c.ToTestDisplayString()); + Assert.Equal("F0__C", c.MetadataName); + + void symbolValidator(ModuleSymbol module) + { + Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); + var expectedSymbol = module.GlobalNamespace.GetMember("F0__C"); + Assert.Equal("F0__C", expectedSymbol.MetadataName); + Assert.Equal(new[] { "M", ".ctor" }, expectedSymbol.MemberNames); + } + } + + [Fact] + public void Pdb_01() + { + var source = """ + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """; + + var expectedMetadataName = "F0__C"; + var verifier = CompileAndVerify(SyntaxFactory.ParseSyntaxTree(source, options: TestOptions.RegularPreview, path: "path/to/My+File.cs", encoding: Encoding.Default), expectedOutput: "1", symbolValidator: validateSymbols); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var c = comp.GetMember("C"); + Assert.Equal("C@My_File", c.ToTestDisplayString()); + Assert.Equal(expectedMetadataName, c.MetadataName); + verifier.VerifyPdb(""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.NormalizeLineEndings()); + + void validateSymbols(ModuleSymbol module) + { + var type = module.GlobalNamespace.GetMember(expectedMetadataName); + Assert.NotNull(type); + Assert.Equal(new[] { "M", ".ctor" }, type.MemberNames); + } + } + + [Theory] + [InlineData("file", "file", "<>F0__C", "<>F1__C")] + [InlineData("file", "", "<>F0__C", "C")] + [InlineData("", "file", "C", "<>F1__C")] + public void Duplication_01(string firstFileModifier, string secondFileModifier, string firstMetadataName, string secondMetadataName) { // A file type is allowed to have the same name as a non-file type from a different file. // When both a file type and non-file type with the same name are in scope, the file type is preferred, since it's "more local". @@ -269,24 +688,25 @@ static void Main() } """; - // PROTOTYPE(ft): execute and check expectedOutput once name mangling is done - // expectedOutput: "1" - var comp = CreateCompilation(new[] { source1 + main, source2 }); + var verifier = CompileAndVerify(new[] { source1 + main, source2 }, expectedOutput: "1"); + var comp = (CSharpCompilation)verifier.Compilation; var cs = comp.GetMembers("C"); var tree = comp.SyntaxTrees[0]; var expectedSymbol = cs[0]; + Assert.Equal(firstMetadataName, expectedSymbol.MetadataName); verify(); - // expectedOutput: "2" - comp = CreateCompilation(new[] { source1, source2 + main }); + verifier = CompileAndVerify(new[] { source1, source2 + main }, expectedOutput: "2"); + comp = (CSharpCompilation)verifier.Compilation; cs = comp.GetMembers("C"); tree = comp.SyntaxTrees[1]; expectedSymbol = cs[1]; + Assert.Equal(secondMetadataName, expectedSymbol.MetadataName); verify(); void verify() { - comp.VerifyDiagnostics(); + verifier.VerifyDiagnostics(); Assert.Equal(2, cs.Length); Assert.Equal(comp.SyntaxTrees[0], cs[0].DeclaringSyntaxReferences.Single().SyntaxTree); Assert.Equal(comp.SyntaxTrees[1], cs[1].DeclaringSyntaxReferences.Single().SyntaxTree); @@ -405,7 +825,8 @@ static void Main() } """; - var comp = CreateCompilation(new[] { source1, source2, main }); // expectedOutput: 2 + var verifier = CompileAndVerify(new[] { source1, source2, main }, expectedOutput: "2"); + var comp = (CSharpCompilation)verifier.Compilation; comp.VerifyDiagnostics(); var cs = comp.GetMembers("C"); @@ -470,7 +891,8 @@ static void Main() } """; - var comp = CreateCompilation(new[] { source1, main }); // expectedOutput: 2 + var verifier = CompileAndVerify(new[] { source1, main }, expectedOutput: "2"); + var comp = (CSharpCompilation)verifier.Compilation; comp.VerifyDiagnostics(); var cs = comp.GetMembers("C"); @@ -531,7 +953,8 @@ static void Main() } """; - var comp = CreateCompilation(new[] { source1, main }); // expectedOutput: 2 + var verifier = CompileAndVerify(new[] { source1, main }, expectedOutput: "2"); + var comp = (CSharpCompilation)verifier.Compilation; comp.VerifyDiagnostics(); var cs = comp.GetMembers("C"); @@ -834,16 +1257,15 @@ static void Main() } """; - // PROTOTYPE(ft): execute and check expectedOutput once name mangling is done - // expectedOutput: "1" - var comp = CreateCompilation(new[] { source1 + main, source2 }); + var verifier = CompileAndVerify(new[] { source1 + main, source2 }, expectedOutput: "1"); + var comp = (CSharpCompilation)verifier.Compilation; var cs = comp.GetMembers("Program.C"); var tree = comp.SyntaxTrees[0]; var expectedSymbol = cs[0]; verify(); - // expectedOutput: "2" - comp = CreateCompilation(new[] { source1, source2 + main }); + verifier = CompileAndVerify(new[] { source1, source2 + main }, expectedOutput: "2"); + comp = (CSharpCompilation)verifier.Compilation; cs = comp.GetMembers("Program.C"); tree = comp.SyntaxTrees[1]; expectedSymbol = cs[1]; @@ -909,17 +1331,16 @@ static void Main() } """; - // PROTOTYPE(ft): execute and check expectedOutput once name mangling is done - // expectedOutput: "1" - var comp = CreateCompilation(new[] { source1 + main, source2 }); + var comp = CreateCompilation(new[] { source1 + main, source2 }, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); var outers = comp.GetMembers("Outer"); var cs = outers.Select(o => ((NamedTypeSymbol)o).GetMember("C")).ToArray(); var tree = comp.SyntaxTrees[0]; var expectedSymbol = cs[0]; verify(); - // expectedOutput: "2" - comp = CreateCompilation(new[] { source1, source2 + main }); + comp = CreateCompilation(new[] { source1, source2 + main }, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); outers = comp.GetMembers("Outer"); cs = outers.Select(o => ((NamedTypeSymbol)o).GetMember("C")).ToArray(); tree = comp.SyntaxTrees[1]; @@ -928,7 +1349,6 @@ static void Main() void verify() { - comp.VerifyDiagnostics(); Assert.Equal(2, cs.Length); Assert.Equal(comp.SyntaxTrees[0], cs[0].DeclaringSyntaxReferences.Single().SyntaxTree); Assert.Equal(comp.SyntaxTrees[1], cs[1].DeclaringSyntaxReferences.Single().SyntaxTree); @@ -1336,8 +1756,9 @@ static void Main() } """; - var comp = CreateCompilation(new[] { source1, source2 }); // PROTOTYPE(ft): expectedOutput: 2 - comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "2"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; var tree = comp.SyntaxTrees[1]; var model = comp.GetSemanticModel(tree); @@ -1375,8 +1796,9 @@ static void Main() } """; - var comp = CreateCompilation(new[] { source1, source2 }); // PROTOTYPE(ft): expectedOutput: 1 - comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; var tree = comp.SyntaxTrees[1]; var model = comp.GetSemanticModel(tree); @@ -1942,13 +2364,9 @@ public class D } """; - // var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1"); - // verifier.VerifyDiagnostics(); - // var comp = (CSharpCompilation)verifier.Compilation; - - // PROTOTYPE(ft): replace the following with the above commented lines once name mangling is done - var comp = CreateCompilation(new[] { source1, source2 }); - comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); @@ -1994,16 +2412,12 @@ class D } """; - // var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "2"); - // verifier.VerifyDiagnostics(); - // var comp = (CSharpCompilation)verifier.Compilation; - - // PROTOTYPE(ft): replace the following with the above commented lines once name mangling is done - var comp = CreateCompilation(new[] { source1, source2 }); - comp.VerifyDiagnostics( + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "2"); + verifier.VerifyDiagnostics( // (2,1): hidden CS8019: Unnecessary using directive. // using static C; Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using static C;").WithLocation(2, 1)); + var comp = (CSharpCompilation)verifier.Compilation; var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); @@ -2443,12 +2857,11 @@ public static void M() { } var c1 = comp.GetMember("C1"); var c2 = comp.GetMember("C2"); - var format = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes); - Assert.Equal("C1@", c1.ToDisplayString(format)); - Assert.Equal("C2@FileB", c2.ToDisplayString(format)); + Assert.Equal("C1@", c1.ToTestDisplayString()); + Assert.Equal("C2@FileB", c2.ToTestDisplayString()); - Assert.Equal("void C1@.M()", c1.GetMember("M").ToDisplayString(format)); - Assert.Equal("void C2@FileB.M()", c2.GetMember("M").ToDisplayString(format)); + Assert.Equal("void C1@.M()", c1.GetMember("M").ToTestDisplayString()); + Assert.Equal("void C2@FileB.M()", c2.GetMember("M").ToTestDisplayString()); } [Fact] diff --git a/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs b/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs index b0cb635e5360a..084f15f4153f2 100644 --- a/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs +++ b/src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs @@ -68,7 +68,7 @@ public ImmutableDictionary GetMethodsByName() } private static readonly SymbolDisplayFormat _testDataKeyFormat = new SymbolDisplayFormat( - compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames | SymbolDisplayCompilerInternalOptions.UseValueTuple, + compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames | SymbolDisplayCompilerInternalOptions.UseValueTuple | SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes, globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance, diff --git a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs index 123739a331653..d16b004e69fd8 100644 --- a/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs +++ b/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs @@ -592,6 +592,8 @@ public TypeDefinitionHandle TypeDef public bool MangleName => false; + public string? AssociatedFileIdentifier => null; + public virtual ushort Alignment => 0; public virtual Cci.ITypeReference GetBaseClass(EmitContext context) diff --git a/src/Compilers/Core/Portable/Emit/ErrorType.cs b/src/Compilers/Core/Portable/Emit/ErrorType.cs index 79a43e38fc187..83d61eb70c3c3 100644 --- a/src/Compilers/Core/Portable/Emit/ErrorType.cs +++ b/src/Compilers/Core/Portable/Emit/ErrorType.cs @@ -55,6 +55,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return null; + } + } +#nullable disable + bool Cci.ITypeReference.IsEnum { get diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs index c5bf550d98e2a..2ab12dba66c50 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedType.cs @@ -675,6 +675,16 @@ bool Cci.INamedTypeReference.MangleName } } +#nullable enable + string? Cci.INamedTypeReference.AssociatedFileIdentifier + { + get + { + return UnderlyingNamedType.AssociatedFileIdentifier; + } + } +#nullable disable + string Cci.INamedEntity.Name { get diff --git a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs index fa2721e6fce14..ea3df2705b7c4 100644 --- a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs +++ b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs @@ -478,10 +478,12 @@ internal static string GetAritySuffix(int arity) return (arity <= 9) ? s_aritySuffixesOneToNine[arity - 1] : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture)); } - internal static string ComposeAritySuffixedMetadataName(string name, int arity) +#nullable enable + internal static string ComposeAritySuffixedMetadataName(string name, int arity, string? associatedFileIdentifier) { - return arity == 0 ? name : name + GetAritySuffix(arity); + return associatedFileIdentifier + (arity == 0 ? name : name + GetAritySuffix(arity)); } +#nullable disable internal static int InferTypeArityFromMetadataName(string emittedTypeName) { diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index e8fd04f5faace..af9ad75e62e3f 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -926,7 +926,7 @@ public static string GetMangledName(INamedTypeReference namedType, int generatio string unmangledName = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; return namedType.MangleName - ? MetadataHelpers.ComposeAritySuffixedMetadataName(unmangledName, namedType.GenericParameterCount) + ? MetadataHelpers.ComposeAritySuffixedMetadataName(unmangledName, namedType.GenericParameterCount, namedType.AssociatedFileIdentifier) : unmangledName; } diff --git a/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs b/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs index f90f9f573c9e6..dd6391debef0f 100644 --- a/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs +++ b/src/Compilers/Core/Portable/PEWriter/RootModuleType.cs @@ -58,6 +58,11 @@ public bool MangleName get { return false; } } + public string? AssociatedFileIdentifier + { + get { return null; } + } + public string Name { get { return ""; } diff --git a/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs b/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs index 29115ffdff6a5..59118989d74e5 100644 --- a/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs +++ b/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using System.Text; using System.Diagnostics; +using System.Linq; namespace Microsoft.Cci { @@ -199,6 +200,13 @@ private static string GetMangledAndEscapedName(INamedTypeReference namedType) StringBuilder mangledName = pooled.Builder; const string needsEscaping = "\\[]*.+,& "; + if (namedType.AssociatedFileIdentifier is string fileIdentifier) + { + Debug.Assert(namedType.MangleName); + Debug.Assert(needsEscaping.All(c => !fileIdentifier.Contains(c))); + mangledName.Append(fileIdentifier); + } + foreach (var ch in namedType.Name) { if (needsEscaping.IndexOf(ch) >= 0) diff --git a/src/Compilers/Core/Portable/PEWriter/Types.cs b/src/Compilers/Core/Portable/PEWriter/Types.cs index 97e957df38f1c..8f8e50db3c85c 100644 --- a/src/Compilers/Core/Portable/PEWriter/Types.cs +++ b/src/Compilers/Core/Portable/PEWriter/Types.cs @@ -250,9 +250,15 @@ internal interface INamedTypeReference : ITypeReference, INamedEntity ushort GenericParameterCount { get; } /// - /// If true, the persisted type name is mangled by appending "`n" where n is the number of type parameters, if the number of type parameters is greater than 0. - /// + /// If true, the persisted type name is mangled. + /// If the number of type parameters is greater than 0, mangling means appending "`n" where n is the number of type parameters. + /// If is non-null, prefixes the name with a file-specific reserved prefix. + /// bool MangleName { get; } + + /// Indicates that the type is scoped to the file it is declared in. + /// If non-, must also be . + string? AssociatedFileIdentifier { get; } } /// diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs index a41c45d1070d7..4c9be61470591 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs @@ -202,7 +202,8 @@ public class SymbolDisplayFormat SymbolDisplayCompilerInternalOptions.IncludeScriptType | SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames | SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes | - SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers); + SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers | + SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes); /// /// A verbose format for displaying symbols (useful for testing). diff --git a/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb b/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb index 65f794bec2e8b..463918f19773a 100644 --- a/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb +++ b/src/Compilers/VisualBasic/Portable/Binding/Binder_Symbols.vb @@ -375,7 +375,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If rightPart.Kind = SyntaxKind.GenericName Then arity = DirectCast(rightPart, GenericNameSyntax).Arity - fullName = MetadataHelpers.ComposeAritySuffixedMetadataName(currDiagName, arity) + fullName = MetadataHelpers.ComposeAritySuffixedMetadataName(currDiagName, arity, associatedFileIdentifier:=Nothing) End If forwardedToAssembly = GetForwardedToAssembly(containingAssembly, fullName, arity, typeSyntax, diagBag) diff --git a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb index bafab6592ced3..30f4422a89728 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeReference.vb @@ -30,6 +30,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Get End Property + Private ReadOnly Property INamedTypeReferenceAssociatedFileIdentifier As String Implements Cci.INamedTypeReference.AssociatedFileIdentifier + Get + Return Nothing + End Get + End Property + Private ReadOnly Property INamedEntityName As String Implements Cci.INamedEntity.Name Get ' CCI automatically handles type suffix, so use Name instead of MetadataName diff --git a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb index 7e5181c4d4a07..c857d34202f5a 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/NamedTypeSymbolAdapter.vb @@ -758,6 +758,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property INamedTypeReferenceAssociatedFileIdentifier As String Implements INamedTypeReference.AssociatedFileIdentifier + Get + Return Nothing + End Get + End Property + Private ReadOnly Property INamedEntityName As String Implements INamedEntity.Name Get ' CCI automatically adds the arity suffix, so we return Name, not MetadataName here. diff --git a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb index 77b23983ca0b2..c75ff30f818e3 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb @@ -159,7 +159,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols ' Therefore it is a good practice to avoid type names with dots. Debug.Assert(Me.IsErrorType OrElse Not (TypeOf Me Is SourceNamedTypeSymbol) OrElse Not Name.Contains("."), "type name contains dots: " + Name) - Return If(MangleName, MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity), Name) + Return If(MangleName, MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, associatedFileIdentifier:=Nothing), Name) End Get End Property diff --git a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs index d602dcf4c985e..b2cf692ad5453 100644 --- a/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs +++ b/src/ExpressionEvaluator/Core/Test/ExpressionCompiler/NamespaceTypeDefinitionNoBase.cs @@ -77,6 +77,10 @@ internal NamespaceTypeDefinitionNoBase(INamespaceTypeDefinition underlyingType) bool INamedTypeReference.MangleName => UnderlyingType.MangleName; +#nullable enable + string? INamedTypeReference.AssociatedFileIdentifier => UnderlyingType.AssociatedFileIdentifier; +#nullable disable + string INamedEntity.Name => UnderlyingType.Name; string INamespaceTypeReference.NamespaceName => UnderlyingType.NamespaceName; From 99ab5750cc710bb7ec7b9e65e028c9d62b810ae4 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 3 Jun 2022 12:41:15 -0700 Subject: [PATCH 02/13] drop pdb verification for now --- .../Symbols/Source/FileModifierTests.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index b7ccb77fcb505..d7bc8f97ca571 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -604,38 +604,6 @@ file class C var c = comp.GetMember("C"); Assert.Equal("C@My_File", c.ToTestDisplayString()); Assert.Equal(expectedMetadataName, c.MetadataName); - verifier.VerifyPdb(""" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - """.NormalizeLineEndings()); void validateSymbols(ModuleSymbol module) { From ad996ebfac47dde20f5c28cfdd5b297fd02b4721 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 9 Jun 2022 20:12:32 -0700 Subject: [PATCH 03/13] Address some feedback --- .../Symbols/Synthesized/GeneratedNames.cs | 33 +++ .../Portable/Symbols/TypeSymbolExtensions.cs | 45 +--- .../EditAndContinue/EditAndContinueTests.cs | 244 ++++++++++++++++++ .../Symbols/Source/FileModifierTests.cs | 38 +++ 4 files changed, 318 insertions(+), 42 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs index cf9acb4bcd6e0..74b6ef0e57a8e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs @@ -494,5 +494,38 @@ internal static string LambdaCopyParameterName(int ordinal) { return ""; } + + internal static string MakeFileIdentifier(string filePath, int ordinal) + { + var pooledBuilder = PooledStringBuilder.GetInstance(); + var sb = pooledBuilder.Builder; + sb.Append('<'); + AppendFileName(filePath, sb); + sb.Append('>'); + sb.Append((char)GeneratedNameKind.FileType); + sb.Append(ordinal); + sb.Append("__"); + return pooledBuilder.ToStringAndFree(); + } + + internal static void AppendFileName(string? filePath, StringBuilder sb) + { + var fileName = FileNameUtilities.GetFileName(filePath, includeExtension: false); + if (fileName is null) + { + return; + } + + foreach (var ch in fileName) + { + sb.Append(ch switch + { + >= 'a' and <= 'z' => ch, + >= 'A' and <= 'Z' => ch, + >= '0' and <= '9' => ch, + _ => '_' + }); + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index b255ed17ade78..1f94236b255c9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -1364,62 +1364,23 @@ public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) internal static string? AssociatedFileIdentifier(this NamedTypeSymbol type) { + Debug.Assert(type.IsDefinition); if (type is not SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree }) { return null; } var ordinal = type.DeclaringCompilation.GetSyntaxTreeOrdinal(tree); - - var pooledBuilder = PooledStringBuilder.GetInstance(); - var sb = pooledBuilder.Builder; - sb.Append('<'); - AppendFileName(tree, sb); - sb.Append('>'); - sb.Append((char)GeneratedNameKind.FileType); - sb.Append(ordinal); - sb.Append("__"); - return pooledBuilder.ToStringAndFree(); + return GeneratedNames.MakeFileIdentifier(tree.FilePath, ordinal); } internal static string GetDisplayFileName(this SyntaxTree tree) { var pooledBuilder = PooledStringBuilder.GetInstance(); var sb = pooledBuilder.Builder; - AppendFileName(tree, sb); + GeneratedNames.AppendFileName(tree.FilePath, sb); return pooledBuilder.ToStringAndFree(); } - private static void AppendFileName(SyntaxTree tree, StringBuilder sb) - { - // note: a "file path" for a syntax tree can be basically any string. - // since methods like Path.GetFileNameWithoutExtension throw when invalid path characters are found, - // we implement more of a heuristic here. - var originalFilePath = tree.FilePath; - var fileNameStartIndex = Math.Max(originalFilePath.LastIndexOf('/'), originalFilePath.LastIndexOf('\\')) + 1; - var fileNameEndIndex = originalFilePath.LastIndexOf('.'); - if (fileNameEndIndex < fileNameStartIndex) - { - fileNameEndIndex = originalFilePath.Length; - } - - for (int i = fileNameStartIndex; i < fileNameEndIndex; i++) - { - var ch = originalFilePath[i]; - switch (ch) - { - case '_': - case >= 'a' and <= 'z': - case >= 'A' and <= 'Z': - case >= '0' and <= '9': - sb.Append(ch); - break; - default: - sb.Append('_'); - break; - } - } - } - public static bool IsPointerType(this TypeSymbol type) { return type is PointerTypeSymbol; diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 6b6ac34fcf5df..18dc17d942686 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -13159,6 +13159,250 @@ .locals init ([unchanged] V_0, .maxstack 1 IL_0000: ldc.i4.s 10 IL_0002: ret +}"); + } + + [Fact] + public void FileTypes_01() + { + var source0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file1.cs"); + + var compilation0 = CreateCompilation(source0.Tree, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(source1.Tree); + + var method0 = compilation0.GetMember("C.M"); + var method1 = compilation1.GetMember("C.M"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, method0, method1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + } + + [Fact] + public void FileTypes_02() + { + var source0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file1.cs"); + var source2 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(3); + } +}", "file2.cs"); + + var compilation0 = CreateCompilation(source0.Tree, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1.Tree, source2.Tree }); + + var cm1_gen0 = compilation0.GetMember("C.M"); + var cm1_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[0]).GetMember("M"); + var c2_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[1]); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, cm1_gen0, cm1_gen1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + + diff.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + } + + [Fact] + public void FileTypes_03() + { + var source0_gen0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source1_gen1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file2.cs"); + var source0_gen1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(3); + } +}", "file1.cs"); + + var compilation0 = CreateCompilation(source0_gen0.Tree, options: ComSafeDebugDll); + // Because the order of syntax trees has changed here, the original type is considered deleted and the two new types are completely new, unrelated types. + var compilation1 = compilation0.WithSource(new[] { source1_gen1.Tree, source0_gen1.Tree }); + + var c1_gen0 = compilation0.GetMember("C"); + var c1_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[0]); + var c2_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[1]); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Delete, c1_gen0, null, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c1_gen1, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file1.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +}"); + + diff.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret }"); } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index d7bc8f97ca571..ab415012b6e41 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -520,6 +520,43 @@ void symbolValidator(ModuleSymbol module) } } + [Fact] + public void DuplicateFileNames_01() + { + var path = "path/to/file.cs"; + var source1 = SyntaxFactory.ParseSyntaxTree(""" + using System; + + C.M(); + + file class C + { + public static void M() { Console.Write(1); } + } + """, options: TestOptions.RegularPreview, path: path, encoding: Encoding.Default); + var source2 = SyntaxFactory.ParseSyntaxTree(""" + using System; + + file class C + { + public static void M() { Console.Write(2); } + } + """, options: TestOptions.RegularPreview, path: path, encoding: Encoding.Default); + + var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + // PROTOTYPE(ft): VerifyIL doesn't work in this specific scenario because the files have the same name. + // Does that matter? Should we include an ordinal in the symbol display? + verifier.VerifyIL("C@file.M", @""); + + void symbolValidator(ModuleSymbol module) + { + Assert.NotNull(module.GlobalNamespace.GetMember("F0__C")); + Assert.NotNull(module.GlobalNamespace.GetMember("F1__C")); + } + } + // Data based on Lexer.ScanIdentifier_FastPath, excluding '/' and '\'. [Theory] [InlineData('&')] @@ -552,6 +589,7 @@ void symbolValidator(ModuleSymbol module) [InlineData('~')] [InlineData('"')] [InlineData('\'')] + [InlineData('`')] public void BadFileNames_03(char badChar) { var source = """ From ffb137f80848074fcde01f88cdeebe9d6f4a2c99 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 14 Jun 2022 10:28:16 -0700 Subject: [PATCH 04/13] Don't refer to file types emit names as mangled --- .../Portable/Emitter/Model/PEModuleBuilder.cs | 2 +- .../Portable/Symbols/NamedTypeSymbol.cs | 4 +- .../Portable/Symbols/NamespaceOrTypeSymbol.cs | 23 +++++ .../Source/SourceMemberContainerSymbol.cs | 2 +- .../Portable/Symbols/TypeSymbolExtensions.cs | 1 - .../EditAndContinue/EditAndContinueTests.cs | 84 +++++++++++++++++++ .../Symbols/Source/FileModifierTests.cs | 65 +++++++++++--- .../Portable/Emit/CommonPEModuleBuilder.cs | 2 +- .../MetadataReader/MetadataHelpers.cs | 15 ++++ .../SymWriterMetadataProvider.cs | 2 +- .../Core/Portable/PEWriter/MetadataWriter.cs | 18 ++-- .../Portable/PEWriter/TypeNameSerializer.cs | 7 +- src/Compilers/Core/Portable/PEWriter/Types.cs | 9 +- .../Portable/Emit/PEModuleBuilder.vb | 2 +- 14 files changed, 199 insertions(+), 37 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs index a16378eed574f..fc2b6fc942183 100644 --- a/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs @@ -660,7 +660,7 @@ private void ReportExportedTypeNameCollisions(ImmutableArray e // exported types are not emitted in EnC deltas (hence generation 0): string fullEmittedName = MetadataHelpers.BuildQualifiedName( ((Cci.INamespaceTypeReference)type.GetCciAdapter()).NamespaceName, - Cci.MetadataWriter.GetMangledName(type.GetCciAdapter(), generation: 0)); + Cci.MetadataWriter.GetMetadataName(type.GetCciAdapter(), generation: 0)); // First check against types declared in the primary module if (ContainsTopLevelType(fullEmittedName)) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index f677ef23cacfa..dc3ccc50e0566 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -486,9 +486,10 @@ public override string MetadataName { get { - return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, this.AssociatedFileIdentifier()) : Name; + return MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, this.AssociatedFileIdentifier()); } } +#nullable disable /// /// Should the name returned by Name property be mangled with [`arity] suffix in order to get metadata name. @@ -499,7 +500,6 @@ internal abstract bool MangleName // Intentionally no default implementation to force consideration of appropriate implementation for each new subclass get; } -#nullable disable /// /// Collection of names of members declared within this type. May return duplicates. diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index c6f9cfe5caaac..15624e8079648 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -325,6 +325,29 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } } + // PROTOTYPE(ft): fully disallow nested file types + if (isTopLevel && MetadataHelpers.DecodeFileType(emittedTypeName.UnmangledTypeName) is (not -1 and var ordinal, var typeName)) + { + // also do a lookup for file types from source. + namespaceOrTypeMembers = scope.GetTypeMembers(typeName); + foreach (var named in namespaceOrTypeMembers) + { + if (named is SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } + && named.DeclaringCompilation.GetSyntaxTreeOrdinal(tree) == ordinal + && (emittedTypeName.ForcedArity == -1 || emittedTypeName.ForcedArity == emittedTypeName.InferredArity) + && emittedTypeName.InferredArity == named.Arity) + { + if ((object?)namedType != null) + { + namedType = null; + break; + } + + namedType = named; + } + } + } + Done: if ((object?)namedType == null) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 75619164470b4..84a4499cd0b71 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -932,7 +932,7 @@ internal override bool MangleName { get { - return Arity > 0 || IsFile; + return Arity > 0; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 1f94236b255c9..cb30e9adf3f4a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -1364,7 +1364,6 @@ public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) internal static string? AssociatedFileIdentifier(this NamedTypeSymbol type) { - Debug.Assert(type.IsDefinition); if (type is not SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree }) { return null; diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 18dc17d942686..cb1148562ee86 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -13349,6 +13349,11 @@ void M() var compilation0 = CreateCompilation(source0_gen0.Tree, options: ComSafeDebugDll); // Because the order of syntax trees has changed here, the original type is considered deleted and the two new types are completely new, unrelated types. + + // https://github.com/dotnet/roslyn/issues/61999 + // we should handle this as a modification of an existing type rather than deletion and insertion of distinct types. + // most likely, we either need to identify file types based on something stable like the SyntaxTree.FilePath, or store a mapping of the ordinals from one generation to the next. + // although "real-world" compilations disallow duplicated file paths, duplicated or empty file paths are very common via direct use of the APIs, so there's not necessarily a single slam-dunk answer here. var compilation1 = compilation0.WithSource(new[] { source1_gen1.Tree, source0_gen1.Tree }); var c1_gen0 = compilation0.GetMember("C"); @@ -13403,6 +13408,85 @@ .maxstack 1 IL_0002: call ""void System.Console.Write(int)"" IL_0007: nop IL_0008: ret +}"); + } + + [Fact] + public void FileTypes_04() + { + var source1_gen0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(1); + } +}", "file1.cs"); + var source2_gen0 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(2); + } +}", "file2.cs"); + var source2_gen1 = MarkedSource(@" +using System; +file class C +{ + void M() + { + Console.Write(3); + } +}", "file2.cs"); + + var compilation0 = CreateCompilation(new[] { source1_gen0.Tree, source2_gen0.Tree }, options: ComSafeDebugDll); + + var compilation1 = compilation0.WithSource(new[] { source2_gen1.Tree }); + + var c1_gen0 = ((NamedTypeSymbol)compilation0.GetMembers("C")[0]); + var c2_gen0 = ((NamedTypeSymbol)compilation0.GetMembers("C")[1]); + var c2_gen1 = compilation1.GetMember("C"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + v0.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret +} +"); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Delete, c1_gen0, null, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Delete, c2_gen0, null, syntaxMap: null, preserveLocalVariables: true), + SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); + + // There should be no diagnostics from rude edits + diff.EmitResult.Diagnostics.Verify(); + + diff.VerifyIL("C@file2.M", @" +{ + // Code size 9 (0x9) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: call ""void System.Console.Write(int)"" + IL_0007: nop + IL_0008: ret }"); } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index ab415012b6e41..52670770ccc7a 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests; @@ -200,7 +199,7 @@ static void Main() Assert.Equal("<>F0__C", symbol.MetadataName); // The qualified name here is based on `SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes`. - // We don't actually look up based on the mangled name of the type. + // We don't actually look up based on the file-encoded name of the type. // This is similar to how generic types work (lookup based on 'C' instead of 'C`1'). verifier.VerifyIL("C@.M", @" { @@ -450,8 +449,6 @@ void symbolValidator(ModuleSymbol module) { Assert.Equal(new[] { "", "Program", "F0__C" }, module.GlobalNamespace.GetMembers().Select(m => m.Name)); - // note: the arity is "unmangled" out of the name when loading metadata symbols. - // however, since file types aren't meant to be referenced outside the declaring file, we don't go to the effort of "unmangling" the file identifier out of the name. var classC = module.GlobalNamespace.GetMember("F0__C"); Assert.Equal("F0__C`1", classC.MetadataName); Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); @@ -546,9 +543,7 @@ file class C var verifier = CompileAndVerify(new[] { source1, source2 }, expectedOutput: "1", symbolValidator: symbolValidator); verifier.VerifyDiagnostics(); - // PROTOTYPE(ft): VerifyIL doesn't work in this specific scenario because the files have the same name. - // Does that matter? Should we include an ordinal in the symbol display? - verifier.VerifyIL("C@file.M", @""); + // note that VerifyIL doesn't work in this specific scenario because the files have the same name. void symbolValidator(ModuleSymbol module) { @@ -557,7 +552,7 @@ void symbolValidator(ModuleSymbol module) } } - // Data based on Lexer.ScanIdentifier_FastPath, excluding '/' and '\'. + // Data based on Lexer.ScanIdentifier_FastPath, excluding '/', '\', and ':' because those are path separators. [Theory] [InlineData('&')] [InlineData('\0')] @@ -574,7 +569,6 @@ void symbolValidator(ModuleSymbol module) [InlineData(',')] [InlineData('-')] [InlineData('.')] - [InlineData(':')] [InlineData(';')] [InlineData('<')] [InlineData('=')] @@ -2916,5 +2910,56 @@ file class Void { } Assert.Equal("System.Void@", typeInfo.Type!.ToDisplayString(SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes))); } - // PROTOTYPE(ft): public API (INamedTypeSymbol.IsFile?) + [Fact] + public void GetTypeByMetadataName_01() + { + var source1 = """ + file class C { } + """; + + // from source + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics(); + var sourceMember = comp.GetMember("C"); + Assert.Equal("<>F0__C", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(sourceMember, sourceType); + + // from metadata + var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + var metadataMember = comp2.GetMember("<>F0__C"); + Assert.Equal("<>F0__C", metadataMember.MetadataName); + + var metadataType = comp2.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(metadataMember, metadataType); + } + + [Fact] + public void GetTypeByMetadataName_02() + { + var source1 = """ + file class C { } + """; + + // from source + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics(); + var sourceMember = comp.GetMember("C"); + Assert.Equal("<>F0__C`1", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C`1"); + Assert.Equal(sourceMember, sourceType); + + // from metadata + var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + + var metadataMember = comp2.GetMember("<>F0__C"); + Assert.Equal("<>F0__C`1", metadataMember.MetadataName); + + var metadataType = comp2.GetTypeByMetadataName("<>F0__C`1"); + Assert.Equal(metadataMember, metadataType); + } } diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index e26ebb058b82b..bd9687d74c9a2 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -691,7 +691,7 @@ protected bool ContainsTopLevelType(string fullEmittedName) static void AddTopLevelType(HashSet names, Cci.INamespaceTypeDefinition type) // _namesOfTopLevelTypes are only used to generated exported types, which are not emitted in EnC deltas (hence generation 0): - => names?.Add(MetadataHelpers.BuildQualifiedName(type.NamespaceName, Cci.MetadataWriter.GetMangledName(type, generation: 0))); + => names?.Add(MetadataHelpers.BuildQualifiedName(type.NamespaceName, Cci.MetadataWriter.GetMetadataName(type, generation: 0))); } public virtual ImmutableArray GetAdditionalTopLevelTypes() diff --git a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs index ea3df2705b7c4..93431843ac720 100644 --- a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs +++ b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Reflection.Metadata; using System.Text; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -531,6 +532,20 @@ private static short InferTypeArityFromMetadataName(string emittedTypeName, out return (short)arity; } + // FN__ClassName`OptionalArity + private static readonly Regex s_fileTypeOrdinalPattern = new Regex(@">F(\d)+__", RegexOptions.Compiled); + + internal static (int ordinal, string unmangledName) DecodeFileType(string emittedTypeName) + { + if (s_fileTypeOrdinalPattern.Match(emittedTypeName) is Match { Success: true, Groups: var groups, Length: var prefixEndsAt } + && int.TryParse(groups[1].Value, out int ordinal)) + { + return (ordinal, emittedTypeName[(prefixEndsAt + 1)..]); + } + + return (-1, emittedTypeName); + } + internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeName, out short arity) { int suffixStartsAt; diff --git a/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs b/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs index bcd9b1cc00509..e306f811888c4 100644 --- a/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs +++ b/src/Compilers/Core/Portable/NativePdbWriter/SymWriterMetadataProvider.cs @@ -45,7 +45,7 @@ public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespa else { int generation = (t is INamedTypeDefinition namedType) ? _writer.Module.GetTypeDefinitionGeneration(namedType) : 0; - typeName = MetadataWriter.GetMangledName((INamedTypeReference)t, generation); + typeName = MetadataWriter.GetMetadataName((INamedTypeReference)t, generation); INamespaceTypeDefinition namespaceTypeDef; if ((namespaceTypeDef = t.AsNamespaceTypeDefinition(_writer.Context)) != null) diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index af9ad75e62e3f..3cd55cd78448c 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -921,12 +921,12 @@ private static uint GetManagedResourceOffset(BlobBuilder resource, BlobBuilder r return (uint)result; } - public static string GetMangledName(INamedTypeReference namedType, int generation) + public static string GetMetadataName(INamedTypeReference namedType, int generation) { string unmangledName = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; - - return namedType.MangleName - ? MetadataHelpers.ComposeAritySuffixedMetadataName(unmangledName, namedType.GenericParameterCount, namedType.AssociatedFileIdentifier) + string fileIdentifier = namedType.AssociatedFileIdentifier; + return namedType.MangleName || fileIdentifier != null + ? MetadataHelpers.ComposeAritySuffixedMetadataName(unmangledName, namedType.GenericParameterCount, fileIdentifier) : unmangledName; } @@ -2224,7 +2224,7 @@ private void PopulateExportedTypeTableRows() if ((namespaceTypeRef = exportedType.Type.AsNamespaceTypeReference) != null) { // exported types are not emitted in EnC deltas (hence generation 0): - string mangledTypeName = GetMangledName(namespaceTypeRef, generation: 0); + string mangledTypeName = GetMetadataName(namespaceTypeRef, generation: 0); typeName = GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); typeNamespace = GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); @@ -2236,7 +2236,7 @@ private void PopulateExportedTypeTableRows() Debug.Assert(exportedType.ParentIndex != -1); // exported types are not emitted in EnC deltas (hence generation 0): - string mangledTypeName = GetMangledName(nestedRef, generation: 0); + string mangledTypeName = GetMetadataName(nestedRef, generation: 0); typeName = GetStringHandleForNameAndCheckLength(mangledTypeName, nestedRef); typeNamespace = default(StringHandle); @@ -2715,7 +2715,7 @@ private void PopulateTypeDefTableRows() var moduleBuilder = Context.Module; int generation = moduleBuilder.GetTypeDefinitionGeneration(typeDef); - string mangledTypeName = GetMangledName(typeDef, generation); + string mangledTypeName = GetMetadataName(typeDef, generation); ITypeReference baseType = typeDef.GetBaseClass(Context); metadata.AddTypeDefinition( @@ -2790,7 +2790,7 @@ private void PopulateTypeRefTableRows() // It's not possible to reference newer versions of reloadable types from another assembly, hence generation 0: // TODO: https://github.com/dotnet/roslyn/issues/54981 - string mangledTypeName = GetMangledName(nestedTypeRef, generation: 0); + string mangledTypeName = GetMetadataName(nestedTypeRef, generation: 0); name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, nestedTypeRef); @namespace = default(StringHandle); @@ -2807,7 +2807,7 @@ private void PopulateTypeRefTableRows() // It's not possible to reference newer versions of reloadable types from another assembly, hence generation 0: // TODO: https://github.com/dotnet/roslyn/issues/54981 - string mangledTypeName = GetMangledName(namespaceTypeRef, generation: 0); + string mangledTypeName = GetMetadataName(namespaceTypeRef, generation: 0); name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); @namespace = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); diff --git a/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs b/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs index 59118989d74e5..8a1639677e959 100644 --- a/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs +++ b/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs @@ -72,7 +72,7 @@ internal static string GetSerializedTypeName(this ITypeReference typeReference, sb.Append('.'); } - sb.Append(GetMangledAndEscapedName(namespaceType)); + sb.Append(GetEscapedMetadataName(namespaceType)); goto done; } @@ -114,7 +114,7 @@ internal static string GetSerializedTypeName(this ITypeReference typeReference, bool nestedTypeIsAssemblyQualified = false; sb.Append(GetSerializedTypeName(nestedType.GetContainingType(context), context, ref nestedTypeIsAssemblyQualified)); sb.Append('+'); - sb.Append(GetMangledAndEscapedName(nestedType)); + sb.Append(GetEscapedMetadataName(nestedType)); goto done; } @@ -194,7 +194,7 @@ private static void AppendAssemblyQualifierIfNecessary(StringBuilder sb, ITypeRe } } - private static string GetMangledAndEscapedName(INamedTypeReference namedType) + private static string GetEscapedMetadataName(INamedTypeReference namedType) { var pooled = PooledStringBuilder.GetInstance(); StringBuilder mangledName = pooled.Builder; @@ -202,7 +202,6 @@ private static string GetMangledAndEscapedName(INamedTypeReference namedType) const string needsEscaping = "\\[]*.+,& "; if (namedType.AssociatedFileIdentifier is string fileIdentifier) { - Debug.Assert(namedType.MangleName); Debug.Assert(needsEscaping.All(c => !fileIdentifier.Contains(c))); mangledName.Append(fileIdentifier); } diff --git a/src/Compilers/Core/Portable/PEWriter/Types.cs b/src/Compilers/Core/Portable/PEWriter/Types.cs index 8f8e50db3c85c..2a4e50b50ac52 100644 --- a/src/Compilers/Core/Portable/PEWriter/Types.cs +++ b/src/Compilers/Core/Portable/PEWriter/Types.cs @@ -250,14 +250,11 @@ internal interface INamedTypeReference : ITypeReference, INamedEntity ushort GenericParameterCount { get; } /// - /// If true, the persisted type name is mangled. - /// If the number of type parameters is greater than 0, mangling means appending "`n" where n is the number of type parameters. - /// If is non-null, prefixes the name with a file-specific reserved prefix. - /// + /// If true, the persisted type name is mangled by appending "`n" where n is the number of type parameters, if the number of type parameters is greater than 0. + /// bool MangleName { get; } - /// Indicates that the type is scoped to the file it is declared in. - /// If non-, must also be . + /// Indicates that the type is scoped to the file it is declared in. Used as a prefix for the metadata name. string? AssociatedFileIdentifier { get; } } diff --git a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb index fdf4abe8ad6d3..170fbc3551e03 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb @@ -444,7 +444,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit ' exported types are not emitted in EnC deltas (hence generation 0): Dim fullEmittedName As String = MetadataHelpers.BuildQualifiedName( DirectCast(typeReference, Cci.INamespaceTypeReference).NamespaceName, - Cci.MetadataWriter.GetMangledName(DirectCast(typeReference, Cci.INamedTypeReference), generation:=0)) + Cci.MetadataWriter.GetMetadataName(DirectCast(typeReference, Cci.INamedTypeReference), generation:=0)) ' First check against types declared in the primary module If ContainsTopLevelType(fullEmittedName) Then From 4600d6ebeb245d692fbd5065626341ca9e7ae223 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 17 Jun 2022 22:34:20 -0700 Subject: [PATCH 05/13] Disallow nested file types --- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../Portable/Symbols/NamespaceOrTypeSymbol.cs | 1 - .../Source/SourceMemberContainerSymbol.cs | 5 + .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Symbols/Source/FileModifierTests.cs | 145 ++++++++++++++---- 18 files changed, 193 insertions(+), 27 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 20ac3f3e3b0bb..4146cfed25529 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7112,6 +7112,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ File type '{0}' cannot be used as a base type of non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + unsigned right shift diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 8104acb4a1b97..604d715d53c5f 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2092,6 +2092,7 @@ internal enum ErrorCode ERR_FileTypeDisallowedInSignature = 9300, ERR_FileTypeNoExplicitAccessibility = 9301, ERR_FileTypeBase = 9302, + ERR_FileTypeNested = 9303, #endregion diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 15624e8079648..737af12fd4696 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -325,7 +325,6 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } } - // PROTOTYPE(ft): fully disallow nested file types if (isTopLevel && MetadataHelpers.DecodeFileType(emittedTypeName.UnmangledTypeName) is (not -1 and var ordinal, var typeName)) { // also do a lookup for file types from source. diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 84a4499cd0b71..abaf48ea9757f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1743,6 +1743,11 @@ protected void AfterMembersChecks(BindingDiagnosticBag diagnostics) } } + if (IsFile && (object?)ContainingType != null) + { + diagnostics.Add(ErrorCode.ERR_FileTypeNested, location, this); + } + return; bool hasBaseTypeOrInterface(Func predicate) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index ec7515844a8a4..55f12111a21cb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 93e3417e1a7b1..b08519988862b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index b19124392f858..762341b601057 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 050b0378c62bf..80979cb9734bd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 822679273bf8c..db9c14e80a0d4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 9ec00b48bad7f..1567e53a4efb1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 6647208105eeb..d903bdb1afb80 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index f373e9654b8a5..7b5f6645a2fa1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 379359eaef664..bb06db8800bad 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 878346391a8a2..cb29b2f889354 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 595078d8818e9..92fac8c96b925 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index e994c104c5cd5..33e684e59a3e2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index a75e83c291478..f2bb97b99d7be 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -577,6 +577,11 @@ File type '{0}' cannot be used in a member signature in non-file type '{1}'. + + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + File type '{0}' must be defined in a top level type; '{0}' is a nested type. + + File type '{0}' cannot use accessibility modifiers. File type '{0}' cannot use accessibility modifiers. diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index 52670770ccc7a..b1f2584218d54 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -45,10 +45,16 @@ file class C { } comp.VerifyDiagnostics( // (3,16): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // file class C { } - Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(3, 16)); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(3, 16), + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); comp = CreateCompilation(source); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); } [Fact] @@ -94,7 +100,6 @@ file class C { } } """; - // PROTOTYPE(ft): determine whether an inner file class within a file class is an error or if it's just fine. var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); comp.VerifyDiagnostics( // (1,12): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. @@ -102,10 +107,16 @@ file class C { } Diagnostic(ErrorCode.ERR_FeatureInPreview, "Outer").WithArguments("file types").WithLocation(1, 12), // (3,16): error CS8652: The feature 'file types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // file class C { } - Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(3, 16)); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "C").WithArguments("file types").WithLocation(3, 16), + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); comp = CreateCompilation(source); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); } [Fact] @@ -1157,7 +1168,13 @@ public static void M() { } """; var compilation = CreateCompilation(new[] { source1, source2, source3 }); - compilation.VerifyDiagnostics(); + compilation.VerifyDiagnostics( + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16), + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); var classOuter = compilation.GetMember("Outer"); var cs = classOuter.GetMembers("C"); @@ -1257,15 +1274,13 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source1 + main, source2 }, expectedOutput: "1"); - var comp = (CSharpCompilation)verifier.Compilation; + var comp = CreateCompilation(new[] { source1 + main, source2 }); var cs = comp.GetMembers("Program.C"); var tree = comp.SyntaxTrees[0]; var expectedSymbol = cs[0]; verify(); - verifier = CompileAndVerify(new[] { source1, source2 + main }, expectedOutput: "2"); - comp = (CSharpCompilation)verifier.Compilation; + comp = CreateCompilation(new[] { source1, source2 + main }); cs = comp.GetMembers("Program.C"); tree = comp.SyntaxTrees[1]; expectedSymbol = cs[1]; @@ -1273,7 +1288,7 @@ static void Main() void verify() { - comp.VerifyDiagnostics(); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.ERR_FileTypeNested).Verify(); Assert.Equal(2, cs.Length); Assert.Equal(comp.SyntaxTrees[0], cs[0].DeclaringSyntaxReferences.Single().SyntaxTree); Assert.Equal(comp.SyntaxTrees[1], cs[1].DeclaringSyntaxReferences.Single().SyntaxTree); @@ -1332,7 +1347,7 @@ static void Main() """; var comp = CreateCompilation(new[] { source1 + main, source2 }, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.ERR_FileTypeNested).Verify(); var outers = comp.GetMembers("Outer"); var cs = outers.Select(o => ((NamedTypeSymbol)o).GetMember("C")).ToArray(); var tree = comp.SyntaxTrees[0]; @@ -1340,7 +1355,7 @@ static void Main() verify(); comp = CreateCompilation(new[] { source1, source2 + main }, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + comp.GetDiagnostics().Where(d => d.Code is not (int)ErrorCode.ERR_FileTypeNested).Verify(); outers = comp.GetMembers("Outer"); cs = outers.Select(o => ((NamedTypeSymbol)o).GetMember("C")).ToArray(); tree = comp.SyntaxTrees[1]; @@ -2149,7 +2164,7 @@ public void AccessThroughType_01() class Outer { - file class C + file class C // 1 { public static void M() => Console.Write(1); } @@ -2159,17 +2174,18 @@ class Program { public static void Main() { - Outer.C.M(); // 1 + Outer.C.M(); // 2 } } """; - // note: there's no way to make 'file class C' internal here. it's forced to be private, at least for the initial release of the feature. - // we access it within the same containing type in test 'Duplication_10'. var comp = CreateCompilation(source); comp.VerifyDiagnostics( + // (5,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C // 1 + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(5, 16), // (15,15): error CS0122: 'Outer.C' is inaccessible due to its protection level - // Outer.C.M(); // 1 + // Outer.C.M(); // 2 Diagnostic(ErrorCode.ERR_BadAccess, "C").WithArguments("Outer.C").WithLocation(15, 15)); } @@ -2205,7 +2221,10 @@ static void Main() comp.VerifyDiagnostics( // (5,15): error CS0117: 'Outer' does not contain a definition for 'C' // Outer.C.M(); // 1 - Diagnostic(ErrorCode.ERR_NoSuchMember, "C").WithArguments("Outer", "C").WithLocation(5, 15)); + Diagnostic(ErrorCode.ERR_NoSuchMember, "C").WithArguments("Outer", "C").WithLocation(5, 15), + // (5,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(5, 16)); } [Fact] @@ -2466,9 +2485,20 @@ public static void Main() } """; - // 'Derived.C' is not actually accessible from 'Program', so we just bind to 'Base.C' and things work. - var compilation = CompileAndVerify(new[] { source, main }, expectedOutput: "1"); - compilation.VerifyDiagnostics(); + // 'Derived.C' is not actually accessible from 'Program', so we just bind to 'Base.C'. + var compilation = CreateCompilation(new[] { source, main }); + compilation.VerifyDiagnostics( + // (16,20): error CS9303: File type 'Derived.C' must be defined in a top level type; 'Derived.C' is a nested type. + // new file class C + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Derived.C").WithLocation(16, 20)); + + var expected = compilation.GetMember("Base.C.M"); + + var tree = compilation.SyntaxTrees[1]; + var model = compilation.GetSemanticModel(tree); + var invoked = tree.GetRoot().DescendantNodes().OfType().Single().Expression; + var symbolInfo = model.GetSymbolInfo(invoked); + Assert.Equal(expected, symbolInfo.Symbol.GetSymbol()); } [Fact] @@ -2870,16 +2900,22 @@ public void Script_01() var source1 = """ using System; - C1.M(); + C1.M("a"); - file class C1 + static file class C1 { - public static void M() { } + public static void M(this string s) { } } """; var comp = CreateSubmission(source1, parseOptions: TestOptions.Script.WithLanguageVersion(LanguageVersion.Preview)); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (5,19): error CS9303: File type 'C1' must be defined in a top level type; 'C1' is a nested type. + // static file class C1 + Diagnostic(ErrorCode.ERR_FileTypeNested, "C1").WithArguments("C1").WithLocation(5, 19), + // (7,24): error CS1109: Extension methods must be defined in a top level static class; C1 is a nested class + // public static void M(this string s) { } + Diagnostic(ErrorCode.ERR_ExtensionMethodsDecl, "M").WithArguments("C1").WithLocation(7, 24)); } [Fact] @@ -2962,4 +2998,61 @@ file class C { } var metadataType = comp2.GetTypeByMetadataName("<>F0__C`1"); Assert.Equal(metadataMember, metadataType); } + + [Fact] + public void GetTypeByMetadataName_03() + { + var source1 = """ + class Outer + { + file class C { } // 1 + } + """; + + // from source + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics( + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } // 1 + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16)); + var sourceMember = comp.GetMember("Outer.C"); + Assert.Equal("<>F0__C", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("Outer.<>F0__C"); + // Note: strictly speaking, it would be reasonable to return the (invalid) nested file type symbol here. + // However, since we don't actually support nested file types, we don't think we need the API to do the additional lookup + // when the requested type is nested, and so we end up giving a null here. + Assert.Null(sourceType); + } + + [Fact] + public void GetTypeByMetadataName_04() + { + var source1 = """ + file class C { } + """; + + var source2 = """ + class C { } + """; + + // from source + var comp = CreateCompilation(new[] { source1, source2 }); + comp.VerifyDiagnostics(); + var sourceMember = comp.GetMembers("C")[0]; + Assert.Equal("<>F0__C", sourceMember.MetadataName); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(sourceMember, sourceType); + + // from metadata + var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); + + var metadataMember = comp2.GetMember("<>F0__C"); + Assert.Equal("<>F0__C", metadataMember.MetadataName); + + var metadataType = comp2.GetTypeByMetadataName("<>F0__C"); + Assert.Equal(metadataMember, metadataType); + } } From 86f8ffaa56603d0dbc28c0bfb7adacc07329b334 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 20 Jun 2022 11:11:38 -0700 Subject: [PATCH 06/13] fix whitespace --- .../SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs index a0a688a22b402..2bf7dd3ca6047 100644 --- a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs +++ b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayCompilerInternalOptions.cs @@ -62,6 +62,7 @@ internal enum SymbolDisplayCompilerInternalOptions /// Display `System.[U]IntPtr` instead of `n[u]int`. /// UseNativeIntegerUnderlyingType = 1 << 7, + /// /// Separate out nested types from containing types using + instead of . (dot). /// From d37ce9f6a3ec33d0e190e01ab088e89b26ab0b54 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 20 Jun 2022 11:20:39 -0700 Subject: [PATCH 07/13] fix test failures and address a small test gap --- .../Parsing/FileModifierParsingTests.cs | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs index 95e79ed4401d2..0f7fbe85446b8 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/FileModifierParsingTests.cs @@ -664,7 +664,13 @@ class Outer { file class C { } } - """); + """, + expectedBindingDiagnostics: new[] + { + // (3,16): error CS9303: File type 'Outer.C' must be defined in a top level type; 'Outer.C' is a nested type. + // file class C { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "C").WithArguments("Outer.C").WithLocation(3, 16) + }); N(SyntaxKind.CompilationUnit); { @@ -1998,7 +2004,13 @@ class C { file record X(); } - """); + """, + expectedBindingDiagnostics: new[] + { + // (3,17): error CS9303: File type 'C.X' must be defined in a top level type; 'C.X' is a nested type. + // file record X(); + Diagnostic(ErrorCode.ERR_FileTypeNested, "X").WithArguments("C.X").WithLocation(3, 17) + }); N(SyntaxKind.CompilationUnit); { @@ -2090,7 +2102,13 @@ class C { file record X() { } } - """); + """, + expectedBindingDiagnostics: new[] + { + // (3,17): error CS9303: File type 'C.X' must be defined in a top level type; 'C.X' is a nested type. + // file record X() { } + Diagnostic(ErrorCode.ERR_FileTypeNested, "X").WithArguments("C.X").WithLocation(3, 17) + }); N(SyntaxKind.CompilationUnit); { @@ -2179,7 +2197,13 @@ class C { file record X; } - """); + """, + expectedBindingDiagnostics: new[] + { + // (3,17): error CS9303: File type 'C.X' must be defined in a top level type; 'C.X' is a nested type. + // file record X; + Diagnostic(ErrorCode.ERR_FileTypeNested, "X").WithArguments("C.X").WithLocation(3, 17) + }); N(SyntaxKind.CompilationUnit); { @@ -2202,6 +2226,32 @@ class C EOF(); } + [Fact] + public void FileRecord_04_CSharpNext() + { + UsingNode(""" + file record X(); + """); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.FileKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "X"); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + [Fact] public void LocalVariable_01() { From 6175fce1f899059c7901a331483e5336c49bb3c7 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Wed, 22 Jun 2022 15:49:31 -0700 Subject: [PATCH 08/13] Address some feedback --- .../SymbolDisplayVisitor.Types.cs | 18 ++++- ...ymousManager.TypeOrDelegatePublicSymbol.cs | 2 + ...nymousType.TypeOrDelegateTemplateSymbol.cs | 2 + .../Portable/Symbols/ErrorTypeSymbol.cs | 2 + .../Symbols/ExtendedErrorTypeSymbol.cs | 2 + .../Symbols/Metadata/PE/PENamedTypeSymbol.cs | 2 + .../Symbols/MissingMetadataTypeSymbol.cs | 3 + .../Portable/Symbols/NamedTypeSymbol.cs | 7 +- .../Portable/Symbols/NamespaceOrTypeSymbol.cs | 8 +-- .../NoPiaAmbiguousCanonicalTypeSymbol.cs | 2 + .../NoPiaIllegalGenericInstantiationSymbol.cs | 2 + .../NoPiaMissingCanonicalTypeSymbol.cs | 2 + .../Symbols/PlaceholderTypeArgumentSymbol.cs | 2 + .../Source/SourceMemberContainerSymbol.cs | 5 +- .../Synthesized/GeneratedNameParser.cs | 18 +++++ .../Synthesized/SynthesizedContainer.cs | 4 ++ .../SynthesizedEmbeddedAttributeSymbol.cs | 2 + .../Portable/Symbols/TypeSymbolExtensions.cs | 10 +-- .../Portable/Symbols/UnboundGenericType.cs | 2 + .../Symbols/UnsupportedMetadataTypeSymbol.cs | 2 + .../Symbols/Wrapped/WrappedNamedTypeSymbol.cs | 2 + .../Symbol/Symbols/MockNamedTypeSymbol.cs | 2 + .../Symbols/Source/FileModifierTests.cs | 71 +++++++++++++++++++ .../MetadataReader/MetadataHelpers.cs | 15 ---- .../Symbols/EENamedTypeSymbol.cs | 4 ++ 25 files changed, 157 insertions(+), 34 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 6a27270f41cfc..6bad689cf0e76 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -10,6 +10,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.SymbolDisplay; using Roslyn.Utilities; @@ -182,15 +183,28 @@ public override void VisitNamedType(INamedTypeSymbol symbol) AddNullableAnnotations(symbol); if ((format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes) != 0 - && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } internalSymbol }) + && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } internalSymbol }) { - var fileDescription = tree.GetDisplayFileName() is { Length: not 0 } path + var fileDescription = getDisplayFileName(tree) is { Length: not 0 } path ? path : $""; builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "@")); builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, fileDescription)); } + + static string getDisplayFileName(SyntaxTree tree) + { + if (tree.FilePath.Length == 0) + { + return ""; + } + + var pooledBuilder = PooledStringBuilder.GetInstance(); + var sb = pooledBuilder.Builder; + GeneratedNames.AppendFileName(tree.FilePath, sb); + return pooledBuilder.ToStringAndFree(); + } } private void VisitNamedTypeWithoutNullability(INamedTypeSymbol symbol) diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs index 36e8bb11c2d01..ca8f87f34c1f7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/PublicSymbols/AnonymousManager.TypeOrDelegatePublicSymbol.cs @@ -73,6 +73,8 @@ internal sealed override bool MangleName get { return false; } } + internal sealed override SyntaxTree? AssociatedSyntaxTree => null; + public sealed override int Arity { get { return 0; } diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs index 7f335aea333b8..db12a912c9cfa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeOrDelegateTemplateSymbol.cs @@ -237,6 +237,8 @@ internal sealed override bool MangleName get { return this.Arity > 0; } } + internal sealed override SyntaxTree? AssociatedSyntaxTree => null; + internal sealed override ImmutableArray TypeArgumentsWithAnnotationsNoUseSiteDiagnostics { get { return GetTypeParametersAsTypeArguments(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs index 286394b84209e..c786c186d65ce 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ErrorTypeSymbol.cs @@ -578,6 +578,8 @@ internal override bool MangleName get { return _originalDefinition.MangleName; } } + internal override SyntaxTree? AssociatedSyntaxTree => _originalDefinition.AssociatedSyntaxTree; + internal override DiagnosticInfo? ErrorInfo { get { return _originalDefinition.ErrorInfo; } diff --git a/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs index 08ce38645e2c7..1f141ab0f5a38 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ExtendedErrorTypeSymbol.cs @@ -145,6 +145,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public override Symbol? ContainingSymbol { get diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 47d94ec32230c..9542e39a6f7d6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -376,6 +376,8 @@ internal abstract override bool MangleName get; } + internal override SyntaxTree AssociatedSyntaxTree => null; + internal abstract int MetadataArity { get; diff --git a/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs index 2dfb36efb2f81..f81ebf3f77af2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MissingMetadataTypeSymbol.cs @@ -47,6 +47,9 @@ internal override bool MangleName return mangleName; } } + + internal override SyntaxTree? AssociatedSyntaxTree => null; + /// /// Get the arity of the missing type. /// diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 93a5032c436b9..50eb0758642f5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -487,9 +487,14 @@ public override string MetadataName { get { - return MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, this.AssociatedFileIdentifier()); + return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, this.AssociatedFileIdentifier()) : Name; } } + + /// + /// If this type is a file type, returns the syntax tree where this type is visible. Otherwise, returns null. + /// + internal abstract SyntaxTree? AssociatedSyntaxTree { get; } #nullable disable /// diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 737af12fd4696..2af631280d750 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -243,6 +243,7 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted Debug.Assert(!emittedTypeName.IsNull); NamespaceOrTypeSymbol scope = this; + Debug.Assert(scope is not MergedNamespaceSymbol); if (scope.Kind == SymbolKind.ErrorType) { @@ -325,15 +326,15 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } } - if (isTopLevel && MetadataHelpers.DecodeFileType(emittedTypeName.UnmangledTypeName) is (not -1 and var ordinal, var typeName)) +Done: + if (isTopLevel && GeneratedNameParser.TryParseFileTypeName(emittedTypeName.UnmangledTypeName, out int ordinal, out string typeName)) { // also do a lookup for file types from source. namespaceOrTypeMembers = scope.GetTypeMembers(typeName); foreach (var named in namespaceOrTypeMembers) { - if (named is SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } + if (named.AssociatedSyntaxTree is SyntaxTree tree && named.DeclaringCompilation.GetSyntaxTreeOrdinal(tree) == ordinal - && (emittedTypeName.ForcedArity == -1 || emittedTypeName.ForcedArity == emittedTypeName.InferredArity) && emittedTypeName.InferredArity == named.Arity) { if ((object?)namedType != null) @@ -347,7 +348,6 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } } -Done: if ((object?)namedType == null) { if (isTopLevel) diff --git a/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs index 0b578b6715240..a1b600de31315 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NoPiaAmbiguousCanonicalTypeSymbol.cs @@ -49,6 +49,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public AssemblySymbol EmbeddingAssembly { get diff --git a/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs index f8247bddef230..6161835bfa505 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NoPiaIllegalGenericInstantiationSymbol.cs @@ -41,6 +41,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public NamedTypeSymbol UnderlyingSymbol { get diff --git a/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs index c50c29cbe0fad..2b31203cb7a28 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NoPiaMissingCanonicalTypeSymbol.cs @@ -73,6 +73,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + public string Guid { get diff --git a/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs index 3c8eb1aa884d2..3178fe12f7762 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PlaceholderTypeArgumentSymbol.cs @@ -48,6 +48,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + internal override DiagnosticInfo? ErrorInfo { get diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index cd57c4581f320..e1d9774171f24 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -826,8 +826,7 @@ internal override ManagedKind GetManagedKind(ref CompoundUseSiteInfo HasFlag(DeclarationModifiers.File); - /// If this symbol is only available within a single syntax tree, returns that syntax tree. Otherwise, returns null. - internal SyntaxTree? AssociatedSyntaxTree => IsFile ? declaration.Declarations[0].Location.SourceTree : null; + internal sealed override SyntaxTree? AssociatedSyntaxTree => IsFile ? declaration.Declarations[0].Location.SourceTree : null; [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool HasFlag(DeclarationModifiers flag) => (_declModifiers & flag) != 0; @@ -927,7 +926,7 @@ internal override bool MangleName { get { - return Arity > 0; + return Arity > 0 || IsFile; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs index b9d6d4b44fa23..76613356dd515 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Text.RegularExpressions; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -170,5 +171,22 @@ internal static bool TryParseAnonymousTypeParameterName(string typeParameterName propertyName = null; return false; } + + // FN__ClassName`OptionalArity + private static readonly Regex s_fileTypeOrdinalPattern = new Regex(@">F(\d)+__", RegexOptions.Compiled); + + internal static bool TryParseFileTypeName(string unmangledTypeName, out int ordinal, out string originalTypeName) + { + if (s_fileTypeOrdinalPattern.Match(unmangledTypeName) is Match { Success: true, Groups: var groups, Length: var prefixEndsAt } + && int.TryParse(groups[1].Value, out ordinal)) + { + originalTypeName = unmangledTypeName.Substring(prefixEndsAt + 1); + return true; + } + + ordinal = -1; + originalTypeName = unmangledTypeName; + return false; + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs index dd677239e1078..12ec75a9c91ca 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedContainer.cs @@ -165,6 +165,10 @@ internal override IEnumerable GetFieldsToEmit() internal override bool MangleName => Arity > 0; +#nullable enable + internal sealed override SyntaxTree? AssociatedSyntaxTree => null; +#nullable disable + public override bool IsImplicitlyDeclared => true; internal override bool ShouldAddWinRTMembers => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs index a03039d83a6ca..6e36128fc5162 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEmbeddedAttributeSymbol.cs @@ -99,6 +99,8 @@ public SynthesizedEmbeddedAttributeSymbolBase( internal override bool MangleName => false; + internal override SyntaxTree AssociatedSyntaxTree => null; + internal override bool HasCodeAnalysisEmbeddedAttribute => true; internal override bool IsInterpolatedStringHandlerType => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index ddc0e381a6217..2df49e75c19ab 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -1364,7 +1364,7 @@ public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) internal static string? AssociatedFileIdentifier(this NamedTypeSymbol type) { - if (type is not SourceMemberContainerTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree }) + if (type.AssociatedSyntaxTree is not SyntaxTree tree) { return null; } @@ -1372,14 +1372,6 @@ public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) return GeneratedNames.MakeFileIdentifier(tree.FilePath, ordinal); } - internal static string GetDisplayFileName(this SyntaxTree tree) - { - var pooledBuilder = PooledStringBuilder.GetInstance(); - var sb = pooledBuilder.Builder; - GeneratedNames.AppendFileName(tree.FilePath, sb); - return pooledBuilder.ToStringAndFree(); - } - public static bool IsPointerType(this TypeSymbol type) { return type is PointerTypeSymbol; diff --git a/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs b/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs index 30e4a67d87744..1ee9dbccbb271 100644 --- a/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs +++ b/src/Compilers/CSharp/Portable/Symbols/UnboundGenericType.cs @@ -91,6 +91,8 @@ internal override bool MangleName } } + internal override SyntaxTree? AssociatedSyntaxTree => null; + internal override DiagnosticInfo ErrorInfo { get diff --git a/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs index e4ca579dc1b86..80b81912ae070 100644 --- a/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/UnsupportedMetadataTypeSymbol.cs @@ -37,5 +37,7 @@ internal override bool MangleName return false; } } + + internal override SyntaxTree? AssociatedSyntaxTree => null; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs index 48a0389a946be..aa26a0c66f5c8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedNamedTypeSymbol.cs @@ -95,6 +95,8 @@ internal override bool MangleName } } + internal override SyntaxTree AssociatedSyntaxTree => _underlyingType.AssociatedSyntaxTree; + public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken)) { return _underlyingType.GetDocumentationCommentXml(preferredCulture, expandIncludes, cancellationToken); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs index d5e358d3b6939..db91e25e01b09 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MockNamedTypeSymbol.cs @@ -44,6 +44,8 @@ internal override bool MangleName } } + internal override SyntaxTree AssociatedSyntaxTree => null; + public override ImmutableArray TypeParameters { get diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index b1f2584218d54..6864ebb8ae9e9 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -2962,6 +2962,10 @@ file class C { } var sourceType = comp.GetTypeByMetadataName("<>F0__C"); Assert.Equal(sourceMember, sourceType); + Assert.Null(comp.GetTypeByMetadataName("<>F0__D")); + Assert.Null(comp.GetTypeByMetadataName("<>F1__C")); + Assert.Null(comp.GetTypeByMetadataName("F0__C")); + // from metadata var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); comp2.VerifyDiagnostics(); @@ -2987,6 +2991,7 @@ file class C { } var sourceType = comp.GetTypeByMetadataName("<>F0__C`1"); Assert.Equal(sourceMember, sourceType); + Assert.Null(comp.GetTypeByMetadataName("<>F0__C")); // from metadata var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); @@ -3055,4 +3060,70 @@ class C { } var metadataType = comp2.GetTypeByMetadataName("<>F0__C"); Assert.Equal(metadataMember, metadataType); } + + [Fact] + public void AssociatedSyntaxTree_01() + { + var source = """ + file class C + { + void M(C c) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetTypeInfo(node.Type!).Type; + Assert.Equal("C@", type.ToTestDisplayString()); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + } + + [Fact] + public void AssociatedSyntaxTree_02() + { + var source = """ + class C + { + void M(C c) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetTypeInfo(node.Type!).Type; + Assert.Equal("C", type.ToTestDisplayString()); + Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree); + } + + [Fact] + public void AssociatedSyntaxTree_03() + { + var source = """ + file class C + { + void M(C c) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var node = tree.GetRoot().DescendantNodes().OfType().Single(); + var type = model.GetTypeInfo(node.Type!).Type; + Assert.Equal("C@", type.ToTestDisplayString()); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + } } diff --git a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs index ce3a4297a13ed..e15e537b25a7d 100644 --- a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs +++ b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs @@ -12,7 +12,6 @@ using System.Linq; using System.Reflection.Metadata; using System.Text; -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -524,20 +523,6 @@ private static short InferTypeArityFromMetadataName(string emittedTypeName, out return (short)arity; } - // FN__ClassName`OptionalArity - private static readonly Regex s_fileTypeOrdinalPattern = new Regex(@">F(\d)+__", RegexOptions.Compiled); - - internal static (int ordinal, string unmangledName) DecodeFileType(string emittedTypeName) - { - if (s_fileTypeOrdinalPattern.Match(emittedTypeName) is Match { Success: true, Groups: var groups, Length: var prefixEndsAt } - && int.TryParse(groups[1].Value, out int ordinal)) - { - return (ordinal, emittedTypeName[(prefixEndsAt + 1)..]); - } - - return (-1, emittedTypeName); - } - internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeName, out short arity) { int suffixStartsAt; diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs index b6856a5f05ff1..2ee0b8d7df52f 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EENamedTypeSymbol.cs @@ -159,6 +159,10 @@ internal override bool MangleName get { return false; } } + // https://github.com/dotnet/roslyn/issues/61999 + // Determine if 'null' is the right return value here + internal override SyntaxTree AssociatedSyntaxTree => null; + public override IEnumerable MemberNames { get { throw ExceptionUtilities.Unreachable; } From 582709cceea741a752bc0cebcd7eb6534aece299 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Wed, 22 Jun 2022 18:15:51 -0700 Subject: [PATCH 09/13] Don't do file types lookup on a namespace from metadata --- src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 2af631280d750..9435dc9e51550 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; @@ -327,7 +328,7 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } Done: - if (isTopLevel && GeneratedNameParser.TryParseFileTypeName(emittedTypeName.UnmangledTypeName, out int ordinal, out string typeName)) + if (isTopLevel && scope is not PENamespaceSymbol && GeneratedNameParser.TryParseFileTypeName(emittedTypeName.UnmangledTypeName, out int ordinal, out string typeName)) { // also do a lookup for file types from source. namespaceOrTypeMembers = scope.GetTypeMembers(typeName); From 20efb7f366a4fc4e2fcc00dc88b2f319ab14d46d Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 23 Jun 2022 12:18:57 -0700 Subject: [PATCH 10/13] Require file display names to match --- .../Portable/Symbols/NamespaceOrTypeSymbol.cs | 16 ++++++++-- .../Synthesized/GeneratedNameParser.cs | 29 ++++++++++++++----- .../Symbols/Source/FileModifierTests.cs | 1 + 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 9435dc9e51550..66292531128d2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; @@ -328,15 +330,16 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } Done: - if (isTopLevel && scope is not PENamespaceSymbol && GeneratedNameParser.TryParseFileTypeName(emittedTypeName.UnmangledTypeName, out int ordinal, out string typeName)) + if (isTopLevel && scope is not PENamespaceSymbol && GeneratedNameParser.TryParseFileTypeName(emittedTypeName.UnmangledTypeName, out string? displayFileName, out int ordinal, out string? sourceName)) { // also do a lookup for file types from source. - namespaceOrTypeMembers = scope.GetTypeMembers(typeName); + namespaceOrTypeMembers = scope.GetTypeMembers(sourceName); foreach (var named in namespaceOrTypeMembers) { if (named.AssociatedSyntaxTree is SyntaxTree tree + && getDisplayName(tree) == displayFileName && named.DeclaringCompilation.GetSyntaxTreeOrdinal(tree) == ordinal - && emittedTypeName.InferredArity == named.Arity) + && named.Arity == emittedTypeName.InferredArity) { if ((object?)namedType != null) { @@ -362,6 +365,13 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } return namedType; + + static string getDisplayName(SyntaxTree tree) + { + var sb = PooledStringBuilder.GetInstance(); + GeneratedNames.AppendFileName(tree.FilePath, sb); + return sb.ToStringAndFree(); + } } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs index 76613356dd515..55f6d2b85c572 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs @@ -172,20 +172,33 @@ internal static bool TryParseAnonymousTypeParameterName(string typeParameterName return false; } - // FN__ClassName`OptionalArity - private static readonly Regex s_fileTypeOrdinalPattern = new Regex(@">F(\d)+__", RegexOptions.Compiled); - - internal static bool TryParseFileTypeName(string unmangledTypeName, out int ordinal, out string originalTypeName) + // A full metadata name for a generic file type looks like: + // FN__ClassName`A + // where 'N' is the syntax tree ordinal, 'A' is the arity, + // and 'ClassName' is the source name of the type. + // + // The "unmangled" name of a generic file type looks like: + // FN__ClassName + private static readonly Regex s_fileTypeOrdinalPattern = new Regex(@"<([a-zA-Z_0-9]*)>F(\d)+__", RegexOptions.Compiled); + + /// + /// This method will work with either unmangled or mangled type names as input, but it does not remove any arity suffix if present. + /// + internal static bool TryParseFileTypeName(string generatedName, [NotNullWhen(true)] out string? displayFileName, out int ordinal, [NotNullWhen(true)] out string? originalTypeName) { - if (s_fileTypeOrdinalPattern.Match(unmangledTypeName) is Match { Success: true, Groups: var groups, Length: var prefixEndsAt } - && int.TryParse(groups[1].Value, out ordinal)) + if (s_fileTypeOrdinalPattern.Match(generatedName) is Match { Success: true, Groups: var groups, Index: var index, Length: var length } + && int.TryParse(groups[2].Value, out ordinal)) { - originalTypeName = unmangledTypeName.Substring(prefixEndsAt + 1); + displayFileName = groups[1].Value; + + var prefixEndsAt = index + length; + originalTypeName = generatedName.Substring(prefixEndsAt); return true; } ordinal = -1; - originalTypeName = unmangledTypeName; + displayFileName = null; + originalTypeName = null; return false; } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index 6864ebb8ae9e9..c2c0a6586d726 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -2965,6 +2965,7 @@ file class C { } Assert.Null(comp.GetTypeByMetadataName("<>F0__D")); Assert.Null(comp.GetTypeByMetadataName("<>F1__C")); Assert.Null(comp.GetTypeByMetadataName("F0__C")); + Assert.Null(comp.GetTypeByMetadataName("F0__C")); // from metadata var comp2 = CreateCompilation("", references: new[] { comp.EmitToImageReference() }); From 6ba8e19e7c41a8c5d3267a56d1b84addbf35b369 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 23 Jun 2022 13:35:19 -0700 Subject: [PATCH 11/13] fix indenting --- .../CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 66292531128d2..1457971f476bd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -330,7 +330,13 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted } Done: - if (isTopLevel && scope is not PENamespaceSymbol && GeneratedNameParser.TryParseFileTypeName(emittedTypeName.UnmangledTypeName, out string? displayFileName, out int ordinal, out string? sourceName)) + if (isTopLevel + && scope is not PENamespaceSymbol + && GeneratedNameParser.TryParseFileTypeName( + emittedTypeName.UnmangledTypeName, + out string? displayFileName, + out int ordinal, + out string? sourceName)) { // also do a lookup for file types from source. namespaceOrTypeMembers = scope.GetTypeMembers(sourceName); From ea89de4d8111ecc1a495b497753d1fb78faa3331 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 27 Jun 2022 13:48:38 -0700 Subject: [PATCH 12/13] Don't check IsFile in MangleName. Prefer term MetadataName to MangledName. --- .../Portable/Symbols/NamedTypeSymbol.cs | 10 +++++- .../Source/SourceMemberContainerSymbol.cs | 2 +- .../Core/Portable/PEWriter/MetadataWriter.cs | 32 +++++++++---------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 50eb0758642f5..7371c3e8b3bbc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -487,7 +487,10 @@ public override string MetadataName { get { - return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, this.AssociatedFileIdentifier()) : Name; + var fileIdentifier = this.AssociatedFileIdentifier(); + return fileIdentifier != null || MangleName + ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, fileIdentifier) + : Name; } } @@ -501,6 +504,11 @@ public override string MetadataName /// Should the name returned by Name property be mangled with [`arity] suffix in order to get metadata name. /// Must return False for a type with Arity == 0. /// + /// + /// Some types with Arity > 0 still have MangleName == false. For example, EENamedTypeSymbol. + /// Note that other differences between source names and metadata names exist and are not controlled by this property, + /// such as the 'AssociatedFileIdentifier' prefix for file types. + /// internal abstract bool MangleName { // Intentionally no default implementation to force consideration of appropriate implementation for each new subclass diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index e1d9774171f24..44a6c46bbb5db 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -926,7 +926,7 @@ internal override bool MangleName { get { - return Arity > 0 || IsFile; + return Arity > 0; } } diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index aca23ec42cbb0..30bb8fa8e6d43 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -918,11 +918,11 @@ private static uint GetManagedResourceOffset(BlobBuilder resource, BlobBuilder r public static string GetMetadataName(INamedTypeReference namedType, int generation) { - string unmangledName = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; + string nameWithGeneration = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; string fileIdentifier = namedType.AssociatedFileIdentifier; return namedType.MangleName || fileIdentifier != null - ? MetadataHelpers.ComposeAritySuffixedMetadataName(unmangledName, namedType.GenericParameterCount, fileIdentifier) - : unmangledName; + ? MetadataHelpers.ComposeAritySuffixedMetadataName(nameWithGeneration, namedType.GenericParameterCount, fileIdentifier) + : nameWithGeneration; } internal MemberReferenceHandle GetMemberReferenceHandle(ITypeMemberReference memberRef) @@ -2219,10 +2219,10 @@ private void PopulateExportedTypeTableRows() if ((namespaceTypeRef = exportedType.Type.AsNamespaceTypeReference) != null) { // exported types are not emitted in EnC deltas (hence generation 0): - string mangledTypeName = GetMetadataName(namespaceTypeRef, generation: 0); + string metadataTypeName = GetMetadataName(namespaceTypeRef, generation: 0); - typeName = GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); - typeNamespace = GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); + typeName = GetStringHandleForNameAndCheckLength(metadataTypeName, namespaceTypeRef); + typeNamespace = GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, metadataTypeName); implementation = GetExportedTypeImplementation(namespaceTypeRef); attributes = exportedType.IsForwarder ? TypeAttributes.NotPublic | Constants.TypeAttributes_TypeForwarder : TypeAttributes.Public; } @@ -2231,9 +2231,9 @@ private void PopulateExportedTypeTableRows() Debug.Assert(exportedType.ParentIndex != -1); // exported types are not emitted in EnC deltas (hence generation 0): - string mangledTypeName = GetMetadataName(nestedRef, generation: 0); + string metadataTypeName = GetMetadataName(nestedRef, generation: 0); - typeName = GetStringHandleForNameAndCheckLength(mangledTypeName, nestedRef); + typeName = GetStringHandleForNameAndCheckLength(metadataTypeName, nestedRef); typeNamespace = default(StringHandle); implementation = MetadataTokens.ExportedTypeHandle(exportedType.ParentIndex + 1); attributes = exportedType.IsForwarder ? TypeAttributes.NotPublic : TypeAttributes.NestedPublic; @@ -2710,13 +2710,13 @@ private void PopulateTypeDefTableRows() var moduleBuilder = Context.Module; int generation = moduleBuilder.GetTypeDefinitionGeneration(typeDef); - string mangledTypeName = GetMetadataName(typeDef, generation); + string metadataTypeName = GetMetadataName(typeDef, generation); ITypeReference baseType = typeDef.GetBaseClass(Context); metadata.AddTypeDefinition( attributes: GetTypeAttributes(typeDef), - @namespace: (namespaceType != null) ? GetStringHandleForNamespaceAndCheckLength(namespaceType, mangledTypeName) : default(StringHandle), - name: GetStringHandleForNameAndCheckLength(mangledTypeName, typeDef), + @namespace: (namespaceType != null) ? GetStringHandleForNamespaceAndCheckLength(namespaceType, metadataTypeName) : default(StringHandle), + name: GetStringHandleForNameAndCheckLength(metadataTypeName, typeDef), baseType: (baseType != null) ? GetTypeHandle(baseType) : default(EntityHandle), fieldList: GetFirstFieldDefinitionHandle(typeDef), methodList: GetFirstMethodDefinitionHandle(typeDef)); @@ -2785,9 +2785,9 @@ private void PopulateTypeRefTableRows() // It's not possible to reference newer versions of reloadable types from another assembly, hence generation 0: // TODO: https://github.com/dotnet/roslyn/issues/54981 - string mangledTypeName = GetMetadataName(nestedTypeRef, generation: 0); + string metadataTypeName = GetMetadataName(nestedTypeRef, generation: 0); - name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, nestedTypeRef); + name = this.GetStringHandleForNameAndCheckLength(metadataTypeName, nestedTypeRef); @namespace = default(StringHandle); } else @@ -2802,10 +2802,10 @@ private void PopulateTypeRefTableRows() // It's not possible to reference newer versions of reloadable types from another assembly, hence generation 0: // TODO: https://github.com/dotnet/roslyn/issues/54981 - string mangledTypeName = GetMetadataName(namespaceTypeRef, generation: 0); + string metadataTypeName = GetMetadataName(namespaceTypeRef, generation: 0); - name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); - @namespace = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); + name = this.GetStringHandleForNameAndCheckLength(metadataTypeName, namespaceTypeRef); + @namespace = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, metadataTypeName); } metadata.AddTypeReference( From 59d58850f5f5edc88422c352af4aa1b89cb1c846 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 28 Jun 2022 12:54:45 -0700 Subject: [PATCH 13/13] Address feedback --- .../Portable/Symbols/NamedTypeSymbol.cs | 2 + .../Portable/Symbols/NamespaceOrTypeSymbol.cs | 1 + .../Symbols/Source/FileModifierTests.cs | 127 +++++++++++++++++- 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 7371c3e8b3bbc..e1f1c52db543e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -488,6 +488,8 @@ public override string MetadataName get { var fileIdentifier = this.AssociatedFileIdentifier(); + // If we have a fileIdentifier, the type will definitely use CLS arity encoding for nonzero arity. + Debug.Assert(!(fileIdentifier != null && !MangleName && Arity > 0)); return fileIdentifier != null || MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity, fileIdentifier) : Name; diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 1457971f476bd..5271e6e4a4ec2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -332,6 +332,7 @@ internal virtual NamedTypeSymbol LookupMetadataType(ref MetadataTypeName emitted Done: if (isTopLevel && scope is not PENamespaceSymbol + && (emittedTypeName.ForcedArity == -1 || emittedTypeName.ForcedArity == emittedTypeName.InferredArity) && GeneratedNameParser.TryParseFileTypeName( emittedTypeName.UnmangledTypeName, out string? displayFileName, diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index c2c0a6586d726..0fbb15b774fbb 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -180,7 +180,7 @@ class C : B.A2 // ok: base type is bound as A1.A2 } [Fact] - public void SameFileUse() + public void SameFileUse_01() { var source = """ using System; @@ -229,6 +229,56 @@ void symbolValidator(ModuleSymbol symbol) } } + [Fact] + public void SameFileUse_02() + { + var source = """ + using System; + + file class C + { + public static void M() + { + Console.Write(1); + } + } + + class Program + { + static void Main() + { + C.M(); + } + } + """; + + var verifier = CompileAndVerify(new[] { "", source }, expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var symbol = comp.GetMember("C"); + Assert.Equal("<>F1__C", symbol.MetadataName); + + // The qualified name here is based on `SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes`. + // We don't actually look up based on the file-encoded name of the type. + // This is similar to how generic types work (lookup based on 'C' instead of 'C`1'). + verifier.VerifyIL("C@.M", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: call ""void System.Console.Write(int)"" + IL_0006: ret +}"); + + void symbolValidator(ModuleSymbol symbol) + { + Assert.Equal(new[] { "", "<>F1__C", "Program" }, symbol.GlobalNamespace.GetMembers().Select(m => m.Name)); + var classC = symbol.GlobalNamespace.GetMember("<>F1__C"); + Assert.Equal(new[] { "M", ".ctor" }, classC.MemberNames); + } + } + [Fact] public void FileEnum_01() { @@ -3062,6 +3112,81 @@ class C { } Assert.Equal(metadataMember, metadataType); } + [CombinatorialData] + [Theory] + public void GetTypeByMetadataName_05(bool firstIsMetadataReference, bool secondIsMetadataReference) + { + var source1 = """ + file class C { } + """; + + // Create two references containing identically-named file types + var ref1 = CreateCompilation(source1, assemblyName: "ref1"); + var ref2 = CreateCompilation(source1, assemblyName: "ref2"); + + var comp = CreateCompilation("", references: new[] + { + firstIsMetadataReference ? ref1.ToMetadataReference() : ref1.EmitToImageReference(), + secondIsMetadataReference ? ref2.ToMetadataReference() : ref2.EmitToImageReference() + }); + comp.VerifyDiagnostics(); + + var sourceType = comp.GetTypeByMetadataName("<>F0__C"); + Assert.Null(sourceType); + + var types = comp.GetTypesByMetadataName("<>F0__C"); + Assert.Equal(2, types.Length); + Assert.Equal(firstIsMetadataReference ? "C@" : "<>F0__C", types[0].ToTestDisplayString()); + Assert.Equal(secondIsMetadataReference ? "C@" : "<>F0__C", types[1].ToTestDisplayString()); + Assert.NotEqual(types[0], types[1]); + } + + [Fact] + public void GetTypeByMetadataName_06() + { + var source1 = """ + file class C { } + file class C { } + """; + + var comp = CreateCompilation(source1); + comp.VerifyDiagnostics( + // (2,12): error CS0101: The namespace '' already contains a definition for 'C' + // file class C { } + Diagnostic(ErrorCode.ERR_DuplicateNameInNS, "C").WithArguments("C", "").WithLocation(2, 12)); + + var sourceType = ((Compilation)comp).GetTypeByMetadataName("<>F0__C"); + Assert.Equal("C@", sourceType.ToTestDisplayString()); + + var types = comp.GetTypesByMetadataName("<>F0__C"); + Assert.Equal(1, types.Length); + Assert.Same(sourceType, types[0]); + } + + [Fact] + public void GetTypeByMetadataName_07() + { + var source1 = """ + file class C { } + """; + + var comp = CreateCompilation(SyntaxFactory.ParseSyntaxTree(source1, options: TestOptions.RegularPreview, path: "path/to/SomeFile.cs")); + comp.VerifyDiagnostics(); + + Assert.Null(comp.GetTypeByMetadataName("<>F0__C")); + Assert.Empty(comp.GetTypesByMetadataName("<>F0__C")); + + Assert.Null(comp.GetTypeByMetadataName("F0__C")); + Assert.Empty(comp.GetTypesByMetadataName("F0__C")); + + var sourceType = ((Compilation)comp).GetTypeByMetadataName("F0__C"); + Assert.Equal("C@SomeFile", sourceType.ToTestDisplayString()); + + var types = comp.GetTypesByMetadataName("F0__C"); + Assert.Equal(1, types.Length); + Assert.Same(sourceType, types[0]); + } + [Fact] public void AssociatedSyntaxTree_01() {