From c562920f71f096d6b7e89b284f28aab586d68530 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 31 Aug 2021 12:02:59 -0500 Subject: [PATCH 1/2] Support duplicated type names with src gen --- .../gen/JsonSourceGenerator.Parser.cs | 9 +- .../Reflection/MetadataLoadContextInternal.cs | 2 +- .../gen/Reflection/RoslynExtensions.cs | 129 ++++++++++++++++++ .../JsonSourceGeneratorTests.cs | 64 +++++++++ 4 files changed, 200 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index fecdb3792b8c19..2c5f3ebf0fced5 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -32,6 +32,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; @@ -145,9 +148,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..b231cff4687064 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs @@ -65,5 +65,134 @@ public static MethodAttributes GetMethodAttributes(this IMethodSymbol methodSymb return attributes; } + + // Copied from: https://github.com/roslyn/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/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()); + } } } From 0a1c1e08ec4992efad2d90ab7a70e6bac833346a Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 2 Sep 2021 15:57:40 -0500 Subject: [PATCH 2/2] Move helpers to common location --- .../src/Roslyn/GetBestTypeByMetadataName.cs | 138 ++++++++++++++++++ .../gen/Reflection/RoslynExtensions.cs | 130 +---------------- .../System.Text.Json.SourceGeneration.csproj | 3 +- 3 files changed, 141 insertions(+), 130 deletions(-) create mode 100644 src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs 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/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs index b231cff4687064..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) { @@ -65,134 +65,6 @@ public static MethodAttributes GetMethodAttributes(this IMethodSymbol methodSymb return attributes; } - - // Copied from: https://github.com/roslyn/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/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 @@ +