Skip to content

Commit

Permalink
[release/8.0] Disambiguate type names by containing assembly in WellK…
Browse files Browse the repository at this point in the history
…nownTypes (#52130)

* Disambiguate type names by containing assembly in WellKnownTypes

* Address peer review

* Update comment

* More feedback

---------

Co-authored-by: Safia Abdalla <[email protected]>
  • Loading branch information
github-actions[bot] and captainsafia committed Nov 16, 2023
1 parent 84f174f commit fa7f7ba
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Collections.Immutable;
using Microsoft.AspNetCore.Analyzer.Testing;
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.AspNetCore.Razor.Hosting;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;

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

[Theory]
[InlineData("ExternAssembly")]
[InlineData("SystemFoo")]
[InlineData("MicrosoftFoo")]
public async Task ResolveAllWellKnownTypes_ToleratesDuplicateTypeNames(string assemblyName)
{
// Arrange
var source = TestSource.Read(@"
class Program
{
static void Main()
{
}
}
");
var referenceSource = """
namespace Microsoft.AspNetCore.Builder
{
public static class EndpointRouteBuilderExtensions
{
}
}
""";
// Act
var project = TestDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, source.Source);
Stream assemblyStream = GetInMemoryAssemblyStreamForCode(referenceSource, assemblyName, project.MetadataReferences.ToArray());
project = project.AddMetadataReference(MetadataReference.CreateFromStream(assemblyStream));
var diagnostics = await Runner.GetDiagnosticsAsync(project);

// Assert
Assert.Collection(diagnostics, d => Assert.Equal("TEST001", d.Id));
}

private static Stream GetInMemoryAssemblyStreamForCode(string code, string assemblyName, params MetadataReference[] references)
{
var tree = CSharpSyntaxTree.ParseText(code);
var trees = ImmutableArray.Create(tree);
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(assemblyName, trees).WithOptions(options);
compilation = compilation.AddReferences(references);
var stream = new MemoryStream();
var emitResult = compilation.Emit(stream);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}

[DiagnosticAnalyzer(LanguageNames.CSharp)]
private class TestAnalyzer : DiagnosticAnalyzer
{
Expand Down
29 changes: 28 additions & 1 deletion src/Shared/RoslynUtils/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type)

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

// Filter for types within well-known (framework-owned) assemblies only.
private INamedTypeSymbol? GetTypeByMetadataNameInTargetAssembly(string metadataName)
{
var types = _compilation.GetTypesByMetadataName(metadataName);
if (types.Length == 0)
{
return null;
}

if (types.Length == 1)
{
return types[0];
}

// Multiple types match the name. This is most likely caused by someone reusing the namespace + type name in their apps or libraries.
// Workaround this situation by prioritizing types in System and Microsoft assemblies.
foreach (var type in types)
{
if (type.ContainingAssembly.Identity.Name.StartsWith("System.", StringComparison.Ordinal)
|| type.ContainingAssembly.Identity.Name.StartsWith("Microsoft.", StringComparison.Ordinal))
{
return type;
}
}
return null;
}

public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnownTypes) => IsType(type, wellKnownTypes, out var _);

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

0 comments on commit fa7f7ba

Please sign in to comment.