Skip to content

Commit

Permalink
Add support for implementation edges
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonmalinowski committed Sep 24, 2022
1 parent 77a26f0 commit b7a23d0
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 9 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

0 comments on commit b7a23d0

Please sign in to comment.