Skip to content

Commit

Permalink
Update FindSourceDefinitionAsync to chase through retargeted symbols
Browse files Browse the repository at this point in the history
FindSourceDefinitionAsync, when presented with a source symbol, would
immediately return that symbol. If the source symbol is a retargeted
source symbol, we were still returning that symbol instead of returning
the "original" definition in the other source assembly. This changes the
behavior to now return the original source assembly, a behavior which
seems more consistent and allows FindSourceDefinitionAsync to be used
in scenarios where we care about IAssemblySymbol identity.
  • Loading branch information
jasonmalinowski committed Jan 27, 2016
1 parent 85c5a7c commit 4c49bf2
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,42 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Assert.True(mappedMember.Locations.All(Function(Loc) Loc.IsInSource))
End Using
End Function


<Fact>
Public Async Function TestFindRetargetedClass() As Task
Dim workspaceDefinition =
<Workspace>
<Project Language="C#" AssemblyName="CSharpAssembly" CommonReferencesPortable="true">
<Document>
namespace N
{
public class CSClass
{
}
}
</Document>
</Project>
<Project Language="C#" AssemblyName="CSharpAssembly2" CommonReferences="true">
<ProjectReference>CSharpAssembly</ProjectReference>
</Project>
</Workspace>

Using workspace = Await TestWorkspace.CreateAsync(workspaceDefinition)
Dim retargetedCompilation = Await GetProject(workspace.CurrentSolution, "CSharpAssembly").GetCompilationAsync()
Dim originalClass = retargetedCompilation.GlobalNamespace.GetMembers("N").Single().GetTypeMembers("CSClass").Single()
Dim retargetingCompilation = Await GetProject(workspace.CurrentSolution, "CSharpAssembly2").GetCompilationAsync()
Dim retargetedClass = retargetingCompilation.GlobalNamespace.GetMembers("N").Single().GetTypeMembers("CSClass").Single()

' The retargeted class should not have the same assembly identity as the originating assembly, but
' should come through the compilation reference
Assert.NotEqual(retargetedClass.ContainingAssembly, retargetedCompilation.Assembly)
Assert.IsAssignableFrom(Of CompilationReference)(retargetingCompilation.GetMetadataReference(retargetedClass.ContainingAssembly))

Dim mappedMember = Await SymbolFinder.FindSourceDefinitionAsync(retargetedClass, workspace.CurrentSolution)

Assert.Equal(Of ISymbol)(originalClass, mappedMember)
End Using
End Function
End Class
End Namespace
34 changes: 29 additions & 5 deletions src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,39 @@ private static async Task<ISymbol> FindSourceDefinitionWorkerAsync(
Solution solution,
CancellationToken cancellationToken)
{
// If it's already in source, then just return it.
// If it's already in source, then we might already be done
if (InSource(symbol))
{
return symbol;
}
// If our symbol doesn't have a containing assembly, there's nothing better we can do to map this
// symbol somewhere else. The common case for this is a merged INamespaceSymbol that spans assemblies.
if (symbol.ContainingAssembly == null)
{
return symbol;
}

// If it's not in metadata, then we can't map it back to source.
if (!symbol.Locations.Any(loc => loc.IsInMetadata))
// Just because it's a source symbol doesn't mean we have the final symbol we actually want. In retargeting cases,
// the retargeted symbol is from "source" but isn't equal to the actual source definition in the other project. Thus,
// we only want to return symbols from source here if it actually came from a project's compilation's assembly. If it isn't
// then we have a retargeting scenario and want to take our usual path below as if it was a metadata reference
foreach (var sourceProject in solution.Projects)
{
Compilation compilation;

// If our symbol is actually a "regular" source symbol, then we know the compilation is holding the symbol alive
// and thus TryGetCompilation is sufficient. For another example of this pattern, see Solution.GetProject(IAssemblySymbol)
// which we happen to call below.
if (sourceProject.TryGetCompilation(out compilation))
{
if (symbol.ContainingAssembly.Equals(compilation.Assembly))
{
return symbol;
}
}
}
}
else if (!symbol.Locations.Any(loc => loc.IsInMetadata))
{
// We have a symbol that's neither in source nor metadata
return null;
}

Expand Down

0 comments on commit 4c49bf2

Please sign in to comment.