diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs
new file mode 100644
index 00000000000000..6a64948f3828a2
--- /dev/null
+++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs
@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.Reflection
+{
+ internal static partial class RoslynExtensions
+ {
+ // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs
+ ///
+ /// Gets a type by its metadata name to use for code analysis within a . This method
+ /// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
+ /// following rules.
+ ///
+ ///
+ /// -
+ /// If only one type with the given name is found within the compilation and its referenced assemblies, that
+ /// type is returned regardless of accessibility.
+ ///
+ /// -
+ /// If the current defines the symbol, that symbol is returned.
+ ///
+ /// -
+ /// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
+ /// , that symbol is returned.
+ ///
+ /// -
+ /// Otherwise, this method returns .
+ ///
+ ///
+ ///
+ /// The to consider for analysis.
+ /// The fully-qualified metadata type name to find.
+ /// The symbol to use for code analysis; otherwise, .
+ public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
+ {
+ // Try to get the unique type with this name, ignoring accessibility
+ var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
+
+ // Otherwise, try to get the unique type with this name originally defined in 'compilation'
+ type ??= compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
+
+ // Otherwise, try to get the unique accessible type with this name from a reference
+ if (type is null)
+ {
+ foreach (var module in compilation.Assembly.Modules)
+ {
+ foreach (var referencedAssembly in module.ReferencedAssemblySymbols)
+ {
+ var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
+ if (currentType is null)
+ continue;
+
+ switch (currentType.GetResultantVisibility())
+ {
+ case SymbolVisibility.Public:
+ case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
+ break;
+
+ default:
+ continue;
+ }
+
+ if (type is object)
+ {
+ // Multiple visible types with the same metadata name are present
+ return null;
+ }
+
+ type = currentType;
+ }
+ }
+ }
+
+ return type;
+ }
+
+ // copied from https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs
+ private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
+ {
+ // Start by assuming it's visible.
+ SymbolVisibility visibility = SymbolVisibility.Public;
+
+ switch (symbol.Kind)
+ {
+ case SymbolKind.Alias:
+ // Aliases are uber private. They're only visible in the same file that they
+ // were declared in.
+ return SymbolVisibility.Private;
+
+ case SymbolKind.Parameter:
+ // Parameters are only as visible as their containing symbol
+ return GetResultantVisibility(symbol.ContainingSymbol);
+
+ case SymbolKind.TypeParameter:
+ // Type Parameters are private.
+ return SymbolVisibility.Private;
+ }
+
+ while (symbol != null && symbol.Kind != SymbolKind.Namespace)
+ {
+ switch (symbol.DeclaredAccessibility)
+ {
+ // If we see anything private, then the symbol is private.
+ case Accessibility.NotApplicable:
+ case Accessibility.Private:
+ return SymbolVisibility.Private;
+
+ // If we see anything internal, then knock it down from public to
+ // internal.
+ case Accessibility.Internal:
+ case Accessibility.ProtectedAndInternal:
+ visibility = SymbolVisibility.Internal;
+ break;
+
+ // For anything else (Public, Protected, ProtectedOrInternal), the
+ // symbol stays at the level we've gotten so far.
+ }
+
+ symbol = symbol.ContainingSymbol;
+ }
+
+ return visibility;
+ }
+
+ // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs
+#pragma warning disable CA1027 // Mark enums with FlagsAttribute
+ private enum SymbolVisibility
+#pragma warning restore CA1027 // Mark enums with FlagsAttribute
+ {
+ Public = 0,
+ Internal = 1,
+ Private = 2,
+ Friend = Internal,
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
index cd47bcae0c0755..5dec808c207fc1 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
@@ -31,6 +31,9 @@ private sealed class Parser
private const string JsonNumberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonNumberHandlingAttribute";
private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute";
+ private const string JsonSerializerContextFullName = "System.Text.Json.Serialization.JsonSerializerContext";
+ private const string JsonSerializerAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";
+ private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute";
private readonly Compilation _compilation;
private readonly SourceProductionContext _sourceProductionContext;
@@ -144,9 +147,9 @@ public Parser(Compilation compilation, in SourceProductionContext sourceProducti
public SourceGenerationSpec? GetGenerationSpec(ImmutableArray classDeclarationSyntaxList)
{
Compilation compilation = _compilation;
- INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext");
- INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute");
- INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute");
+ INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerContextFullName);
+ INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerAttributeFullName);
+ INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSourceGenerationOptionsAttributeFullName);
if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSourceGenerationOptionsAttributeSymbol == null)
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
index c088cc6d7e1cbf..189a842f44f5c6 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
@@ -23,7 +23,7 @@ public MetadataLoadContextInternal(Compilation compilation)
public Type? Resolve(string fullyQualifiedMetadataName)
{
- INamedTypeSymbol? typeSymbol = _compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
+ INamedTypeSymbol? typeSymbol = _compilation.GetBestTypeByMetadataName(fullyQualifiedMetadataName);
return typeSymbol.AsType(this);
}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
index 4e2479784de13e..aa3f431fced8d0 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
@@ -7,7 +7,7 @@
namespace System.Text.Json.Reflection
{
- internal static class RoslynExtensions
+ internal static partial class RoslynExtensions
{
public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContextInternal metadataLoadContext)
{
@@ -67,3 +67,4 @@ public static MethodAttributes GetMethodAttributes(this IMethodSymbol methodSymb
}
}
}
+
diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
index de7b135a86f188..e92c127bed8095 100644
--- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
+++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
false
@@ -37,6 +37,7 @@
+
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
index f12e5b07c2057b..0aa811d64fcce4 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
@@ -467,5 +468,68 @@ private void CheckFieldsPropertiesMethods(Type type, string[] expectedFields, st
}
// TODO: add test guarding against (de)serializing static classes.
+
+ [Fact]
+ public void TestMultipleDefinitions()
+ {
+ // Adding a dependency to an assembly that has internal definitions of public types
+ // should not result in a collision and break generation.
+ // This verifies the usage of GetBestTypeByMetadataName() instead of GetTypeByMetadataName().
+ var referencedSource = @"
+ namespace System.Text.Json.Serialization
+ {
+ internal class JsonSerializerContext { }
+ internal class JsonSerializableAttribute { }
+ internal class JsonSourceGenerationOptionsAttribute { }
+ }";
+
+ // Compile the referenced assembly first.
+ Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource);
+
+ // Obtain the image of the referenced assembly.
+ byte[] referencedImage;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ var emitResult = referencedCompilation.Emit(ms);
+ if (!emitResult.Success)
+ {
+ throw new InvalidOperationException();
+ }
+ referencedImage = ms.ToArray();
+ }
+
+ // Generate the code
+ string source = @"
+ using System.Text.Json.Serialization;
+ namespace HelloWorld
+ {
+ [JsonSerializable(typeof(HelloWorld.MyType))]
+ internal partial class JsonContext : JsonSerializerContext
+ {
+ }
+
+ public class MyType
+ {
+ public int MyInt { get; set; }
+ }
+ }";
+
+ MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };
+ Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences);
+ JsonSourceGenerator generator = new JsonSourceGenerator();
+
+ Compilation newCompilation = CompilationHelper.RunGenerators(
+ compilation,
+ out ImmutableArray generatorDiags, generator);
+
+ // Make sure compilation was successful.
+ Assert.Empty(generatorDiags.Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error)));
+ Assert.Empty(newCompilation.GetDiagnostics().Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error)));
+
+ // Should find the generated type.
+ Dictionary types = generator.GetSerializableTypes();
+ Assert.Equal(1, types.Count);
+ Assert.Equal("HelloWorld.MyType", types.Keys.First());
+ }
}
}