-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Add a Workspace API to represent source generated documents #49198
Add a Workspace API to represent source generated documents #49198
Conversation
3f61c17
to
97b67c0
Compare
|
||
The only surprise you might run into is taking a `SyntaxTree` from an earlier `Solution`, passing it to a newer `Solution`, and getting a document that no longer exists because in the later `Solution` is no longer generating this document. However, passing a `SyntaxTree` between snapshots like that is questionable in the first place. Any code that is working with multiple snapshots and knew that a Document between the snapshots had the same tree could have done something like this, but that code is potentially broken _anyways_ with source generators since now one tree can change due to another tree changing. | ||
|
||
## APIs on `Workspace` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the open file handling isn't implemented in this PR, yet. The proposal is still open to review of course!
@@ -36,24 +35,17 @@ public override Task<Compilation> TransformCompilationAsync(Compilation oldCompi | |||
|
|||
internal sealed class TouchAdditionalDocumentAction : CompilationAndGeneratorDriverTranslationAction | |||
{ | |||
#pragma warning disable IDE0052 // Remove unread private members |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To explain a bit of the history of the code here: the source generator API from the compiler originally had an API for doing incremental updates; that was hastily commented out in a preview release, and as a result the TransformGeneratorDriver functions weren't doing anything. I've removed the TransformGeneratorDriver code for now, but am leaving the additional actions in place because there's already been a certain amount of investment in creating these actions correctly in all of the various calls where we fork snapshots. Since the expectation is the compiler API will get worked on soonish, I'm going to leave the actions in place for now.
|
||
[ImportingConstructor] | ||
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
public VisualStudioDocumentNavigationService( | ||
IThreadingContext threadingContext, | ||
SVsServiceProvider serviceProvider, | ||
IVsEditorAdaptersFactoryService editorAdaptersFactoryService) | ||
IVsEditorAdaptersFactoryService editorAdaptersFactoryService, | ||
Lazy<SourceGeneratedFileManager> sourceGeneratedFileManager /* lazy to avoid circularities */) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of this commit, the SourceGeneratedFileManager imports VisualStudioDocumentNavigationService, and the latter also imports the former. This isn't great but longer term I'm hoping the "interesting" bits of SourceGeneratedFileManager move down a few layers so they can be shared with other hosts, and the leftovers merge into the VisualStudioDocumentNavigationService.
This clarifies it also makes compilations weak too.
cc0e31b
to
cb6b20a
Compare
=> _context.OnReferenceFoundAsync(reference.Rehydrate(_solution, GetDefinition(reference.DefinitionId))); | ||
public async ValueTask OnReferenceFoundAsync(SerializableSourceReferenceItem reference) | ||
{ | ||
var rehydrated = await reference.RehydrateAsync(_solution, GetDefinition(reference.DefinitionId), _context.CancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me if _context.CancellationToken is usable here: this would mean that if the client has requested cancellation, that we'll throw here, but that may end up remoting the exception to the server which then breaks OOP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would expect that to be ok as any callbacks must be cancellation safe as well. if not, we have serious issues on our hands. @tmat ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So while testing this I think I got an assert once, which was worrisome. My worry is the callback may be expecting a different token than this one -- basically the callback creates it's own linked token in some way, and we need to be throwing with that one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a quick idea: while we wait for @tmat's answer on this, I can just add a try/catch here and eat the cancellation exception: there's nothing the remote side could do with the cancelation exception anyways, and presumably it's having it's own cancellation process which will also cancel the work off.
cb6b20a
to
e91f380
Compare
docs/ide/api-designs/Workspace and Source Generated Documents.md
Outdated
Show resolved
Hide resolved
e91f380
to
75ec025
Compare
@@ -155,38 +147,32 @@ public override Task<Compilation> TransformCompilationAsync(Compilation oldCompi | |||
|
|||
internal sealed class AddAnalyzerReferencesAction : CompilationAndGeneratorDriverTranslationAction | |||
{ | |||
#pragma warning disable IDE0052 // Remove unread private members | |||
// https://github.com/dotnet/roslyn/issues/44161: right now there is no way to tell a GeneratorDriver that an analyzer reference has been added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is GeneratorDriver.{Add,Remove}Generators()
that can be used for this purpose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chsienki Do we get any benefit from it though at this point?
@@ -200,14 +186,6 @@ public AddAdditionalDocumentsAction(ImmutableArray<TextDocumentState> additional | |||
{ | |||
_additionalDocuments = additionalDocuments; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is GeneratorDriver.{Add,Remove}AdditionalTexts()
for this scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto: we're not really doing incremental use of this at this point, so it doesn't seem helpful? I agree we'll go back to that at some point...
|
||
// The particular choice of crypto algorithm here is arbitrary and can be always changed as necessary. | ||
var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(hashInput.ToArray()); | ||
Array.Resize(ref hash, 16); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It says above we're using a crypto hash to avoid collisions, but if we're only using the first 16 bits, then that isn't guaranteed. Should the comment above be 'to reduce the likelihood of collisions?'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
16 bytes, not bits. I tried (and failed) to find the similar aproach in the compiler where we compute the MVID in deterministic build cases -- do you know where that is?
src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs
Show resolved
Hide resolved
src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.State.cs
Show resolved
Hide resolved
193675f
to
fdb1a27
Compare
This will be returned from the Workspace API to represent syntax trees that are generated by a source generator. It still acts like a normal Document in that you can ask it for text or a tree, but it gives you some additional information about how it was generated. The intent is this also provides easy way for somebody to do a type check if the document is actually source generated, so we can take appropriate actions, like skipping a refactoring.
…tedDocumentsAsync() This rips out the TrackedGeneratorDriver code, which was implemented as an initial attempt to work with the compiler's incremental support for updating. Since the incremental support was removed this type didn't have much reason to still be around.
If we had already transitioned the tracker to a final state, and then the compilation was garbage collected, we still have the generated result that is correct. We can just reuse it in that case.
Right now the caller doesn't have to do anything to start getting source generated documents; this allows code that assumes that all SyntaxTrees have documents still functions reasonably well. Fixes dotnet#45449
Now that we have proper documents to represent source generated documents, we can clean this up. Previously we were opening source generated documents in the symbol navigation service, now we can do this in the document navigaton service, which the symbol service sits atop.
We want the DocumentId generated for a generated output to be stable between Compilations; this is so features that track a document by DocumentId can find it after some change has happened that requires generators to run again.
This code will need fixing to update it for source generators, but I wanted to ensure the unchecked null reference could first be found by the compiler.
This is equivalent to Project.GetSourceGeneratedDocumentAsync() but saves you a step.
This is just replacing some calls to GetDocument() to also fetch the generated document if that fails; most of the work is just spreading some asynchrony.
…in generated output
fdb1a27
to
efbf28d
Compare
…be expected While we sort out precisely which cancellation token to use, for now we can just eat it to ensure stability.
efbf28d
to
1a72d4d
Compare
At this point I'm going to merge this PR since I think I've addressed most of @CyrusNajmabadi's concerns (and especially the bugs he found). @tmat and @chsienki there's some unanswered questions which I don't believe will fundamentally change the PR and are easy to address in a mop-up. And it's now fixing two dogfood-reported bugs so I think it's a net win, and it's also something I want to get in before we snap Preview 2. |
All these methods should take cancellationToken as a parameter. Refers to: src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs:31 in 1a72d4d. [](commit_id = 1a72d4d, deletion_comment = False) |
High level, this allows us to represent source generated documents as a Document object so it can be used in other code without too much special work. Commit-at-a-time recommended. A spec is included too.
Fixes #45449
Fixes #49448