Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow CSharpSyntaxGenerator to be used as a source generator #47252

Conversation

jasonmalinowski
Copy link
Member

This takes a fairly hacky approach to updating the code by converting the CSharpSyntaxGenerator project to build twice: once as a
netcoreapp3.1 console app like it always did, and now also as a netstandard2.0 library that is a source generator. The source generator one only supports building the .cs outputs that are consumed as regular APIs, and ignores the test or grammar outputs for now.

@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from 6902552 to 36a8e96 Compare September 1, 2020 00:07
@jasonmalinowski jasonmalinowski self-assigned this Sep 1, 2020
@@ -5,22 +5,24 @@
<Platform Condition="'$(Platform)' == ''">x64</Platform>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms>
<OutputType>Exe</OutputType>
<RootNamespace>Roslyn.Compilers.CSharp.Internal.CSharpSyntaxGenerator</RootNamespace>
<RootNamespace>CSharpSyntaxGenerator</RootNamespace>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the code in this project was in CSharpSyntaxGenerator; I don't know why the RootNamespace was what it was originally set to.

Comment on lines +19 to +23
<!-- If we're building the command line tool, we have to include some dependencies used for grammar generation -->
<Compile Include="..\..\..\..\..\Compilers\Core\Portable\Symbols\WellKnownMemberNames.cs" Link="Grammar\WellKnownMemberNames.cs" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />
<Compile Include="..\..\..\..\..\Compilers\CSharp\Portable\Declarations\DeclarationModifiers.cs" Link="Grammar\DeclarationModifiers.cs" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />
<Compile Include="..\..\..\..\..\Compilers\CSharp\Portable\Syntax\SyntaxKind.cs" Link="Grammar\SyntaxKind.cs" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />
<Compile Include="..\..\..\..\..\Compilers\CSharp\Portable\Syntax\SyntaxKindFacts.cs" Link="Grammar\SyntaxKindFacts.cs" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was why I'm doing this funky build hackery: if you're producing the grammar tool, it actually builds by linking in the source files of the compiler. I wanted the assurance that the source generator form was behaving as a true 'source generator' and any information about the source was actually being read out of the inputs rather than being linked at the time of build. If it was consuming stuff from the earlier build then we could end up in some really funky situations where a change would have to be made the compiler directory, then you'd have to build the new generator, and then build the other project again to get everything into sync.

}

// And create a SourceText from the StringBuilder, once again avoiding allocating a single massive string
context.AddSource(hintName, SourceText.From(new StringBuilderReader(stringBuilder), stringBuilder.Length, encoding: Encoding.UTF8));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chsienki Should we have an API that lets you do AddSource and directly give it a StringBuilder? Having to write this myself seems icky.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the overload that takes a string?

Suggested change
context.AddSource(hintName, SourceText.From(new StringBuilderReader(stringBuilder), stringBuilder.Length, encoding: Encoding.UTF8));
context.AddSource(hintName, stringBuilder.ToString());

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sharwell: because that'll allocate a 2MB string. 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasonmalinowski I'm 100% fine with adding at string builder overload. Do you want me to add it to this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say no, so if we have to revert this we're also not reverting a public API change that somebody else might depend on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #49082 to track it.

@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from 36a8e96 to 59782ac Compare September 1, 2020 00:45
@jasonmalinowski jasonmalinowski marked this pull request as ready for review September 1, 2020 00:50
@jasonmalinowski jasonmalinowski requested review from a team as code owners September 1, 2020 00:50
@333fred
Copy link
Member

333fred commented Sep 1, 2020

I want to make sure that we don't merge this before we've validated the experience works on vscode, or this will effectively break my ability to work.

@jasonmalinowski jasonmalinowski marked this pull request as draft September 2, 2020 01:04
@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from 59782ac to ba81203 Compare September 2, 2020 01:04
@jasonmalinowski
Copy link
Member Author

