Skip to content

Commit

Permalink
Add support for implementation edges
Browse files Browse the repository at this point in the history
Fixes #59617
  • Loading branch information
jasonmalinowski committed Sep 24, 2022
1 parent 77a26f0 commit e2dd6dc
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 10 deletions.
26 changes: 25 additions & 1 deletion src/Features/Lsif/Generator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.ResultSetTracking;
using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Writing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using Methods = Microsoft.VisualStudio.LanguageServer.Protocol.Methods;

Expand Down Expand Up @@ -228,6 +229,29 @@ SymbolKind.RangeVariable or
{
var definitionResultsId = symbolResultsTracker.GetResultIdForSymbol(declaredSymbol, Methods.TextDocumentDefinitionName, () => new DefinitionResult(idFactory));
lsifJsonWriter.Write(new Item(definitionResultsId.As<DefinitionResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory));

// If this declared symbol also implements an interface member, we count this as a definition of the interface member as well.
// Note in C# there are estoeric cases where a method can implement an interface member even though the containing type does not
// implement the interface, for example in this case:
//
// interface I { void M(); }
// class Base { public void M() { } }
// class Derived : Base, I { }
//
// We don't worry about supporting these cases here.
var implementedMembers = declaredSymbol.ExplicitOrImplicitInterfaceImplementations();

foreach (var implementedMember in implementedMembers)
{
// First we create a definition link for the reference results for the interface method
var referenceResultsId = symbolResultsTracker.GetResultSetReferenceResultId(implementedMember.OriginalDefinition, idFactory);
lsifJsonWriter.Write(new Item(referenceResultsId.As<ReferenceResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory, property: "definitions"));

// Then also link the result set for the method to the moniker that it implements
referenceResultsId = symbolResultsTracker.GetResultSetReferenceResultId(declaredSymbol.OriginalDefinition, idFactory);
var implementedMemberMoniker = symbolResultsTracker.GetResultIdForSymbol<Moniker>(implementedMember.OriginalDefinition, "moniker", () => throw new Exception("When we produced the resultSet, we should have already created a moniker for it."));
lsifJsonWriter.Write(new Item(referenceResultsId.As<ReferenceResult, Vertex>(), implementedMemberMoniker, documentVertex.GetId(), idFactory, property: "referenceLinks"));
}
}

if (referencedSymbol != null)
Expand All @@ -236,7 +260,7 @@ SymbolKind.RangeVariable or
// symbol but the range can point a different symbol's resultSet. This can happen if the token is
// both a definition of a symbol (where we will point to the definition) but also a reference to some
// other symbol.
var referenceResultsId = symbolResultsTracker.GetResultIdForSymbol(referencedSymbol.OriginalDefinition, Methods.TextDocumentReferencesName, () => new ReferenceResult(idFactory));
var referenceResultsId = symbolResultsTracker.GetResultSetReferenceResultId(referencedSymbol.OriginalDefinition, idFactory);
lsifJsonWriter.Write(new Item(referenceResultsId.As<ReferenceResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory, property: "references"));
}

