Skip to content

Conversation

@jasonmalinowski
Copy link
Member

@jasonmalinowski jasonmalinowski commented Aug 20, 2025

To best understand this fix, we first need to discuss how CodeLens works in Visual Studio. The editor calls our implementation of IAsyncCodeLensDataPointProvider which exists in a ServiceHub process created for CodeLens which is not our usual ServiceHub process. That implementation uses the callback feature to marshal a call back to devenv, where from there we then do an OOP operation to our ServiceHub process via the usual mechanism. This is where we compute the actual results, which then get marshalled back to devenv and then back to the CodeLens ServiceHub process, where we finally send the data via the CodeLens APIs.

The core type of this is a ReferenceLocationDescriptor which represented a reference location in a specific location in a document. However, because this struct returned from our ServiceHub to devenv, and then from devenv to the CodeLens service hub, it needs to be serialized and deserialized twice. The serialization channels are different though, so whereas a DocumentId can serialize between devenv and our ServiceHub process, it would get tripped up when it serializes between devenv and the CodeLens ServiceHub process. Because of this, the type instead decomposed a DocumentId into the GUIDs and manually serialized/deserialized it.

This serialization/deserialization was broken once DocumentIds got a "is source generated" bit, since we'd lose that bit which would break things. I could have added that bit in and updated the serialization but the question we hit was "why can't I just pass a DocumentId?" Well, the answer was because we might try sending that DocumentId to the CodeLens ServiceHub process, but upon closer inspection, the result of that was never used. The flow was that we'd take the results with document IDs out of our ServiceHub process, then do some filtering and mapping for Razor scenarios (which used those document IDs); at this point the results don't need document IDs anymore. So to do this I split out the "mapping" as a separate operation for clarity, and then used different types, one with the ID and one without, for the different flows.

There is still one bug left here: code lens will now show references in source generated documents, but you can't open those since we aren't returning a file path VS will understand there. That's fixable too, but this is a significant improvement so let's get this done first. I'm not sure if fixing that might require other code changes in the CodeLens system...

Fixes #79699
Fixes #76608
Fixes #74104