Marking back as draft as there's some toolset changes and other stuff that needs fixing.

@jasonmalinowski
Copy link
Member Author

jasonmalinowski commented Sep 2, 2020

So I'm able to see the generator running fine as long as I'm running the latest Omnisharp by setting omnisharp.path to latest in my settings.json. It doesn't seem that editing the Syntax.xml results in the generators rerunning though. @JoeRobich is there some known bug where additional files don't get updated in the Workspace? I don't see any code doing updates in OmnisharpWorkspace for additional files, but it's possible I don't know what I'm looking for.

@jasonmalinowski jasonmalinowski marked this pull request as ready for review September 2, 2020 21:19
@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from 2a3fd64 to 08f43ad Compare September 2, 2020 22:54
@RikkiGibson
Copy link
Contributor

RikkiGibson commented Sep 2, 2020

I want to make sure that we don't merge this before we've validated the experience works on vscode, or this will effectively break my ability to work.

I also work primarily in vscode and losing the ability to go-to-def etc. will be a significant inconvenience for me.

In addition, I'm concerned about moving forward to a model where, because the generated syntax isn't version controlled, it's difficult for us to audit changes to it over time, or for example review a change to the generator in terms of the diff of the output code.

@CyrusNajmabadi
Copy link
Member

In addition, I'm concerned about moving forward to a model where

These are fundamental development concerns. I think if we're going to be pushing SGs as a solution to codegen, we'll need answers there. If we can't even use them for a basic case like this for us (i.e. simple model produces simple codegen'ed api), i hesitate about us stating that others should use it in production settings.

@RikkiGibson
Copy link
Contributor

Thinking over it a bit. I think that it's just a matter of distinguishing the "generator user" versus "generator author" experiences. We are the latter. In our case, the output of the generator when we feed in Syntax.xml for example is a test baseline. CI needs to fail when the baseline is out of sync with what the generator actually produces. So if we can make sure that happens, then I'm good.

@jasonmalinowski
Copy link
Member Author

We are the latter.

@RikkiGibson In this case, I think we're both, right? If you're adding new syntax nodes as part of feature development, do you you need to show the diff of the underlying generated file in that case? You might want to reference it while you're developing, but is that actually reviewed on it's own? I agree that if you're changing the generator then it's a different story.

In our case, the output of the generator when we feed in Syntax.xml for example is a test baseline. CI needs to fail when the baseline is out of sync with what the generator actually produces. So if we can make sure that happens, then I'm good.

@RikkiGibson can you clarify this a bit? The CI today fails if you failed to run the generator before checking in; the point of moving this to a source generator is it's not possible to be out of sync anymore. (indeed that's part of the point of generators in the first place...)

@RikkiGibson
Copy link
Contributor

In this case, I think we're both, right?

Yes.

If you're adding new syntax nodes as part of feature development, do you you need to show the diff of the underlying generated file in that case?

No, that is not particularly useful to do. Perhaps if an input change exposed a bug in the generator, but that seems like something you'd examine locally, not in code review.

can you clarify this a bit?

Sorry, I stated this too much in terms of "how it works right now". When someone PRs an implementation change for a generator like CSharpSyntaxGenerator I just want to be able to easily review the diff of the generator output.

Less importantly: I can also conceive of situations where I'd want to f12 on a generated method, then run a blame to figure out when the generator was changed to produce the implementation that particular way.

@CyrusNajmabadi
Copy link
Member

Less importantly: I can also conceive of situations where I'd want to f12 on a generated method, then run a blame to figure out when the generator was changed to produce the implementation that particular way.

This is also in teh scope of "i think everyone who uses source generators would want this". So by dogfooding this, we get a realistic understanding around requests for that sort of thing that will likely come in the future. :)

@chsienki
Copy link
Member

chsienki commented Sep 3, 2020

