diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/ImplementationAssemblyLookupServiceTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/ImplementationAssemblyLookupServiceTests.cs
index 80bd80ba40c7f..52e8f4a35a791 100644
--- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/ImplementationAssemblyLookupServiceTests.cs
+++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/ImplementationAssemblyLookupServiceTests.cs
@@ -188,6 +188,126 @@ public class C
});
}
+ [Fact]
+ public async Task FollowTypeForwards_Namespace()
+ {
+ var source = @"
+namespace A
+{
+ namespace B
+ {
+ public class C
+ {
+ public class D
+ {
+ // A change
+ public event System.EventHandler [|E|] { add { } remove { } }
+ }
+ }
+ }
+}";
+ var typeForwardSource = @"
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(A.B.C))]
+";
+
+ await RunTestAsync(async path =>
+ {
+ MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
+
+ // Compile reference assembly
+ var sourceText = SourceText.From(metadataSource, encoding: Encoding.UTF8);
+ var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("A.B.C.D.E"), buildReferenceAssembly: true);
+
+ // Compile implementation assembly to a different DLL
+ var dllFilePath = Path.Combine(path, "implementation.dll");
+ var sourceCodePath = Path.Combine(path, "implementation.cs");
+ var pdbFilePath = Path.Combine(path, "implementation.pdb");
+ var assemblyName = "implementation";
+
+ var workspace = TestWorkspace.Create(@$"
+
+
+
+", composition: GetTestComposition());
+
+ var implProject = workspace.CurrentSolution.Projects.First();
+ CompileTestSource(dllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+
+ // Compile type forwarding implementation DLL
+ var typeForwardDllFilePath = Path.Combine(path, "typeforward.dll");
+ assemblyName = "typeforward";
+
+ implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(dllFilePath));
+ sourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
+ CompileTestSource(typeForwardDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+
+ var service = workspace.GetService();
+
+ var foundImplementationFilePath = service.FollowTypeForwards(symbol, typeForwardDllFilePath, new NoDuplicatesLogger());
+ Assert.Equal(dllFilePath, foundImplementationFilePath);
+ });
+ }
+
+ [Fact]
+ public async Task FollowTypeForwards_Generics()
+ {
+ var source = @"
+namespace A
+{
+ namespace B
+ {
+ public class C
+ {
+ public class D
+ {
+ // A change
+ public event System.EventHandler [|E|] { add { } remove { } }
+ }
+ }
+ }
+}";
+ var typeForwardSource = @"
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(A.B.C<>))]
+";
+
+ await RunTestAsync(async path =>
+ {
+ MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
+
+ // Compile reference assembly
+ var sourceText = SourceText.From(metadataSource, encoding: Encoding.UTF8);
+ var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("A.B.C.D.E"), buildReferenceAssembly: true);
+
+ // Compile implementation assembly to a different DLL
+ var dllFilePath = Path.Combine(path, "implementation.dll");
+ var sourceCodePath = Path.Combine(path, "implementation.cs");
+ var pdbFilePath = Path.Combine(path, "implementation.pdb");
+ var assemblyName = "implementation";
+
+ var workspace = TestWorkspace.Create(@$"
+
+
+
+", composition: GetTestComposition());
+
+ var implProject = workspace.CurrentSolution.Projects.First();
+ CompileTestSource(dllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+
+ // Compile type forwarding implementation DLL
+ var typeForwardDllFilePath = Path.Combine(path, "typeforward.dll");
+ assemblyName = "typeforward";
+
+ implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(dllFilePath));
+ sourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
+ CompileTestSource(typeForwardDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+
+ var service = workspace.GetService();
+
+ var foundImplementationFilePath = service.FollowTypeForwards(symbol, typeForwardDllFilePath, new NoDuplicatesLogger());
+ Assert.Equal(dllFilePath, foundImplementationFilePath);
+ });
+ }
+
[Fact]
public async Task FollowTypeForwards_NestedType()
{
@@ -402,28 +522,36 @@ public class C
var typeForwardDllFilePath = Path.Combine(path, "typeforward.dll");
assemblyName = "typeforward";
- implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(dllFilePath));
- sourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
- CompileTestSource(typeForwardDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+ implProject = workspace.CurrentSolution.Projects.First().AddMetadataReference(MetadataReference.CreateFromFile(dllFilePath));
+ var typeForwardSourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
+ CompileTestSource(typeForwardDllFilePath, sourceCodePath, pdbFilePath, assemblyName, typeForwardSourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
- var typeForward2DllFilePath = Path.Combine(path, "typeforward2.dll");
- assemblyName = "typeforward2";
+ // Now compile a new implementation in realimplementation.dll
+ var realImplementationDllFilePath = Path.Combine(path, "realimplementation.dll");
+ assemblyName = "realimplementation";
- implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(typeForwardDllFilePath));
- sourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
- CompileTestSource(typeForward2DllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+ implProject = workspace.CurrentSolution.Projects.First();
+ CompileTestSource(realImplementationDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+
+ // Now compile a new implementation.dll that typeforwards to realimplementation.dll
+ assemblyName = "implementation";
+
+ implProject = workspace.CurrentSolution.Projects.First().AddMetadataReference(MetadataReference.CreateFromFile(realImplementationDllFilePath));
+ CompileTestSource(dllFilePath, sourceCodePath, pdbFilePath, assemblyName, typeForwardSourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
var service = workspace.GetService();
- Assert.Equal(dllFilePath, service.FollowTypeForwards(symbol, typeForward2DllFilePath, new NoDuplicatesLogger()));
+ var foundImplementationFilePath = service.FollowTypeForwards(symbol, typeForwardDllFilePath, new NoDuplicatesLogger());
+ Assert.Equal(realImplementationDllFilePath, foundImplementationFilePath);
// We need the DLLs to exist, in order for some checks to pass correct, but to ensure
// that the file isn't read, we just zero it out.
File.WriteAllBytes(typeForwardDllFilePath, Array.Empty());
- File.WriteAllBytes(typeForward2DllFilePath, Array.Empty());
+ File.WriteAllBytes(realImplementationDllFilePath, Array.Empty());
File.WriteAllBytes(dllFilePath, Array.Empty());
- Assert.Equal(dllFilePath, service.FollowTypeForwards(symbol, typeForward2DllFilePath, new NoDuplicatesLogger()));
+ foundImplementationFilePath = service.FollowTypeForwards(symbol, typeForwardDllFilePath, new NoDuplicatesLogger());
+ Assert.Equal(realImplementationDllFilePath, foundImplementationFilePath);
});
}
diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs
index 59fc5c3a401ca..51c4f0a787903 100644
--- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs
+++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs
@@ -457,6 +457,50 @@ public class C
{
}
}
+";
+
+ await RunTestAsync(async path =>
+ {
+ MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
+
+ var packDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "ref", "net6.0")).FullName;
+ var dataDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "data")).FullName;
+ var sharedDir = Directory.CreateDirectory(Path.Combine(path, "shared", "MyPack", "1.0")).FullName;
+
+ var sourceText = SourceText.From(metadataSource, Encoding.UTF8);
+ var (project, symbol) = await CompileAndFindSymbolAsync(packDir, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C.M"), buildReferenceAssembly: true);
+
+ var workspace = TestWorkspace.Create(@$"
+
+
+
+", composition: GetTestComposition());
+
+ var implProject = workspace.CurrentSolution.Projects.First();
+
+ // Compile implementation assembly
+ CompileTestSource(sharedDir, sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+
+ // Create FrameworkList.xml
+ File.WriteAllText(Path.Combine(dataDir, "FrameworkList.xml"), """
+
+
+ """);
+
+ await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, metadataSource.ToString(), expectedSpan, expectNullResult: false);
+ });
+ }
+
+ [Fact]
+ public async Task Net6SdkLayout_TypeForward()
+ {
+ var source = @"
+public class [|C|]
+{
+ public void M(string d)
+ {
+ }
+}
";
var typeForwardSource = @"
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(C))]
@@ -471,7 +515,7 @@ public class C
var sharedDir = Directory.CreateDirectory(Path.Combine(path, "shared", "MyPack", "1.0")).FullName;
var sourceText = SourceText.From(metadataSource, Encoding.UTF8);
- var (project, symbol) = await CompileAndFindSymbolAsync(packDir, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C.M"), buildReferenceAssembly: true);
+ var (project, symbol) = await CompileAndFindSymbolAsync(packDir, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C"), buildReferenceAssembly: true);
var workspace = TestWorkspace.Create(@$"
@@ -482,15 +526,20 @@ public class C
var implProject = workspace.CurrentSolution.Projects.First();
// Compile implementation assembly
- CompileTestSource(sharedDir, sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
+ var implementationDllFilePath = Path.Combine(sharedDir, "implementation.dll");
+ var sourceCodePath = Path.Combine(sharedDir, "implementation.cs");
+ var pdbFilePath = Path.Combine(sharedDir, "implementation.pdb");
+ var assemblyName = "implementation";
+
+ CompileTestSource(implementationDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
- // Compile type forwarding implementation DLL
- var typeForwardDllFilePath = Path.Combine(sharedDir, "typeforward.dll");
- var sourceCodePath = Path.Combine(sharedDir, "typeforward.cs");
- var pdbFilePath = Path.Combine(sharedDir, "typeforward.pdb");
- var assemblyName = "typeforward";
+ // Compile type forwarding implementation DLL, that looks like reference.dll
+ var typeForwardDllFilePath = Path.Combine(sharedDir, "reference.dll");
+ sourceCodePath = Path.Combine(sharedDir, "reference.cs");
+ pdbFilePath = Path.Combine(sharedDir, "reference.pdb");
+ assemblyName = "reference";
- implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(GetDllPath(sharedDir)));
+ implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(implementationDllFilePath));
sourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
CompileTestSource(typeForwardDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
diff --git a/src/Features/Core/Portable/PdbSourceDocument/ImplementationAssemblyLookupService.cs b/src/Features/Core/Portable/PdbSourceDocument/ImplementationAssemblyLookupService.cs
index e06c69826cd20..f6fa8cbf50d7d 100644
--- a/src/Features/Core/Portable/PdbSourceDocument/ImplementationAssemblyLookupService.cs
+++ b/src/Features/Core/Portable/PdbSourceDocument/ImplementationAssemblyLookupService.cs
@@ -20,6 +20,12 @@ namespace Microsoft.CodeAnalysis.PdbSourceDocument
[Export(typeof(IImplementationAssemblyLookupService)), Shared]
internal class ImplementationAssemblyLookupService : IImplementationAssemblyLookupService
{
+ // We need to generate the namespace name in the same format that is used in metadata, which
+ // is SymbolDisplayFormat.QualifiedNameOnlyFormat, which this is a copy of.
+ private static readonly SymbolDisplayFormat s_metadataSymbolDisplayFormat = new SymbolDisplayFormat(
+ globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
+
private static readonly string PathSeparatorString = Path.DirectorySeparatorChar.ToString();
// Cache for any type forwards. Key is the dll being inspected. Value is a dictionary
@@ -56,6 +62,7 @@ public bool TryFindImplementationAssemblyPath(string referencedDllPath, [NotNull
// Only the top most containing type in the ExportedType table actually points to an assembly
// so no point looking for nested types.
var typeSymbol = MetadataAsSourceHelpers.GetTopLevelContainingNamedType(symbol);
+ var namespaceName = typeSymbol.ContainingNamespace.ToDisplayString(s_metadataSymbolDisplayFormat);
try
{
@@ -66,7 +73,7 @@ public bool TryFindImplementationAssemblyPath(string referencedDllPath, [NotNull
{
// If there are no type forwards in this DLL, or not one for this type, then it means
// we've found the right DLL
- if (typeForwards?.TryGetValue((typeSymbol.ContainingNamespace.MetadataName, typeSymbol.MetadataName), out var assemblyName) != true)
+ if (typeForwards?.TryGetValue((namespaceName, typeSymbol.MetadataName), out var assemblyName) != true)
{
return dllPath;
}
diff --git a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs
index f9d75c28ed977..fa410592ec2a7 100644
--- a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs
+++ b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs
@@ -117,11 +117,11 @@ internal sealed class PdbSourceDocumentMetadataAsSourceFileProvider : IMetadataA
var tmpCompilation = compilationFactory
.CreateCompilation("tmp", compilationFactory.GetDefaultCompilationOptions())
- .AddReferences(project.MetadataReferences)
.AddReferences(dllReference);
var key = SymbolKey.Create(symbolToFind, cancellationToken);
- var newSymbol = key.Resolve(tmpCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
+ var resolution = key.Resolve(tmpCompilation, ignoreAssemblyKey: true, cancellationToken);
+ var newSymbol = resolution.Symbol;
if (newSymbol is null)
{
_logger?.Log(FeaturesResources.Could_not_find_implementation_of_symbol_0, symbolToFind.MetadataName);