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

Initial work to create a service for returning related documents for copilot purposes. #74906

Merged
merged 37 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
af2b409
Add related documents interface
CyrusNajmabadi Aug 26, 2024
ac1f843
Add remote side
CyrusNajmabadi Aug 26, 2024
bf2dd1c
Break things out
CyrusNajmabadi Aug 26, 2024
67ea275
Add remote impl
CyrusNajmabadi Aug 26, 2024
d58e435
Remote field
CyrusNajmabadi Aug 26, 2024
1f78cd8
Simplify
CyrusNajmabadi Aug 26, 2024
262a509
Callbacks
CyrusNajmabadi Aug 26, 2024
d0db45a
wrapping
CyrusNajmabadi Aug 26, 2024
d4f9e56
C# impl
CyrusNajmabadi Aug 26, 2024
1a018b5
in progress
CyrusNajmabadi Aug 26, 2024
2916771
Producer consumer
CyrusNajmabadi Aug 26, 2024
4226c0c
Simplify
CyrusNajmabadi Aug 26, 2024
35f8d96
Add test harness
CyrusNajmabadi Aug 26, 2024
6911ac0
test
CyrusNajmabadi Aug 26, 2024
c03680e
test export
CyrusNajmabadi Aug 26, 2024
0d334d6
Accessibility
CyrusNajmabadi Aug 26, 2024
05a2b3c
consistency
CyrusNajmabadi Aug 26, 2024
65c546d
consistency
CyrusNajmabadi Aug 26, 2024
3a99080
move
CyrusNajmabadi Aug 26, 2024
fd5703f
move
CyrusNajmabadi Aug 26, 2024
71a09a7
Add service
CyrusNajmabadi Aug 26, 2024
c65fa43
sort
CyrusNajmabadi Aug 27, 2024
e8a4899
consistentcy
CyrusNajmabadi Aug 27, 2024
193f4e4
move back
CyrusNajmabadi Aug 27, 2024
cf82fe6
internal
CyrusNajmabadi Aug 27, 2024
e15839b
in progress
CyrusNajmabadi Aug 27, 2024
7401f5f
move
CyrusNajmabadi Aug 27, 2024
3314264
Merge remote-tracking branch 'upstream/main' into relatedDocuments
CyrusNajmabadi Aug 27, 2024
2f25c4d
Add test
CyrusNajmabadi Aug 27, 2024
94c4839
Add tests
CyrusNajmabadi Aug 27, 2024
8013e77
Add tests
CyrusNajmabadi Aug 27, 2024
9e990df
Move logic
CyrusNajmabadi Aug 27, 2024
ed4f78e
Remove logic
CyrusNajmabadi Aug 27, 2024
00151b6
Docs
CyrusNajmabadi Aug 27, 2024
7b0129c
Docs
CyrusNajmabadi Aug 27, 2024
4e52ff7
Simplify
CyrusNajmabadi Aug 27, 2024
d57aff7
Fix
CyrusNajmabadi Aug 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions eng/targets/Services.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,39 @@
<ItemGroup>
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.AssetSynchronization" ClassName="Microsoft.CodeAnalysis.Remote.RemoteAssetSynchronizationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.AsynchronousOperationListener" ClassName="Microsoft.CodeAnalysis.Remote.RemoteAsynchronousOperationListenerService+Factory" Audience="AllClientsIncludingGuests"/>
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.CodeLensReferences" ClassName="Microsoft.CodeAnalysis.Remote.RemoteCodeLensReferencesService+Factory" />
Copy link
Member Author

Choose a reason for hiding this comment

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

just sorted.

