Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SyntaxRewriter and multiple statements

Tags:

roslyn

I've encountered a tricky situation using the SyntaxRewriter in Roslyn. I'd like to rewrite certain kinds of statements, including local variable declarations. The solution requires me to transform the statements in question into multiple statements, as in the following trivial example:

void method()
{
    int i;
}

becomes

void method()
{
    int i;
    Console.WriteLine("I declared a variable.");
}

I've seen other examples where blocks are used to accomplish something similar, but of course in the case of a variable declaration, the scope of the declaration will be affected. I came up with the following solution, but I'm balking at it. It seems over-complicated, and requires a break in the visitor pattern:

class Rewriter: SyntaxRewriter
{
    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
    {
        if (typeof(TNode) == typeof(StatementSyntax))
            return Syntax.List<TNode>(list.SelectMany(st => RewriteStatementInList(st as StatementSyntax).Cast<TNode>()));
        else
            return base.VisitList<TNode>(list);
    }

    private IEnumerable<SyntaxNode> RewriteStatementInList(StatementSyntax node)
    {
        if (node is LocalDeclarationStatementSyntax)
            return PerformRewrite((LocalDeclarationStatementSyntax)node);
        //else if other cases (non-visitor) 

        return Visit(node).AsSingleEnumerableOf();
    }

    private IEnumerable<SyntaxNode> PerformRewrite(LocalDeclarationStatementSyntax orig)
    {
        yield return orig;
        yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");");
    }
}

What am I missing? Editing statements and removing them (via empty statement) seem more straight forward than rewriting to multiples.

My take on the answer:

class Rewriter : SyntaxRewriter
{
    readonly ListVisitor _visitor = new ListVisitor();

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
    {
        var result = Syntax.List(list.SelectMany(_visitor.Visit).Cast<TNode>());
        return base.VisitList(result);
    }

    private class ListVisitor : SyntaxVisitor<IEnumerable<SyntaxNode>>
    {
        protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node)
        {
            yield return node;
        }

        protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
             LocalDeclarationStatementSyntax node)
        {
            yield return node;
            yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");");
        }
    }
}
like image 806
Jeff Griffin Avatar asked Mar 19 '12 17:03

Jeff Griffin


2 Answers

I think there is a simple way to make your Rewriter more visitor-like: use another visitor to process the nodes in the list:

class Rewriter: SyntaxRewriter
{
    readonly Visitor m_visitor = new Visitor();

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
    {
        var result = Syntax.List(list.SelectMany(m_visitor.Visit).Cast<TNode>());
        return base.VisitList(result);
    }
}

class Visitor : SyntaxVisitor<IEnumerable<SyntaxNode>>
{
    protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node)
    {
        return new[] { node };
    }

    protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
         LocalDeclarationStatementSyntax node)
    {
        return new SyntaxNode[]
               {
                   node,
                   Syntax.ParseStatement(
                       "Console.WriteLine(\"I declared a variable.\");")
               };
    }
}

Note that this is not safe and will throw an InvalidCastException if you return a collection that contains object that is not TNode from the Visitor.

like image 158
svick Avatar answered Nov 25 '22 12:11

svick


I don't know of a radically better way to handle this. One other approach that is slightly more "visitor like" is to use VisitLocalDeclaration to annotate nodes that you want to replace with something like: return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);. Then in VisitList, you can just find the child nodes that your annotation and do the rewrite at that point.

like image 39
Kevin Pilch Avatar answered Nov 25 '22 14:11

Kevin Pilch