Skip to content

Add a SymbolWalker class #6150

Closed
Closed
@JoshVarty

Description

@JoshVarty

I like the approach taken by the SyntaxWalker. It separates the traversal scaffolding code from my implementation. It would be nice to have a similar API available at the symbol level. Currently to traverse the symbols in a given compilation we must inherit from SymbolVisitor and write all the scaffolding ourselves.

If you think this is relatively simple (the SyntaxWalker seems pretty basic) you can mark is at "Up for grabs" and I'll take a stab at it.

Activity

CyrusNajmabadi

CyrusNajmabadi commented on Oct 19, 2015

@CyrusNajmabadi
Member

THe problem here is that Syntax is a tree, while Symbols are a graph. How does the SymbolWalker terminate?

JoshVarty

JoshVarty commented on Oct 20, 2015

@JoshVarty
ContributorAuthor

My thought was to visit them from the global namespace descending to child namespaces and types. Then from those types to methods and properties. Then from those methods/properties to their parameters, type arguments and locals. (Perhaps I'm missing something, but can we not think of this approach as traversing a tree?)

However, in looking over IMethodSymbol and IPropertySymbol I don't see any ways in which we can get to locals. I don't believe we should build this unless we could cover all the symbols in the compilation, so I'm going to close this for now.

CyrusNajmabadi

CyrusNajmabadi commented on Oct 20, 2015

@CyrusNajmabadi
Member

Ok. So now you get to an IParameterSymbol. Would you then 'walk' into it's type? If so, then you face recursion problems. If not, then what are you walking, just trees?

If that's the case, you could simply use a SyntaxWalker and call GetDeclaredSymbol on all the nodes it hits. You'll then have a SymbolWalker.

JoshVarty

JoshVarty commented on Oct 20, 2015

@JoshVarty
ContributorAuthor

No I wasn't thinking about walking the type of the IParameterSymbol, we'd stop when we reached it. The idea is that the underlying type there would have been visited elsewhere. (It's an INamedTypeSymbol so it would have been visited as the child of a namespace at some point).

If that's the case, you could simply use a SyntaxWalker and call GetDeclaredSymbol on all the nodes it hits. You'll then have a SymbolWalker.

I think you're thinking about visiting symbols on a document level. My thought process was to create something that would visit symbols on the compilation level.

I'm trying to (maybe wrongly?) think of symbols in a child-parent relationship. A namespace contains types. Types contain members. Members contain parameters, locals, type arguments etc. Unfortunately I think my approach breaks down once we reach the member level. There's no way I can see to easily get their "Child Symbols".

CyrusNajmabadi

CyrusNajmabadi commented on Oct 20, 2015

@CyrusNajmabadi
Member

I'm trying to (maybe wrongly?) think of symbols in a child-parent relationship.

I don't think it's wrong. I think it's just an interesting view over something intrinsically graph-like.

There's no way I can see to easily get their "Child Symbols".

Why not, once you hit a member, just go back to the syntax and go dive in and get the symbols from it and a semantic model?

jnm2

jnm2 commented on Oct 13, 2018

@jnm2
Contributor

I'm trying to (maybe wrongly?) think of symbols in a child-parent relationship. A namespace contains types. Types contain members. Members contain parameters, locals, type arguments etc. Unfortunately I think my approach breaks down once we reach the member level. There's no way I can see to easily get their "Child Symbols".

So I'm interested in walking only declarations of things (types, members, parameters), not references (base class, parameter type, etc). I think this neatly solves the graph problem. SymbolDeclarationWalker?

CyrusNajmabadi

CyrusNajmabadi commented on Oct 13, 2018

@CyrusNajmabadi
Member

@jnm2

If that's the case, you could simply use a SyntaxWalker and call GetDeclaredSymbol on all the nodes it hits. You'll then have a SymbolWalker.

:)

jnm2

jnm2 commented on Oct 13, 2018

@jnm2
Contributor

@CyrusNajmabadi But I don't want to walk syntax. I want a unified semantic view of things like partial classes. Also, walking the syntax and calling GetDeclaredSymbol on every node can't be as smart as looking through ISymbol members.

jnm2

jnm2 commented on Oct 13, 2018

@jnm2
Contributor

Also, it's not inconceivable that there will be members in the symbolic model that are not present in the syntax. For example, With* methods if we get withers.

CyrusNajmabadi

CyrusNajmabadi commented on Oct 13, 2018

@CyrusNajmabadi
Member

Gotcha. FWIW though, what you're describing is just a small handlful of LOCs. I'm skeptical about the overal value here. It may be good for your very specific case. But it's unclear to me if it's going to be suitable elsewhere. I mean, some users will say "i don't want implicit members" some will say " i want to walk into some of these referenced types". some will say "i don't want members from other parts not in this file", etc. etc.

The walking code is so trivial that just writing it yourself seems super simple and easy to just do that way.

jnm2

jnm2 commented on Oct 13, 2018

@jnm2
Contributor

Does this cover every ISymbol that represents an actual declaration in today's Roslyn?

public abstract class SymbolDeclarationWalker : SymbolVisitor
{
    public override void VisitAssembly(IAssemblySymbol symbol)
    {
        base.VisitAssembly(symbol);

        foreach (var module in symbol.Modules)
        {
            module.Accept(this);
        }
    }

    public override void VisitModule(IModuleSymbol symbol)
    {
        base.VisitModule(symbol);

        symbol.GlobalNamespace?.Accept(this);
    }

    public override void VisitNamespace(INamespaceSymbol symbol)
    {
        base.VisitNamespace(symbol);

        foreach (var member in symbol.GetMembers())
        {
            member.Accept(this);
        }
    }

    public override void VisitNamedType(INamedTypeSymbol symbol)
    {
        base.VisitNamedType(symbol);

        foreach (var typeParameter in symbol.TypeParameters)
        {
            VisitTypeParameter(typeParameter);
        }

        foreach (var member in symbol.GetMembers())
        {
            member.Accept(this);
        }
    }

    public override void VisitEvent(IEventSymbol symbol)
    {
        base.VisitEvent(symbol);

        if (symbol.AddMethod != null)
        {
            VisitMethod(symbol.AddMethod);
        }

        if (symbol.RemoveMethod != null)
        {
            VisitMethod(symbol.RemoveMethod);
        }

        if (symbol.RaiseMethod != null)
        {
            VisitMethod(symbol.RaiseMethod);
        }
    }

    public override void VisitProperty(IPropertySymbol symbol)
    {
        base.VisitProperty(symbol);

        foreach (var parameter in symbol.Parameters)
        {
            VisitParameter(parameter);
        }

        if (symbol.GetMethod != null)
        {
            VisitMethod(symbol.GetMethod);
        }

        if (symbol.SetMethod != null)
        {
            VisitMethod(symbol.SetMethod);
        }
    }

    public override void VisitMethod(IMethodSymbol symbol)
    {
        base.VisitMethod(symbol);

        foreach (var typeParameter in symbol.TypeParameters)
        {
            VisitTypeParameter(typeParameter);
        }

        foreach (var parameter in symbol.Parameters)
        {
            VisitParameter(parameter);
        }
    }
}
CyrusNajmabadi

CyrusNajmabadi commented on Oct 13, 2018

@CyrusNajmabadi
Member

i don't see it handling things like local declarations.

jnm2

jnm2 commented on Oct 13, 2018

@jnm2
Contributor

I mean, some users will say "i don't want implicit members"

You often don't want most of what a walker walks through. The concept of filtering out what you don't care about is well understood.

some will say " i want to walk into some of these referenced types"

They can override and add a symbol.BaseType?.Accept(this); call then.

some will say "i don't want members from other parts not in this file"

Seems like they care enough about syntax in this scenario that they should start there.

CyrusNajmabadi

CyrusNajmabadi commented on Oct 13, 2018

@CyrusNajmabadi
Member

also fields.

25 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @JoshVarty@CyrusNajmabadi@jnm2

        Issue actions

          Add a SymbolWalker class · Issue #6150 · dotnet/roslyn