@jasonmalinowski which analyzer rules are failing to be taken into account? I see a couple in the correctness leg, but they look like they're in actual files, not generated ones? I see now you made it a global config. I've written up some thoughts on #47384 about how we should handle this.

@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch 2 times, most recently from 0c010b8 to acc724c Compare September 9, 2020 21:46
@jasonmalinowski jasonmalinowski requested a review from a team as a code owner September 10, 2020 20:23
@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from 085898b to 67a8cd1 Compare September 10, 2020 20:34
@chsienki
Copy link
Member

chsienki commented Oct 6, 2020

The analyzer consistency problem wasn't directly caused by Jason's changes, but rather the changes are surfacing an issue we already had:

The net5.0 sdk now includes the NetAnalyzers package directly. We also reference it via NuGet, meaning we're trying to load the same analyzers twice. Previously when we disabled analyzers we did so by removing them at the MSBuild layer, meaning we never actually tried to load them. As part of this PR we're now disabling them via the new flag, so we're now loading them (even though they don't run) meaning we correctly detect the duplicate analyzers.

For now I've disabled the in-box SDK analyzers, as we want to run them on other TFM builds too (and keep them in-sync) but we might consider conditionally including/excluding depending on the TFM we're building in the future.

@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch 2 times, most recently from 9a6f186 to ec1782f Compare October 12, 2020 19:08
@jasonmalinowski
Copy link
Member Author

Tagging @dotnet/roslyn-compiler, @dotnet/roslyn-infrastructure for reviews.

And in general recognition to the conversation above: it is absolutely understood that our tooling around source generators is not perfect. Our goal of enabling this is to get dogfooding (since right now we have literally no dogfooding at all) and to use our experiences to help guide which experiences to improve next. We believe we've addressed core needs that would cause outright blockage of workflows that would have no workarounds whatsoever. We know there's going to be some pain, but it's a tradeoff we're willing to make to deliver a better product to our customers.

@jasonmalinowski
Copy link
Member Author

@dotnet/roslyn-compiler could I get some reviews?

@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from 89a7db7 to b32f294 Compare October 27, 2020 02:09
@jasonmalinowski
Copy link
Member Author

@jaredpar @chsienki Diagnostic handling updated. Ready for review again.

jasonmalinowski and others added 8 commits November 3, 2020 10:59
This takes a fairly hacky approach to updating the code by converting
the CSharpSyntaxGenerator project to build twice: once as a
netcoreapp3.1 console app like it always did, and now also as a
netstandard2.0 library that is a source generator. The source generator
one only supports building the .cs outputs that are consumed as regular
APIs, and ignores the test or grammar outputs for now.

There is also no handling for cancellation yet which is not good for
performance.
There's built-in support for this now, and it's more robust than the
version we had, so let's use it.
This should apply to source-generated files too.
If we match the toolset version, it might be a version too new to load
in the various IDEs our team uses; this forces there to be a separate
version to control this.
@jasonmalinowski jasonmalinowski force-pushed the convert-csharp-syntax-generator-to-a-source-generator branch from b32f294 to 4cdaef4 Compare November 3, 2020 20:09
For now, it's been decided to keep these files checked in to simplify
code review. But unlike before the compiler is no longer going to
consume them.
…ax-generator-to-a-source-generator

This also consolidates the global .editorconfigs we have.
@jasonmalinowski jasonmalinowski merged commit 4758a7b into dotnet:master Nov 13, 2020
@ghost ghost added this to the Next milestone Nov 13, 2020
@jasonmalinowski jasonmalinowski deleted the convert-csharp-syntax-generator-to-a-source-generator branch November 13, 2020 02:15
@allisonchou allisonchou modified the milestones: Next, 16.9.P2 Nov 24, 2020
}
}

private sealed class SourceTextReader : TextReader
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a TextReader from SourceText seems like this is something that should be provided by Roslyn as well. Otherwise, people are more likely to create a string out of it than make their own reader, which is less efficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.