Expand Down
9 changes: 8 additions & 1 deletion src/Features/Lsif/Generator/Graph/Item.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph
{
/// <summary>
/// Represents a single item that points to a range from a result. See https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#request-textdocumentreferences
/// Represents a single item that points to a range or moniker from a result. See https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#request-textdocumentreferences
/// for an example of item edges.
/// </summary>
internal sealed class Item : Edge
Expand All @@ -19,5 +19,12 @@ public Item(Id<Vertex> outVertex, Id<Range> range, Id<LsifDocument> document, Id
Document = document;
Property = property;
}

public Item(Id<Vertex> outVertex, Id<Moniker> moniker, Id<LsifDocument> document, IdFactory idFactory, string? property = null)
: base(label: "item", outVertex, new[] { moniker.As<Moniker, Vertex>() }, idFactory)
{
Document = document;
Property = property;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.ResultSetTracking
{
internal static class IResultSetTrackerExtensions
{
/// <summary>
/// Returns the ID of the <see cref="ReferenceResult"/> for a <see cref="ResultSet"/>.
/// </summary>
public static Id<ReferenceResult> GetResultSetReferenceResultId(this IResultSetTracker tracker, ISymbol symbol, IdFactory idFactory)
=> tracker.GetResultIdForSymbol(symbol, Methods.TextDocumentReferencesName, () => new ReferenceResult(idFactory));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ private TrackedResultSet GetTrackedResultSet(ISymbol symbol)

if (monikerVertex != null)
{
_lsifJsonWriter.Write(monikerVertex);
_lsifJsonWriter.Write(Edge.Create("moniker", trackedResultSet.Id, monikerVertex.GetId(), _idFactory));
// Attach the moniker vertex for this result set
_ = GetResultIdForSymbol(symbol, "moniker", () => monikerVertex);
}
}

Expand Down Expand Up @@ -157,15 +157,15 @@ public TrackedResultSet(Id<ResultSet> id)
Id = id;
}

public Id<T> GetResultId<T>(string edgeKind, Func<T> vertexCreator, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) where T : Vertex
public Id<T> GetResultId<T>(string edgeLabel, Func<T> vertexCreator, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) where T : Vertex
{
lock (_edgeKindToVertexId)
{
if (_edgeKindToVertexId.TryGetValue(edgeKind, out var existingId))
if (_edgeKindToVertexId.TryGetValue(edgeLabel, out var existingId))
{
if (!existingId.HasValue)
{
throw new Exception($"This ResultSet already has an edge of {edgeKind} as {nameof(ResultSetNeedsInformationalEdgeAdded)} was called with this edge kind.");
throw new Exception($"This ResultSet already has an edge of {edgeLabel} as {nameof(ResultSetNeedsInformationalEdgeAdded)} was called with this edge label.");
}

// TODO: this is a violation of the type system here, really: we're assuming that all calls to this function with the same edge kind
Expand All @@ -174,10 +174,10 @@ public Id<T> GetResultId<T>(string edgeKind, Func<T> vertexCreator, ILsifJsonWri
}

var vertex = vertexCreator();
_edgeKindToVertexId.Add(edgeKind, vertex.GetId().As<T, Vertex>());
_edgeKindToVertexId.Add(edgeLabel, vertex.GetId().As<T, Vertex>());

lsifJsonWriter.Write(vertex);
lsifJsonWriter.Write(Edge.Create(edgeKind, Id, vertex.GetId(), idFactory));
lsifJsonWriter.Write(Edge.Create(edgeLabel, Id, vertex.GetId(), idFactory));

return vertex.GetId();
}
Expand Down
23 changes: 23 additions & 0 deletions src/Features/Lsif/GeneratorTest/RangeResultSetTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -179,5 +179,28 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests
End If
Next
End Function

<Fact>
Public Async Function InterfaceImplementation() As Task
Dim lsif = Await TestLsifOutput.GenerateForWorkspaceAsync(
TestWorkspace.CreateWorkspace(
<Workspace>
<Project Language="C#" AssemblyName=<%= TestProjectAssemblyName %> FilePath="Z:\TestProject.csproj" CommonReferences="true">
<Document Name="A.cs" FilePath="Z:\A.cs">
interface I { void [|M|](); }
class C : I { public void M() { } }
</Document>
</Project>
</Workspace>))

Dim rangeVertex = Await lsif.GetSelectedRangeAsync()
Dim resultSetVertex = lsif.GetLinkedVertices(Of Graph.ResultSet)(rangeVertex, "next").Single()
Dim referencesVertex = lsif.GetLinkedVertices(Of Graph.ReferenceResult)(resultSetVertex, Methods.TextDocumentReferencesName).Single()

' The references vertex should have one item pointing to the class
Dim definitionRangeForImplementingMethod = Assert.Single(lsif.GetLinkedVertices(Of Graph.Range)(referencesVertex, Function(e) DirectCast(e, Graph.Item).Property = "definitions"))
Assert.Equal(2, definitionRangeForImplementingMethod.Start.Line)
Assert.Equal(26, definitionRangeForImplementingMethod.Start.Character)
End Function
End Class
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,19 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U
''' Returns all the vertices linked to the given vertex by the edge type.
''' </summary>
Public Function GetLinkedVertices(Of T As Vertex)(vertex As Graph.Vertex, edgeLabel As String) As ImmutableArray(Of T)
Return GetLinkedVertices(Of T)(vertex, Function(e) e.Label = edgeLabel)
End Function

''' <summary>
''' Returns all the vertices linked to the given vertex by the edge predicate.
''' </summary>
Public Function GetLinkedVertices(Of T As Vertex)(vertex As Graph.Vertex, predicate As Func(Of Edge, Boolean)) As ImmutableArray(Of T)
SyncLock _gate
Dim builder = ImmutableArray.CreateBuilder(Of T)

Dim edges As List(Of Edge) = Nothing
If _edgesByOutVertex.TryGetValue(vertex, edges) Then
Dim inVerticesId = edges.Where(Function(e) e.Label = edgeLabel).SelectMany(Function(e) e.InVertices)
Dim inVerticesId = edges.Where(predicate).SelectMany(Function(e) e.InVertices)

For Each inVertexId In inVerticesId
' This is an unsafe "cast" if you will converting the ID to the expected type;
Expand Down
4 changes: 4 additions & 0 deletions src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U
Return _testLsifJsonWriter.GetElementById(id)
End Function

Public Function GetLinkedVertices(Of T As Vertex)(vertex As Graph.Vertex, predicate As Func(Of Edge, Boolean)) As ImmutableArray(Of T)
Return _testLsifJsonWriter.GetLinkedVertices(Of T)(vertex, predicate)
End Function

''' <summary>
''' Returns all the vertices linked to the given vertex by the edge type.
''' </summary>
Expand Down

0 comments on commit e2dd6dc

Please sign in to comment.