<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.CompilationAvailable" ClassName="Microsoft.CodeAnalysis.Remote.RemoteCompilationAvailableService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ConvertTupleToStructCodeRefactoring" ClassName="Microsoft.CodeAnalysis.Remote.RemoteConvertTupleToStructCodeRefactoringService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.DependentTypeFinder" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDependentTypeFinderService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.DesignerAttributeDiscovery" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDesignerAttributeDiscoveryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.DiagnosticAnalyzer" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDiagnosticAnalyzerService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SemanticClassification" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSemanticClassificationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.DocumentHighlights" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDocumentHighlightsService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.EditAndContinue" ClassName="Microsoft.CodeAnalysis.EditAndContinue.RemoteEditAndContinueService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.EncapsulateField" ClassName="Microsoft.CodeAnalysis.Remote.RemoteEncapsulateFieldService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.KeepAlive" ClassName="Microsoft.CodeAnalysis.Remote.RemoteKeepAliveService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.Renamer" ClassName="Microsoft.CodeAnalysis.Remote.RemoteRenamerService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ConvertTupleToStructCodeRefactoring" ClassName="Microsoft.CodeAnalysis.Remote.RemoteConvertTupleToStructCodeRefactoringService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SymbolFinder" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSymbolFinderService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ExtensionMethodImportCompletion" ClassName="Microsoft.CodeAnalysis.Remote.RemoteExtensionMethodImportCompletionService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.FindUsages" ClassName="Microsoft.CodeAnalysis.Remote.RemoteFindUsagesService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.FullyQualify" ClassName="Microsoft.CodeAnalysis.Remote.RemoteFullyQualifyService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.GlobalNotificationDelivery" ClassName="Microsoft.CodeAnalysis.Remote.RemoteGlobalNotificationDeliveryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.InheritanceMargin" ClassName="Microsoft.CodeAnalysis.Remote.RemoteInheritanceMarginService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.KeepAlive" ClassName="Microsoft.CodeAnalysis.Remote.RemoteKeepAliveService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.LegacySolutionEventsAggregation" ClassName="Microsoft.CodeAnalysis.Remote.RemoteLegacySolutionEventsAggregationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.MissingImportDiscovery" ClassName="Microsoft.CodeAnalysis.Remote.RemoteMissingImportDiscoveryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.NavigateToSearch" ClassName="Microsoft.CodeAnalysis.Remote.RemoteNavigateToSearchService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.NavigationBarItem" ClassName="Microsoft.CodeAnalysis.Remote.RemoteNavigationBarItemService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.MissingImportDiscovery" ClassName="Microsoft.CodeAnalysis.Remote.RemoteMissingImportDiscoveryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ExtensionMethodImportCompletion" ClassName="Microsoft.CodeAnalysis.Remote.RemoteExtensionMethodImportCompletionService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.DependentTypeFinder" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDependentTypeFinderService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.GlobalNotificationDelivery" ClassName="Microsoft.CodeAnalysis.Remote.RemoteGlobalNotificationDeliveryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.CodeLensReferences" ClassName="Microsoft.CodeAnalysis.Remote.RemoteCodeLensReferencesService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.DesignerAttributeDiscovery" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDesignerAttributeDiscoveryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ProcessTelemetry" ClassName="Microsoft.CodeAnalysis.Remote.RemoteProcessTelemetryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ProjectTelemetry" ClassName="Microsoft.CodeAnalysis.Remote.RemoteProjectTelemetryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.LegacySolutionEventsAggregation" ClassName="Microsoft.CodeAnalysis.Remote.RemoteLegacySolutionEventsAggregationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.RelatedDocuments" ClassName="Microsoft.CodeAnalysis.Remote.RemoteRelatedDocumentsService+Factory" />
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 new.

<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.Renamer" ClassName="Microsoft.CodeAnalysis.Remote.RemoteRenamerService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SemanticClassification" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSemanticClassificationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SemanticSearch" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSemanticSearchService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SourceGeneration" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSourceGenerationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.StackTraceExplorer" ClassName="Microsoft.CodeAnalysis.Remote.RemoteStackTraceExplorerService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SymbolFinder" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSymbolFinderService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SymbolSearchUpdate" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSymbolSearchUpdateService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.TaskList" ClassName="Microsoft.CodeAnalysis.Remote.RemoteTaskListService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.EditAndContinue" ClassName="Microsoft.CodeAnalysis.EditAndContinue.RemoteEditAndContinueService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ValueTracking" ClassName="Microsoft.CodeAnalysis.Remote.RemoteValueTrackingService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.InheritanceMargin" ClassName="Microsoft.CodeAnalysis.Remote.RemoteInheritanceMarginService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.UnusedReferenceAnalysis" ClassName="Microsoft.CodeAnalysis.Remote.RemoteUnusedReferenceAnalysisService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ProcessTelemetry" ClassName="Microsoft.CodeAnalysis.Remote.RemoteProcessTelemetryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.CompilationAvailable" ClassName="Microsoft.CodeAnalysis.Remote.RemoteCompilationAvailableService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.StackTraceExplorer" ClassName="Microsoft.CodeAnalysis.Remote.RemoteStackTraceExplorerService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.UnitTestingSearch" ClassName="Microsoft.CodeAnalysis.Remote.RemoteUnitTestingSearchService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SourceGeneration" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSourceGenerationService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.SemanticSearch" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSemanticSearchService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.UnusedReferenceAnalysis" ClassName="Microsoft.CodeAnalysis.Remote.RemoteUnusedReferenceAnalysisService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ValueTracking" ClassName="Microsoft.CodeAnalysis.Remote.RemoteValueTrackingService+Factory" />
</ItemGroup>

<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CSharp" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.ExternalAccess.Razor" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.DiagnosticsTests.Utilities"/>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.DiagnosticsTests.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.Test.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Features.UnitTests" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RelatedDocuments;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.RelatedDocuments;

