diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index e486aa84c6b89..f7e83ebc8a5cd 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -5299,6 +5299,8 @@ public string ToMinimalDisplayString(SemanticModel semanticModel, int position, public bool IsComImport => throw new NotImplementedException(); + public ImmutableArray TupleElementNames => throw new NotImplementedException(); + #endregion } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 6bed613f3a196..115d9993b288f 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -39,6 +39,7 @@ Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBloc Microsoft.CodeAnalysis.ILocalSymbol.RefKind.get -> Microsoft.CodeAnalysis.RefKind Microsoft.CodeAnalysis.IMethodSymbol.RefKind.get -> Microsoft.CodeAnalysis.RefKind Microsoft.CodeAnalysis.IMethodSymbol.ReturnsByRefReadonly.get -> bool +Microsoft.CodeAnalysis.INamedTypeSymbol.TupleElementNames.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.IOperation.Accept(Microsoft.CodeAnalysis.Operations.OperationVisitor visitor) -> void Microsoft.CodeAnalysis.IOperation.Accept(Microsoft.CodeAnalysis.Operations.OperationVisitor visitor, TArgument argument) -> TResult diff --git a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs index 6c3051261cefe..77aef7a6d8cd7 100644 --- a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs @@ -165,5 +165,7 @@ public interface INamedTypeSymbol : ITypeSymbol /// If this type is not a tuple, then returns default. /// ImmutableArray TupleElements { get; } + + ImmutableArray TupleElementNames { get; } } } diff --git a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb index 0a64afbc92294..d6bf0dc80a09f 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb @@ -1227,6 +1227,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property INamedTypeSymbol_TupleElementNames As ImmutableArray(Of String) Implements INamedTypeSymbol.TupleElementNames + Get + Return TupleElementNames + End Get + End Property + Private ReadOnly Property INamedTypeSymbol_TupleUnderlyingType As INamedTypeSymbol Implements INamedTypeSymbol.TupleUnderlyingType Get Return TupleUnderlyingType diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs index 112e722da4615..9fee4ec3f297c 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs @@ -121,6 +121,14 @@ private async Task FindSymbolReferencesAsync( await FindSymbolReferencesAsync( context, symbolAndProject?.symbol, symbolAndProject?.project, cancellationToken).ConfigureAwait(false); + + // PROTOTYPE + var secondary = symbolAndProject.Value.secondary; + if (secondary != null) + { + await FindSymbolReferencesAsync( + context, secondary, symbolAndProject?.project, cancellationToken).ConfigureAwait(false); + } } /// diff --git a/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs b/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs index 4587af293bb92..9529e0e7be015 100644 --- a/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs +++ b/src/EditorFeatures/Core/FindUsages/FindUsagesHelpers.cs @@ -27,29 +27,34 @@ public static string GetDisplayName(ISymbol symbol) /// there may be symbol mapping involved (for example in Metadata-As-Source /// scenarios). /// - public static async Task<(ISymbol symbol, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( + public static async Task<(ISymbol symbol, ISymbol secondary, Project project)?> GetRelevantSymbolAndProjectAtPositionAsync( Document document, int position, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false); - if (symbol == null) + var symbols = await SymbolFinder.FindSymbolExAtPositionAsync(document, position, cancellationToken: cancellationToken).ConfigureAwait(false); + if (symbols.primary == null) { return null; } + if (symbols.secondary != null) + { + return (symbols.primary, symbols.secondary, document.Project); + } + // If this document is not in the primary workspace, we may want to search for results // in a solution different from the one we started in. Use the starting workspace's // ISymbolMappingService to get a context for searching in the proper solution. var mappingService = document.Project.Solution.Workspace.Services.GetService(); - var mapping = await mappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false); + var mapping = await mappingService.MapSymbolAsync(document, symbols.primary, cancellationToken).ConfigureAwait(false); if (mapping == null) { return null; } - return (mapping.Symbol, mapping.Project); + return (mapping.Symbol, null, mapping.Project); } public static async Task<(ISymbol symbol, Project project, ImmutableArray implementations, string message)?> FindImplementationsAsync(Document document, int position, CancellationToken cancellationToken) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb index b662ef4d22870..c3693d7e50af3 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.Tuples.vb @@ -248,8 +248,8 @@ namespace System { static void Main(string[] args) { - var x = ({|Definition:Alice|}:1, Bob: 2); - var y = (Alice:1, Bob: 2); + var x = ([|{|Definition:Alice|}|]:1, Bob: 2); + var y = ([|Alice|]:1, Bob: 2); // PROTOTYPE confirm if this is desirable var z = x.$$[|Item1|]; } @@ -278,7 +278,7 @@ namespace System var x = ($$[|{|Definition:Alice|}|]:1, Bob: 2); var y = ([|Alice|]:1, Bob: 2); - var z = x.Item1; + var z = x.[|Item1|]; } } @@ -346,5 +346,109 @@ namespace System Await TestAPIAndFeature(input) End Function + + + + Public Async Function TestDualViewOnTupleElement() As Task + Dim input = + + + + <%= tuple2 %> + + + + Await TestAPIAndFeature(input) + End Function + + + + Public Async Function TestDualViewOnTupleElement2() As Task + Dim input = + + + + <%= tuple2 %> + + + + Await TestAPIAndFeature(input) + End Function + + + + Public Async Function TestDualViewOnTupleElement3() As Task + Dim input = + + + + <%= tuple2 %> + + + + Await TestAPIAndFeature(input) + End Function + + + + Public Async Function TestDualViewOnTupleElement4() As Task + Dim input = + + + + <%= tuple2 %> + + + + Await TestAPIAndFeature(input) + End Function End Class End Namespace diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb index 1bfff48e5289a..cb77e3faf0865 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb @@ -181,13 +181,17 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Dim document = workspace.CurrentSolution.GetDocument(cursorDocument.Id) Assert.NotNull(document) - Dim symbol = Await SymbolFinder.FindSymbolAtPositionAsync(document, cursorPosition) + Dim symbols = Await SymbolFinder.FindSymbolExAtPositionAsync(document, cursorPosition) Dim result = SpecializedCollections.EmptyEnumerable(Of ReferencedSymbol)() - If symbol IsNot Nothing Then + If symbols.primary IsNot Nothing Then Dim scope = If(searchSingleFileOnly, ImmutableHashSet.Create(Of Document)(document), Nothing) - result = result.Concat(Await SymbolFinder.FindReferencesAsync(symbol, document.Project.Solution, progress:=Nothing, documents:=scope)) + result = result.Concat(Await SymbolFinder.FindReferencesAsync(symbols.primary, document.Project.Solution, progress:=Nothing, documents:=scope)) + + If symbols.secondary IsNot Nothing Then + result = result.Concat(Await SymbolFinder.FindReferencesAsync(symbols.secondary, document.Project.Solution, progress:=Nothing, documents:=scope)) + End If End If Dim actualDefinitions = @@ -208,7 +212,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Dim actual = actualDefinitions(GetFilePathAndProjectLabel(workspace, doc)).Order() If Not TextSpansMatch(expected, actual) Then - Assert.True(False, PrintSpans(expected, actual, workspace.CurrentSolution.GetDocument(doc.Id), "{|Definition:", "|}")) + Assert.True(False, PrintSpans(expected, actual, "Definition", workspace.CurrentSolution.GetDocument(doc.Id))) End If Next @@ -230,13 +234,26 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Dim actualSpans = actualReferences(GetFilePathAndProjectLabel(workspace, doc)).Order() AssertEx.Equal(expectedSpans, actualSpans, - message:=PrintSpans(expectedSpans, actualSpans, workspace.CurrentSolution.GetDocument(doc.Id), "[|", "|]", messageOnly:=True)) + message:=PrintSpans(expectedSpans, actualSpans, name:=Nothing, workspace.CurrentSolution.GetDocument(doc.Id), messageOnly:=True)) Next Next End Using End Function - Private Shared Function PrintSpans(expected As IOrderedEnumerable(Of TextSpan), actual As IOrderedEnumerable(Of TextSpan), doc As Document, prefix As String, suffix As String, Optional messageOnly As Boolean = False) As String + Public Shared Function PrintSpans(expected As IEnumerable(Of TextSpan), + actual As IEnumerable(Of TextSpan), + name As String, doc As Document, Optional messageOnly As Boolean = False) As String + Return PrintSpans(expected.Select(Function(e) (name, e)), actual.Select(Function(a) (name, a)), doc, messageOnly) + End Function + + Public Shared Function PrintSpans(expected As IEnumerable(Of (name As String, span As TextSpan)), + actual As IEnumerable(Of (name As String, span As TextSpan)), + doc As Document, Optional messageOnly As Boolean = False) As String + + expected = expected.OrderBy(Function(e) e.span) + actual = actual.OrderBy(Function(a) a.span) + + Debug.Assert(expected IsNot Nothing) Debug.Assert(actual IsNot Nothing) @@ -245,20 +262,33 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences builder.AppendLine() If Not messageOnly Then - builder.AppendLine($"Expected: {String.Join(", ", expected.Select(Function(e) e.ToString()))}") - builder.AppendLine($"Actual: {String.Join(", ", actual.Select(Function(a) a.ToString()))}") + builder.AppendLine($"Expected: {String.Join(", ", expected.Select(Function(e) e.name + ":" + e.span.ToString()))}") + builder.AppendLine($"Actual: {String.Join(", ", actual.Select(Function(a) a.name + ":" + a.span.ToString()))}") End If Dim text As SourceText = Nothing doc.TryGetText(text) Dim position = 0 - For Each span In actual - builder.Append(text.GetSubText(New TextSpan(position, span.Start - position))) - builder.Append(prefix) - builder.Append(text.GetSubText(span)) - builder.Append(suffix) - position = span.End + Dim actualGrouped = actual.GroupBy(Function(a) a.span) + For Each group In actualGrouped + builder.Append(text.GetSubText(New TextSpan(position, group.Key.Start - position))) + + For Each name In group.Select(Function(g) g.name) + builder.Append(If(name Is Nothing, "[|", "{|")) + If name IsNot Nothing Then + builder.Append(name) + builder.Append(":") + End If + Next + + builder.Append(text.GetSubText(group.Key)) + + For Each name In group.Select(Function(g) g.name) + builder.Append(If(name Is Nothing, "|]", "|}")) + Next + + position = group.Key.End Next builder.Append(text.GetSubText(New TextSpan(position, text.Length - position))) diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb index 35c3d66528894..a906a014dc2d4 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.Notification Imports Microsoft.CodeAnalysis.Remote Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities.RemoteHost +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.VisualStudio.Text Imports Roslyn.Utilities @@ -50,19 +51,22 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReferenceHighlighting Order By tag.Span.Start Let spanType = If(tag.Tag.Type = DefinitionHighlightTag.TagId, "Definition", If(tag.Tag.Type = WrittenReferenceHighlightTag.TagId, "WrittenReference", "Reference")) - Select spanType + ":" + tag.Span.Span.ToTextSpan().ToString() + Select (name:=spanType, Span:=tag.Span.Span.ToTextSpan()) - Dim expectedTags As New List(Of String) + Dim expectedTags As New List(Of (name As String, span As TextSpan)) For Each hostDocument In workspace.Documents For Each nameAndSpans In hostDocument.AnnotatedSpans For Each span In nameAndSpans.Value - expectedTags.Add(nameAndSpans.Key + ":" + span.ToString()) + expectedTags.Add((nameAndSpans.Key, span)) Next Next Next - AssertEx.Equal(expectedTags, producedTags) + AssertEx.Equal(expectedTags, producedTags, + message:=FindReferences.FindReferencesTests.PrintSpans(expectedTags, + producedTags, + document, messageOnly:=True)) End Using End Function End Class diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb index 3e12a8c68d006..8133d9eeb7d26 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/CSharpReferenceHighlightingTests.vb @@ -37,6 +37,173 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReferenceHighlighting ) End Function + + + Public Async Function TestVerifyHighlightsForParameterUsedAsTupleElement() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int {|Definition:Al$$ice|}) + { + var x = ({|Reference:Alice|}, Bob: 2); + var y = (Alice:1, Bob: 2); + var z = (Alice:1, Carol: 2); + + var z1 = x.Alice; + var z2 = x.Item1; + } +} + + + ) + End Function + + + + Public Async Function TestVerifyHighlightsForInferredTupleElement() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int {|Definition:Alice|}) + { + var x = ({|Definition:{|Reference:Al$$ice|}|}, Bob: 2); + var y = ({|Reference:Alice|}:1, Bob: 2); + var z = (Alice:1, Carol: 2); + + var z = x.{|Reference:Alice|}; + var z2 = x.{|Reference:Item1|}; + } +} + + + ) + End Function + + + + Public Async Function TestVerifyHighlightsForNamedTupleElement() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int Alice) + { + var x = (Alice, Bob: 2); // PROTOTYPE This probably should be highlighted, although the field name is inferred + var y = ({|Definition:Ali$$ce|}:1, Bob: 2); + var z = (Alice:1, Carol: 2); + + var z1 = x.{|Reference:Alice|}; + var z2 = x.{|Reference:Item1|}; + } +} + + + ) + End Function + + + + Public Async Function TestVerifyHighlightsForNamedTupleElement2() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int Alice) + { + var x = (Alice, Bob: 2); + var y = (Alice:1, Bob: 2); + var z = ({|Definition:Al$$ice|}:1, Carol: 2); + + var z1 = x.Alice; + var z2 = x.Item1; + } +} + + + ) + End Function + + + + Public Async Function TestVerifyHighlightsForNamedTupleElementUsage() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int Alice) + { + var x = ({|Definition:Alice|}, Bob: 2); + var y = ({|Reference:Alice|}:1, Bob: 2); + var z = (Alice:1, Carol: 2); + + var z1 = x.{|Reference:Al$$ice|}; + var z2 = x.{|Reference:Item1|}; + } +} + + + ) + End Function + + + + Public Async Function TestVerifyHighlightsForUnnamedTupleElementUsage() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int Alice) + { + var x = ({|Definition:Alice|}, Bob: 2); + var y = ({|Reference:Alice|}:1, Bob: 2); + var z = (Alice:1, Carol: 2); + + var z1 = x.{|Reference:Alice|}; + var z2 = x.{|Reference:Ite$$m1|}; + } +} + + + ) + End Function + + + + Public Async Function TestVerifyHighlightsForOtherNamedTupleElement() As Task + Await VerifyHighlightsAsync( + + + +class Program +{ + static void Main(int {|Definition:Alice|}) + { + var x = (Other: 1, Bob: 2); + var z = ({|Definition:{|Reference:Al$$ice|}|}, Carol: 2); + + var z1 = x.Other; + var z2 = x.Item1; + } +} + + + ) + End Function + Public Async Function TestVerifyHighlightsForScriptReference() As Task diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index d7d3b5506f387..377f16104cba7 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -66,23 +66,38 @@ private async Task> GetDocumentHighlightsInCu var solution = document.Project.Solution; var semanticModel = await document.GetSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false); - var symbol = await SymbolFinder.FindSymbolAtPositionAsync( + // PROTOTYPE Should get two symbols back here + var symbols = await SymbolFinder.FindSymbolExAtPositionAsync( semanticModel, position, solution.Workspace, cancellationToken).ConfigureAwait(false); - if (symbol == null) + if (symbols.primary == null) { return ImmutableArray.Empty; } - symbol = await GetSymbolToSearchAsync(document, position, semanticModel, symbol, cancellationToken).ConfigureAwait(false); - if (symbol == null) + symbols.primary = await GetSymbolToSearchAsync(document, position, semanticModel, symbols.primary, cancellationToken).ConfigureAwait(false); + if (symbols.primary == null) { return ImmutableArray.Empty; } + if (symbols.secondary != null) + { + symbols.secondary = await GetSymbolToSearchAsync(document, position, semanticModel, symbols.secondary, cancellationToken).ConfigureAwait(false); + } // Get unique tags for referenced symbols - return await GetTagsForReferencedSymbolAsync( - new SymbolAndProjectId(symbol, document.Project.Id), + var result = await GetTagsForReferencedSymbolAsync( + new SymbolAndProjectId(symbols.primary, document.Project.Id), document, documentsToSearch, cancellationToken).ConfigureAwait(false); + + if (symbols.secondary != null) + { + var secondaryResult = await GetTagsForReferencedSymbolAsync( + new SymbolAndProjectId(symbols.secondary, document.Project.Id), + document, documentsToSearch, cancellationToken).ConfigureAwait(false); + result = result.Concat(secondaryResult); + } + + return result; } private static async Task GetSymbolToSearchAsync(Document document, int position, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs index b8eca168c7524..47bce3e4cbe43 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs @@ -92,6 +92,7 @@ private static ISymbol WrapMember(ISymbol m, bool canImplementImplicitly, IDocum public ImmutableArray Interfaces => _symbol.Interfaces; public ImmutableArray AllInterfaces => _symbol.AllInterfaces; public ImmutableArray TupleElements => _symbol.TupleElements; + public ImmutableArray TupleElementNames => _symbol.TupleElementNames; public ImmutableArray GetTypeArgumentCustomModifiers(int ordinal) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 90b6336f1e0e6..ae95a8d00d939 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -187,7 +187,7 @@ protected static async Task> FindReferencesInD var symbolToMatch = symbolInfoToMatch.Symbol; var symbolToMatchCompilation = model.Compilation; - if (SymbolFinder.OriginalSymbolsMatch(searchSymbol, symbolInfoToMatch.Symbol, solution, null, symbolToMatchCompilation, cancellationToken)) + if (SymbolFinder.OriginalSymbolsMatch(searchSymbol, symbolToMatch, solution, null, symbolToMatchCompilation, cancellationToken)) { return (matched: true, CandidateReason.None); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 22ef79cc4c97a..bf8bb8b7ed272 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders @@ -22,6 +23,33 @@ protected override Task> DetermineCascadedSym CancellationToken cancellationToken) { var symbol = symbolAndProjectId.Symbol; + if (symbol.IsTupleField()) + { + if (symbol == symbol.CorrespondingTupleField) + { + // We have a default element field (Item1, ...), let's find it's corresponding named field (if any) + var tuple = symbol.ContainingType; + foreach (var member in tuple.GetMembers()) + { + if (member.IsTupleField()) + { + var field = (IFieldSymbol)member; + if (field.CorrespondingTupleField != field && field.CorrespondingTupleField == symbol) + { + return Task.FromResult( + ImmutableArray.Create(symbolAndProjectId.WithSymbol((ISymbol)field))); + } + } + } + } + else + { + // We have a named field, let's cascade to the default field + return Task.FromResult( + ImmutableArray.Create(symbolAndProjectId.WithSymbol((ISymbol)symbol.CorrespondingTupleField))); + } + } + if (symbol.AssociatedSymbol != null) { return Task.FromResult( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index 6e0a045340fef..31768052b96d3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs @@ -45,6 +45,24 @@ public static async Task FindSymbolAtPositionAsync( return semanticInfo.GetAnySymbol(includeType: false); } + internal static async Task<(ISymbol primary, ISymbol secondary)> FindSymbolExAtPositionAsync( + SemanticModel semanticModel, + int position, + Workspace workspace, + CancellationToken cancellationToken = default) + { + var semanticInfo = await GetSemanticInfoAtPositionAsync( + semanticModel, position, workspace, cancellationToken: cancellationToken).ConfigureAwait(false); + + // PROTOTYPE also should handle anonymous types + if (semanticInfo.DeclaredSymbol?.IsTupleField() == true && semanticInfo.ReferencedSymbols.Length == 1) + { + return (semanticInfo.DeclaredSymbol, semanticInfo.ReferencedSymbols[0]); + } + + return (semanticInfo.GetAnySymbol(includeType: false), null); + } + internal static async Task GetSemanticInfoAtPositionAsync( SemanticModel semanticModel, int position, @@ -73,6 +91,25 @@ public static async Task FindSymbolAtPositionAsync( return await FindSymbolAtPositionAsync(semanticModel, position, document.Project.Solution.Workspace, cancellationToken).ConfigureAwait(false); } + internal static async Task<(ISymbol primary, ISymbol secondary)> FindSymbolExAtPositionAsync( + Document document, + int position, + CancellationToken cancellationToken = default) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var semanticInfo = await GetSemanticInfoAtPositionAsync( + semanticModel, position, document.Project.Solution.Workspace, cancellationToken: cancellationToken).ConfigureAwait(false); + + // PROTOTYPE also should handle anonymous types + if (semanticInfo.DeclaredSymbol?.IsTupleField() == true && semanticInfo.ReferencedSymbols.Length == 1) + { + return (semanticInfo.DeclaredSymbol, semanticInfo.ReferencedSymbols[0]); + } + + return (semanticInfo.GetAnySymbol(includeType: false), null); + } + /// /// Finds the definition symbol declared in source code for a corresponding reference symbol. /// Returns null if no such symbol can be found in the specified solution. diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/SymbolEquivalenceComparer.EquivalenceVisitor.cs b/src/Workspaces/Core/Portable/Shared/Utilities/SymbolEquivalenceComparer.EquivalenceVisitor.cs index 284ef3ea39918..84466349e0599 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/SymbolEquivalenceComparer.EquivalenceVisitor.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/SymbolEquivalenceComparer.EquivalenceVisitor.cs @@ -362,6 +362,21 @@ private bool HandleNamedTypesWorker(INamedTypeSymbol x, INamedTypeSymbol y, Dict } } + var xElementNames = x.TupleElementNames; + var yElementNames = y.TupleElementNames; + if (xElementNames.Length != yElementNames.Length) + { + return false; + } + + for (int i = 0; i < xElementNames.Length; i++) + { + if (xElementNames[i] != yElementNames[i]) + { + return false; + } + } + return true; }