{
var isSourceGenerated = false;

if (descriptorProperties.TryGetValue("RoslynDocumentIsSourceGenerated", out var isSourceGeneratedObj))
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 fixes code lens in a source generated file; a fix in the internal VS repo will be needed to provide this property.

Copy link
Member Author

Choose a reason for hiding this comment

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

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 is a rename of the RemoteCodeLensReferencesService in the same folder; minus the stuff that got moved to the base service. The rename was to avoid the fact we had two RemoteCodeLensReferencesServices, implementing different interface names. A followup PR can get rid of the base one now I think.

@CyrusNajmabadi

This comment was marked as outdated.

To best understand this fix, we first need to discuss how CodeLens works
in Visual Studio. The editor calls our implementation of
IAsyncCodeLensDataPointProvider which exists in a ServiceHub process
created for CodeLens which is not our usual ServiceHub process. That
implementation uses the callback feature to marshal a call back to
devenv, where from there we then do an OOP operation to our ServiceHub
process via the usual mechanism. This is where we compute the actual
results, which then get marshalled back to devenv and then back
to the CodeLens ServiceHub process, where we finally send the data
via the CodeLens APIs.

The core type of this is a ReferenceLocationDescriptor which
represented a reference location in a specific location in a document.
However, because this struct returned from our ServiceHub to devenv,
and then from devenv to the CodeLens service hub, it needs to be
serialized and deserialized twice. The serialization channels are
different though, so whereas a DocumentId can serialize between devenv
and our ServiceHub process, it would get tripped up when it serializes
between devenv and the CodeLens ServiceHub process. Because of this, the
type instead decomposed a DocumentId into the GUIDs and manually
serialized/deserialized it.

This serialization/deserialization was broken once DocumentIds got
a "is source generated" bit, since we'd lose that bit which would
break things. I could have added that bit in and updated the
serialization but the question we hit was "why can't I just pass a
DocumentId?" Well, the answer was because we might try sending that
DocumentId to the CodeLens ServiceHub process, but upon closer
inspection, the result of that was never used. The flow was that
we'd take the results with document IDs out of our ServiceHub process,
then do some filtering and mapping for Razor scenarios (which used those
document IDs); at this point the results don't need document IDs
anymore. So to do this I split out the "mapping" as a separate
operation for clarity, and then used different types, one with the ID
and one without, for the different flows.

Fixes dotnet#79699
@jasonmalinowski jasonmalinowski force-pushed the fix-codelens-in-source-generated-files branch from 5828d38 to 8416cd5 Compare August 21, 2025 21:20
@jasonmalinowski jasonmalinowski marked this pull request as ready for review August 21, 2025 21:20
@jasonmalinowski jasonmalinowski requested a review from a team as a code owner August 21, 2025 21:20
foreach (var descriptorAndDocument in referenceLocations)
{
var document = await solution.GetDocumentAsync(descriptorAndDocument.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
if (document == null)
Copy link
Member

Choose a reason for hiding this comment

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

maybe doc if this is espected (like if trying to find an item for data that might be stale)?

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 moved and that check predates this. That said, I can't think of a reason this would ever be hit now. I can just switch it to a required form unless you really want to count this as "code not to touch".

Copy link
Member Author

Choose a reason for hiding this comment

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

(merging with this question open, since things didn't here, but easy to address in a mop-up)

}

return lines[index].ToString().TrimEnd();
}
Copy link
Member

Choose a reason for hiding this comment

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

ill be honest that i basically ignored all of this. :)

@jasonmalinowski jasonmalinowski merged commit 9f8e62d into dotnet:main Aug 21, 2025
25 checks passed
@jasonmalinowski jasonmalinowski deleted the fix-codelens-in-source-generated-files branch August 21, 2025 22:57
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Aug 21, 2025
JoeRobich added a commit to dotnet/roslyn-tools that referenced this pull request Aug 29, 2025
46 PRs before and 36 after
```diff
+    * [Cache diagnostic analyzer computation](dotnet/roslyn#80045)
+    * [Remove parameter always passed the same value](dotnet/roslyn#80042)
     * [Update doc for IMethodSymbol.IsExtensionMethod](dotnet/roslyn#80016)
     * [Don't cache known-broken compositions](dotnet/roslyn#80021)
     * [Cleanup methods in DiagAnalyzerService](dotnet/roslyn#80013)
     * [Simplify processing of errors reported by the build](dotnet/roslyn#79964)
     * [Additional cleanup of the DiagnosticAnalyzerServier](dotnet/roslyn#80005)
     * [Fix Code Lens around source generated files](dotnet/roslyn#79992)
     * [Remove superflous DiagService api that can be achieved with existing apis](dotnet/roslyn#80007)
     * [Generate `init` accessor for required properties inside `readonly struct`s](dotnet/roslyn#80004)
     * [Remove existing low level diag oop code now that it's all handled at higher levels.](dotnet/roslyn#79994)
     * [Allow large InlineHint ArrayBuilder pooling](dotnet/roslyn#79857)
     * [Reduce allocations obtaining classified spans in ClassifierHelper](dotnet/roslyn#79856)
     * [Compute span diagnostics in oop](dotnet/roslyn#79991)
     * [Allow Razor cohosting to work with non-Razor SDK projects](dotnet/roslyn#79953)
     * [Move computation of deprioritized analyzers to oop](dotnet/roslyn#79989)
     * [EnC: Fix symbol mapping of delegates with indexed name](dotnet/roslyn#79837)
     * [Emit telemetry 'durations' with known radix point '.'](dotnet/roslyn#79988)
     * [Move logic up into DiagService](dotnet/roslyn#79985)
     * [Move the StateManager type up to the DiagnosticService from the DiagnosticIncrementalANalyzer](dotnet/roslyn#79984)
     * [Immediately remote diagnostics call to OOP](dotnet/roslyn#79983)
     * [Build Microsoft.CodeAnalysis.SemanticSearch.Extension ref assembly for use in semantic search queries](dotnet/roslyn#79972)
     * [Only cache compilation if we have the same set of analyzers](dotnet/roslyn#79978)
     * [Delete unused property](dotnet/roslyn#79963)
     * [Update 'use expr body' to be a purely syntactic analyzer](dotnet/roslyn#79979)
     * [Mark 'Use expr body' as a syntax-only fixer](dotnet/roslyn#79971)
     * [♻️ MSBuildWorkspaceDirectory - Fallback to AppContext.BaseDirectory when Assembly Location is empty](dotnet/roslyn#79934)
     * [Merge runtime async support into main](dotnet/roslyn#79833)
     * [Implement "Simplify property accessor" feature](dotnet/roslyn#79754)
-    * Merge main to runtime async branch (PR: [#79961](dotnet/roslyn#79961))
     * [Redo how and when we report source generator telemetry](dotnet/roslyn#79951)
     * [Allow MEF components to supply assembly path resolvers](dotnet/roslyn#79218)
     * [Allow Razor to hook up the source generator in misc files](dotnet/roslyn#79891)
     * [Block ENC for extension blocks](dotnet/roslyn#79883)
     * [Upgrade servicehub.client to fix test source discovery](dotnet/roslyn#79899)
     * [Update package restore error message.](dotnet/roslyn#79876)
-    * Merge main (PR: [#79834](dotnet/roslyn#79834))
-    * Merge main (PR: [#79830](dotnet/roslyn#79830))
     * [Baseline struct lifting tests](dotnet/roslyn#79505)
-    * Merge main to runtime async branch (PR: [#79582](dotnet/roslyn#79582))
-    * Merge main (PR: [#79424](dotnet/roslyn#79424))
-    * Merge main (PR: [#78994](dotnet/roslyn#78994))
-    * Merge main to runtime async branch (PR: [#78740](dotnet/roslyn#78740))
-    * Merge main to runtime async branch (PR: [#78517](dotnet/roslyn#78517))
-    * Merge main to runtime async branch (PR: [#78114](dotnet/roslyn#78114))
-    * Merge main to runtime async branch (PR: [#77700](dotnet/roslyn#77700))
-    * Merge main to runtime async branch (PR: [#77533](dotnet/roslyn#77533))
-    * Merge main to runtime async branch (PR: [#77265](dotnet/roslyn#77265))
```
@akhera99 akhera99 modified the milestones: Next, 18.0 P1, 18.0 P2 Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants