|
1 | 1 | using System; |
2 | 2 | using System.Collections.Generic; |
| 3 | +using System.Diagnostics.CodeAnalysis; |
3 | 4 | using System.Linq; |
4 | 5 | using System.Threading; |
5 | | - |
6 | | -using Microsoft.CodeAnalysis.CSharp; |
7 | | -using Microsoft.CodeAnalysis; |
| 6 | +using System.Threading.Tasks; |
8 | 7 |
|
9 | 8 | using Acuminator.Utilities.Common; |
10 | | -using System.Threading.Tasks; |
11 | | -using System.Diagnostics.CodeAnalysis; |
| 9 | +using Acuminator.Utilities.Roslyn.Syntax; |
| 10 | + |
| 11 | +using Microsoft.CodeAnalysis; |
| 12 | +using Microsoft.CodeAnalysis.CSharp; |
| 13 | +using Microsoft.CodeAnalysis.CSharp.Syntax; |
12 | 14 |
|
13 | 15 | namespace Acuminator.Utilities.Roslyn.Semantic |
14 | 16 | { |
@@ -52,12 +54,61 @@ public static class SemanticModelUtils |
52 | 54 | /// <returns> |
53 | 55 | /// The symbol or the first candidate symbol. |
54 | 56 | /// </returns> |
55 | | - public static ISymbol? GetSymbolOrBestCandidate(this SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellation) |
| 57 | + public static ISymbol? GetSymbolOrBestCandidate(this SemanticModel semanticModel, SyntaxNode node, |
| 58 | + CancellationToken cancellation) |
56 | 59 | { |
57 | 60 | node.ThrowOnNull(); |
58 | | - |
59 | 61 | var symbolInfo = semanticModel.CheckIfNull().GetSymbolInfo(node, cancellation); |
60 | | - return symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); |
| 62 | + |
| 63 | + // Fast paths |
| 64 | + if (symbolInfo.Symbol != null) |
| 65 | + return symbolInfo.Symbol; |
| 66 | + else if (symbolInfo.CandidateSymbols.Length == 1) |
| 67 | + return symbolInfo.CandidateSymbols[0]; |
| 68 | + else if (symbolInfo.CandidateSymbols.IsDefaultOrEmpty || |
| 69 | + symbolInfo.CandidateReason is not (CandidateReason.Inaccessible or |
| 70 | + CandidateReason.OverloadResolutionFailure or |
| 71 | + CandidateReason.Ambiguous)) |
| 72 | + { |
| 73 | + return null; |
| 74 | + } |
| 75 | + |
| 76 | + // Try to match symbol with node based on arguments count heuristic |
| 77 | + var argumentList = node.GetArgumentsList(); |
| 78 | + |
| 79 | + if (argumentList == null) |
| 80 | + return symbolInfo.CandidateSymbols.FirstOrDefault(); |
| 81 | + |
| 82 | + return GetBestCandidateHeuristicallyByArgsCount(symbolInfo, argumentList.Arguments.Count); |
| 83 | + } |
| 84 | + |
| 85 | + private static ISymbol? GetBestCandidateHeuristicallyByArgsCount(in SymbolInfo symbolInfo, int argsCount) |
| 86 | + { |
| 87 | + int minSuitableParametersCount = int.MaxValue; |
| 88 | + ISymbol? heuristicBestCandidate = null; |
| 89 | + |
| 90 | + foreach (ISymbol candidate in symbolInfo.CandidateSymbols) |
| 91 | + { |
| 92 | + var parameters = candidate.Parameters(); |
| 93 | + |
| 94 | + if (parameters == null) // symbol doesn't have parameters |
| 95 | + continue; |
| 96 | + |
| 97 | + int parametersCount = parameters.Value.Length; |
| 98 | + |
| 99 | + if (argsCount > parametersCount) |
| 100 | + continue; |
| 101 | + else if (argsCount == parametersCount) |
| 102 | + return candidate; // perfect match |
| 103 | + else if (minSuitableParametersCount > parametersCount) |
| 104 | + { |
| 105 | + // Keep the overload with fewest parameters |
| 106 | + minSuitableParametersCount = parametersCount; |
| 107 | + heuristicBestCandidate = candidate; |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + return heuristicBestCandidate ?? symbolInfo.CandidateSymbols.FirstOrDefault(); |
61 | 112 | } |
62 | 113 |
|
63 | 114 | [SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Aggregated await is used")] |
|
0 commit comments