[ExportLanguageService(typeof(IRelatedDocumentsService), LanguageNames.CSharp), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class CSharpRelatedDocumentsService() : AbstractRelatedDocumentsService
{
protected override async ValueTask GetRelatedDocumentIdsInCurrentProcessAsync(
Document document,
int position,
Copy link
Member

Choose a reason for hiding this comment

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

position needs to be optional.

Copy link
Member Author

Choose a reason for hiding this comment

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

it is. you can just pass 0.

Copy link
Member

Choose a reason for hiding this comment

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

Consider using -1 for this purpose. I think 0 is a valid position in file, so potentially we could return a different list of related files for "position 0" compare to "the entire file"

Func<ImmutableArray<DocumentId>, CancellationToken, ValueTask> callbackAsync,
CancellationToken cancellationToken)
{
var solution = document.Project.Solution;

// Don't need nullable analysis, and we're going to walk a lot of the tree, so speed things up by not doing
// excess semantic work.
var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var seenDocumentIds = new ConcurrentSet<DocumentId>();
var seenTypeNames = new ConcurrentSet<string>();

// Bind as much as we can in parallel to get the results as quickly as possible. We call into the
// ProducerConsumer overload that will batch up values into arrays, and call into the callback with them. While
// that callback is executing, the ProducerConsumer continues to run, computing more results and getting them
// ready for when that call returns. This approach ensures that we're not pausing while we're reporting the
// results to whatever client is calling into us.
await ProducerConsumer<DocumentId>.RunParallelAsync(
// Order the nodes by the distance from the requested position.
IteratePotentialTypeNodes(root).OrderBy(t => t.expression.SpanStart - position),
Copy link
Member

Choose a reason for hiding this comment

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

t.expression.SpanStart - position

Should this be the absolute value of t.expression.SpanStart - position ?

Copy link
Member Author

Choose a reason for hiding this comment

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

yup. That would make more sense!

produceItems: static (tuple, callback, args, cancellationToken) =>
{
var (solution, semanticModel, seenDocumentIds, seenTypeNames, callbackAsync) = args;
ProduceItems(
solution, semanticModel, tuple.expression, tuple.nameToken, callback, seenDocumentIds, seenTypeNames, cancellationToken);

return Task.CompletedTask;
},
consumeItems: static async (array, args, cancellationToken) =>
{
var (solution, semanticModel, seenDocumentIds, seenTypeNames, callbackAsync) = args;
await callbackAsync(array, cancellationToken).ConfigureAwait(false);
},
args: (solution, semanticModel, seenDocumentIds, seenTypeNames, callbackAsync),
cancellationToken).ConfigureAwait(false);

return;

static IEnumerable<(ExpressionSyntax expression, SyntaxToken nameToken)> IteratePotentialTypeNodes(SyntaxNode root)
{
using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var stack);
stack.Push(root);

while (stack.TryPop(out var current))
{
if (current is MemberAccessExpressionSyntax memberAccess)
{
// Could be a static member access off of a type name. Check the left side, and if it's just a
// dotted name, return that.

if (IsPossibleTypeName(memberAccess.Expression, out var nameToken))
{
// Something like `X.Y.Z` where `X.Y` is a type name. Bind X.Y
yield return (memberAccess.Expression, nameToken);
Copy link
Member

Choose a reason for hiding this comment

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

For X.Y.Z, memberAccess.Expression is X.Y, but the nameToken returned would be X, right? Shouldn't it be Y?

Copy link
Member Author

Choose a reason for hiding this comment

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

not exactly. we have X.Y.Z. But we pass X.Y Into IsPossibleTypeName, and if it succeeds, we return Y.

}
else
{
// Something like `(...).Y`. Recurse down the left side of the member access to see if there
// are types in there. We don't want to recurse down the name portion as it will never be a
// type.
stack.Push(memberAccess.Expression);
}

continue;
}
else if (current is NameSyntax name)
{
yield return (name, name.GetNameToken());

// Intentionally continue to recurse down the name so that if we have things like `X<Y>` we'll bind
// the inner `Y` as well.
}

// Don't need to recurse in order as our caller is ordering results anyways.
foreach (var child in current.ChildNodesAndTokens())
{
if (child.AsNode(out var childNode))
stack.Push(childNode);
}
}
}

static bool IsPossibleTypeName(ExpressionSyntax expression, out SyntaxToken nameToken)
{
while (expression is MemberAccessExpressionSyntax memberAccessExpression)
expression = memberAccessExpression.Expression;

if (expression is not NameSyntax name)
{
nameToken = default;
return false;
}

nameToken = name.GetNameToken();
return true;
}

static void ProduceItems(
Solution solution,
SemanticModel semanticModel,
ExpressionSyntax expression,
SyntaxToken nameToken,
Action<DocumentId> callback,
ConcurrentSet<DocumentId> seenDocumentIds,
ConcurrentSet<string> seenTypeNames,
CancellationToken cancellationToken)
{
if (nameToken.Kind() != SyntaxKind.IdentifierToken)
return;

if (nameToken.ValueText == "")
return;
// Don't rebind a type name we've already seen. Note: this is a conservative/inaccurate check.
// Specifically, there could be different types with the same last name portion (from different
// namespaces). In that case, we'll miss the one that is further away. We can revisit this in the
// future if we think it's necessary.
if (!seenTypeNames.Add(nameToken.ValueText))
return;

var symbol = semanticModel.GetSymbolInfo(expression, cancellationToken).GetAnySymbol();
if (symbol is not ITypeSymbol)
return;

foreach (var syntaxReference in symbol.DeclaringSyntaxReferences)
{
var documentId = solution.GetDocument(syntaxReference.SyntaxTree)?.Id;
if (documentId != null && seenDocumentIds.Add(documentId))
callback(documentId);
}
}
}
}
Loading
Loading