Skip to content

Commit 83f6621

Browse files
authored
Support duplicated type names with src gen (#58448)
1 parent 683899d commit 83f6621

File tree

6 files changed

+213
-6
lines changed

6 files changed

+213
-6
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.CodeAnalysis;
5+
6+
namespace System.Text.Json.Reflection
7+
{
8+
internal static partial class RoslynExtensions
9+
{
10+
// Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs
11+
/// <summary>
12+
/// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
13+
/// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
14+
/// following rules.
15+
///
16+
/// <list type="number">
17+
/// <item><description>
18+
/// If only one type with the given name is found within the compilation and its referenced assemblies, that
19+
/// type is returned regardless of accessibility.
20+
/// </description></item>
21+
/// <item><description>
22+
/// If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
23+
/// </description></item>
24+
/// <item><description>
25+
/// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
26+
/// <paramref name="compilation"/>, that symbol is returned.
27+
/// </description></item>
28+
/// <item><description>
29+
/// Otherwise, this method returns <see langword="null"/>.
30+
/// </description></item>
31+
/// </list>
32+
/// </summary>
33+
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
34+
/// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
35+
/// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
36+
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
37+
{
38+
// Try to get the unique type with this name, ignoring accessibility
39+
var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
40+
41+
// Otherwise, try to get the unique type with this name originally defined in 'compilation'
42+
type ??= compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
43+
44+
// Otherwise, try to get the unique accessible type with this name from a reference
45+
if (type is null)
46+
{
47+
foreach (var module in compilation.Assembly.Modules)
48+
{
49+
foreach (var referencedAssembly in module.ReferencedAssemblySymbols)
50+
{
51+
var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
52+
if (currentType is null)
53+
continue;
54+
55+
switch (currentType.GetResultantVisibility())
56+
{
57+
case SymbolVisibility.Public:
58+
case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
59+
break;
60+
61+
default:
62+
continue;
63+
}
64+
65+
if (type is object)
66+
{
67+
// Multiple visible types with the same metadata name are present
68+
return null;
69+
}
70+
71+
type = currentType;
72+
}
73+
}
74+
}
75+
76+
return type;
77+
}
78+
79+
// copied from https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs
80+
private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
81+
{
82+
// Start by assuming it's visible.
83+
SymbolVisibility visibility = SymbolVisibility.Public;
84+
85+
switch (symbol.Kind)
86+
{
87+
case SymbolKind.Alias:
88+
// Aliases are uber private. They're only visible in the same file that they
89+
// were declared in.
90+
return SymbolVisibility.Private;
91+
92+
case SymbolKind.Parameter:
93+
// Parameters are only as visible as their containing symbol
94+
return GetResultantVisibility(symbol.ContainingSymbol);
95+
96+
case SymbolKind.TypeParameter:
97+
// Type Parameters are private.
98+
return SymbolVisibility.Private;
99+
}
100+
101+
while (symbol != null && symbol.Kind != SymbolKind.Namespace)
102+
{
103+
switch (symbol.DeclaredAccessibility)
104+
{
105+
// If we see anything private, then the symbol is private.
106+
case Accessibility.NotApplicable:
107+
case Accessibility.Private:
108+
return SymbolVisibility.Private;
109+
110+
// If we see anything internal, then knock it down from public to
111+
// internal.
112+
case Accessibility.Internal:
113+
case Accessibility.ProtectedAndInternal:
114+
visibility = SymbolVisibility.Internal;
115+
break;
116+
117+
// For anything else (Public, Protected, ProtectedOrInternal), the
118+
// symbol stays at the level we've gotten so far.
119+
}
120+
121+
symbol = symbol.ContainingSymbol;
122+
}
123+
124+
return visibility;
125+
}
126+
127+
// Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs
128+
#pragma warning disable CA1027 // Mark enums with FlagsAttribute
129+
private enum SymbolVisibility
130+
#pragma warning restore CA1027 // Mark enums with FlagsAttribute
131+
{
132+
Public = 0,
133+
Internal = 1,
134+
Private = 2,
135+
Friend = Internal,
136+
}
137+
}
138+
}

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ private sealed class Parser
3232
private const string JsonNumberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonNumberHandlingAttribute";
3333
private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
3434
private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute";
35+
private const string JsonSerializerContextFullName = "System.Text.Json.Serialization.JsonSerializerContext";
36+
private const string JsonSerializerAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";
37+
private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute";
3538

3639
private readonly Compilation _compilation;
3740
private readonly SourceProductionContext _sourceProductionContext;
@@ -145,9 +148,9 @@ public Parser(Compilation compilation, in SourceProductionContext sourceProducti
145148
public SourceGenerationSpec? GetGenerationSpec(ImmutableArray<ClassDeclarationSyntax> classDeclarationSyntaxList)
146149
{
147150
Compilation compilation = _compilation;
148-
INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext");
149-
INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute");
150-
INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute");
151+
INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerContextFullName);
152+
INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerAttributeFullName);
153+
INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSourceGenerationOptionsAttributeFullName);
151154

