diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/AsyncVoidMethodsAndLambdas/AsyncVoidMethodsAndLambdasAnalyzer.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/AsyncVoidMethodsAndLambdas/AsyncVoidMethodsAndLambdasAnalyzer.cs index 63e29662e..b1c33b0b1 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/AsyncVoidMethodsAndLambdas/AsyncVoidMethodsAndLambdasAnalyzer.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/AsyncVoidMethodsAndLambdas/AsyncVoidMethodsAndLambdasAnalyzer.cs @@ -108,7 +108,7 @@ private static void AnalyzeLambdaDeclaration(SyntaxNodeAnalysisContext syntaxCon if (!lambdaOrAnonymousDelegateDeclaration.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword)) return; - var lambdaMethodSymbol = syntaxContext.SemanticModel.GetSymbolOrFirstCandidate(lambdaOrAnonymousDelegateDeclaration, + var lambdaMethodSymbol = syntaxContext.SemanticModel.GetSymbolOrBestCandidate(lambdaOrAnonymousDelegateDeclaration, syntaxContext.CancellationToken) as IMethodSymbol; if (lambdaMethodSymbol == null || !lambdaMethodSymbol.ReturnsVoid) return; diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/BannedApi/BannedApiAnalyzer.ApiNodesWalker.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/BannedApi/BannedApiAnalyzer.ApiNodesWalker.cs index 68d688957..1a40c8868 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/BannedApi/BannedApiAnalyzer.ApiNodesWalker.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/BannedApi/BannedApiAnalyzer.ApiNodesWalker.cs @@ -94,7 +94,7 @@ public override void VisitUsingDirective(UsingDirectiveSyntax usingDirectiveNode Cancellation.ThrowIfCancellationRequested(); if (usingDirectiveNode.Name == null || - SemanticModel.GetSymbolOrFirstCandidate(usingDirectiveNode.Name, Cancellation) is not ISymbol typeOrNamespaceSymbol) + SemanticModel.GetSymbolOrBestCandidate(usingDirectiveNode.Name, Cancellation) is not ISymbol typeOrNamespaceSymbol) { return; } @@ -139,7 +139,7 @@ public override void VisitGenericName(GenericNameSyntax genericNameNode) { Cancellation.ThrowIfCancellationRequested(); - if (SemanticModel.GetSymbolOrFirstCandidate(genericNameNode, Cancellation) is not ISymbol symbol) + if (SemanticModel.GetSymbolOrBestCandidate(genericNameNode, Cancellation) is not ISymbol symbol) { Cancellation.ThrowIfCancellationRequested(); base.VisitGenericName(genericNameNode); @@ -202,8 +202,8 @@ private bool AnalyzeAccessExpressionAndDecideIfShouldVisitChildNodes(ExpressionS { Cancellation.ThrowIfCancellationRequested(); - ISymbol? accessedMember = SemanticModel.GetSymbolOrFirstCandidate(wholeAccessExpression, Cancellation); - accessedMember ??= SemanticModel.GetSymbolOrFirstCandidate(accessMemberExpression, Cancellation); + ISymbol? accessedMember = SemanticModel.GetSymbolOrBestCandidate(wholeAccessExpression, Cancellation); + accessedMember ??= SemanticModel.GetSymbolOrBestCandidate(accessMemberExpression, Cancellation); if (accessedMember == null) return true; @@ -214,7 +214,7 @@ private bool AnalyzeAccessExpressionAndDecideIfShouldVisitChildNodes(ExpressionS return false; expressionBeingAccessed = UnwrapAccessExpressionFromArrayAccess(expressionBeingAccessed); - var symbolBeingAccessed = SemanticModel.GetSymbolOrFirstCandidate(expressionBeingAccessed, Cancellation); + var symbolBeingAccessed = SemanticModel.GetSymbolOrBestCandidate(expressionBeingAccessed, Cancellation); if (symbolBeingAccessed == null || symbolBeingAccessed.Equals(accessedMember.ContainingType, SymbolEqualityComparer.Default)) return !IsAllowedApi(accessedMember); @@ -238,7 +238,7 @@ public override void VisitIdentifierName(IdentifierNameSyntax identifierNode) { Cancellation.ThrowIfCancellationRequested(); - if (SemanticModel.GetSymbolOrFirstCandidate(identifierNode, Cancellation) is not ISymbol symbol) + if (SemanticModel.GetSymbolOrBestCandidate(identifierNode, Cancellation) is not ISymbol symbol) return; Cancellation.ThrowIfCancellationRequested(); @@ -249,7 +249,7 @@ public override void VisitQualifiedName(QualifiedNameSyntax qualifiedName) { Cancellation.ThrowIfCancellationRequested(); - if (SemanticModel.GetSymbolOrFirstCandidate(qualifiedName, Cancellation) is not ISymbol symbol) + if (SemanticModel.GetSymbolOrBestCandidate(qualifiedName, Cancellation) is not ISymbol symbol) { base.VisitQualifiedName(qualifiedName); return; diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/DatabaseQueries/DatabaseQueriesInRowSelectingAnalyzer.PXConnectionScopeVisitor.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/DatabaseQueries/DatabaseQueriesInRowSelectingAnalyzer.PXConnectionScopeVisitor.cs index 0b3f27a7a..8898a251b 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/DatabaseQueries/DatabaseQueriesInRowSelectingAnalyzer.PXConnectionScopeVisitor.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/DatabaseQueries/DatabaseQueriesInRowSelectingAnalyzer.PXConnectionScopeVisitor.cs @@ -35,14 +35,14 @@ public override bool VisitUsingStatement(UsingStatementSyntax node) public override bool VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) { var semanticModel = _semanticModelGetter(node.SyntaxTree); - var typeSymbol = semanticModel?.GetSymbolOrFirstCandidate(node.Type, _cancellation) as ITypeSymbol; + var typeSymbol = semanticModel?.GetSymbolOrBestCandidate(node.Type, _cancellation) as ITypeSymbol; return IsPXConnectionScope(typeSymbol); } public override bool VisitImplicitObjectCreationExpression(ImplicitObjectCreationExpressionSyntax node) { var semanticModel = _semanticModelGetter(node.SyntaxTree); - var constructor = semanticModel?.GetSymbolOrFirstCandidate(node, _cancellation) as IMethodSymbol; + var constructor = semanticModel?.GetSymbolOrBestCandidate(node, _cancellation) as IMethodSymbol; if (constructor?.ContainingType == null || constructor.MethodKind != MethodKind.Constructor) return false; diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/IncorrectTaskUsageInAsyncCode/IncorrectTaskUsageInAsyncCodeAnalyzer.TaskUsageCheckingWalker.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/IncorrectTaskUsageInAsyncCode/IncorrectTaskUsageInAsyncCodeAnalyzer.TaskUsageCheckingWalker.cs index f97518f58..24b8112a1 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/IncorrectTaskUsageInAsyncCode/IncorrectTaskUsageInAsyncCodeAnalyzer.TaskUsageCheckingWalker.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/IncorrectTaskUsageInAsyncCode/IncorrectTaskUsageInAsyncCodeAnalyzer.TaskUsageCheckingWalker.cs @@ -65,7 +65,7 @@ private void CheckVariableOrParameterTypeIsNotTask(TypeSyntax? typeNode) if (typeNode == null) return; - var variableType = SemanticModel.GetSymbolOrFirstCandidate(typeNode, Cancellation) as ITypeSymbol; + var variableType = SemanticModel.GetSymbolOrBestCandidate(typeNode, Cancellation) as ITypeSymbol; if (variableType == null || !IsTaskType(variableType)) return; @@ -159,7 +159,7 @@ private void CheckTypeMemberReturningTaskHasTaskReturnType(ExpressionSyntax? ret { if (containingMethodOrLocalFunction is AnonymousFunctionExpressionSyntax lambdaDeclaration) { - var lambdaSymbol = SemanticModel.GetSymbolOrFirstCandidate(lambdaDeclaration, Cancellation) as IMethodSymbol; + var lambdaSymbol = SemanticModel.GetSymbolOrBestCandidate(lambdaDeclaration, Cancellation) as IMethodSymbol; return lambdaSymbol?.ReturnType; } @@ -174,7 +174,7 @@ private void CheckTypeMemberReturningTaskHasTaskReturnType(ExpressionSyntax? ret if (returnTypeNode == null) return null; - var returnTypeSymbol = SemanticModel.GetSymbolOrFirstCandidate(returnTypeNode, Cancellation) as ITypeSymbol; + var returnTypeSymbol = SemanticModel.GetSymbolOrBestCandidate(returnTypeNode, Cancellation) as ITypeSymbol; return returnTypeSymbol; } diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/Localization/LocalizationPXExceptionAndPXexceptionInfoAnalyzer.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/Localization/LocalizationPXExceptionAndPXexceptionInfoAnalyzer.cs index 9fe086d52..00b788489 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/Localization/LocalizationPXExceptionAndPXexceptionInfoAnalyzer.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/Localization/LocalizationPXExceptionAndPXexceptionInfoAnalyzer.cs @@ -78,7 +78,7 @@ private void AnalyzePXExceptionAndPXExceptionInfoConstructorInvocations(SyntaxNo if (!isLocalizableException && !isPXExceptionInfo) return; - var symbol = syntaxContext.SemanticModel.GetSymbolOrFirstCandidate(constructorCall, syntaxContext.CancellationToken); + var symbol = syntaxContext.SemanticModel.GetSymbolOrBestCandidate(constructorCall, syntaxContext.CancellationToken); if (symbol is not IMethodSymbol constructor) return; @@ -116,7 +116,7 @@ private void AnalyzePXExceptionChainedConstructorCall(SyntaxNodeAnalysisContext foreach (ConstructorInitializerSyntax constructorCall in baseOrThisConstructorCalls) { - var symbol = syntaxContext.SemanticModel.GetSymbolOrFirstCandidate(constructorCall, syntaxContext.CancellationToken); + var symbol = syntaxContext.SemanticModel.GetSymbolOrBestCandidate(constructorCall, syntaxContext.CancellationToken); if (symbol is not IMethodSymbol constructorSymbol) continue; @@ -145,9 +145,9 @@ private void AnalyzePXExceptionChainedConstructorCall(SyntaxNodeAnalysisContext for (int argIndex = 0; argIndex < args.Arguments.Count; argIndex++) { - IParameterSymbol mappedParameter = argumentsToParametersMapping.Value.GetMappedParameter(constructor, argIndex); + IParameterSymbol? mappedParameter = argumentsToParametersMapping.Value.GetMappedParameter(constructor, argIndex); - if (parametersWithLocalizableText.Contains(mappedParameter.Name, StringComparer.Ordinal)) + if (mappedParameter != null && parametersWithLocalizableText.Contains(mappedParameter.Name, StringComparer.Ordinal)) { var argument = args.Arguments[argIndex]; return argument.Expression; diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateClosuresAnalyzer.LongOperationsChecker.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateClosuresAnalyzer.LongOperationsChecker.cs index 357251ed0..08e9f7556 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateClosuresAnalyzer.LongOperationsChecker.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateClosuresAnalyzer.LongOperationsChecker.cs @@ -698,7 +698,11 @@ private void FilterReassignedParameters(NonCapturableElementsInfo nonCapturableE continue; var mappedParameter = argumentsToParametersMapping.Value.GetMappedParameter(calledMethod, argument.Index); - nonCapturableParametersOfCalledMethodFromCallArgs.Add(new PassedParameter(mappedParameter.Name, capturedTypes)); + + if (mappedParameter != null) + { + nonCapturableParametersOfCalledMethodFromCallArgs.Add(new PassedParameter(mappedParameter.Name, capturedTypes)); + } } return nonCapturableParametersOfCalledMethodFromCallArgs; diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateTypeClassifier.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateTypeClassifier.cs index 1ae18534e..19a9a1ef4 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateTypeClassifier.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/LongOperationDelegateClosures/LongOperationDelegateTypeClassifier.cs @@ -51,7 +51,7 @@ private static (LongOperationDelegateType Type, IMethodSymbol Method)? GetLongOp { case DelegateNames.Processing.SetProcessDelegate: case DelegateNames.Processing.SetAsyncProcessDelegate: - var setDelegateSymbol = semanticModel.GetSymbolOrFirstCandidate(methodAccessNode, cancellationToken) as IMethodSymbol; + var setDelegateSymbol = semanticModel.GetSymbolOrBestCandidate(methodAccessNode, cancellationToken) as IMethodSymbol; if (setDelegateSymbol != null && setDelegateSymbol.ContainingType.ConstructedFrom.InheritsFromOrEquals(pxContext.PXProcessingBase.Type)) return (LongOperationDelegateType.ProcessingDelegate, setDelegateSymbol); @@ -61,7 +61,7 @@ private static (LongOperationDelegateType Type, IMethodSymbol Method)? GetLongOp case DelegateNames.Async.StartOperation: case DelegateNames.Async.StartAsyncOperation: case DelegateNames.Async.Await: - var longRunDelegate = semanticModel.GetSymbolOrFirstCandidate(methodAccessNode, cancellationToken) as IMethodSymbol; + var longRunDelegate = semanticModel.GetSymbolOrBestCandidate(methodAccessNode, cancellationToken) as IMethodSymbol; if (longRunDelegate == null) return null; diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/NoPrimaryViewForPrimaryDac/NoPrimaryViewForPrimaryDacAnalyzer.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/NoPrimaryViewForPrimaryDac/NoPrimaryViewForPrimaryDacAnalyzer.cs index e3e13fea4..09f7d24c2 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/NoPrimaryViewForPrimaryDac/NoPrimaryViewForPrimaryDacAnalyzer.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/NoPrimaryViewForPrimaryDac/NoPrimaryViewForPrimaryDacAnalyzer.cs @@ -63,7 +63,7 @@ public override void Analyze(SymbolAnalysisContext context, PXContext pxContext, foreach (GenericNameSyntax baseClassTypeNode in baseClassesTypeNodes) { - var baseClassTypeSymbol = semanticModel.GetSymbolOrFirstCandidate(baseClassTypeNode, context.CancellationToken) as INamedTypeSymbol; + var baseClassTypeSymbol = semanticModel.GetSymbolOrBestCandidate(baseClassTypeNode, context.CancellationToken) as INamedTypeSymbol; Location? location = GetLocationFromBaseClassTypeNode(baseClassTypeNode, baseClassTypeSymbol, declaredPrimaryDacType); if (location != null) diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceAnalyzer.Walker.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceAnalyzer.Walker.cs index 0f5ffc809..0a9c67558 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceAnalyzer.Walker.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceAnalyzer.Walker.cs @@ -30,7 +30,7 @@ public override void VisitObjectCreationExpression(ObjectCreationExpressionSynta { _context.CancellationToken.ThrowIfCancellationRequested(); - if (node.Type == null || _semanticModel.GetSymbolOrFirstCandidate(node.Type, _context.CancellationToken) is not ITypeSymbol typeSymbol) + if (node.Type == null || _semanticModel.GetSymbolOrBestCandidate(node.Type, _context.CancellationToken) is not ITypeSymbol typeSymbol) { base.VisitObjectCreationExpression(node); return; @@ -53,7 +53,7 @@ public override void VisitImplicitObjectCreationExpression(ImplicitObjectCreatio { _context.CancellationToken.ThrowIfCancellationRequested(); - var constructor = _semanticModel.GetSymbolOrFirstCandidate(node, _context.CancellationToken) as IMethodSymbol; + var constructor = _semanticModel.GetSymbolOrBestCandidate(node, _context.CancellationToken) as IMethodSymbol; if (constructor?.ContainingType == null || constructor.MethodKind != MethodKind.Constructor) { diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceFix.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceFix.cs index 391ab4e0e..18533962b 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceFix.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreateInstance/PXGraphCreateInstanceFix.cs @@ -87,7 +87,7 @@ public Rewriter(PXContext pxContext, Document document, SemanticModel semanticMo if (_generator == null) return base.VisitObjectCreationExpression(node); - var graphTypeSymbol = _semanticModel.GetSymbolOrFirstCandidate(node.Type, _cancellation) as ITypeSymbol; + var graphTypeSymbol = _semanticModel.GetSymbolOrBestCandidate(node.Type, _cancellation) as ITypeSymbol; var createInstanceCallNode = GeneratePXGraphCreateInstanceCall(graphTypeSymbol); return createInstanceCallNode ?? base.VisitObjectCreationExpression(node); @@ -100,7 +100,7 @@ public Rewriter(PXContext pxContext, Document document, SemanticModel semanticMo if (_generator == null) return base.VisitImplicitObjectCreationExpression(node); - var constructor = _semanticModel.GetSymbolOrFirstCandidate(node, _cancellation) as IMethodSymbol; + var constructor = _semanticModel.GetSymbolOrBestCandidate(node, _cancellation) as IMethodSymbol; if (constructor?.ContainingType == null || constructor.MethodKind != MethodKind.Constructor) return base.VisitImplicitObjectCreationExpression(node); diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/PXGraphCreationForBqlQueriesAnalyzer.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/PXGraphCreationForBqlQueriesAnalyzer.cs index b917673f6..f07ebabbb 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/PXGraphCreationForBqlQueriesAnalyzer.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/PXGraphCreationForBqlQueriesAnalyzer.cs @@ -135,7 +135,7 @@ private void AnalyseCaseWithNoAvailableExistingGraphs(CodeBlockAnalysisContext c private HashSet GetDifferentSymbolsFromCallArgs(SemanticModel semanticModel, List bqlSelectGraphArgNodes, CancellationToken cancellation) => - bqlSelectGraphArgNodes.Select(graphArgSyntax => semanticModel.GetSymbolOrFirstCandidate(graphArgSyntax, cancellation)) + bqlSelectGraphArgNodes.Select(graphArgSyntax => semanticModel.GetSymbolOrBestCandidate(graphArgSyntax, cancellation)) .Where(graphArgSymbol => graphArgSymbol != null && graphArgSymbol is not (IPropertySymbol or IFieldSymbol)) .ToHashSet(SymbolEqualityComparer.Default)!; @@ -150,7 +150,7 @@ private Dictionary> GetGraphSymbolsUsages(CSharpSyntax foreach (var subNode in nodesToVisit) { - var symbol = semanticModel.GetSymbolOrFirstCandidate(subNode, cancellation); + var symbol = semanticModel.GetSymbolOrBestCandidate(subNode, cancellation); if (symbol != null && existingGraphs.Contains(symbol, SymbolEqualityComparer.Default)) { @@ -183,7 +183,7 @@ private void AnalyzeGraphArgumentOfBqlQuery(CodeBlockAnalysisContext context, PX return; } - var localVar = context.SemanticModel.GetSymbolOrFirstCandidate(graphArgSyntax, context.CancellationToken) as ILocalSymbol; + var localVar = context.SemanticModel.GetSymbolOrBestCandidate(graphArgSyntax, context.CancellationToken) as ILocalSymbol; // Do not report and do not suggest to change the graph if it is used somewhere else to avoid disruptive side effects in the business logic // This includes fields and properties that store graph or a graph extension. The analysis of type members that store graphs diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/Walker.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/Walker.cs index 5472d4221..8b2412fc3 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/Walker.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXGraphCreationForBqlQueries/Walker.cs @@ -47,7 +47,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) return; } - var methodSymbol = _semanticModel.GetSymbolOrFirstCandidate(node, _cancellation) as IMethodSymbol; + var methodSymbol = _semanticModel.GetSymbolOrBestCandidate(node, _cancellation) as IMethodSymbol; if (IsBqlSelectOrSearch(methodSymbol)) { @@ -85,7 +85,7 @@ private bool ArgumentIsPropertyOrField(ExpressionSyntax bqlCallGraphArg) private bool ArgumentIsPropertyOrField(IdentifierNameSyntax identifier) { - var graphArgSymbol = _semanticModel.GetSymbolOrFirstCandidate(identifier, _cancellation); + var graphArgSymbol = _semanticModel.GetSymbolOrBestCandidate(identifier, _cancellation); return graphArgSymbol is IPropertySymbol or IFieldSymbol; } } diff --git a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXOverride/Helpers/BaseMethodReferenceInXmlDocComment.cs b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXOverride/Helpers/BaseMethodReferenceInXmlDocComment.cs index 8321882a2..b44da140b 100644 --- a/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXOverride/Helpers/BaseMethodReferenceInXmlDocComment.cs +++ b/src/Acuminator/Acuminator.Analyzers/StaticAnalysis/PXOverride/Helpers/BaseMethodReferenceInXmlDocComment.cs @@ -91,7 +91,7 @@ private static bool IsSeeAlsoWithCrefAttributeToBaseMethod(SemanticModel semanti if (attributes.Value[i] is not XmlCrefAttributeSyntax crefAttribute) continue; - var referencedSymbol = semanticModel.GetSymbolOrFirstCandidate(crefAttribute.Cref, cancellation); + var referencedSymbol = semanticModel.GetSymbolOrBestCandidate(crefAttribute.Cref, cancellation); if (DoesReferencedSymbolPointToBaseMethod(referencedSymbol, baseMethod)) return true; diff --git a/src/Acuminator/Acuminator.Tests/Tests/Utilities/LocalFunction/LocalFunctionTests.cs b/src/Acuminator/Acuminator.Tests/Tests/Utilities/LocalFunction/LocalFunctionTests.cs index 1319ecbd2..a3555379d 100644 --- a/src/Acuminator/Acuminator.Tests/Tests/Utilities/LocalFunction/LocalFunctionTests.cs +++ b/src/Acuminator/Acuminator.Tests/Tests/Utilities/LocalFunction/LocalFunctionTests.cs @@ -73,7 +73,7 @@ public async Task Static_Lambdas_Have_StaticSymbols(string text) foreach (var lambda in lambdas) { - var symbol = semanticModel.GetSymbolOrFirstCandidate(lambda, default) as IMethodSymbol; + var symbol = semanticModel.GetSymbolOrBestCandidate(lambda, default) as IMethodSymbol; symbol.Should().NotBeNull(); symbol!.MethodKind.Should().Be(MethodKind.LambdaMethod); diff --git a/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ArgumentsToParametersMapping/ArgumentsToParametersMapping.cs b/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ArgumentsToParametersMapping/ArgumentsToParametersMapping.cs index a284da2ed..81d574cb7 100644 --- a/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ArgumentsToParametersMapping/ArgumentsToParametersMapping.cs +++ b/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ArgumentsToParametersMapping/ArgumentsToParametersMapping.cs @@ -57,10 +57,15 @@ public ArgumentsToParametersMapping(int[] parametersMapping) _length = parametersMapping.Length; } - public IParameterSymbol GetMappedParameter(IMethodSymbol methodSymbol, int argIndex) + public IParameterSymbol? GetMappedParameter(IMethodSymbol methodSymbol, int argIndex) { methodSymbol.ThrowOnNull(); int parameterIndex = GetMappedParameterPosition(argIndex); + + // In case of a broken solution we can get index out of range error because of a method node being mapped to a wrong symbol + if (parameterIndex >= methodSymbol.Parameters.Length) + return null; + return methodSymbol.Parameters[parameterIndex]; } diff --git a/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ISymbolGenericUtils.cs b/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ISymbolGenericUtils.cs index 0875276e0..5ab330e2a 100644 --- a/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ISymbolGenericUtils.cs +++ b/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/ISymbolGenericUtils.cs @@ -1,7 +1,7 @@ #nullable enable - using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; @@ -28,6 +28,14 @@ public static bool IsReadOnly(this ISymbol symbol) => _ => false }; + public static ImmutableArray? Parameters(this ISymbol symbol) => + symbol.CheckIfNull() switch + { + IMethodSymbol method => method.Parameters, + IPropertySymbol property => property.Parameters, + _ => null + }; + public static bool IsReadOnly(this ITypeSymbol typeSymbol) { typeSymbol.ThrowOnNull(); diff --git a/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/SemanticModelUtils.cs b/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/SemanticModelUtils.cs index 1fc944d3b..6745d62c5 100644 --- a/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/SemanticModelUtils.cs +++ b/src/Acuminator/Acuminator.Utilities/Roslyn/Semantic/SemanticModelUtils.cs @@ -1,14 +1,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; - -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis; +using System.Threading.Tasks; using Acuminator.Utilities.Common; -using System.Threading.Tasks; -using System.Diagnostics.CodeAnalysis; +using Acuminator.Utilities.Roslyn.Syntax; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Acuminator.Utilities.Roslyn.Semantic { @@ -44,20 +46,95 @@ public static class SemanticModelUtils } /// - /// Get symbol or first candidate symbol from the . + /// Get symbol or best candidate symbol from the . /// /// The semanticModel to act on. /// The node to retrieve symbol for. /// Cancellation token. /// - /// The symbol or the first candidate symbol. + /// The symbol or the best candidate symbol. /// - public static ISymbol? GetSymbolOrFirstCandidate(this SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellation) + /// + /// The best candidate symbol is determined heuristically based on argument count in case of method group or overloaded method invocation.
+ /// The candidate with the closest match in terms of argument count is selected as the best candidate. + ///
+ public static ISymbol? GetSymbolOrBestCandidate(this SemanticModel semanticModel, SyntaxNode node, + CancellationToken cancellation) { node.ThrowOnNull(); - var symbolInfo = semanticModel.CheckIfNull().GetSymbolInfo(node, cancellation); - return symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); + + // Fast paths + if (symbolInfo.Symbol != null) + return symbolInfo.Symbol; + else if (symbolInfo.CandidateSymbols.Length == 1) + return symbolInfo.CandidateSymbols[0]; + else if (symbolInfo.CandidateSymbols.IsDefaultOrEmpty || + symbolInfo.CandidateReason is not (CandidateReason.Inaccessible or + CandidateReason.OverloadResolutionFailure or + CandidateReason.Ambiguous)) + { + return null; + } + + // Try to match symbol with node based on arguments count heuristic + var argumentList = node.GetArgumentsList(); + + if (argumentList == null) + return symbolInfo.CandidateSymbols.FirstOrDefault(); + + return GetBestCandidateHeuristicallyByArgsCount(symbolInfo, argumentList.Arguments.Count); + } + + private static ISymbol? GetBestCandidateHeuristicallyByArgsCount(in SymbolInfo symbolInfo, int argsCount) + { + int minSuitableParametersCount = int.MaxValue; + ISymbol? heuristicBestCandidate = null; + + foreach (ISymbol candidate in symbolInfo.CandidateSymbols) + { + var parameters = candidate.Parameters(); + + if (parameters == null) // symbol doesn't have parameters + continue; + + int parametersCount = parameters.Value.Length; + bool hasParamsParameter = parametersCount > 0 && parameters.Value[parametersCount - 1].IsParams; + + if (!hasParamsParameter) + { + // perfect match heuristic: the same number of arguments and parameters and no params parameter + if (argsCount == parametersCount) + return candidate; + else if (argsCount > parametersCount) + { + // Filtering out candidates with too few parameters when the invocation has more arguments than parameters, + // but only if there is no params parameter that can match extra arguments + continue; + } + } + + int optionalCount = parameters.Value.Count(p => p.IsOptional); + int minRequiredParametersCount = hasParamsParameter + ? parametersCount - optionalCount - 1 // params parameter can match 0 or more arguments, so it's not required + : parametersCount - optionalCount; + + // If the invocation has fewer arguments than required parameters, then this candidate is not a good match + if (argsCount < minRequiredParametersCount) + continue; + + if (minSuitableParametersCount > parametersCount) + { + // Keep the overload with fewest parameters + // In theory, we could specify parametersCount - 1 here for candidate with params parameter, + // but in C# overload resolution method with params parameter is considered less specific + // than the same method without params parameter, so we can keep the current heuristic simple + minSuitableParametersCount = parametersCount; + heuristicBestCandidate = candidate; + } + } + + return heuristicBestCandidate ?? symbolInfo.CandidateSymbols.FirstOrDefault(); } [SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Aggregated await is used")] diff --git a/src/Acuminator/Acuminator.Utilities/Roslyn/Syntax/MultipleParametersUsagesRewriter.cs b/src/Acuminator/Acuminator.Utilities/Roslyn/Syntax/MultipleParametersUsagesRewriter.cs index 3c37214a8..ed1c41301 100644 --- a/src/Acuminator/Acuminator.Utilities/Roslyn/Syntax/MultipleParametersUsagesRewriter.cs +++ b/src/Acuminator/Acuminator.Utilities/Roslyn/Syntax/MultipleParametersUsagesRewriter.cs @@ -34,7 +34,7 @@ public MultipleParametersUsagesRewriter(IDictionary