diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs index 9a3b0fdbac5b3..80058e60e229f 100644 --- a/src/Features/Lsif/Generator/Generator.cs +++ b/src/Features/Lsif/Generator/Generator.cs @@ -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; @@ -228,6 +229,29 @@ SymbolKind.RangeVariable or { var definitionResultsId = symbolResultsTracker.GetResultIdForSymbol(declaredSymbol, Methods.TextDocumentDefinitionName, () => new DefinitionResult(idFactory)); lsifJsonWriter.Write(new Item(definitionResultsId.As(), 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(), 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(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(), implementedMemberMoniker, documentVertex.GetId(), idFactory, property: "referenceLinks")); + } } if (referencedSymbol != null) @@ -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(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory, property: "references")); } diff --git a/src/Features/Lsif/Generator/Graph/Item.cs b/src/Features/Lsif/Generator/Graph/Item.cs index 6f9e95e51583d..fba6a6dc82a76 100644 --- a/src/Features/Lsif/Generator/Graph/Item.cs +++ b/src/Features/Lsif/Generator/Graph/Item.cs @@ -5,7 +5,7 @@ namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph { /// - /// 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. /// internal sealed class Item : Edge @@ -19,5 +19,12 @@ public Item(Id outVertex, Id range, Id document, Id Document = document; Property = property; } + + public Item(Id outVertex, Id moniker, Id document, IdFactory idFactory, string? property = null) + : base(label: "item", outVertex, new[] { moniker.As() }, idFactory) + { + Document = document; + Property = property; + } } } diff --git a/src/Features/Lsif/Generator/ResultSetTracking/IResultSetTrackerExtensions.cs b/src/Features/Lsif/Generator/ResultSetTracking/IResultSetTrackerExtensions.cs new file mode 100644 index 0000000000000..13d39b62c0186 --- /dev/null +++ b/src/Features/Lsif/Generator/ResultSetTracking/IResultSetTrackerExtensions.cs @@ -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 + { + /// + /// Returns the ID of the for a . + /// + public static Id GetResultSetReferenceResultId(this IResultSetTracker tracker, ISymbol symbol, IdFactory idFactory) + => tracker.GetResultIdForSymbol(symbol, Methods.TextDocumentReferencesName, () => new ReferenceResult(idFactory)); + } +} diff --git a/src/Features/Lsif/Generator/ResultSetTracking/SymbolHoldingResultSetTracker.cs b/src/Features/Lsif/Generator/ResultSetTracking/SymbolHoldingResultSetTracker.cs index ad3ba74aced3f..7d4ea5572a2f5 100644 --- a/src/Features/Lsif/Generator/ResultSetTracking/SymbolHoldingResultSetTracker.cs +++ b/src/Features/Lsif/Generator/ResultSetTracking/SymbolHoldingResultSetTracker.cs @@ -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); } } @@ -157,15 +157,15 @@ public TrackedResultSet(Id id) Id = id; } - public Id GetResultId(string edgeKind, Func vertexCreator, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) where T : Vertex + public Id GetResultId(string edgeLabel, Func 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 @@ -174,10 +174,10 @@ public Id GetResultId(string edgeKind, Func vertexCreator, ILsifJsonWri } var vertex = vertexCreator(); - _edgeKindToVertexId.Add(edgeKind, vertex.GetId().As()); + _edgeKindToVertexId.Add(edgeLabel, vertex.GetId().As()); lsifJsonWriter.Write(vertex); - lsifJsonWriter.Write(Edge.Create(edgeKind, Id, vertex.GetId(), idFactory)); + lsifJsonWriter.Write(Edge.Create(edgeLabel, Id, vertex.GetId(), idFactory)); return vertex.GetId(); } diff --git a/src/Features/Lsif/GeneratorTest/RangeResultSetTests.vb b/src/Features/Lsif/GeneratorTest/RangeResultSetTests.vb index 4b3fe24cb05ac..2fd2685b9e8ad 100644 --- a/src/Features/Lsif/GeneratorTest/RangeResultSetTests.vb +++ b/src/Features/Lsif/GeneratorTest/RangeResultSetTests.vb @@ -179,5 +179,28 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests End If Next End Function + + + Public Async Function InterfaceImplementation() As Task + Dim lsif = Await TestLsifOutput.GenerateForWorkspaceAsync( + TestWorkspace.CreateWorkspace( + + FilePath="Z:\TestProject.csproj" CommonReferences="true"> + +interface I { void [|M|](); } +class C : I { public void M() { } } + + + )) + + 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 diff --git a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb index ae3470f1f2e83..dafcb579855c3 100644 --- a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb +++ b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb @@ -66,12 +66,19 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U ''' Returns all the vertices linked to the given vertex by the edge type. ''' 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 + + ''' + ''' Returns all the vertices linked to the given vertex by the edge predicate. + ''' + 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; diff --git a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb index 939464005eac2..80579951a1175 100644 --- a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb +++ b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb @@ -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 + ''' ''' Returns all the vertices linked to the given vertex by the edge type. '''