Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does SyntaxNode.ReplaceNode change the SyntaxTree options?

Tags:

c#

roslyn

I'm trying to replace nodes within a syntax tree in Roslyn, and it's just about working, but with an annoyance which feels it shouldn't be a problem.

The syntax tree is generated from a script, and I want the result to be a script-based syntax tree too - but for some reason, replacing a node in the tree creates a new syntax tree with changed options: the Kind becomes Regular instead of Script. That's fixable with SyntaxTree.WithRootAndOptions but it feels like I'm doing something wrong if I need to call that.

Sample program:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        Script script = CSharpScript.Create("Console.WriteLine(\"Before\")",
            ScriptOptions.Default.AddImports("System"));

        var compilation = script.GetCompilation();
        var tree = compilation.SyntaxTrees.Single();

        var after = SyntaxFactory.LiteralExpression(
            SyntaxKind.StringLiteralExpression,
            SyntaxFactory.Literal("After"));

        var root = tree.GetRoot();
        var before = root.DescendantNodes().OfType<LiteralExpressionSyntax>().Single();
        var newRoot = root.ReplaceNode(before, after);
        var fixedTree = newRoot.SyntaxTree.WithRootAndOptions(newRoot, tree.Options);

        Console.WriteLine(newRoot);                         // Console.WriteLine("After")
        Console.WriteLine(tree.Options.Kind);               // Script
        Console.WriteLine(newRoot.SyntaxTree.Options.Kind); // Regular
        Console.WriteLine(fixedTree.Options.Kind);          // Script
    }
}

(Output is in comments.)

Is this workaround actually correct, or is there some different way I should be replacing the node in the tree?

like image 485
Jon Skeet Avatar asked May 19 '17 02:05

Jon Skeet


1 Answers

When you replace nodes in a tree, you create a new sub-tree of nodes. Essentially, this new sub-tree is not contained within a SyntaxTree. However, the SyntaxTree property on the node conjurs up a new one if you ever observe it. At the time it does this, the original SyntaxTree is long gone, so its not possible to retain the parse options. Even if it were possible, retaining the options would be meaningless because you no longer have a tree produced by the parser.

The reason Roslyn creates this SyntaxTree is that so all sub-trees are technically contained within a SyntaxTree instance, so that Roslyn can associated diagnostics with it. This is useful if you use the SemanticModel's exploratory API's to attempt to bind & get semantic info for fragments of trees that are not currently part of the compilation. The diagnostic reports the error and its location, which denotes the tree instance it is within.

like image 74
Matt Warren Avatar answered Oct 21 '22 05:10

Matt Warren