|
// 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.Enum or
DeclarationKind.Script or
DeclarationKind.Submission or
DeclarationKind.ImplicitClass or
DeclarationKind.Record or
DeclarationKind.RecordStruct => 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)
{
return VisitTypeDeclaration(node, DeclarationKind.Struct);
}
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);
}
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;
}
}
}
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());
}
}
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());
}
}
return new SingleTypeDeclaration(
kind: kind,
name: node.Identifier.ValueText,
arity: node.Arity,
modifiers: modifiers,
declFlags: declFlags,
syntaxReference: _syntaxTree.GetReference(node),
nameLocation: new SourceLocation(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 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 (!anyMemberHasAttributes && CheckMemberForAttributes(member))
{
anyMemberHasAttributes = true;
}
if (!anyRequiredMembers && checkPropertyOrFieldMemberForRequiredModifier(member))
{
anyRequiredMembers = true;
}
// Break early if we've hit all sorts of members.
if (anyNonTypeMembers && anyMethodHadExtensionSyntax && anyMemberHasAttributes && anyRequiredMembers)
{
break;
}
}
if (anyMethodHadExtensionSyntax)
{
declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.AnyMemberHasExtensionMethodSyntax;
}
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.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.RecordDeclaration:
case SyntaxKind.RecordStructDeclaration:
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;
}
}
}
|