/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs
414 строк
17 KB
Cyrus Najmabadi
use patterns
06 янв 2026, 06:55
06 янв 2026, 06:55
c44ff60
Код
Авторство
О чём код?
// 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.Linq; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Simplification.Simplifiers; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; /// <summary> /// Contains helpers used by several simplifier subclasses. /// </summary> internal abstract class AbstractCSharpSimplifier<TSyntax, TSimplifiedSyntax> : AbstractSimplifier<TSyntax, TSimplifiedSyntax, CSharpSimplifierOptions> where TSyntax : SyntaxNode where TSimplifiedSyntax : SyntaxNode { private static readonly ConditionalWeakTable<SemanticModel, StrongBox<bool>> s_modelToHasUsingAliasesMap = new(); /// <summary> /// Returns the predefined keyword kind for a given <see cref="SpecialType"/>. /// </summary> /// <param name="specialType">The <see cref="SpecialType"/> of this type.</param> /// <returns>The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type.</returns> protected static SyntaxToken? TryGetPredefinedKeywordToken(SemanticModel semanticModel, SpecialType specialType) { var kind = specialType switch { SpecialType.System_Boolean => SyntaxKind.BoolKeyword, SpecialType.System_Byte => SyntaxKind.ByteKeyword, SpecialType.System_SByte => SyntaxKind.SByteKeyword, SpecialType.System_Int32 => SyntaxKind.IntKeyword, SpecialType.System_UInt32 => SyntaxKind.UIntKeyword, SpecialType.System_Int16 => SyntaxKind.ShortKeyword, SpecialType.System_UInt16 => SyntaxKind.UShortKeyword, SpecialType.System_Int64 => SyntaxKind.LongKeyword, SpecialType.System_UInt64 => SyntaxKind.ULongKeyword, SpecialType.System_Single => SyntaxKind.FloatKeyword, SpecialType.System_Double => SyntaxKind.DoubleKeyword, SpecialType.System_Decimal => SyntaxKind.DecimalKeyword, SpecialType.System_String => SyntaxKind.StringKeyword, SpecialType.System_Char => SyntaxKind.CharKeyword, SpecialType.System_Object => SyntaxKind.ObjectKeyword, SpecialType.System_Void => SyntaxKind.VoidKeyword, _ => SyntaxKind.None, }; if (kind != SyntaxKind.None) return SyntaxFactory.Token(kind); if (specialType is SpecialType.System_IntPtr or SpecialType.System_UIntPtr && semanticModel.UnifiesNativeIntegers()) return SyntaxFactory.Identifier(specialType == SpecialType.System_IntPtr ? "nint" : "nuint"); return null; } [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")] protected static bool TryReplaceExpressionWithAlias( ExpressionSyntax node, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken, out IAliasSymbol aliasReplacement) { aliasReplacement = null; if (!IsAliasReplaceableExpression(node)) return false; // Avoid the TryReplaceWithAlias algorithm if the tree has no using alias directives. Since the input node // might be a speculative node (not fully rooted in a tree), we use the original semantic model to find the // equivalent node in the original tree, and from there determine if the tree has any using alias // directives. var originalModel = semanticModel.GetOriginalSemanticModel(); var hasUsingAliases = HasUsingAliases(originalModel, cancellationToken); if (!hasUsingAliases) return false; // If the Symbol is a constructor get its containing type if (symbol.IsConstructor()) { symbol = symbol.ContainingType; } if (node is QualifiedNameSyntax or AliasQualifiedNameSyntax) { SyntaxAnnotation aliasAnnotationInfo = null; // The following condition checks if the user has used alias in the original code and // if so the expression is replaced with the Alias if (node is QualifiedNameSyntax qualifiedNameNode) { if (qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind)) { aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); } } if (node is AliasQualifiedNameSyntax aliasQualifiedNameNode) { if (aliasQualifiedNameNode.Name.Identifier.HasAnnotations(AliasAnnotation.Kind)) { aliasAnnotationInfo = aliasQualifiedNameNode.Name.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); } } if (aliasAnnotationInfo != null) { var aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo); var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName); var aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace); if (aliasTypeInfo != null) { aliasReplacement = aliasTypeInfo; return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); } } } if (node.Kind() == SyntaxKind.IdentifierName && semanticModel.GetAliasInfo((IdentifierNameSyntax)node, cancellationToken) != null) { return false; } // an alias can only replace a type or namespace if (symbol == null || symbol is { Kind: not SymbolKind.Namespace and not SymbolKind.NamedType }) { return false; } var preferAliasToQualifiedName = true; if (node is QualifiedNameSyntax qualifiedName) { if (!qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) { var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; if (type != null) { var keywordToken = TryGetPredefinedKeywordToken(semanticModel, type.SpecialType); if (keywordToken != null) preferAliasToQualifiedName = false; } } } if (node is AliasQualifiedNameSyntax aliasQualifiedNameSyntax) { if (!aliasQualifiedNameSyntax.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) { var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; if (type != null) { var keywordToken = TryGetPredefinedKeywordToken(semanticModel, type.SpecialType); if (keywordToken != null) preferAliasToQualifiedName = false; } } } aliasReplacement = GetAliasForSymbol((INamespaceOrTypeSymbol)symbol, node.GetFirstToken(), semanticModel, cancellationToken); if (aliasReplacement != null && preferAliasToQualifiedName) { return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); } return false; static bool IsAliasReplaceableExpression(ExpressionSyntax expression) { var current = expression; while (current is MemberAccessExpressionSyntax(SyntaxKind.SimpleMemberAccessExpression) currentMember) { current = currentMember.Expression; continue; } return current.Kind() is SyntaxKind.AliasQualifiedName or SyntaxKind.IdentifierName or SyntaxKind.GenericName or SyntaxKind.QualifiedName; } } private static bool HasUsingAliases(SemanticModel semanticModel, CancellationToken cancellationToken) { if (!s_modelToHasUsingAliasesMap.TryGetValue(semanticModel, out var hasAliases)) { hasAliases = new StrongBox<bool>(ComputeHasUsingAliases(semanticModel, cancellationToken)); lock (s_modelToHasUsingAliasesMap) { s_modelToHasUsingAliasesMap.Remove(semanticModel); s_modelToHasUsingAliasesMap.Add(semanticModel, hasAliases); } } return hasAliases.Value; } private static bool ComputeHasUsingAliases(SemanticModel model, CancellationToken cancellationToken) { if (!model.SyntaxTree.HasCompilationUnitRoot) return false; var root = (CompilationUnitSyntax)model.SyntaxTree.GetRoot(cancellationToken); if (HasUsingAliasDirective(root)) return true; var firstMember = root.Members.Count > 0 ? root.Members[0] : root.AttributeLists.Count > 0 ? root.AttributeLists[0] : (SyntaxNode)null; if (firstMember == null) return false; var scopes = model.GetImportScopes(firstMember.SpanStart, cancellationToken); return scopes.Any(static s => s.Aliases.Length > 0); static bool HasUsingAliasDirective(SyntaxNode syntax) { var (usings, members) = syntax switch { BaseNamespaceDeclarationSyntax ns => (ns.Usings, ns.Members), CompilationUnitSyntax compilationUnit => (compilationUnit.Usings, compilationUnit.Members), _ => default, }; foreach (var usingDirective in usings) { if (usingDirective.Alias != null) return true; } foreach (var member in members) { if (HasUsingAliasDirective(member)) return true; } return false; } } // We must verify that the alias actually binds back to the thing it's aliasing. // It's possible there's another symbol with the same name as the alias that binds // first private static bool ValidateAliasForTarget(IAliasSymbol aliasReplacement, SemanticModel semanticModel, ExpressionSyntax node, ISymbol symbol) { var aliasName = aliasReplacement.Name; // If we're the argument of a nameof(X.Y) call, then we can't simplify to an // alias unless the alias has the same name as us (i.e. 'Y'). if (node.IsNameOfArgumentExpression()) { var nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent); if (!nameofValueOpt.HasValue) { return false; } if (nameofValueOpt.Value is string existingVal && existingVal != aliasName) { return false; } } // If something is dotting off the node we need to make sure the name couldn't // be a different symbol that has a different type to the alias. if (node.IsLeftSideOfDot()) { var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName); var symbolInfo = semanticModel.GetSpeculativeSymbolInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsExpression); if (symbolInfo.Symbol is not INamespaceOrTypeSymbol) { // We bound the alias to something other than a namespace or a type, which is normally not good, but if the // types are the same then it is okay. var typeInfo = semanticModel.GetSpeculativeTypeInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsExpression); if (!symbol.Equals(typeInfo.Type)) { return false; } } } var boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name: aliasName); if (boundSymbols.Length == 1) { if (boundSymbols[0] is IAliasSymbol && aliasReplacement.Target.Equals(symbol)) { return true; } } return false; } private static IAliasSymbol GetAliasForSymbol(INamespaceOrTypeSymbol symbol, SyntaxToken token, SemanticModel semanticModel, CancellationToken cancellationToken) { var originalSemanticModel = semanticModel.GetOriginalSemanticModel(); if (!originalSemanticModel.SyntaxTree.HasCompilationUnitRoot) return null; var namespaceId = GetNamespaceIdForAliasSearch(semanticModel, token, cancellationToken); if (namespaceId == null) return null; if (!AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId.Value, symbol, out var aliasSymbol)) { // add cache AliasSymbolCache.AddAliasSymbols( originalSemanticModel, namespaceId.Value, semanticModel.LookupNamespacesAndTypes(token.SpanStart).OfType<IAliasSymbol>()); // retry AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId.Value, symbol, out aliasSymbol); } return aliasSymbol; } private static int? GetNamespaceIdForAliasSearch(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { var startNode = GetStartNodeForNamespaceId(semanticModel, token, cancellationToken); if (!startNode.SyntaxTree.HasCompilationUnitRoot) return null; // NOTE: If we're currently in a block of usings, then we want to collect the // aliases that are higher up than this block. Using aliases declared in a block of // usings are not usable from within that same block. var usingDirective = startNode.GetAncestorOrThis<UsingDirectiveSyntax>(); if (usingDirective != null) { startNode = usingDirective.Parent.Parent; if (startNode == null) return null; } // check whether I am under a namespace var @namespace = startNode.GetAncestorOrThis<BaseNamespaceDeclarationSyntax>(); if (@namespace != null) return @namespace.SpanStart; // no namespace, under compilation unit directly. Pass -1 so there is no ambiguity with a namespace decl // that starts at position 0. return -1; } private static SyntaxNode GetStartNodeForNamespaceId(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { if (!semanticModel.IsSpeculativeSemanticModel) return token.Parent; var originalSemanticMode = semanticModel.GetOriginalSemanticModel(); token = originalSemanticMode.SyntaxTree.GetRoot(cancellationToken).FindToken(semanticModel.OriginalPositionForSpeculation); return token.Parent; } protected static TypeSyntax CreatePredefinedTypeSyntax(SyntaxNode nodeToReplace, SyntaxToken token) { TypeSyntax node = token.Kind() == SyntaxKind.IdentifierToken ? SyntaxFactory.IdentifierName(token) : SyntaxFactory.PredefinedType(token); return node.WithTriviaFrom(nodeToReplace); } protected static bool InsideNameOfExpression(ExpressionSyntax expression, SemanticModel semanticModel) { var nameOfInvocationExpr = expression.FirstAncestorOrSelf<InvocationExpressionSyntax>( invocationExpr => { return invocationExpr.Expression is IdentifierNameSyntax identifierName && identifierName.Identifier.Text == "nameof" && semanticModel.GetConstantValue(invocationExpr).HasValue && semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String; }); return nameOfInvocationExpr != null; } protected static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, CSharpSimplifierOptions options, SemanticModel semanticModel) { if (!options.PreferPredefinedTypeKeywordInMemberAccess.Value) return false; return (expression.IsDirectChildOfMemberAccessExpression() || expression.InsideCrefReference()) && !InsideNameOfExpression(expression, semanticModel); } protected static bool WillConflictWithExistingLocal( ExpressionSyntax expression, ExpressionSyntax simplifiedNode, SemanticModel semanticModel) { if (simplifiedNode is IdentifierNameSyntax identifierName && !SyntaxFacts.IsInNamespaceOrTypeContext(expression)) { var symbols = semanticModel.LookupSymbols(expression.SpanStart, name: identifierName.Identifier.ValueText); return symbols.Any(static s => s is ILocalSymbol); } return false; } }