-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Use document with partial semantic for completion #55745
Conversation
2248c18
to
03faadf
Compare
@sharwell @CyrusNajmabadi @jasonmalinowski Please take a look. thanks! |
var usePartialSemantic = _workspace.Options.GetOption(CompletionServiceOptions.UsePartialSemanticForCompletion); | ||
if (usePartialSemantic) | ||
{ | ||
(document, _) = await document.GetPartialSemanticModelAsync(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.
So it's a bit worrisome for me that we're throwing away the SemanticModel here. If the full Compilation was available and we didn't actually do the freeze operation, the fact we've thrown away the SemanticModel could mean that theoretically the SemanticModel and Compilation could GC, and then the request for the SemanticModel has to do extra work. It won't run generators. Put another way, throwing away the SemanticModel here is potentially undoing the optimization that GetPartialSemanticModelAsync is trying to do in the first place. This would require a pretty badly timed GC though.
I'm suspicious of GetPartialSemanticModelAsync in general so maybe we should just delete it if that seems easier, but I'm happy for that to be done in another PR.
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.
That's a good point, I will hold on to the reference to returned model until completion providers finished their work
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.
and I will leave cleaning up GetPartialSemanticModelAsync
to a follow up PR
var semanticModel = usePartialSemantic | ||
? await document.GetPartialSemanticModelAsync(cancellationToken).ConfigureAwait(false) | ||
: await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
var semanticModel = await document.GetRequiredSemanticModelAsync(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.
You don't still want a partial semantic model in this case? Note that a "partial" model is different than the "speculative" model that the comment mentions; the speculative guarantees the tree you have will be up to date for the file, which won't run into what I think that comment is worried about.
If you really do still need the full model, then revise the comment since the comment doesn't match the code anymore.
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.
The document passed in is frozen, so I'd think GetRequiredSemanticModelAsync
would return partial model, right? Providers don't know if they are getting full model or partial model, so regular here is in contrast to speculative, and I believe the comment is still accurate in that sense
Dim semanticModel = If(usePartialSemantic, | ||
Await document.GetPartialSemanticModelAsync(cancellationToken).ConfigureAwait(False), | ||
Await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(False)) | ||
Dim semanticModel = (Await document.GetPartialSemanticModelAsync(cancellationToken).ConfigureAwait(False)).semanticModel |
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.
Same comment as C# side.
#55745 (comment)
@CyrusNajmabadi Any concern how this will interact with the work you did in #45565? It's news to me that we had stopped using partial semantics in completion that this PR is fixing. |
@CyrusNajmabadi More generally, it occurred to me that the optimization the speculative semantic model is doing is technically broken in the face of source generators, if you have a generator producing new top-level types based on code written inside a method body, it's now the case that a method body edit can impact top-level semantics. I'm not sure if that's actually a real-world concern though, or at least common enough to warrant adjusting that somehow. |
@@ -263,6 +280,9 @@ private CompletionProvider GetProviderByName(string providerName) | |||
OptionSet options, | |||
CancellationToken cancellationToken) | |||
{ | |||
// We don't need SemanticModel here, just want keep an reference so it won't get GC'd before CompletionProviders are able to get it. | |||
(document, var semanticModel) = await GetDocumentWithFrozenPartialSemanticsAsync(document, 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.
You need to GC.KeepAlive this.
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.
Oh, interesting... Let me try someting.
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'd like to see this:
(document, var semanticModel) = await GetDocumentWithFrozenPartialSemanticsAsync(document, cancellationToken).ConfigureAwait(false); | |
(document, var semanticModel) = await GetDocumentWithFrozenPartialSemanticsAsync(document, cancellationToken).ConfigureAwait(false); | |
using var _ = new KeepAlive(semanticModel); |
Should be easy with this helper type:
internal readonly struct KeepAlive : IDisposable
{
private readonly object? _instance;
public KeepAlive(object? instance)
=> _instance = instance;
public void Dispose()
=> GC.KeepAlive(_instance);
}
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.
// References the specified object, which makes it ineligible for garbage collection
// from the start of the current routine to the point where this method is called.
Based on the doc, the helper type only guarantee _instance to be GC ineligible after Dispose
is called at the end of current method, right? It can still get GC'd before then, unless Dispose
is inlined
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.
The call to Dispose will occur immediately before returning, which is where we want the call to KeepAlive
placed.
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.
from the start of the current routine
Wouldn't that make current routine be Dispose
instead in this case?
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 seems like this code generally needs a cleanup to be a bit more consistent with how we're acquiring documents and stuff, but this will work.
Closes #51407