xoofx
xoofx

Reputation: 3762

How to collect all MethodDeclarationSyntax transitively with Roslyn?

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

Answers (1)

Marius Ungureanu
Marius Ungureanu

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 ISymbols, rather than SyntaxNodes.

ISymbol already contains the SyntaxReferences 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

Related Questions