Skip to content

Commit fa7f7ba

Browse files
[release/8.0] Disambiguate type names by containing assembly in WellKnownTypes (#52130)
* Disambiguate type names by containing assembly in WellKnownTypes * Address peer review * Update comment * More feedback --------- Co-authored-by: Safia Abdalla <[email protected]>
1 parent 84f174f commit fa7f7ba

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

src/Framework/AspNetCoreAnalyzers/test/Infrastructure/WellKnownTypesTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using System.Collections.Immutable;
55
using Microsoft.AspNetCore.Analyzer.Testing;
66
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
7+
using Microsoft.AspNetCore.Razor.Hosting;
78
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
810
using Microsoft.CodeAnalysis.Diagnostics;
911

1012
namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
@@ -34,6 +36,52 @@ static void Main()
3436
Assert.Collection(diagnostics, d => Assert.Equal("TEST001", d.Id));
3537
}
3638

39+
[Theory]
40+
[InlineData("ExternAssembly")]
41+
[InlineData("SystemFoo")]
42+
[InlineData("MicrosoftFoo")]
43+
public async Task ResolveAllWellKnownTypes_ToleratesDuplicateTypeNames(string assemblyName)
44+
{
45+
// Arrange
46+
var source = TestSource.Read(@"
47+
class Program
48+
{
49+
static void Main()
50+
{
51+
}
52+
}
53+
");
54+
var referenceSource = """
55+
namespace Microsoft.AspNetCore.Builder
56+
{
57+
public static class EndpointRouteBuilderExtensions
58+
{
59+
}
60+
}
61+
""";
62+
// Act
63+
var project = TestDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, source.Source);
64+
Stream assemblyStream = GetInMemoryAssemblyStreamForCode(referenceSource, assemblyName, project.MetadataReferences.ToArray());
65+
project = project.AddMetadataReference(MetadataReference.CreateFromStream(assemblyStream));
66+
var diagnostics = await Runner.GetDiagnosticsAsync(project);
67+
68+
// Assert
69+
Assert.Collection(diagnostics, d => Assert.Equal("TEST001", d.Id));
70+
}
71+
72+
private static Stream GetInMemoryAssemblyStreamForCode(string code, string assemblyName, params MetadataReference[] references)
73+
{
74+
var tree = CSharpSyntaxTree.ParseText(code);
75+
var trees = ImmutableArray.Create(tree);
76+
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
77+
var compilation = CSharpCompilation.Create(assemblyName, trees).WithOptions(options);
78+
compilation = compilation.AddReferences(references);
79+
var stream = new MemoryStream();
80+
var emitResult = compilation.Emit(stream);
81+
stream.Seek(0, SeekOrigin.Begin);
82+
return stream;
83+
}
84+
3785
[DiagnosticAnalyzer(LanguageNames.CSharp)]
3886
private class TestAnalyzer : DiagnosticAnalyzer
3987
{

src/Shared/RoslynUtils/WellKnownTypes.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type)
7474

7575
private INamedTypeSymbol GetAndCache(int index)
7676
{
77-
var result = _compilation.GetTypeByMetadataName(WellKnownTypeData.WellKnownTypeNames[index]);
77+
var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]);
7878
if (result == null)
7979
{
8080
throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'.");
@@ -86,6 +86,33 @@ private INamedTypeSymbol GetAndCache(int index)
8686
return _lazyWellKnownTypes[index]!;
8787
}
8888

89+
// Filter for types within well-known (framework-owned) assemblies only.
90+
private INamedTypeSymbol? GetTypeByMetadataNameInTargetAssembly(string metadataName)
91+
{
92+
var types = _compilation.GetTypesByMetadataName(metadataName);
93+
if (types.Length == 0)
94+
{
95+
return null;
96+
}
97+
98+
if (types.Length == 1)
99+
{
100+
return types[0];
101+
}
102+
103+
// Multiple types match the name. This is most likely caused by someone reusing the namespace + type name in their apps or libraries.
104+
// Workaround this situation by prioritizing types in System and Microsoft assemblies.
105+
foreach (var type in types)
106+
{
107+
if (type.ContainingAssembly.Identity.Name.StartsWith("System.", StringComparison.Ordinal)
108+
|| type.ContainingAssembly.Identity.Name.StartsWith("Microsoft.", StringComparison.Ordinal))
109+
{
110+
return type;
111+
}
112+
}
113+
return null;
114+
}
115+
89116
public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes) => IsType(type, wellKnownTypes, out var _);
90117

91118
public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes, [NotNullWhen(true)] out WellKnownTypeData.WellKnownType? match)

0 commit comments

Comments
 (0)