/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/AddExplicitCast/Fixer.cs
220 строк
11 KB
Cyrus Najmabadi
Switch to ToImmutableAndClear
13 апр 2024, 05:09
13 апр 2024, 05:09
72d701e
Код
Авторство
О чём код?
// 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. using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CodeFixes.AddExplicitCast; internal abstract partial class AbstractAddExplicitCastCodeFixProvider<TExpressionSyntax> { protected abstract class Fixer<TArgumentSyntax, TArgumentListSyntax, TInvocationSyntax> where TArgumentSyntax : SyntaxNode where TArgumentListSyntax : SyntaxNode where TInvocationSyntax : SyntaxNode { protected abstract TExpressionSyntax GetExpressionOfArgument(TArgumentSyntax argument); protected abstract TArgumentSyntax GenerateNewArgument(TArgumentSyntax oldArgument, ITypeSymbol conversionType); protected abstract TArgumentListSyntax GenerateNewArgumentList(TArgumentListSyntax oldArgumentList, ArrayBuilder<TArgumentSyntax> newArguments); protected abstract SeparatedSyntaxList<TArgumentSyntax> GetArgumentsOfArgumentList(TArgumentListSyntax argumentList); protected abstract SymbolInfo GetSpeculativeSymbolInfo(SemanticModel semanticModel, TArgumentListSyntax newArgumentList); /// <summary> /// Collect all the available cast pairs, format is (target argument expression, potential conversion type) /// </summary> /// <param name="targetArgument"> The argument that need to be cast</param> /// <param name="argumentList"> The argument list that contains the target argument to be cast </param> /// <param name="invocationNode"> The invocation node that is the parent of "argumentList"</param> /// <returns> /// Return all the available cast pairs, format is (target argument expression, potential conversion type) /// </returns> public ImmutableArray<(TExpressionSyntax, ITypeSymbol)> GetPotentialConversionTypes( Document document, SemanticModel semanticModel, SyntaxNode root, TArgumentSyntax targetArgument, TArgumentListSyntax argumentList, TInvocationSyntax invocationNode, CancellationToken cancellationToken) { // Implicit downcast appears on the argument of invocation node, // get all candidate functions and extract potential conversion types var symbolInfo = semanticModel.GetSymbolInfo(invocationNode, cancellationToken); using var _ = ArrayBuilder<ISymbol>.GetInstance(out var candidateSymbols); if (symbolInfo.Symbol != null) // BC42016: the only candidate symbol is symbolInfo.Symbol { candidateSymbols.Add(symbolInfo.Symbol); } else { candidateSymbols.AddRange(symbolInfo.CandidateSymbols); } using var __ = ArrayBuilder<(TExpressionSyntax, ITypeSymbol)>.GetInstance(out var mutablePotentialConversionTypes); foreach (var candidateSymbol in candidateSymbols.OfType<IMethodSymbol>()) { if (CanArgumentTypesBeConvertedToParameterTypes( document, semanticModel, root, argumentList, candidateSymbol.Parameters, targetArgument, cancellationToken, out var targetArgumentConversionType) && GetExpressionOfArgument(targetArgument) is TExpressionSyntax argumentExpression) { mutablePotentialConversionTypes.Add((argumentExpression, targetArgumentConversionType)); } } // Sort the potential conversion types by inheritance distance, so that // operations are in order and user can choose least specific types(more accurate) mutablePotentialConversionTypes.Sort(new InheritanceDistanceComparer<TExpressionSyntax>(semanticModel)); return mutablePotentialConversionTypes.ToImmutableAndClear(); } /// <summary> /// Test if all argument types can be converted to corresponding parameter types. /// </summary> /// For example: /// class Base { } /// class Derived1 : Base { } /// class Derived2 : Base { } /// class Derived3 : Base { } /// void DoSomething(int i, Derived1 d) { } /// void DoSomething(string s, Derived2 d) { } /// void DoSomething(int i, Derived3 d) { } /// /// Base b; /// DoSomething(1, [||]b); /// /// *void DoSomething(string s, Derived2 d) { }* is not the perfect match candidate function for /// *DoSomething(1, [||]b)* because int and string are not ancestor-descendant relationship. Thus, /// Derived2 is not a potential conversion type. /// /// <param name="argumentList"> The argument list of invocation expression</param> /// <param name="parameters"> The parameters of function</param> /// <param name="targetArgument">The argument need to be cast.</param> /// <param name="targetArgumentConversionType"> Output the corresponding parameter type of /// "targetArgument" if function returns true</param> /// <returns> /// True, if arguments and parameters match perfectly. /// "targetArgumentConversionType" outputs the corresponding parameter type of "targetArgument" /// False, otherwise. /// </returns> public bool CanArgumentTypesBeConvertedToParameterTypes( Document document, SemanticModel semanticModel, SyntaxNode root, TArgumentListSyntax argumentList, ImmutableArray<IParameterSymbol> parameters, TArgumentSyntax targetArgument, CancellationToken cancellationToken, [NotNullWhen(true)] out ITypeSymbol? targetArgumentConversionType) { targetArgumentConversionType = null; // No conversion happens under this case if (parameters.Length == 0) return false; var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>(); var arguments = GetArgumentsOfArgumentList(argumentList); using var _ = ArrayBuilder<TArgumentSyntax>.GetInstance(out var newArguments); for (var i = 0; i < arguments.Count; i++) { // Parameter index cannot out of its range, #arguments is larger than #parameter only if // the last parameter with keyword params var parameterIndex = Math.Min(i, parameters.Length - 1); // If the argument has a name, get the corresponding parameter index if (syntaxFacts.GetNameForArgument(arguments[i]) is string name && name != string.Empty && !FindCorrespondingParameterByName(name, parameters, ref parameterIndex)) { return false; } // The argument is either in order with parameters, or have a matched name with parameters. var argumentExpression = GetExpressionOfArgument(arguments[i]); if (argumentExpression == null) { // argumentExpression is null when it is an omitted argument in VB .NET newArguments.Add(arguments[i]); continue; } var parameterType = parameters[parameterIndex].Type; if (parameters[parameterIndex].IsParams && parameterType is IArrayTypeSymbol paramsType && semanticFacts.ClassifyConversion(semanticModel, argumentExpression, paramsType.ElementType).Exists) { newArguments.Add(GenerateNewArgument(arguments[i], paramsType.ElementType)); if (arguments[i].Equals(targetArgument)) targetArgumentConversionType = paramsType.ElementType; } else if (semanticFacts.ClassifyConversion(semanticModel, argumentExpression, parameterType).Exists) { newArguments.Add(GenerateNewArgument(arguments[i], parameterType)); if (arguments[i].Equals(targetArgument)) targetArgumentConversionType = parameterType; } else if (syntaxFacts.IsDeclarationExpression(argumentExpression) && semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type is ITypeSymbol argumentType && semanticModel.Compilation.ClassifyCommonConversion(argumentType, parameterType).IsIdentity) { // Direct conversion from a declaration expression to a type is unspecified, thus we classify the // conversion from the type of declaration expression to the parameter type // An example for this case: // void Goo(out int i) { i = 1; } // Goo([|out var i|]); // "var i" is a declaration expression // // In addition, since this case is with keyword "out", the type of declaration expression and the // parameter type must be identical in order to match. newArguments.Add(arguments[i]); } else { return false; } } return targetArgumentConversionType != null && IsInvocationExpressionWithNewArgumentsApplicable( semanticModel, root, argumentList, newArguments, targetArgument); } /// <summary> /// Check whether the invocation expression with new arguments is applicable. /// </summary> /// <param name="oldArgumentList" >old argumentList node</param> /// <param name="newArguments"> new arguments that are cast by corresponding parameter types</param> /// <param name="targetNode"> The node needs to be cast.</param> /// <returns> /// Return true if the invocation expression with new arguments is applicable. /// Otherwise, return false /// </returns> private bool IsInvocationExpressionWithNewArgumentsApplicable(SemanticModel semanticModel, SyntaxNode root, TArgumentListSyntax oldArgumentList, ArrayBuilder<TArgumentSyntax> newArguments, SyntaxNode targetNode) { var newRoot = root.ReplaceNode(oldArgumentList, GenerateNewArgumentList(oldArgumentList, newArguments)); if (newRoot.FindNode(targetNode.Span).GetAncestorOrThis<TArgumentListSyntax>() is TArgumentListSyntax newArgumentList) { var symbolInfo = GetSpeculativeSymbolInfo(semanticModel, newArgumentList); return symbolInfo.Symbol != null; } return false; } } }