/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs
1 301 строка
57 KB
AlekseyTs
Use dedicated type for union syntax. (#83808)
26 май 2026, 20:24
Не верифицирован
26 май 2026, 20:24
3d56308
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; using CoreInternalSyntax = Microsoft.CodeAnalysis.Syntax.InternalSyntax; namespace Microsoft.CodeAnalysis.CSharp { using BoxedMemberNames = StrongBox<ImmutableSegmentedHashSet<string>>; internal sealed class DeclarationTreeBuilder : CSharpSyntaxVisitor<SingleNamespaceOrTypeDeclaration> { /// <summary> /// Cache of node to the last set of member names computed for it. Helpful for avoiding excess hashset /// allocations for common scenarios where compilations are created with the same trees used to create prior /// compilations. /// </summary> /// <remarks> /// We store as green nodes for two purposes. First, it allows the data to be stored, without holding onto the /// whole tree. It allows files with multiple types in them to reuse the member names for untouched types when /// others in the file are edited. /// </remarks> private static readonly ConditionalWeakTable<GreenNode, BoxedMemberNames> s_nodeToMemberNames = new ConditionalWeakTable<GreenNode, BoxedMemberNames>(); private static readonly BoxedMemberNames s_emptyMemberNames = new BoxedMemberNames(ImmutableSegmentedHashSet<string>.Empty); private readonly SyntaxTree _syntaxTree; private readonly string _scriptClassName; private readonly bool _isSubmission; /// <summary> /// Stored in lexical order for the types in this tree that had member names the last time we created decls for /// the tree. /// </summary> private readonly OneOrMany<WeakReference<BoxedMemberNames>> _previousMemberNames; /// <summary> /// Any special attributes we may be referencing through a using alias in the file. /// For example <c>using X = System.Runtime.CompilerServices.TypeForwardedToAttribute</c>. /// </summary> private QuickAttributes _nonGlobalAliasedQuickAttributes; /// <summary> /// The index of the current type we're processing in lexicographic order with respect to all other types in the /// file. For example: /// <code> /// class A // Index 0 /// { /// class B // Index 1 /// { /// } /// } /// /// class C // Index 2 /// { /// } /// </code> /// </summary> private int _currentTypeIndex; private DeclarationTreeBuilder( SyntaxTree syntaxTree, string scriptClassName, bool isSubmission, OneOrMany<WeakReference<BoxedMemberNames>> previousMemberNames) { _syntaxTree = syntaxTree; _scriptClassName = scriptClassName; _isSubmission = isSubmission; _previousMemberNames = previousMemberNames; } public static RootSingleNamespaceDeclaration ForTree( SyntaxTree syntaxTree, string scriptClassName, bool isSubmission, OneOrMany<WeakReference<BoxedMemberNames>>? previousMemberNames = null) { var builder = new DeclarationTreeBuilder( syntaxTree, scriptClassName, isSubmission, previousMemberNames ?? OneOrMany<WeakReference<BoxedMemberNames>>.Empty); return (RootSingleNamespaceDeclaration)builder.Visit(syntaxTree.GetRoot()); } public static bool CachesComputedMemberNames(SingleTypeDeclaration typeDeclaration) { return typeDeclaration.Kind switch { // A type declaration can't ever be a namespace. DeclarationKind.Namespace => throw ExceptionUtilities.Unreachable(), // Delegates also do not cache any members names as the member names are always a known fixed set. DeclarationKind.Delegate => false, DeclarationKind.Class or DeclarationKind.Interface or DeclarationKind.Struct or DeclarationKind.Union or DeclarationKind.Enum or DeclarationKind.Script or DeclarationKind.Submission or DeclarationKind.ImplicitClass or DeclarationKind.Record or DeclarationKind.RecordStruct => true, DeclarationKind.Extension => true, _ => throw ExceptionUtilities.UnexpectedValue(typeDeclaration.Kind) }; } private ImmutableArray<SingleNamespaceOrTypeDeclaration> VisitNamespaceChildren( CSharpSyntaxNode node, SyntaxList<MemberDeclarationSyntax> members, CoreInternalSyntax.SyntaxList<Syntax.InternalSyntax.MemberDeclarationSyntax> internalMembers) { Debug.Assert( node.Kind() is SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration || (node.Kind() == SyntaxKind.CompilationUnit && _syntaxTree.Options.Kind == SourceCodeKind.Regular)); if (members.Count == 0) { return ImmutableArray<SingleNamespaceOrTypeDeclaration>.Empty; } // We look for members that are not allowed in a namespace. // If there are any we create an implicit class to wrap them. bool hasGlobalMembers = false; bool acceptSimpleProgram = node.Kind() == SyntaxKind.CompilationUnit && _syntaxTree.Options.Kind == SourceCodeKind.Regular; bool hasAwaitExpressions = false; bool isIterator = false; bool hasReturnWithExpression = false; GlobalStatementSyntax firstGlobalStatement = null; bool hasNonEmptyGlobalStatement = false; var childrenBuilder = ArrayBuilder<SingleNamespaceOrTypeDeclaration>.GetInstance(); foreach (var member in members) { SingleNamespaceOrTypeDeclaration namespaceOrType = Visit(member); if (namespaceOrType != null) { childrenBuilder.Add(namespaceOrType); } else if (acceptSimpleProgram && member.IsKind(SyntaxKind.GlobalStatement)) { var global = (GlobalStatementSyntax)member; firstGlobalStatement ??= global; var topLevelStatement = global.Statement; if (!topLevelStatement.IsKind(SyntaxKind.EmptyStatement)) { hasNonEmptyGlobalStatement = true; } if (!hasAwaitExpressions) { hasAwaitExpressions = SyntaxFacts.HasAwaitOperations(topLevelStatement); } if (!isIterator) { isIterator = SyntaxFacts.HasYieldOperations(topLevelStatement); } if (!hasReturnWithExpression) { hasReturnWithExpression = SyntaxFacts.HasReturnWithExpression(topLevelStatement); } } else if (!hasGlobalMembers && member.Kind() != SyntaxKind.IncompleteMember) { hasGlobalMembers = true; } } // wrap all global statements in a compilation unit into a simple program type: if (firstGlobalStatement is object) { var diagnostics = ImmutableArray<Diagnostic>.Empty; if (!hasNonEmptyGlobalStatement) { var bag = DiagnosticBag.GetInstance(); bag.Add(ErrorCode.ERR_SimpleProgramIsEmpty, ((EmptyStatementSyntax)firstGlobalStatement.Statement).SemicolonToken.GetLocation()); diagnostics = bag.ToReadOnlyAndFree(); } childrenBuilder.Add(CreateSimpleProgram(firstGlobalStatement, hasAwaitExpressions, isIterator, hasReturnWithExpression, diagnostics)); } // wrap all members that are defined in a namespace or compilation unit into an implicit type: if (hasGlobalMembers) { //The implicit class is not static and has no extensions SingleTypeDeclaration.TypeDeclarationFlags declFlags = SingleTypeDeclaration.TypeDeclarationFlags.None; var memberNames = GetNonTypeMemberNames(node, internalMembers, ref declFlags, skipGlobalStatements: acceptSimpleProgram); var container = _syntaxTree.GetReference(node); childrenBuilder.Add(CreateImplicitClass(memberNames, container, declFlags)); } return childrenBuilder.ToImmutableAndFree(); } private static SingleNamespaceOrTypeDeclaration CreateImplicitClass(BoxedMemberNames memberNames, SyntaxReference container, SingleTypeDeclaration.TypeDeclarationFlags declFlags) { return new SingleTypeDeclaration( kind: DeclarationKind.ImplicitClass, name: TypeSymbol.ImplicitTypeName, arity: 0, modifiers: DeclarationModifiers.Internal | DeclarationModifiers.Partial | DeclarationModifiers.Sealed, declFlags: declFlags, syntaxReference: container, nameLocation: new SourceLocation(container), memberNames: memberNames, children: ImmutableArray<SingleTypeDeclaration>.Empty, diagnostics: ImmutableArray<Diagnostic>.Empty, quickAttributes: QuickAttributes.None); } private static SingleNamespaceOrTypeDeclaration CreateSimpleProgram(GlobalStatementSyntax firstGlobalStatement, bool hasAwaitExpressions, bool isIterator, bool hasReturnWithExpression, ImmutableArray<Diagnostic> diagnostics) { var nameLocation = new SourceLocation(firstGlobalStatement.GetFirstToken()); if (nameLocation.SourceTree is null) { nameLocation = new SourceLocation(firstGlobalStatement.GetFirstToken(includeSkipped: true)); } Debug.Assert(nameLocation.SourceTree is not null); return new SingleTypeDeclaration( kind: DeclarationKind.Class, name: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, arity: 0, modifiers: DeclarationModifiers.Partial, declFlags: (hasAwaitExpressions ? SingleTypeDeclaration.TypeDeclarationFlags.HasAwaitExpressions : SingleTypeDeclaration.TypeDeclarationFlags.None) | (isIterator ? SingleTypeDeclaration.TypeDeclarationFlags.IsIterator : SingleTypeDeclaration.TypeDeclarationFlags.None) | (hasReturnWithExpression ? SingleTypeDeclaration.TypeDeclarationFlags.HasReturnWithExpression : SingleTypeDeclaration.TypeDeclarationFlags.None) | SingleTypeDeclaration.TypeDeclarationFlags.IsSimpleProgram, syntaxReference: firstGlobalStatement.SyntaxTree.GetReference(firstGlobalStatement.Parent), nameLocation: nameLocation, memberNames: s_emptyMemberNames, children: ImmutableArray<SingleTypeDeclaration>.Empty, diagnostics: diagnostics, quickAttributes: QuickAttributes.None); } /// <summary> /// Creates a root declaration that contains a Script class declaration (possibly in a namespace) and namespace declarations. /// Top-level declarations in script code are nested in Script class. /// </summary> private RootSingleNamespaceDeclaration CreateScriptRootDeclaration(CompilationUnitSyntax compilationUnit) { Debug.Assert(_syntaxTree.Options.Kind != SourceCodeKind.Regular); var members = compilationUnit.Members; var rootChildren = ArrayBuilder<SingleNamespaceOrTypeDeclaration>.GetInstance(); var scriptChildren = ArrayBuilder<SingleTypeDeclaration>.GetInstance(); foreach (var member in members) { var decl = Visit(member); if (decl != null) { // Although namespaces are not allowed in script code process them // here as if they were to improve error reporting. if (decl.Kind == DeclarationKind.Namespace) { rootChildren.Add(decl); } else { scriptChildren.Add((SingleTypeDeclaration)decl); } } } //Script class is not static and contains no extensions. SingleTypeDeclaration.TypeDeclarationFlags declFlags = SingleTypeDeclaration.TypeDeclarationFlags.None; var membernames = GetNonTypeMemberNames(compilationUnit, ((Syntax.InternalSyntax.CompilationUnitSyntax)(compilationUnit.Green)).Members, ref declFlags); rootChildren.Add( CreateScriptClass( compilationUnit, scriptChildren.ToImmutableAndFree(), membernames, declFlags)); return CreateRootSingleNamespaceDeclaration(compilationUnit, rootChildren.ToImmutableAndFree(), isForScript: true); } private static ImmutableArray<ReferenceDirective> GetReferenceDirectives(CompilationUnitSyntax compilationUnit) { IList<ReferenceDirectiveTriviaSyntax> directiveNodes = compilationUnit.GetReferenceDirectives( d => !d.File.ContainsDiagnostics && !string.IsNullOrEmpty(d.File.ValueText)); if (directiveNodes.Count == 0) { return ImmutableArray<ReferenceDirective>.Empty; } var directives = ArrayBuilder<ReferenceDirective>.GetInstance(directiveNodes.Count); foreach (var directiveNode in directiveNodes) { directives.Add(new ReferenceDirective(directiveNode.File.ValueText, new SourceLocation(directiveNode))); } return directives.ToImmutableAndFree(); } private SingleNamespaceOrTypeDeclaration CreateScriptClass( CompilationUnitSyntax parent, ImmutableArray<SingleTypeDeclaration> children, BoxedMemberNames memberNames, SingleTypeDeclaration.TypeDeclarationFlags declFlags) { Debug.Assert(parent.Kind() == SyntaxKind.CompilationUnit && _syntaxTree.Options.Kind != SourceCodeKind.Regular); // script type is represented by the parent node: var parentReference = _syntaxTree.GetReference(parent); var fullName = _scriptClassName.Split('.'); // Note: The symbol representing the merged declarations uses parentReference to enumerate non-type members. SingleNamespaceOrTypeDeclaration decl = new SingleTypeDeclaration( kind: _isSubmission ? DeclarationKind.Submission : DeclarationKind.Script, name: fullName.Last(), arity: 0, modifiers: DeclarationModifiers.Internal | DeclarationModifiers.Partial | DeclarationModifiers.Sealed, declFlags: declFlags, syntaxReference: parentReference, nameLocation: new SourceLocation(parentReference), memberNames: memberNames, children: children, diagnostics: ImmutableArray<Diagnostic>.Empty, quickAttributes: QuickAttributes.None); for (int i = fullName.Length - 2; i >= 0; i--) { decl = SingleNamespaceDeclaration.Create( name: fullName[i], hasUsings: false, hasExternAliases: false, syntaxReference: parentReference, nameLocation: new SourceLocation(parentReference), children: ImmutableArray.Create(decl), diagnostics: ImmutableArray<Diagnostic>.Empty); } return decl; } private static QuickAttributes GetQuickAttributes( SyntaxList<UsingDirectiveSyntax> usings, bool global) { var result = QuickAttributes.None; foreach (var directive in usings) { if (directive.Alias == null) { continue; } var isGlobal = directive.GlobalKeyword.Kind() != SyntaxKind.None; if (isGlobal != global) { continue; } if (directive.Name is not NameSyntax name) { continue; } result |= QuickAttributeHelpers.GetQuickAttributes(name.GetUnqualifiedName().Identifier.ValueText, inAttribute: false); } return result; } public override SingleNamespaceOrTypeDeclaration VisitCompilationUnit(CompilationUnitSyntax compilationUnit) { if (_syntaxTree.Options.Kind != SourceCodeKind.Regular) { return CreateScriptRootDeclaration(compilationUnit); } _nonGlobalAliasedQuickAttributes = GetNonGlobalAliasedQuickAttributes(compilationUnit); var children = VisitNamespaceChildren(compilationUnit, compilationUnit.Members, ((Syntax.InternalSyntax.CompilationUnitSyntax)(compilationUnit.Green)).Members); return CreateRootSingleNamespaceDeclaration(compilationUnit, children, isForScript: false); } private static QuickAttributes GetNonGlobalAliasedQuickAttributes(CompilationUnitSyntax compilationUnit) { var result = GetQuickAttributes(compilationUnit.Usings, global: false); foreach (var member in compilationUnit.Members) { if (member is BaseNamespaceDeclarationSyntax @namespace) { result |= GetNonGlobalAliasedQuickAttributes(@namespace); } } return result; } private static QuickAttributes GetNonGlobalAliasedQuickAttributes(BaseNamespaceDeclarationSyntax @namespace) { var result = GetQuickAttributes(@namespace.Usings, global: false); foreach (var member in @namespace.Members) { if (member is BaseNamespaceDeclarationSyntax child) { result |= GetNonGlobalAliasedQuickAttributes(child); } } return result; } private RootSingleNamespaceDeclaration CreateRootSingleNamespaceDeclaration(CompilationUnitSyntax compilationUnit, ImmutableArray<SingleNamespaceOrTypeDeclaration> children, bool isForScript) { bool hasUsings = false; bool hasGlobalUsings = false; bool reportedGlobalUsingOutOfOrder = false; var diagnostics = DiagnosticBag.GetInstance(); foreach (var directive in compilationUnit.Usings) { if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword)) { hasGlobalUsings = true; if (hasUsings && !reportedGlobalUsingOutOfOrder) { reportedGlobalUsingOutOfOrder = true; diagnostics.Add(ErrorCode.ERR_GlobalUsingOutOfOrder, directive.GlobalKeyword.GetLocation()); } } else { hasUsings = true; } } var globalAliasedQuickAttributes = GetQuickAttributes(compilationUnit.Usings, global: true); CheckFeatureAvailabilityForUsings(diagnostics, compilationUnit.Usings); CheckFeatureAvailabilityForExterns(diagnostics, compilationUnit.Externs); return new RootSingleNamespaceDeclaration( hasGlobalUsings: hasGlobalUsings, hasUsings: hasUsings, hasExternAliases: compilationUnit.Externs.Any(), treeNode: _syntaxTree.GetReference(compilationUnit), children: children, referenceDirectives: isForScript ? GetReferenceDirectives(compilationUnit) : ImmutableArray<ReferenceDirective>.Empty, hasAssemblyAttributes: compilationUnit.AttributeLists.Any(), diagnostics: diagnostics.ToReadOnlyAndFree(), globalAliasedQuickAttributes); } private static void CheckFeatureAvailabilityForUsings(DiagnosticBag diagnostics, SyntaxList<UsingDirectiveSyntax> usings) { foreach (var usingDirective in usings) { if (usingDirective.StaticKeyword != default) MessageID.IDS_FeatureUsingStatic.CheckFeatureAvailability(diagnostics, usingDirective, usingDirective.StaticKeyword.GetLocation()); if (usingDirective.GlobalKeyword != default) MessageID.IDS_FeatureGlobalUsing.CheckFeatureAvailability(diagnostics, usingDirective, usingDirective.GlobalKeyword.GetLocation()); } } private static void CheckFeatureAvailabilityForExterns(DiagnosticBag diagnostics, SyntaxList<ExternAliasDirectiveSyntax> externs) { foreach (var externAlias in externs) MessageID.IDS_FeatureExternAlias.CheckFeatureAvailability(diagnostics, externAlias, externAlias.ExternKeyword.GetLocation()); } public override SingleNamespaceOrTypeDeclaration VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) => this.VisitBaseNamespaceDeclaration(node); public override SingleNamespaceOrTypeDeclaration VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) => this.VisitBaseNamespaceDeclaration(node); private SingleNamespaceDeclaration VisitBaseNamespaceDeclaration(BaseNamespaceDeclarationSyntax node) { var children = VisitNamespaceChildren(node, node.Members, ((Syntax.InternalSyntax.BaseNamespaceDeclarationSyntax)node.Green).Members); bool hasUsings = node.Usings.Any(); bool hasExterns = node.Externs.Any(); NameSyntax name = node.Name; CSharpSyntaxNode currentNode = node; while (name is QualifiedNameSyntax dotted) { var ns = SingleNamespaceDeclaration.Create( name: dotted.Right.Identifier.ValueText, hasUsings: hasUsings, hasExternAliases: hasExterns, syntaxReference: _syntaxTree.GetReference(currentNode), nameLocation: new SourceLocation(dotted.Right), children: children, diagnostics: ImmutableArray<Diagnostic>.Empty); children = ImmutableArray.Create<SingleNamespaceOrTypeDeclaration>(ns); currentNode = name = dotted.Left; hasUsings = false; hasExterns = false; } var diagnostics = DiagnosticBag.GetInstance(); if (node is FileScopedNamespaceDeclarationSyntax) { MessageID.IDS_FeatureFileScopedNamespace.CheckFeatureAvailability(diagnostics, node, node.NamespaceKeyword.GetLocation()); if (node.Parent is FileScopedNamespaceDeclarationSyntax) { // Happens when user writes: // namespace A.B; // namespace X.Y; diagnostics.Add(ErrorCode.ERR_MultipleFileScopedNamespace, node.Name.GetLocation()); } else if (node.Parent is NamespaceDeclarationSyntax) { // Happens with: // // namespace A.B // { // namespace X.Y; diagnostics.Add(ErrorCode.ERR_FileScopedAndNormalNamespace, node.Name.GetLocation()); } else { // Happens with cases like: // // namespace A.B { } // namespace X.Y; // // or even // // class C { } // namespace X.Y; Debug.Assert(node.Parent is CompilationUnitSyntax); var compilationUnit = (CompilationUnitSyntax)node.Parent; if (node != compilationUnit.Members[0]) { diagnostics.Add(ErrorCode.ERR_FileScopedNamespaceNotBeforeAllMembers, node.Name.GetLocation()); } } } else { Debug.Assert(node is NamespaceDeclarationSyntax); // namespace X.Y; // namespace A.B { } if (node.Parent is FileScopedNamespaceDeclarationSyntax) { diagnostics.Add(ErrorCode.ERR_FileScopedAndNormalNamespace, node.Name.GetLocation()); } } if (ContainsGeneric(node.Name)) { // We're not allowed to have generics. diagnostics.Add(ErrorCode.ERR_UnexpectedGenericName, node.Name.GetLocation()); } if (ContainsAlias(node.Name)) { diagnostics.Add(ErrorCode.ERR_UnexpectedAliasedName, node.Name.GetLocation()); } if (node.AttributeLists.Count > 0) { diagnostics.Add(ErrorCode.ERR_BadModifiersOnNamespace, node.AttributeLists[0].GetLocation()); } if (node.Modifiers.Count > 0) { diagnostics.Add(ErrorCode.ERR_BadModifiersOnNamespace, node.Modifiers[0].GetLocation()); } foreach (var directive in node.Usings) { if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword)) { diagnostics.Add(ErrorCode.ERR_GlobalUsingInNamespace, directive.GlobalKeyword.GetLocation()); break; } } CheckFeatureAvailabilityForUsings(diagnostics, node.Usings); CheckFeatureAvailabilityForExterns(diagnostics, node.Externs); // NOTE: *Something* has to happen for alias-qualified names. It turns out that we // just grab the part after the colons (via GetUnqualifiedName, below). This logic // must be kept in sync with NamespaceSymbol.GetNestedNamespace. return SingleNamespaceDeclaration.Create( name: name.GetUnqualifiedName().Identifier.ValueText, hasUsings: hasUsings, hasExternAliases: hasExterns, syntaxReference: _syntaxTree.GetReference(currentNode), nameLocation: new SourceLocation(name), children: children, diagnostics: diagnostics.ToReadOnlyAndFree()); } private static bool ContainsAlias(NameSyntax name) { switch (name.Kind()) { case SyntaxKind.GenericName: return false; case SyntaxKind.AliasQualifiedName: return true; case SyntaxKind.QualifiedName: var qualifiedName = (QualifiedNameSyntax)name; return ContainsAlias(qualifiedName.Left); } return false; } private static bool ContainsGeneric(NameSyntax name) { switch (name.Kind()) { case SyntaxKind.GenericName: return true; case SyntaxKind.AliasQualifiedName: return ContainsGeneric(((AliasQualifiedNameSyntax)name).Name); case SyntaxKind.QualifiedName: var qualifiedName = (QualifiedNameSyntax)name; return ContainsGeneric(qualifiedName.Left) || ContainsGeneric(qualifiedName.Right); } return false; } public override SingleNamespaceOrTypeDeclaration VisitClassDeclaration(ClassDeclarationSyntax node) { return VisitTypeDeclaration(node, DeclarationKind.Class); } public override SingleNamespaceOrTypeDeclaration VisitStructDeclaration(StructDeclarationSyntax node) { var declarationKind = node.Kind() switch { SyntaxKind.StructDeclaration => DeclarationKind.Struct, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()) }; return VisitTypeDeclaration(node, declarationKind); } public override SingleNamespaceOrTypeDeclaration VisitUnionDeclaration(UnionDeclarationSyntax node) { var declarationKind = node.Kind() switch { SyntaxKind.UnionDeclaration => DeclarationKind.Union, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()) }; return VisitTypeDeclaration(node, declarationKind); } public override SingleNamespaceOrTypeDeclaration VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) { return VisitTypeDeclaration(node, DeclarationKind.Interface); } public override SingleNamespaceOrTypeDeclaration VisitRecordDeclaration(RecordDeclarationSyntax node) { var declarationKind = node.Kind() switch { SyntaxKind.RecordDeclaration => DeclarationKind.Record, SyntaxKind.RecordStructDeclaration => DeclarationKind.RecordStruct, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()) }; return VisitTypeDeclaration(node, declarationKind); } public override SingleNamespaceOrTypeDeclaration VisitExtensionBlockDeclaration(ExtensionBlockDeclarationSyntax node) { return VisitTypeDeclaration(node, DeclarationKind.Extension); } private SingleTypeDeclaration VisitTypeDeclaration(TypeDeclarationSyntax node, DeclarationKind kind) { var declFlags = node.AttributeLists.Any() ? SingleTypeDeclaration.TypeDeclarationFlags.HasAnyAttributes : SingleTypeDeclaration.TypeDeclarationFlags.None; if (node.BaseList != null) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasBaseDeclarations; } var diagnostics = DiagnosticBag.GetInstance(); if (node.Arity == 0) { Symbol.ReportErrorIfHasConstraints(node.ConstraintClauses, diagnostics); } var hasPrimaryCtor = node.ParameterList != null && node is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax; if (hasPrimaryCtor) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasAnyNontypeMembers; declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasPrimaryConstructor; foreach (var attributeListSyntax in node.AttributeLists) { if (attributeListSyntax.Target?.Identifier.ToAttributeLocation() == AttributeLocation.Method) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.AnyMemberHasAttributes; break; } } } else if (node.Kind() is SyntaxKind.UnionDeclaration) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasAnyNontypeMembers; } var memberNames = GetNonTypeMemberNames( node, ((Syntax.InternalSyntax.TypeDeclarationSyntax)(node.Green)).Members, ref declFlags, hasPrimaryCtor: hasPrimaryCtor); // If we have `record class` or `record struct` check that this is supported in the language. Note: we don't // have to do any check for the simple `record` case as the parser itself would never produce such a node // unless the language version was sufficient (since it actually will not produce the node at all on // previous versions). if (node is RecordDeclarationSyntax record) { if (record.ClassOrStructKeyword.Kind() != SyntaxKind.None) { MessageID.IDS_FeatureRecordStructs.CheckFeatureAvailability(diagnostics, record, record.ClassOrStructKeyword.GetLocation()); } } else if (node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.InterfaceDeclaration) { if (node.ParameterList != null) { if (node.Kind() is SyntaxKind.InterfaceDeclaration) { diagnostics.Add(ErrorCode.ERR_UnexpectedParameterList, node.ParameterList.GetLocation()); } else { MessageID.IDS_FeaturePrimaryConstructors.CheckFeatureAvailability(diagnostics, node.ParameterList); } } else if (node.OpenBraceToken == default && node.CloseBraceToken == default && node.SemicolonToken != default) { MessageID.IDS_FeaturePrimaryConstructors.CheckFeatureAvailability(diagnostics, node, node.SemicolonToken.GetLocation()); } } else if (node.Kind() is SyntaxKind.UnionDeclaration) { MessageID.IDS_FeatureUnions.CheckFeatureAvailability(diagnostics, node, node.Keyword.GetLocation()); } var modifiers = node.Modifiers.ToDeclarationModifiers(isForTypeDeclaration: true, diagnostics: diagnostics); var quickAttributes = GetQuickAttributes(node.AttributeLists); foreach (var modifier in node.Modifiers) { if (modifier.IsKind(SyntaxKind.StaticKeyword) && kind == DeclarationKind.Class) { MessageID.IDS_FeatureStaticClasses.CheckFeatureAvailability(diagnostics, node, modifier.GetLocation()); } else if (modifier.IsKind(SyntaxKind.ReadOnlyKeyword) && kind is DeclarationKind.Struct or DeclarationKind.RecordStruct) { MessageID.IDS_FeatureReadOnlyStructs.CheckFeatureAvailability(diagnostics, node, modifier.GetLocation()); } else if (modifier.IsKind(SyntaxKind.RefKeyword) && kind is DeclarationKind.Struct or DeclarationKind.RecordStruct) { MessageID.IDS_FeatureRefStructs.CheckFeatureAvailability(diagnostics, node, modifier.GetLocation()); } } bool isExtension = kind == DeclarationKind.Extension; return new SingleTypeDeclaration( kind: kind, name: isExtension ? "" : node.Identifier.ValueText, arity: node.Arity, modifiers: modifiers, declFlags: declFlags, syntaxReference: _syntaxTree.GetReference(node), nameLocation: new SourceLocation(isExtension ? node.Keyword : node.Identifier), memberNames: memberNames, children: VisitTypeChildren(node), diagnostics: diagnostics.ToReadOnlyAndFree(), _nonGlobalAliasedQuickAttributes | quickAttributes); } private ImmutableArray<SingleTypeDeclaration> VisitTypeChildren(TypeDeclarationSyntax node) { if (node.Members.Count == 0) { return ImmutableArray<SingleTypeDeclaration>.Empty; } var children = ArrayBuilder<SingleTypeDeclaration>.GetInstance(); foreach (var member in node.Members) { var typeDecl = Visit(member) as SingleTypeDeclaration; children.AddIfNotNull(typeDecl); } return children.ToImmutableAndFree(); } public override SingleNamespaceOrTypeDeclaration VisitDelegateDeclaration(DelegateDeclarationSyntax node) { var declFlags = node.AttributeLists.Any() ? SingleTypeDeclaration.TypeDeclarationFlags.HasAnyAttributes : SingleTypeDeclaration.TypeDeclarationFlags.None; var diagnostics = DiagnosticBag.GetInstance(); if (node.Arity == 0) { Symbol.ReportErrorIfHasConstraints(node.ConstraintClauses, diagnostics); } declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasAnyNontypeMembers; var modifiers = node.Modifiers.ToDeclarationModifiers(isForTypeDeclaration: true, diagnostics: diagnostics); var quickAttributes = DeclarationTreeBuilder.GetQuickAttributes(node.AttributeLists); return new SingleTypeDeclaration( kind: DeclarationKind.Delegate, name: node.Identifier.ValueText, arity: node.Arity, modifiers: modifiers, declFlags: declFlags, syntaxReference: _syntaxTree.GetReference(node), nameLocation: new SourceLocation(node.Identifier), memberNames: s_emptyMemberNames, children: ImmutableArray<SingleTypeDeclaration>.Empty, diagnostics: diagnostics.ToReadOnlyAndFree(), _nonGlobalAliasedQuickAttributes | quickAttributes); } public override SingleNamespaceOrTypeDeclaration VisitEnumDeclaration(EnumDeclarationSyntax node) { var members = node.Members; SingleTypeDeclaration.TypeDeclarationFlags declFlags = node.AttributeLists.Any() ? SingleTypeDeclaration.TypeDeclarationFlags.HasAnyAttributes : SingleTypeDeclaration.TypeDeclarationFlags.None; if (node.BaseList != null) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasBaseDeclarations; } var memberNames = GetEnumMemberNames(node, ref declFlags); var diagnostics = DiagnosticBag.GetInstance(); var modifiers = node.Modifiers.ToDeclarationModifiers(isForTypeDeclaration: true, diagnostics: diagnostics); var quickAttributes = DeclarationTreeBuilder.GetQuickAttributes(node.AttributeLists); if (node.OpenBraceToken == default && node.CloseBraceToken == default && node.SemicolonToken != default) { MessageID.IDS_FeaturePrimaryConstructors.CheckFeatureAvailability(diagnostics, node, node.SemicolonToken.GetLocation()); } return new SingleTypeDeclaration( kind: DeclarationKind.Enum, name: node.Identifier.ValueText, arity: 0, modifiers: modifiers, declFlags: declFlags, syntaxReference: _syntaxTree.GetReference(node), nameLocation: new SourceLocation(node.Identifier), memberNames: memberNames, children: ImmutableArray<SingleTypeDeclaration>.Empty, diagnostics: diagnostics.ToReadOnlyAndFree(), _nonGlobalAliasedQuickAttributes | quickAttributes); } private static QuickAttributes GetQuickAttributes(SyntaxList<AttributeListSyntax> attributeLists) { var result = QuickAttributes.None; foreach (var attributeList in attributeLists) { foreach (var attribute in attributeList.Attributes) { result |= QuickAttributeHelpers.GetQuickAttributes(attribute.Name.GetUnqualifiedName().Identifier.ValueText, inAttribute: true); } } return result; } private BoxedMemberNames GetEnumMemberNames( EnumDeclarationSyntax enumDeclaration, ref SingleTypeDeclaration.TypeDeclarationFlags declFlags) { var members = enumDeclaration.Members; var cnt = members.Count; if (cnt != 0) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasAnyNontypeMembers; } bool anyMemberHasAttributes = members.Any(static m => m.AttributeLists.Any()); if (anyMemberHasAttributes) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.AnyMemberHasAttributes; } return GetOrComputeMemberNames( enumDeclaration, static (memberNamesBuilder, members) => { foreach (var member in members) memberNamesBuilder.Add(member.Identifier.ValueText); }, members); } private BoxedMemberNames GetNonTypeMemberNames( CSharpSyntaxNode parent, CoreInternalSyntax.SyntaxList<Syntax.InternalSyntax.MemberDeclarationSyntax> members, ref SingleTypeDeclaration.TypeDeclarationFlags declFlags, bool skipGlobalStatements = false, bool hasPrimaryCtor = false) { bool anyMethodHadExtensionSyntax = false; bool anyExtensionDeclarationSyntax = false; bool anyMemberHasAttributes = false; bool anyNonTypeMembers = false; bool anyRequiredMembers = false; foreach (var member in members) { if (!anyNonTypeMembers && HasAnyNonTypeMemberNames(member, skipGlobalStatements)) { anyNonTypeMembers = true; } // Check to see if any method contains a 'this' modifier on its first parameter. // This data is used to determine if a type needs to have its members materialized // as part of extension method lookup. if (!anyMethodHadExtensionSyntax && CheckMethodMemberForExtensionSyntax(member)) { anyMethodHadExtensionSyntax = true; } if (!anyExtensionDeclarationSyntax && member.Kind == SyntaxKind.ExtensionBlockDeclaration) { anyExtensionDeclarationSyntax = true; } if (!anyMemberHasAttributes && CheckMemberForAttributes(member)) { anyMemberHasAttributes = true; } if (!anyRequiredMembers && checkPropertyOrFieldMemberForRequiredModifier(member)) { anyRequiredMembers = true; } // Break early if we've hit all sorts of members. if (anyNonTypeMembers && anyMethodHadExtensionSyntax && anyExtensionDeclarationSyntax && anyMemberHasAttributes && anyRequiredMembers) { break; } } if (anyMethodHadExtensionSyntax) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.AnyMemberHasExtensionMethodSyntax; } if (anyExtensionDeclarationSyntax) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.AnyExtensionDeclarationSyntax; } if (anyMemberHasAttributes) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.AnyMemberHasAttributes; } if (anyNonTypeMembers) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasAnyNontypeMembers; } if (anyRequiredMembers) { declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasRequiredMembers; } return GetOrComputeMemberNames( parent, static (memberNamesBuilder, tuple) => { if (tuple.hasPrimaryCtor) memberNamesBuilder.Add(WellKnownMemberNames.InstanceConstructorName); foreach (var member in tuple.members) AddNonTypeMemberNames(member, memberNamesBuilder); }, (members, hasPrimaryCtor)); static bool checkPropertyOrFieldMemberForRequiredModifier(Syntax.InternalSyntax.CSharpSyntaxNode member) { var modifiers = member switch { Syntax.InternalSyntax.FieldDeclarationSyntax fieldDeclaration => fieldDeclaration.Modifiers, Syntax.InternalSyntax.PropertyDeclarationSyntax propertyDeclaration => propertyDeclaration.Modifiers, _ => default }; return modifiers.Any((int)SyntaxKind.RequiredKeyword); } } private BoxedMemberNames GetOrComputeMemberNames<TData>( SyntaxNode parent, Action<HashSet<string>, TData> addMemberNames, TData data) { // Compute the member names, then always ensure we move our current type index pointer forward. var result = getOrComputeMemberNamesWorker(); _currentTypeIndex++; return result; BoxedMemberNames getOrComputeMemberNamesWorker() { // Lookup in the cache first. var greenNode = parent.Green; if (!s_nodeToMemberNames.TryGetValue(greenNode, out BoxedMemberNames memberNames)) { // If not there, make a fresh set, and add all the member names to it. var memberNamesBuilder = PooledHashSet<string>.GetInstance(); addMemberNames(memberNamesBuilder, data); // Try to obtain the prior member names computed for the corresponding type decl (in lexicographic order) // from the prior version of this tree. var previousMemberNames = _currentTypeIndex < _previousMemberNames.Count && _previousMemberNames[_currentTypeIndex].TryGetTarget(out var previousNames) ? previousNames : s_emptyMemberNames; // If the members names are the same as the prior computed ones, then use that instead. memberNames = previousMemberNames.Value.Count == memberNamesBuilder.Count && previousMemberNames.Value.SetEquals(memberNamesBuilder) ? previousMemberNames : memberNamesBuilder.Count == 0 ? s_emptyMemberNames : new BoxedMemberNames(ImmutableSegmentedHashSet.CreateRange(memberNamesBuilder)); memberNamesBuilder.Free(); // Store the names in the cache to be found in the next compilation update. But don't bother caching // when there are no member names (common for most compilation units). If another thread beat us to // this, then use their values instead. if (memberNames.Value.Count > 0) { // Avoid unnecessary allocation (as CWT on NET6 and prior has no non-allocating way to try to // get an existing value, and only add if that particular KVP is not already present). using PooledDelegates.Releaser _ = PooledDelegates.GetPooledCreateValueCallback( static (GreenNode _, BoxedMemberNames memberNames) => memberNames, memberNames, out var pooledCallback); memberNames = s_nodeToMemberNames.GetValue(greenNode, pooledCallback); } } return memberNames; } } private static bool CheckMethodMemberForExtensionSyntax(Syntax.InternalSyntax.CSharpSyntaxNode member) { if (member.Kind == SyntaxKind.MethodDeclaration) { var methodDecl = (Syntax.InternalSyntax.MethodDeclarationSyntax)member; var paramList = methodDecl.parameterList; if (paramList != null) { var parameters = paramList.Parameters; if (parameters.Count != 0) { var firstParameter = parameters[0]; foreach (var modifier in firstParameter.Modifiers) { if (modifier.Kind == SyntaxKind.ThisKeyword) { return true; } } } } } return false; } private static bool CheckMemberForAttributes(Syntax.InternalSyntax.CSharpSyntaxNode member) { switch (member.Kind) { case SyntaxKind.CompilationUnit: return (((Syntax.InternalSyntax.CompilationUnitSyntax)member).AttributeLists).Any(); case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.UnionDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.RecordDeclaration: case SyntaxKind.RecordStructDeclaration: case SyntaxKind.ExtensionBlockDeclaration: return (((Syntax.InternalSyntax.BaseTypeDeclarationSyntax)member).AttributeLists).Any(); case SyntaxKind.DelegateDeclaration: return (((Syntax.InternalSyntax.DelegateDeclarationSyntax)member).AttributeLists).Any(); case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: return (((Syntax.InternalSyntax.BaseFieldDeclarationSyntax)member).AttributeLists).Any(); case SyntaxKind.MethodDeclaration: case SyntaxKind.OperatorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.DestructorDeclaration: return (((Syntax.InternalSyntax.BaseMethodDeclarationSyntax)member).AttributeLists).Any(); case SyntaxKind.PropertyDeclaration: case SyntaxKind.EventDeclaration: case SyntaxKind.IndexerDeclaration: var baseProp = (Syntax.InternalSyntax.BasePropertyDeclarationSyntax)member; bool hasAttributes = baseProp.AttributeLists.Any(); if (!hasAttributes && baseProp.AccessorList != null) { foreach (var accessor in baseProp.AccessorList.Accessors) { hasAttributes |= accessor.AttributeLists.Any(); } } return hasAttributes; } return false; } private static void AddNonTypeMemberNames( Syntax.InternalSyntax.CSharpSyntaxNode member, HashSet<string> set) { switch (member.Kind) { case SyntaxKind.FieldDeclaration: CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList<Syntax.InternalSyntax.VariableDeclaratorSyntax> fieldDeclarators = ((Syntax.InternalSyntax.FieldDeclarationSyntax)member).Declaration.Variables; int numFieldDeclarators = fieldDeclarators.Count; for (int i = 0; i < numFieldDeclarators; i++) { set.Add(fieldDeclarators[i].Identifier.ValueText); } break; case SyntaxKind.EventFieldDeclaration: CoreInternalSyntax.SeparatedSyntaxList<Syntax.InternalSyntax.VariableDeclaratorSyntax> eventDeclarators = ((Syntax.InternalSyntax.EventFieldDeclarationSyntax)member).Declaration.Variables; int numEventDeclarators = eventDeclarators.Count; for (int i = 0; i < numEventDeclarators; i++) { set.Add(eventDeclarators[i].Identifier.ValueText); } break; case SyntaxKind.MethodDeclaration: // Member names are exposed via NamedTypeSymbol.MemberNames and are used primarily // as an acid test to determine whether a more in-depth search of a type is worthwhile. // We decided that it was reasonable to exclude explicit interface implementations // from the list of member names. var methodDecl = (Syntax.InternalSyntax.MethodDeclarationSyntax)member; if (methodDecl.ExplicitInterfaceSpecifier == null) { set.Add(methodDecl.Identifier.ValueText); } break; case SyntaxKind.PropertyDeclaration: // Handle in the same way as explicit method implementations var propertyDecl = (Syntax.InternalSyntax.PropertyDeclarationSyntax)member; if (propertyDecl.ExplicitInterfaceSpecifier == null) { set.Add(propertyDecl.Identifier.ValueText); } break; case SyntaxKind.EventDeclaration: // Handle in the same way as explicit method implementations var eventDecl = (Syntax.InternalSyntax.EventDeclarationSyntax)member; if (eventDecl.ExplicitInterfaceSpecifier == null) { set.Add(eventDecl.Identifier.ValueText); } break; case SyntaxKind.ConstructorDeclaration: set.Add(((Syntax.InternalSyntax.ConstructorDeclarationSyntax)member).Modifiers.Any((int)SyntaxKind.StaticKeyword) ? WellKnownMemberNames.StaticConstructorName : WellKnownMemberNames.InstanceConstructorName); break; case SyntaxKind.DestructorDeclaration: set.Add(WellKnownMemberNames.DestructorName); break; case SyntaxKind.IndexerDeclaration: set.Add(WellKnownMemberNames.Indexer); break; case SyntaxKind.OperatorDeclaration: { // Handle in the same way as explicit method implementations var opDecl = (Syntax.InternalSyntax.OperatorDeclarationSyntax)member; if (opDecl.ExplicitInterfaceSpecifier == null) { var name = OperatorFacts.OperatorNameFromDeclaration(opDecl); set.Add(name); } } break; case SyntaxKind.ConversionOperatorDeclaration: { // Handle in the same way as explicit method implementations var opDecl = (Syntax.InternalSyntax.ConversionOperatorDeclarationSyntax)member; if (opDecl.ExplicitInterfaceSpecifier == null) { var name = OperatorFacts.OperatorNameFromDeclaration(opDecl); set.Add(name); } } break; } } private static bool HasAnyNonTypeMemberNames( Syntax.InternalSyntax.CSharpSyntaxNode member, bool skipGlobalStatements) { switch (member.Kind) { case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.EventDeclaration: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.DestructorDeclaration: case SyntaxKind.IndexerDeclaration: case SyntaxKind.OperatorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: return true; case SyntaxKind.GlobalStatement: return !skipGlobalStatements; } return false; } } }