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/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 3a545b4bd4048..139e9ce8ccbf4 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/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/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/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 4bf65a09750e1..255f4583dd594 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2095,6 +2095,7 @@ internal enum ErrorCode ERR_FileTypeDisallowedInSignature = 9300, ERR_FileTypeNoExplicitAccessibility = 9301, ERR_FileTypeBase = 9302, + ERR_FileTypeNested = 9303, #endregion diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index c913f82010b5e..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,17 +183,28 @@ 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: NamedTypeSymbol { AssociatedSyntaxTree: SyntaxTree tree } internalSymbol }) { - var tree = symbol.DeclaringSyntaxReferences[0].SyntaxTree; - var fileDescription = tree.FilePath is { Length: not 0 } path - ? Path.GetFileNameWithoutExtension(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 b0177ecb18c00..e1f1c52db543e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -479,6 +479,7 @@ public virtual bool IsImplicitClass /// public abstract override string Name { get; } +#nullable enable /// /// Return the name including the metadata arity suffix. /// @@ -486,14 +487,30 @@ public override string MetadataName { get { - return MangleName ? MetadataHelpers.ComposeAritySuffixedMetadataName(Name, Arity) : Name; + 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; } } + /// + /// 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 + /// /// 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/NamespaceOrTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index c6f9cfe5caaac..5271e6e4a4ec2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -2,11 +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; @@ -243,6 +246,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) { @@ -326,6 +330,35 @@ 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, + out int ordinal, + out string? sourceName)) + { + // also do a lookup for file types from source. + namespaceOrTypeMembers = scope.GetTypeMembers(sourceName); + foreach (var named in namespaceOrTypeMembers) + { + if (named.AssociatedSyntaxTree is SyntaxTree tree + && getDisplayName(tree) == displayFileName + && named.DeclaringCompilation.GetSyntaxTreeOrdinal(tree) == ordinal + && named.Arity == emittedTypeName.InferredArity) + { + if ((object?)namedType != null) + { + namedType = null; + break; + } + + namedType = named; + } + } + } + if ((object?)namedType == null) { if (isTopLevel) @@ -339,6 +372,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/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 cc59b10392b0f..44a6c46bbb5db 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; @@ -1743,6 +1742,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/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/Synthesized/GeneratedNameParser.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameParser.cs index b9d6d4b44fa23..55f6d2b85c572 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,35 @@ internal static bool TryParseAnonymousTypeParameterName(string typeParameterName propertyName = null; return false; } + + // 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(generatedName) is Match { Success: true, Groups: var groups, Index: var index, Length: var length } + && int.TryParse(groups[2].Value, out ordinal)) + { + displayFileName = groups[1].Value; + + var prefixEndsAt = index + length; + originalTypeName = generatedName.Substring(prefixEndsAt); + return true; + } + + ordinal = -1; + displayFileName = null; + originalTypeName = null; + return false; + } } } 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/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 508a139a32598..2df49e75c19ab 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,16 @@ public static bool IsFileTypeOrUsesFileTypes(this TypeSymbol type) return foundType is not null; } + internal static string? AssociatedFileIdentifier(this NamedTypeSymbol type) + { + if (type.AssociatedSyntaxTree is not SyntaxTree tree) + { + return null; + } + var ordinal = type.DeclaringCompilation.GetSyntaxTreeOrdinal(tree); + return GeneratedNames.MakeFileIdentifier(tree.FilePath, ordinal); + } + 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/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 54d37aa08912e..4edff182eda01 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 87b0902fd9c30..0d5dbb6ca6d5b 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 83792b53903f8..c146398c4d563 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 3a74a9c7bb492..52798620fbaf0 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 3c47401ea0e07..177e269e704dc 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 8c73c39d1d469..49343625d8876 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 f47b5109c0161..0199e0ab379a3 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 cd69d9f270d30..bfe3edd993e2e 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 74a78e9c99d65..51ef2f7c6d194 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 895aa4d900286..49fe2ce705ee8 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 40c6a24e526ce..9daaaba095219 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 7551f44f01217..211e53063cd85 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 7c52b486b9619..9ac1bddbf4c14 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/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 6b6ac34fcf5df..cb1148562ee86 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -13159,6 +13159,334 @@ .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. + + // 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"); + 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 +}"); + } + + [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/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 b5e473aee9b02..0fbb15b774fbb 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -3,9 +3,11 @@ // 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 Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests; @@ -43,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] @@ -92,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. @@ -100,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] @@ -167,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; @@ -189,9 +202,244 @@ static void Main() } """; - var verifier = CompileAndVerify(source, expectedOutput: "1"); + var verifier = CompileAndVerify(source, expectedOutput: "1", symbolValidator: symbolValidator); + verifier.VerifyDiagnostics(); + + 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 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[] { "", "<>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 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() + { + 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(); - // PROTOTYPE(ft): check metadata names + + 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 +474,243 @@ 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)); + + 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); + } + } + + [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(); + + // note that VerifyIL doesn't work in this specific scenario because the files have the same name. + + 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 ':' because those are path separators. [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); + + 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 +749,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 +886,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 +952,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 +1014,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"); @@ -734,7 +1218,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"); @@ -834,15 +1324,12 @@ static void Main() } """; - // PROTOTYPE(ft): execute and check expectedOutput once name mangling is done - // expectedOutput: "1" var comp = CreateCompilation(new[] { source1 + main, source2 }); var cs = comp.GetMembers("Program.C"); var tree = comp.SyntaxTrees[0]; var expectedSymbol = cs[0]; verify(); - // expectedOutput: "2" comp = CreateCompilation(new[] { source1, source2 + main }); cs = comp.GetMembers("Program.C"); tree = comp.SyntaxTrees[1]; @@ -851,7 +1338,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); @@ -909,17 +1396,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); + 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]; var expectedSymbol = cs[0]; verify(); - // expectedOutput: "2" - comp = CreateCompilation(new[] { source1, source2 + main }); + comp = CreateCompilation(new[] { source1, source2 + main }, options: TestOptions.DebugExe); + 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]; @@ -928,7 +1414,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 +1821,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 +1861,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); @@ -1727,7 +2214,7 @@ public void AccessThroughType_01() class Outer { - file class C + file class C // 1 { public static void M() => Console.Write(1); } @@ -1737,17 +2224,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)); } @@ -1783,7 +2271,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] @@ -1942,13 +2433,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 +2481,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); @@ -2052,9 +2535,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] @@ -2443,12 +2937,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] @@ -2457,16 +2950,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] @@ -2497,5 +2996,260 @@ 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); + + 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() }); + 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); + Assert.Null(comp.GetTypeByMetadataName("<>F0__C")); + + // 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); + } + + [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); + } + + [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() + { + 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/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() { 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/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/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 440db10838871..e15e537b25a7d 100644 --- a/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs +++ b/src/Compilers/Core/Portable/MetadataReader/MetadataHelpers.cs @@ -470,10 +470,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/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 24b0def4eb4c9..30bb8fa8e6d43 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -916,13 +916,13 @@ 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) - : unmangledName; + string nameWithGeneration = (generation == 0) ? namedType.Name : namedType.Name + "#" + generation; + string fileIdentifier = namedType.AssociatedFileIdentifier; + return namedType.MangleName || fileIdentifier != null + ? 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 = GetMangledName(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 = GetMangledName(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 = GetMangledName(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 = GetMangledName(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 = GetMangledName(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( 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..8a1639677e959 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 { @@ -71,7 +72,7 @@ internal static string GetSerializedTypeName(this ITypeReference typeReference, sb.Append('.'); } - sb.Append(GetMangledAndEscapedName(namespaceType)); + sb.Append(GetEscapedMetadataName(namespaceType)); goto done; } @@ -113,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; } @@ -193,12 +194,18 @@ 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; const string needsEscaping = "\\[]*.+,& "; + if (namedType.AssociatedFileIdentifier is string fileIdentifier) + { + 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..2a4e50b50ac52 100644 --- a/src/Compilers/Core/Portable/PEWriter/Types.cs +++ b/src/Compilers/Core/Portable/PEWriter/Types.cs @@ -253,6 +253,9 @@ internal interface INamedTypeReference : ITypeReference, INamedEntity /// 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. Used as a prefix for the metadata name. + string? AssociatedFileIdentifier { get; } } /// 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). /// diff --git a/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs b/src/Compilers/Core/Portable/SymbolDisplay/SymbolDisplayFormat.cs index d8a5dc6aba65e..20f8c6d92d74c 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 22f8d32a16009..0ea8e1ba9b275 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/Emit/PEModuleBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb index 619f9b8c1f77f..22b2eeed16b1e 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb @@ -443,7 +443,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 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/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; } 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;