152155
if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSourceGenerationOptionsAttributeSymbol == null)
153156
{

src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public MetadataLoadContextInternal(Compilation compilation)
2323

2424
public Type? Resolve(string fullyQualifiedMetadataName)
2525
{
26-
INamedTypeSymbol? typeSymbol = _compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
26+
INamedTypeSymbol? typeSymbol = _compilation.GetBestTypeByMetadataName(fullyQualifiedMetadataName);
2727
return typeSymbol.AsType(this);
2828
}
2929

src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace System.Text.Json.Reflection
99
{
10-
internal static class RoslynExtensions
10+
internal static partial class RoslynExtensions
1111
{
1212
public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContextInternal metadataLoadContext)
1313
{
@@ -67,3 +67,4 @@ public static MethodAttributes GetMethodAttributes(this IMethodSymbol methodSymb
6767
}
6868
}
6969
}
70+

src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0</TargetFrameworks>
44
<CLSCompliant>false</CLSCompliant>
@@ -37,6 +37,7 @@
3737
<Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
3838
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
3939
<Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
40+
<Compile Include="$(CommonPath)\Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
4041
<Compile Include="ClassType.cs" />
4142
<Compile Include="CollectionType.cs" />
4243
<Compile Include="JsonConstants.cs" />

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Collections.Immutable;
6+
using System.IO;
67
using System.Linq;
78
using System.Reflection;
89
using Microsoft.CodeAnalysis;
@@ -467,5 +468,68 @@ private void CheckFieldsPropertiesMethods(Type type, string[] expectedFields, st
467468
}
468469

469470
// TODO: add test guarding against (de)serializing static classes.
471+
472+
[Fact]
473+
public void TestMultipleDefinitions()
474+
{
475+
// Adding a dependency to an assembly that has internal definitions of public types
476+
// should not result in a collision and break generation.
477+
// This verifies the usage of GetBestTypeByMetadataName() instead of GetTypeByMetadataName().
478+
var referencedSource = @"
479+
namespace System.Text.Json.Serialization
480+
{
481+
internal class JsonSerializerContext { }
482+
internal class JsonSerializableAttribute { }
483+
internal class JsonSourceGenerationOptionsAttribute { }
484+
}";
485+
486+
// Compile the referenced assembly first.
487+
Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource);
488+
489+
// Obtain the image of the referenced assembly.
490+
byte[] referencedImage;
491+
using (MemoryStream ms = new MemoryStream())
492+
{
493+
var emitResult = referencedCompilation.Emit(ms);
494+
if (!emitResult.Success)
495+
{
496+
throw new InvalidOperationException();
497+
}
498+
referencedImage = ms.ToArray();
499+
}
500+
501+
// Generate the code
502+
string source = @"
503+
using System.Text.Json.Serialization;
504+
namespace HelloWorld
505+
{
506+
[JsonSerializable(typeof(HelloWorld.MyType))]
507+
internal partial class JsonContext : JsonSerializerContext
508+
{
509+
}
510+
511+
public class MyType
512+
{
513+
public int MyInt { get; set; }
514+
}
515+
}";
516+
517+
MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };
518+
Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences);
519+
JsonSourceGenerator generator = new JsonSourceGenerator();
520+
521+
Compilation newCompilation = CompilationHelper.RunGenerators(
522+
compilation,
523+
out ImmutableArray<Diagnostic> generatorDiags, generator);
524+
525+
// Make sure compilation was successful.
526+
Assert.Empty(generatorDiags.Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error)));
527+
Assert.Empty(newCompilation.GetDiagnostics().Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error)));
528+
529+
// Should find the generated type.
530+
Dictionary<string, Type> types = generator.GetSerializableTypes();
531+
Assert.Equal(1, types.Count);
532+
Assert.Equal("HelloWorld.MyType", types.Keys.First());
533+
}
470534
}
471535
}

0 commit comments

Comments
 (0)