Breaking change and possible Bug in SyntaxFactory.ParseCompilationUnit with the new GlobalStatementSyntax #48512
-
Version Used: Microsoft.CodeAnalysis.CSharp.Workspaces 3.8.0-3.final and 3.7.0 Steps to Reproduce:
Codeusing Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Linq;
namespace CompilerTest
{
class Program
{
private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location);
private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location);
private const string methodText = @"#region SetValue
private void SetValue(double value)
{
//whatever
}
#endregion SetValue
";
private const string source = @"namespace Fis.Test{
public class TestItem {
public double Value { get; set; }
}
}";
private const string expectedSource = @"namespace Fis.Test{
public class TestItem {
public double Value { get; set; }
#region SetValue
private void SetValue(double value)
{
//whatever
}
#endregion SetValue
}
}";
static void Main()
{
var projectId = ProjectId.CreateNewId(debugName: "TestProjekt");
Solution solution;
using (var workspace = new AdhocWorkspace())
{
solution = workspace
.CurrentSolution
.AddProject(projectId, "TestProjekt", "TestProjekt", LanguageNames.CSharp)
.AddMetadataReference(projectId, CorlibReference)
.AddMetadataReference(projectId, SystemCoreReference)
.AddMetadataReference(projectId, CSharpSymbolsReference)
.AddMetadataReference(projectId, CodeAnalysisReference);
}
var documentId = DocumentId.CreateNewId(projectId, debugName: "Test.cs");
solution = solution.AddDocument(documentId, "Test.cs", SourceText.From(source));
var doc=solution.GetProject(projectId).GetDocument(documentId);
WriteStatus();
//Modification
var newNodes = SyntaxFactory.ParseCompilationUnit(methodText).ChildNodes();
SyntaxNode root = doc.GetSyntaxRootAsync().Result;
doc= doc.WithSyntaxRoot
(
root.InsertNodesAfter
(
root.DescendantNodes().OfType<PropertyDeclarationSyntax>().First(),
newNodes
)
);
WriteStatus();
void WriteStatus()
{
var diagnostics=doc.GetSemanticModelAsync().Result.GetDiagnostics();
Console.WriteLine($"Diagnostics: {diagnostics.Length}\n{string.Join('\n', diagnostics)}");
Console.WriteLine();
Console.WriteLine(doc.GetTextAsync().Result);
Console.WriteLine();
Console.WriteLine();
}
}
}
}
Output
Expected Behavior: Actual Behavior: I suspect this is due to addition of TopLevelStatements. This probably introduced the following change in The addition of the I did not notice this issue happening in Visual Studio or Visual Studio Preview (yet?) but my AnalyzerUnitTests failed for the Codefixes that used this approach since v3.7. I guess my UnitTests Payed off^^ |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 3 replies
-
This is by design (for a few reasons). First, we do not absolutely guarantee parsing output from version to version. Some construccts will change parsing depending on the versions you're using. For example,
this was never legal code in the past. So the parser just did its best and just produced some concrete syntax tree encoding that (using all the error recovery logic we baked in). That code is still not semantically legal (though it is syntactically legal). Our parser produces the new tree it thinks best represents this here (a local-function). Possible workaround coming up. |
Beta Was this translation helpful? Give feedback.
-
Yes. That is the recommended way to handle this. Or, just synthesize the node you want directly instead of using 'ParseXXX' functions. Then you have 100% control and don't have to worry about the parsing functions changing. |
Beta Was this translation helpful? Give feedback.
-
We could, but that could potentially have other negative implications. We'd probably want this to be a Global-Function so that other functionality (including IDE functionality) works. |
Beta Was this translation helpful? Give feedback.
-
You're manually generating a tree. That won't have any diagnostics in it execpt the ones you put in it, or which are in the nodes you're inserting into the tree. In htis case, you're only inserting the member, and not the trivia that will be on the EOF token in the original compilation unit you parsed. |
Beta Was this translation helpful? Give feedback.
-
Ok thank you for your answers @CyrusNajmabadi. Just one thing left unanswered I think. Does it make sense to allow adding a |
Beta Was this translation helpful? Give feedback.
This is by design (for a few reasons). First, we do not absolutely guarantee parsing output from version to version. Some construccts will change parsing depending on the versions you're using. For example,
record Foo(int i) { }
would previously parse as a method/function. Now it will parse as arecord
. On top of this, we absolutely will change parsing around constructs that were previously not legal and now are legal. In your case you have:this was never legal code in the past. So the parser just did its best and just produced some concrete syntax tree encoding that (using …