Reputation: 3762
Given a list of MethodDeclarationSyntax
I would like to collect all the methods in a solution that are calling this method transitively.
I have been using the following code:
var methods = new Stack<MethodDeclarationSyntax>();
... // fill methods with original method to start from
var visited = new HashSet<MethodDeclarationSyntax>();
while (methods.Count > 0)
{
var method = methods.Pop();
if (!visited.Add(method))
{
continue;
}
var methodSymbol = (await solution.GetDocument(method.SyntaxTree).GetSemanticModelAsync()).GetDeclaredSymbol(method);
foreach (var referencer in await SymbolFinder.FindCallersAsync(methodSymbol, solution))
{
var callingMethod = (MethodDeclarationSyntax) referencer.CallingSymbol.DeclaringSyntaxReferences[0].GetSyntax();
methods.Push(callingMethod);
}
}
The problem is that MethodDeclarationSyntax
doesn't seem to be a singleton, so this loop is running forever, visiting the same methods again and again.
What is the proper way to uniquely identify a MethodDeclarationSyntax
in a Dictionary/Hashset?
Edit 1)
As a workaround, I'm using the following MethodDeclarationSyntaxComparer
to initialize my HashSet
, but it looks very fragile:
private class MethodDeclarationSyntaxComparer: IEqualityComparer<MethodDeclarationSyntax>
{
public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y)
{
var xloc = x.GetLocation();
var yloc = y.GetLocation();
return xloc.SourceTree.FilePath == yloc.SourceTree.FilePath &&
xloc.SourceSpan == yloc.SourceSpan;
}
public int GetHashCode(MethodDeclarationSyntax obj)
{
var loc = obj.GetLocation();
return (loc.SourceTree.FilePath.GetHashCode() * 307) ^ loc.SourceSpan.GetHashCode();
}
}
Upvotes: 8
Views: 1016
Reputation: 196
I'm wondering whether using SyntaxNode
here is the right way to go.
Since you're already using SymbolFinder
and you're using the semantic model, maybe the right way to go is to actually use ISymbol
s, rather than SyntaxNode
s.
ISymbol
already contains the SyntaxReference
s you are using, so:
var methods = new Stack<IMethodSymbol>();
... // fill methods with original method to start from
... // convert methods to symbols via semanticModel.GetDeclaredSymbol (node);
var visited = new HashSet<IMethodSymbol>();
while (methods.Count > 0)
{
var method = methods.Pop();
if (!visited.Add(method))
{
continue;
}
foreach (var referencer in await SymbolFinder.FindCallersAsync(method, solution))
{
var callingMethod = (MethodDeclarationSyntax) referencer.CallingSymbol.DeclaringSyntaxReferences[0].GetSyntax();
methods.Push(callingMethod);
}
}
You could possibly make the visited hashset into a Dictionary<IMethodSymbol, IEnumerable<Location>>
, and concat all the locations, and thus reconstruct the syntaxes from that result.
Upvotes: 5