|
// 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.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Extensions;
internal static partial class SemanticModelExtensions
{
public static IEnumerable<ITypeSymbol> LookupTypeRegardlessOfArity(
this SemanticModel semanticModel,
SyntaxToken name,
CancellationToken cancellationToken)
{
if (name.Parent is ExpressionSyntax expression)
{
var results = semanticModel.LookupName(expression, cancellationToken: cancellationToken);
if (results.Length > 0)
{
return results.OfType<ITypeSymbol>();
}
}
return [];
}
public static ImmutableArray<ISymbol> LookupName(
this SemanticModel semanticModel,
SyntaxToken name,
CancellationToken cancellationToken)
{
if (name.Parent is ExpressionSyntax expression)
{
return semanticModel.LookupName(expression, cancellationToken);
}
return [];
}
/// <summary>
/// Decomposes a name or member access expression into its component parts.
/// </summary>
/// <param name="expression">The name or member access expression.</param>
/// <param name="qualifier">The qualifier (or left-hand-side) of the name expression. This may be null if there is no qualifier.</param>
/// <param name="name">The name of the expression.</param>
/// <param name="arity">The number of generic type parameters.</param>
private static void DecomposeName(ExpressionSyntax expression, out ExpressionSyntax qualifier, out string name, out int arity)
{
switch (expression.Kind())
{
case SyntaxKind.SimpleMemberAccessExpression:
case SyntaxKind.PointerMemberAccessExpression:
var max = (MemberAccessExpressionSyntax)expression;
qualifier = max.Expression;
name = max.Name.Identifier.ValueText;
arity = max.Name.Arity;
break;
case SyntaxKind.QualifiedName:
var qn = (QualifiedNameSyntax)expression;
qualifier = qn.Left;
name = qn.Right.Identifier.ValueText;
arity = qn.Arity;
break;
case SyntaxKind.AliasQualifiedName:
var aq = (AliasQualifiedNameSyntax)expression;
qualifier = aq.Alias;
name = aq.Name.Identifier.ValueText;
arity = aq.Name.Arity;
break;
case SyntaxKind.GenericName:
var gx = (GenericNameSyntax)expression;
qualifier = null;
name = gx.Identifier.ValueText;
arity = gx.Arity;
break;
case SyntaxKind.IdentifierName:
var nx = (IdentifierNameSyntax)expression;
qualifier = null;
name = nx.Identifier.ValueText;
arity = 0;
break;
default:
qualifier = null;
name = null;
arity = 0;
break;
}
}
public static ImmutableArray<ISymbol> LookupName(
this SemanticModel semanticModel,
ExpressionSyntax expression,
CancellationToken cancellationToken)
{
var expr = SyntaxFactory.GetStandaloneExpression(expression);
DecomposeName(expr, out var qualifier, out var name, out _);
INamespaceOrTypeSymbol symbol = null;
if (qualifier != null)
{
var typeInfo = semanticModel.GetTypeInfo(qualifier, cancellationToken);
var symbolInfo = semanticModel.GetSymbolInfo(qualifier, cancellationToken);
if (typeInfo.Type != null)
{
symbol = typeInfo.Type;
}
else if (symbolInfo.Symbol != null)
{
symbol = symbolInfo.Symbol as INamespaceOrTypeSymbol;
}
}
return semanticModel.LookupSymbols(expr.SpanStart, container: symbol, name: name, includeReducedExtensionMethods: true);
}
public static SymbolInfo GetSymbolInfo(this SemanticModel semanticModel, SyntaxToken token)
{
if (!CanBindToken(token))
{
return default;
}
switch (token.Parent)
{
case ExpressionSyntax expression:
return semanticModel.GetSymbolInfo(expression);
case AttributeSyntax attribute:
return semanticModel.GetSymbolInfo(attribute);
case ConstructorInitializerSyntax constructorInitializer:
return semanticModel.GetSymbolInfo(constructorInitializer);
}
return default;
}
private static bool CanBindToken(SyntaxToken token)
{
// Add more token kinds if necessary;
switch (token.Kind())
{
case SyntaxKind.CommaToken:
case SyntaxKind.DelegateKeyword:
return false;
}
return true;
}
public static ISet<INamespaceSymbol> GetUsingNamespacesInScope(this SemanticModel semanticModel, SyntaxNode location)
{
// Avoiding linq here for perf reasons. This is used heavily in the AddImport service
var result = new HashSet<INamespaceSymbol>();
foreach (var @using in location.GetEnclosingUsingDirectives())
{
if (@using.Alias == null)
{
Contract.ThrowIfNull(@using.NamespaceOrType);
var symbolInfo = semanticModel.GetSymbolInfo(@using.NamespaceOrType);
if (symbolInfo.Symbol != null && symbolInfo.Symbol.Kind == SymbolKind.Namespace)
{
result ??= [];
result.Add((INamespaceSymbol)symbolInfo.Symbol);
}
}
}
return result;
}
public static Accessibility DetermineAccessibilityConstraint(
this SemanticModel semanticModel,
TypeSyntax type,
CancellationToken cancellationToken)
{
if (type == null)
{
return Accessibility.Private;
}
type = GetOutermostType(type);
// Interesting cases based on 3.5.4 Accessibility constraints in the language spec.
// If any of the below hold, then we will override the default accessibility if the
// constraint wants the type to be more accessible. i.e. if by default we generate
// 'internal', but a constraint makes us 'public', then be public.
// 1) The direct base class of a class type must be at least as accessible as the
// class type itself.
//
// 2) The explicit base interfaces of an interface type must be at least as accessible
// as the interface type itself.
if (type != null)
{
if (type.Parent is BaseTypeSyntax baseType &&
baseType.Parent is BaseListSyntax baseList &&
baseType.Type == type)
{
var containingType = semanticModel.GetDeclaredSymbol(type.GetAncestor<BaseTypeDeclarationSyntax>(), cancellationToken);
if (containingType != null && containingType.TypeKind == TypeKind.Interface)
{
return containingType.DeclaredAccessibility;
}
else if (baseList.Types[0] == type.Parent)
{
return containingType.DeclaredAccessibility;
}
}
}
// 4) The type of a constant must be at least as accessible as the constant itself.
// 5) The type of a field must be at least as accessible as the field itself.
if (type?.Parent is VariableDeclarationSyntax variableDeclaration &&
variableDeclaration.IsParentKind(SyntaxKind.FieldDeclaration))
{
return semanticModel.GetDeclaredSymbol(
variableDeclaration.Variables[0], cancellationToken).DeclaredAccessibility;
}
// Also do the same check if we are in an object creation expression
if (type.IsParentKind(SyntaxKind.ObjectCreationExpression) &&
type.Parent.IsParentKind(SyntaxKind.EqualsValueClause) &&
type.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclarator) &&
type.Parent.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclaration, out variableDeclaration) &&
variableDeclaration.IsParentKind(SyntaxKind.FieldDeclaration))
{
return semanticModel.GetDeclaredSymbol(
variableDeclaration.Variables[0], cancellationToken).DeclaredAccessibility;
}
// 3) The return type of a delegate type must be at least as accessible as the
// delegate type itself.
// 6) The return type of a method must be at least as accessible as the method
// itself.
// 7) The type of a property must be at least as accessible as the property itself.
// 8) The type of an event must be at least as accessible as the event itself.
// 9) The type of an indexer must be at least as accessible as the indexer itself.
// 10) The return type of an operator must be at least as accessible as the operator
// itself.
if (type.Parent.Kind()
is SyntaxKind.DelegateDeclaration
or SyntaxKind.MethodDeclaration
or SyntaxKind.PropertyDeclaration
or SyntaxKind.EventDeclaration
or SyntaxKind.IndexerDeclaration
or SyntaxKind.OperatorDeclaration)
{
return semanticModel.GetDeclaredSymbol(
type.Parent, cancellationToken).DeclaredAccessibility;
}
// 3) The parameter types of a delegate type must be at least as accessible as the
// delegate type itself.
// 6) The parameter types of a method must be at least as accessible as the method
// itself.
// 9) The parameter types of an indexer must be at least as accessible as the
// indexer itself.
// 10) The parameter types of an operator must be at least as accessible as the
// operator itself.
// 11) The parameter types of an instance constructor must be at least as accessible
// as the instance constructor itself.
if (type.IsParentKind(SyntaxKind.Parameter) && type.Parent.IsParentKind(SyntaxKind.ParameterList))
{
if (type.Parent.Parent.Parent?.Kind()
is SyntaxKind.DelegateDeclaration
or SyntaxKind.MethodDeclaration
or SyntaxKind.IndexerDeclaration
or SyntaxKind.OperatorDeclaration)
{
return semanticModel.GetDeclaredSymbol(
type.Parent.Parent.Parent, cancellationToken).DeclaredAccessibility;
}
if (type.Parent.Parent.IsParentKind(SyntaxKind.ConstructorDeclaration))
{
var symbol = semanticModel.GetDeclaredSymbol(type.Parent.Parent.Parent, cancellationToken);
if (!symbol.IsStatic)
{
return symbol.DeclaredAccessibility;
}
}
}
// 8) The type of an event must be at least as accessible as the event itself.
if (type.IsParentKind(SyntaxKind.VariableDeclaration, out variableDeclaration) &&
variableDeclaration.IsParentKind(SyntaxKind.EventFieldDeclaration))
{
var symbol = semanticModel.GetDeclaredSymbol(variableDeclaration.Variables[0], cancellationToken);
if (symbol != null)
{
return symbol.DeclaredAccessibility;
}
}
// Type constraint must be at least as accessible as the declaring member (class, interface, delegate, method)
if (type.IsParentKind(SyntaxKind.TypeConstraint))
{
return AllContainingTypesArePublicOrProtected(semanticModel, type, cancellationToken)
? Accessibility.Public
: Accessibility.Internal;
}
return Accessibility.Private;
}
public static bool AllContainingTypesArePublicOrProtected(
this SemanticModel semanticModel,
TypeSyntax type,
CancellationToken cancellationToken)
{
if (type == null)
{
return false;
}
var typeDeclarations = type.GetAncestors<TypeDeclarationSyntax>();
foreach (var typeDeclaration in typeDeclarations)
{
var symbol = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
if (symbol.DeclaredAccessibility is Accessibility.Private or
Accessibility.ProtectedAndInternal or
Accessibility.Internal)
{
return false;
}
}
return true;
}
private static TypeSyntax GetOutermostType(TypeSyntax type)
=> type.GetAncestorsOrThis<TypeSyntax>().Last();
/// <summary>
/// Given an expression node, tries to generate an appropriate name that can be used for
/// that expression.
/// </summary>
public static string GenerateNameForExpression(
this SemanticModel semanticModel, ExpressionSyntax expression,
bool capitalize, CancellationToken cancellationToken)
{
// Try to find a usable name node that we can use to name the
// parameter. If we have an expression that has a name as part of it
// then we try to use that part.
var current = expression;
while (true)
{
current = current.WalkDownParentheses();
if (current is IdentifierNameSyntax identifierName)
{
return identifierName.Identifier.ValueText.ToCamelCase();
}
else if (current is MemberAccessExpressionSyntax memberAccess)
{
return memberAccess.Name.Identifier.ValueText.ToCamelCase();
}
else if (current is MemberBindingExpressionSyntax memberBinding)
{
return memberBinding.Name.Identifier.ValueText.ToCamelCase();
}
else if (current is ConditionalAccessExpressionSyntax conditionalAccess)
{
current = conditionalAccess.WhenNotNull;
}
else if (current is CastExpressionSyntax castExpression)
{
current = castExpression.Expression;
}
else if (current is DeclarationExpressionSyntax decl)
{
if (decl.Designation is not SingleVariableDesignationSyntax name)
{
break;
}
return name.Identifier.ValueText.ToCamelCase();
}
else if (current.Parent is ForEachStatementSyntax foreachStatement &&
foreachStatement.Expression == expression)
{
var word = foreachStatement.Identifier.ValueText.ToCamelCase();
return CodeAnalysis.Shared.Extensions.SemanticModelExtensions.Pluralize(word);
}
else
{
break;
}
}
// there was nothing in the expression to signify a name. If we're in an argument
// location, then try to choose a name based on the argument name.
var argumentName = TryGenerateNameForArgumentExpression(
semanticModel, expression, cancellationToken);
if (argumentName != null)
{
return capitalize ? argumentName.ToPascalCase() : argumentName.ToCamelCase();
}
// Otherwise, figure out the type of the expression and generate a name from that
// instead.
var info = semanticModel.GetTypeInfo(expression, cancellationToken);
if (info.Type == null)
{
return CodeAnalysis.Shared.Extensions.ITypeSymbolExtensions.DefaultParameterName;
}
return semanticModel.GenerateNameFromType(info.Type, CSharpSyntaxFacts.Instance, capitalize);
}
private static string TryGenerateNameForArgumentExpression(
SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken)
{
var topExpression = expression.WalkUpParentheses();
if (topExpression?.Parent is ArgumentSyntax argument)
{
if (argument.NameColon != null)
{
return argument.NameColon.Name.Identifier.ValueText;
}
if (argument.Parent is BaseArgumentListSyntax argumentList)
{
var index = argumentList.Arguments.IndexOf(argument);
if (semanticModel.GetSymbolInfo(argumentList.Parent, cancellationToken).Symbol is IMethodSymbol member && index < member.Parameters.Length)
{
var parameter = member.Parameters[index];
if (parameter.Type.OriginalDefinition.TypeKind != TypeKind.TypeParameter)
{
if (SyntaxFacts.GetContextualKeywordKind(parameter.Name) is not SyntaxKind.UnderscoreToken)
{
return parameter.Name;
}
}
}
}
}
return null;
}
public static INamedTypeSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, BaseTypeDeclarationSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
public static IMethodSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ConstructorDeclarationSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
public static IMethodSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, LocalFunctionStatementSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
public static IParameterSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ParameterSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
public static IPropertySymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, PropertyDeclarationSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
public static ISymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, VariableDeclaratorSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
public static ISymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, SingleVariableDesignationSyntax syntax, CancellationToken cancellationToken)
{
return semanticModel.GetDeclaredSymbol(syntax, cancellationToken)
?? throw new InvalidOperationException();
}
}
|