From 816409c3a97a167f449a0384586ed07888dfddee Mon Sep 17 00:00:00 2001 From: Kyung Tak Woo Date: Thu, 23 Jul 2020 18:30:47 -0500 Subject: [PATCH] Json serializer type discovery (#18) * Create JsonSerializableAttribute attribute for type discovery * Include reflection utils and implement basic type discovery * Update unit tests for TypeDiscovery * Use linq for cleaner JsonSerializationSyntaxReceiver * Update license for reflection utils * Update tests and generator to latest model * Add external class unit tests for Type Discovery * Update summary, variable names and other nits * Update csharp coding style applied to vars * Separated Unit Tests into helper classes and minor changes for nullables in Syntax Receiver * Create test cases checking full type wrapper in end to end and change typewrapper to show private methods * Update dicitonary to list for syntax receiver * Separate syntax receiver to new file --- .../JsonSourceGeneratorTests.cs | 80 ++++- ...em.Text.Json.SourceGeneration.Tests.csproj | 2 +- .../JsonSourceGeneratorTests.cs | 165 +++++++++- ...ext.Json.SourceGeneration.UnitTests.csproj | 2 + .../JsonSerializableSyntaxReceiver.cs | 54 ++++ .../JsonSourceGenerator.cs | 151 +++++++-- .../ReflectionUtils/AssemblyWrapper.cs | 54 ++++ .../ReflectionUtils/ConstructorInfoWrapper.cs | 94 ++++++ .../CustomAttributeDataWrapper.cs | 38 +++ .../ReflectionUtils/FieldInfoWrapper.cs | 61 ++++ .../ReflectionUtils/MemberInfoWrapper.cs | 54 ++++ .../ReflectionUtils/MetadataLoadContext.cs | 90 ++++++ .../ReflectionUtils/MethodInfoWrapper.cs | 98 ++++++ .../ReflectionUtils/ParameterInfoWrapper.cs | 34 ++ .../ReflectionUtils/PropertyInfoWrapper.cs | 86 +++++ .../ReflectionUtils/ReflectionExtensions.cs | 33 ++ .../ReflectionUtils/RoslynExtensions.cs | 29 ++ .../ReflectionUtils/TypeWrapper.cs | 296 ++++++++++++++++++ .../System.Text.Json.SourceGeneration.csproj | 13 + .../System.Text.Json/ref/System.Text.Json.cs | 6 + .../src/System.Text.Json.csproj | 3 +- .../Attributes/JsonSerializableAttribute.cs | 29 ++ 22 files changed, 1428 insertions(+), 44 deletions(-) create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/AssemblyWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ConstructorInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/CustomAttributeDataWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/FieldInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MemberInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MetadataLoadContext.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MethodInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ParameterInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/PropertyInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ReflectionExtensions.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/RoslynExtensions.cs create mode 100644 src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/TypeWrapper.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs index 76381debddbb..c8a32bbe05a9 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs @@ -2,16 +2,90 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Text.Json.Serialization; using Xunit; namespace System.Text.Json.SourceGeneration.Tests { - public class JsonSerializerSouceGeneratorTests + public class JsonSerializerSourceGeneratorTests { + [JsonSerializable] + public class SampleInternalTest + { + public char PublicCharField; + private string PrivateStringField; + public int PublicIntPropertyPublic { get; set; } + public int PublicIntPropertyPrivateSet { get; private set; } + public int PublicIntPropertyPrivateGet { private get; set; } + + public SampleInternalTest() + { + PublicCharField = 'a'; + PrivateStringField = "privateStringField"; + } + + public SampleInternalTest(char c, string s) + { + PublicCharField = c; + PrivateStringField = s; + } + + private SampleInternalTest(int i) + { + PublicIntPropertyPublic = i; + } + + private void UseFields() + { + string use = PublicCharField.ToString() + PrivateStringField; + } + } + + [JsonSerializable(typeof(JsonConverterAttribute))] + public class SampleExternalTest { } + [Fact] - public static void TestGeneratedCode() + public void TestGeneratedCode() { - Assert.Equal("Hello", HelloWorldGenerated.HelloWorld.SayHello()); + var internalTypeTest = new HelloWorldGenerated.SampleInternalTestClassInfo(); + var externalTypeTest = new HelloWorldGenerated.SampleExternalTestClassInfo(); + + // Check base class names. + Assert.Equal("SampleInternalTestClassInfo", internalTypeTest.GetClassName()); + Assert.Equal("SampleExternalTestClassInfo", externalTypeTest.GetClassName()); + + // Public and private Ctors are visible. + Assert.Equal(3, internalTypeTest.Ctors.Count); + Assert.Equal(2, externalTypeTest.Ctors.Count); + + // Ctor params along with its types are visible. + Dictionary expectedCtorParamsInternal = new Dictionary { { "c", "Char"}, { "s", "String" }, { "i", "Int32" } }; + Assert.Equal(expectedCtorParamsInternal, internalTypeTest.CtorParams); + + Dictionary expectedCtorParamsExternal = new Dictionary { { "converterType", "Type"} }; + Assert.Equal(expectedCtorParamsExternal, externalTypeTest.CtorParams); + + // Public and private methods are visible. + List expectedMethodsInternal = new List { "get_PublicIntPropertyPublic", "set_PublicIntPropertyPublic", "get_PublicIntPropertyPrivateSet", "set_PublicIntPropertyPrivateSet", "get_PublicIntPropertyPrivateGet", "set_PublicIntPropertyPrivateGet", "UseFields" }; + Assert.Equal(expectedMethodsInternal, internalTypeTest.Methods); + + List expectedMethodsExternal = new List { "get_ConverterType", "CreateConverter" }; + Assert.Equal(expectedMethodsExternal, externalTypeTest.Methods); + + // Public and private fields are visible. + Dictionary expectedFieldsInternal = new Dictionary { { "PublicCharField", "Char" }, { "PrivateStringField", "String" } }; + Assert.Equal(expectedFieldsInternal, internalTypeTest.Fields); + + Dictionary expectedFieldsExternal = new Dictionary { }; + Assert.Equal(expectedFieldsExternal, externalTypeTest.Fields); + + // Public properties are visible. + Dictionary expectedPropertiesInternal = new Dictionary { { "PublicIntPropertyPublic", "Int32" }, { "PublicIntPropertyPrivateSet", "Int32" }, { "PublicIntPropertyPrivateGet", "Int32" } }; + Assert.Equal(expectedPropertiesInternal, internalTypeTest.Properties); + + Dictionary expectedPropertiesExternal = new Dictionary { { "ConverterType", "Type"} }; + Assert.Equal(expectedPropertiesExternal, externalTypeTest.Properties); } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index e760d3494ff9..1fec7debe646 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetFrameworkCurrent) diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs index d76fb67acbad..ae8cfed1dcfe 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs @@ -2,32 +2,181 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.CSharp; using Xunit; namespace System.Text.Json.SourceGeneration.UnitTests { - public static class GeneratorTests + public class GeneratorTests { [Fact] - public static void SourceGeneratorInitializationPass() + public void TypeDiscoveryPrimitivePOCO() { + string source = @" + using System; + using System.Text.Json.Serialization; + + namespace HelloWorld + { + [JsonSerializable] + public class MyType { + public int PublicPropertyInt { get; set; } + public string PublicPropertyString { get; set; } + private int PrivatePropertyInt { get; set; } + private string PrivatePropertyString { get; set; } + + public double PublicDouble; + public char PublicChar; + private double PrivateDouble; + private char PrivateChar; + + public void MyMethod() { } + public void MySecondMethod() { } + } + }"; + + Compilation compilation = CreateCompilation(source); + + JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator(); + + Compilation outCompilation = RunGenerators(compilation, out var generatorDiags, generator); + + // Check base functionality of found types. + Assert.Equal(1, generator.foundTypes.Count); + Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName); + + // Check for received properties in created type. + string[] expectedPropertyNames = { "PublicPropertyInt", "PublicPropertyString", "PrivatePropertyInt", "PrivatePropertyString" }; + string[] receivedPropertyNames = generator.foundTypes["MyType"].GetProperties().Select(property => property.Name).ToArray(); + Assert.Equal(expectedPropertyNames, receivedPropertyNames); + + // Check for fields in created type. + string[] expectedFieldNames = { "PublicDouble", "PublicChar", "PrivateDouble", "PrivateChar" }; + string[] receivedFieldNames = generator.foundTypes["MyType"].GetFields().Select(field => field.Name).ToArray(); + Assert.Equal(expectedFieldNames, receivedFieldNames); + + // Check for methods in created type. + string[] expectedMethodNames = { "get_PublicPropertyInt", "set_PublicPropertyInt", "get_PublicPropertyString", "set_PublicPropertyString", "get_PrivatePropertyInt", "set_PrivatePropertyInt", "get_PrivatePropertyString", "set_PrivatePropertyString", "MyMethod", "MySecondMethod" }; + string[] receivedMethodNames = generator.foundTypes["MyType"].GetMethods().Select(method => method.Name).ToArray(); + Assert.Equal(expectedMethodNames, receivedMethodNames); } [Fact] - public static void SourceGeneratorInitializationFail() + public void TypeDiscoveryPrimitiveTemporaryPOCO() { + string source = @" + using System; + using System.Text.Json.Serialization; + + namespace HelloWorld + { + [JsonSerializable] + public class MyType { + public int PublicPropertyInt { get; set; } + public string PublicPropertyString { get; set; } + private int PrivatePropertyInt { get; set; } + private string PrivatePropertyString { get; set; } + + public double PublicDouble; + public char PublicChar; + private double PrivateDouble; + private char PrivateChar; + + public void MyMethod() { } + public void MySecondMethod() { } + } + + [JsonSerializable(typeof(JsonConverterAttribute))] + public class NotMyType { } + + }"; + + Compilation compilation = CreateCompilation(source); + + JsonSerializerSourceGenerator generator = new JsonSerializerSourceGenerator(); + + Compilation outCompilation = RunGenerators(compilation, out var generatorDiags, generator); + + // Check base functionality of found types. + Assert.Equal(2, generator.foundTypes.Count); + + // Check for MyType. + Assert.Equal("HelloWorld.MyType", generator.foundTypes["MyType"].FullName); + + // Check for received properties in created type. + string[] expectedPropertyNamesMyType = { "PublicPropertyInt", "PublicPropertyString", "PrivatePropertyInt", "PrivatePropertyString" }; + string[] receivedPropertyNamesMyType = generator.foundTypes["MyType"].GetProperties().Select(property => property.Name).ToArray(); + Assert.Equal(expectedPropertyNamesMyType, receivedPropertyNamesMyType); + + // Check for fields in created type. + string[] expectedFieldNamesMyType = { "PublicDouble", "PublicChar", "PrivateDouble", "PrivateChar" }; + string[] receivedFieldNamesMyType = generator.foundTypes["MyType"].GetFields().Select(field => field.Name).ToArray(); + Assert.Equal(expectedFieldNamesMyType, receivedFieldNamesMyType); + + // Check for methods in created type. + string[] expectedMethodNamesMyType = { "get_PublicPropertyInt", "set_PublicPropertyInt", "get_PublicPropertyString", "set_PublicPropertyString", "get_PrivatePropertyInt", "set_PrivatePropertyInt", "get_PrivatePropertyString", "set_PrivatePropertyString", "MyMethod", "MySecondMethod" }; + string[] receivedMethodNamesMyType = generator.foundTypes["MyType"].GetMethods().Select(method => method.Name).ToArray(); + Assert.Equal(expectedMethodNamesMyType, receivedMethodNamesMyType); + + // Check for NotMyType. + Assert.Equal("System.Text.Json.Serialization.JsonConverterAttribute", generator.foundTypes["NotMyType"].FullName); + + // Check for received properties in created type. + string[] expectedPropertyNamesNotMyType = { "ConverterType" }; + string[] receivedPropertyNamesNotMyType = generator.foundTypes["NotMyType"].GetProperties().Select(property => property.Name).ToArray(); + Assert.Equal(expectedPropertyNamesNotMyType, receivedPropertyNamesNotMyType); + + // Check for fields in created type. + string[] expectedFieldNamesNotMyType = { }; + string[] receivedFieldNamesNotMyType = generator.foundTypes["NotMyType"].GetFields().Select(field => field.Name).ToArray(); + Assert.Equal(expectedFieldNamesNotMyType, receivedFieldNamesNotMyType); + + // Check for methods in created type. + string[] expectedMethodNamesNotMyType = { "get_ConverterType", "CreateConverter" }; + string[] receivedMethodNamesNotMyType = generator.foundTypes["NotMyType"].GetMethods().Select(method => method.Name).ToArray(); + Assert.Equal(expectedMethodNamesNotMyType, receivedMethodNamesNotMyType); } - [Fact] - public static void SourceGeneratorExecutionPass() + private Compilation CreateCompilation(string source) { + // Bypass System.Runtime error. + Assembly systemRuntimeAssembly = Assembly.Load("System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + string systemRuntimeAssemblyPath = systemRuntimeAssembly.Location; + + MetadataReference[] references = new MetadataReference[] { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JsonSerializableAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JsonSerializerOptions).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), + MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location), + MetadataReference.CreateFromFile(systemRuntimeAssemblyPath), + }; + + return CSharpCompilation.Create( + "TestAssembly", + syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) }, + references: references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); } - [Fact] - public static void SourceGeneratorExecutionFail() + private GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators) + => new CSharpGeneratorDriver( + new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse), + ImmutableArray.Create(generators), + ImmutableArray.Empty); + + private Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params ISourceGenerator[] generators) { + CreateDriver(compilation, generators).RunFullGeneration(compilation, out Compilation outCompilation, out diagnostics); + return outCompilation; } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj index 6d652493b65f..4dfa011d1375 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs new file mode 100644 index 000000000000..2260a2755dcb --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSerializableSyntaxReceiver.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Text.Json.SourceGeneration +{ + public class JsonSerializableSyntaxReceiver : ISyntaxReceiver + { + public List> ExternalClassTypes = new List>(); + public List> InternalClassTypes = new List>(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + // Look for classes or structs for JsonSerializable Attribute. + if (syntaxNode is ClassDeclarationSyntax || syntaxNode is StructDeclarationSyntax) + { + // Find JsonSerializable Attributes. + IEnumerable? serializableAttributes = null; + AttributeListSyntax attributeList = ((TypeDeclarationSyntax)syntaxNode).AttributeLists.SingleOrDefault(); + if (attributeList != null) + { + serializableAttributes = attributeList.Attributes.Where(node => (node is AttributeSyntax attr && attr.Name.ToString() == "JsonSerializable")).Cast(); + } + + if (serializableAttributes?.Any() == true) + { + // JsonSerializableAttribute has AllowMultiple as False, should only have 1 attribute. + Debug.Assert(serializableAttributes.Count() == 1); + AttributeSyntax attributeNode = serializableAttributes.First(); + + // Check if the attribute is being passed a type. + if (attributeNode.DescendantNodes().Where(node => node is TypeOfExpressionSyntax).Any()) + { + // Get JsonSerializable attribute arguments. + AttributeArgumentSyntax attributeArgumentNode = (AttributeArgumentSyntax)attributeNode.DescendantNodes().Where(node => node is AttributeArgumentSyntax).SingleOrDefault(); + // Get external class token from arguments. + IdentifierNameSyntax externalTypeNode = (IdentifierNameSyntax)attributeArgumentNode?.DescendantNodes().Where(node => node is IdentifierNameSyntax).SingleOrDefault(); + ExternalClassTypes.Add(new KeyValuePair(((TypeDeclarationSyntax)syntaxNode).Identifier.Text, externalTypeNode)); + } + else + { + InternalClassTypes.Add(new KeyValuePair(((TypeDeclarationSyntax)syntaxNode).Identifier.Text, (TypeDeclarationSyntax)syntaxNode)); + } + } + } + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs index 8df499db2791..828f9e16595f 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/JsonSourceGenerator.cs @@ -2,10 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; +using Microsoft.CodeAnalysis.Text; namespace System.Text.Json.SourceGeneration { @@ -16,47 +19,133 @@ namespace System.Text.Json.SourceGeneration [Generator] public class JsonSerializerSourceGenerator : ISourceGenerator { + public Dictionary foundTypes = new Dictionary(); + public void Execute(SourceGeneratorContext context) { - // Foreach type found, call code generator. - StringBuilder sourceBuilder = new StringBuilder(@" -using System; -namespace HelloWorldGenerated -{ - public static class HelloWorld - { - public static string SayHello() - { - return ""Hello""; -"); + JsonSerializableSyntaxReceiver receiver = (JsonSerializableSyntaxReceiver)context.SyntaxReceiver; - sourceBuilder.Append(@" - } - } -}"); + MetadataLoadContext metadataLoadContext = new MetadataLoadContext(context.Compilation); - context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); - } + INamedTypeSymbol namedTypeSymbol; + ITypeSymbol typeSymbol; + IdentifierNameSyntax identifierNameNode; + SemanticModel semanticModel; + Type convertedType; + TypeDeclarationSyntax typeDeclarationNode; - public void Initialize(InitializationContext context) - { - context.RegisterForSyntaxNotifications(() => new JsonSerializableSyntaxReceiver()); - } + // Map type name to type objects. + foreach (KeyValuePair entry in receiver.InternalClassTypes) + { + typeDeclarationNode = entry.Value; + semanticModel = context.Compilation.GetSemanticModel(typeDeclarationNode.SyntaxTree); + namedTypeSymbol = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(typeDeclarationNode); + convertedType = new TypeWrapper(namedTypeSymbol, metadataLoadContext); + foundTypes[entry.Key] = convertedType; + } - // Temporary function for now that reads all types. Should search types with attribute JsonSerializable. - internal class JsonSerializableSyntaxReceiver : ISyntaxReceiver - { - public List GeneratorInputTypes = new List(); + foreach (KeyValuePair entry in receiver.ExternalClassTypes) + { + identifierNameNode = entry.Value; + semanticModel = context.Compilation.GetSemanticModel(identifierNameNode.SyntaxTree); + typeSymbol = context.Compilation.GetSemanticModel(identifierNameNode.SyntaxTree).GetTypeInfo(identifierNameNode).ConvertedType; + convertedType = new TypeWrapper(typeSymbol, metadataLoadContext); + foundTypes[entry.Key] = convertedType; + } + + // Create sources for all found types. + StringBuilder member = new StringBuilder(); + string foundMethods, foundFields, foundProperties, foundCtorParams, foundCtors; - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + foreach (KeyValuePair entry in foundTypes) { - // Get all the type decl in all syntax tree. - if (syntaxNode is TypeDeclarationSyntax tds) + foreach(MethodInfo method in entry.Value.GetMethods()) + { + member.Append(@$"""{method.Name}"", "); + } + foundMethods = member.ToString(); + member.Clear(); + + foreach(FieldInfo field in entry.Value.GetFields()) { - GeneratorInputTypes.Add(tds); + member.Append(@$"{{""{field.Name}"", ""{field.FieldType.Name}""}}, "); } + foundFields = member.ToString(); + member.Clear(); + + foreach(PropertyInfo property in entry.Value.GetProperties()) + { + member.Append(@$"{{""{property.Name}"", ""{property.PropertyType.Name}""}}, "); + } + foundProperties = member.ToString(); + member.Clear(); + + foreach(ConstructorInfo ctor in entry.Value.GetConstructors()) + { + foreach(ParameterInfo param in ctor.GetParameters()) + { + member.Append(@$"{{""{param.Name}"", ""{param.ParameterType.Name}""}}, "); + } + } + foundCtorParams = member.ToString(); + member.Clear(); + + foreach(ConstructorInfo ctor in entry.Value.GetConstructors()) + { + member.Append($@"""{ctor.Name}"", "); + } + foundCtors = member.ToString(); + member.Clear(); + + context.AddSource($"{entry.Key}ClassInfo", SourceText.From($@" +using System; +using System.Collections.Generic; + +namespace HelloWorldGenerated +{{ + public class {entry.Key}ClassInfo + {{ + public {entry.Key}ClassInfo() {{ }} + + private List ClassCtors = new List() + {{ {foundCtors} }}; + private Dictionary ClassCtorParams = new Dictionary() + {{ {foundCtorParams} }}; + private List ClassMethods = new List() + {{ {foundMethods} }}; + private Dictionary ClassFields = new Dictionary() + {{ {foundFields} }}; + private Dictionary ClassProperties = new Dictionary() + {{ {foundProperties} }}; + + public string GetClassName() + {{ + return ""{entry.Key}ClassInfo""; + }} + + public List Ctors + {{ get {{ return ClassCtors; }} }} + + public Dictionary CtorParams + {{ get {{ return ClassCtorParams; }} }} + + public List Methods + {{ get {{ return ClassMethods; }} }} + + public Dictionary Fields + {{ get {{ return ClassFields; }} }} + + public Dictionary Properties + {{ get {{ return ClassProperties; }} }} + }} +}} +", Encoding.UTF8)); } } + public void Initialize(InitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new JsonSerializableSyntaxReceiver()); + } } } diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/AssemblyWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/AssemblyWrapper.cs new file mode 100644 index 000000000000..48e6d518aedf --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/AssemblyWrapper.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class AssemblyWrapper : Assembly + { + private readonly MetadataLoadContext _metadataLoadContext; + + public AssemblyWrapper(IAssemblySymbol assembly, MetadataLoadContext metadataLoadContext) + { + Symbol = assembly; + _metadataLoadContext = metadataLoadContext; + } + + internal IAssemblySymbol Symbol { get; } + + public override Type[] GetExportedTypes() + { + return GetTypes(); + } + + public override Type[] GetTypes() + { + var types = new List(); + var stack = new Stack(); + stack.Push(Symbol.GlobalNamespace); + while (stack.Count > 0) + { + INamespaceSymbol current = stack.Pop(); + + foreach (INamedTypeSymbol type in current.GetTypeMembers()) + { + types.Add(type.AsType(_metadataLoadContext)); + } + + foreach (INamespaceSymbol ns in current.GetNamespaceMembers()) + { + stack.Push(ns); + } + } + return types.ToArray(); + } + + public override Type GetType(string name) + { + return Symbol.GetTypeByMetadataName(name)!.AsType(_metadataLoadContext); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ConstructorInfoWrapper.cs new file mode 100644 index 000000000000..6bd54cbda080 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ConstructorInfoWrapper.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class ConstructorInfoWrapper : ConstructorInfo + { + private readonly IMethodSymbol _ctor; + private readonly MetadataLoadContext _metadataLoadContext; + + public ConstructorInfoWrapper(IMethodSymbol ctor, MetadataLoadContext metadataLoadContext) + { + _ctor = ctor; + _metadataLoadContext = metadataLoadContext; + } + + public override Type DeclaringType => _ctor.ContainingType.AsType(_metadataLoadContext); + + public override MethodAttributes Attributes => throw new NotImplementedException(); + + public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); + + public override string Name => _ctor.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override bool IsGenericMethod => _ctor.IsGenericMethod; + + public override Type[] GetGenericArguments() + { + var typeArguments = new List(); + foreach (ITypeSymbol t in _ctor.TypeArguments) + { + typeArguments.Add(t.AsType(_metadataLoadContext)); + } + return typeArguments.ToArray(); + } + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _ctor.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override MethodImplAttributes GetMethodImplementationFlags() + { + throw new NotImplementedException(); + } + + public override ParameterInfo[] GetParameters() + { + var parameters = new List(); + foreach (IParameterSymbol p in _ctor.Parameters) + { + parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext)); + } + return parameters.ToArray(); + } + + public override object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/CustomAttributeDataWrapper.cs new file mode 100644 index 000000000000..65e1324d1e64 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/CustomAttributeDataWrapper.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class CustomAttributeDataWrapper : CustomAttributeData + { + public CustomAttributeDataWrapper(AttributeData a, MetadataLoadContext metadataLoadContext) + { + var namedArguments = new List(); + foreach (KeyValuePair na in a.NamedArguments) + { + var member = a.AttributeClass!.GetMembers(na.Key).First(); + namedArguments.Add(new CustomAttributeNamedArgument(new MemberInfoWrapper(member, metadataLoadContext), na.Value.Value)); + } + + var constructorArguments = new List(); + foreach (TypedConstant ca in a.ConstructorArguments) + { + constructorArguments.Add(new CustomAttributeTypedArgument(ca.Type.AsType(metadataLoadContext), ca.Value)); + } + Constructor = new ConstructorInfoWrapper(a.AttributeConstructor!, metadataLoadContext); + NamedArguments = namedArguments; + ConstructorArguments = constructorArguments; + } + + public override ConstructorInfo Constructor { get; } + + public override IList NamedArguments { get; } + + public override IList ConstructorArguments { get; } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/FieldInfoWrapper.cs new file mode 100644 index 000000000000..aa44a1d97b39 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/FieldInfoWrapper.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using System.Text; +using System.Reflection; +using System.Globalization; + +namespace System.Reflection +{ + class FieldInfoWrapper : FieldInfo + { + private readonly IFieldSymbol _field; + private readonly MetadataLoadContext _metadataLoadContext; + public FieldInfoWrapper(IFieldSymbol parameter, MetadataLoadContext metadataLoadContext) + { + _field = parameter; + _metadataLoadContext = metadataLoadContext; + } + + public override FieldAttributes Attributes => throw new NotImplementedException(); + + public override RuntimeFieldHandle FieldHandle => throw new NotImplementedException(); + + public override Type FieldType => _field.Type.AsType(_metadataLoadContext); + + public override Type DeclaringType => throw new NotImplementedException(); + + public override string Name => _field.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotImplementedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + public override object GetValue(object obj) + { + throw new NotImplementedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MemberInfoWrapper.cs new file mode 100644 index 000000000000..28398bc64a2d --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MemberInfoWrapper.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class MemberInfoWrapper : MemberInfo + { + private readonly ISymbol _member; + private readonly MetadataLoadContext _metadataLoadContext; + + public MemberInfoWrapper(ISymbol member, MetadataLoadContext metadataLoadContext) + { + _member = member; + _metadataLoadContext = metadataLoadContext; + } + + public override Type DeclaringType => _member.ContainingType.AsType(_metadataLoadContext); + + public override MemberTypes MemberType => throw new NotImplementedException(); + + public override string Name => _member.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _member.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MetadataLoadContext.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MetadataLoadContext.cs new file mode 100644 index 000000000000..5b7ecbe6f784 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MetadataLoadContext.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + public class MetadataLoadContext + { + private readonly Dictionary _assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Compilation _compilation; + + public MetadataLoadContext(Compilation compilation) + { + _compilation = compilation; + Dictionary assemblies = compilation.References + .OfType() + .ToDictionary(r => AssemblyName.GetAssemblyName(r.FilePath), + r => (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(r)!); + + foreach (var item in assemblies) + { + // REVIEW: We need to figure out full framework + // _assemblies[item.Key.FullName] = item.Value; + _assemblies[item.Key.Name] = item.Value!; + } + + CoreAssembly = new AssemblyWrapper(compilation.GetTypeByMetadataName("System.Object")!.ContainingAssembly, this); + MainAssembly = new AssemblyWrapper(compilation.Assembly, this); + } + + public Type Resolve() => Resolve(typeof(T)); + + public Type Resolve(Type type) + { + string asmName = type.Assembly.GetName().Name; + + IAssemblySymbol assemblySymbol; + + if (asmName == "System.Private.CoreLib" || asmName == "mscorlib" || asmName == "System.Runtime") + { + assemblySymbol = CoreAssembly.Symbol; + } + else + { + var typeForwardedFrom = type.GetCustomAttributeData(typeof(TypeForwardedFromAttribute)); + + if (typeForwardedFrom != null) + { + asmName = typeForwardedFrom.GetConstructorArgument(0); + } + + if (!_assemblies.TryGetValue(new AssemblyName(asmName).Name, out assemblySymbol)) + { + return null!; + } + } + + if (type.IsArray) + { + var typeSymbol = assemblySymbol.GetTypeByMetadataName(type.GetElementType().FullName); + if (typeSymbol == null) + { + return null!; + } + + return _compilation.CreateArrayTypeSymbol(typeSymbol).AsType(this); + } + + // Resolve the full name + return assemblySymbol.GetTypeByMetadataName(type.FullName)!.AsType(this); + } + + private AssemblyWrapper CoreAssembly { get; } + public Assembly MainAssembly { get; } + + internal Assembly LoadFromAssemblyName(string fullName) + { + if (_assemblies.TryGetValue(new AssemblyName(fullName).Name, out var assembly)) + { + return new AssemblyWrapper(assembly, this); + } + return null!; + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MethodInfoWrapper.cs new file mode 100644 index 000000000000..cdc5c3dd6c78 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/MethodInfoWrapper.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class MethodInfoWrapper : MethodInfo + { + private readonly IMethodSymbol _method; + private readonly MetadataLoadContext _metadataLoadContext; + + public MethodInfoWrapper(IMethodSymbol method, MetadataLoadContext metadataLoadContext) + { + _method = method; + _metadataLoadContext = metadataLoadContext; + } + + public override ICustomAttributeProvider ReturnTypeCustomAttributes => throw new NotImplementedException(); + + public override MethodAttributes Attributes => throw new NotImplementedException(); + + public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); + + public override Type DeclaringType => _method.ContainingType.AsType(_metadataLoadContext); + + public override Type ReturnType => _method.ReturnType.AsType(_metadataLoadContext); + + public override string Name => _method.Name; + + public override bool IsGenericMethod => _method.IsGenericMethod; + + public override Type ReflectedType => throw new NotImplementedException(); + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _method.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override MethodInfo GetBaseDefinition() + { + throw new NotImplementedException(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override Type[] GetGenericArguments() + { + var typeArguments = new List(); + foreach (ITypeSymbol t in _method.TypeArguments) + { + typeArguments.Add(t.AsType(_metadataLoadContext)); + } + return typeArguments.ToArray(); + } + + public override MethodImplAttributes GetMethodImplementationFlags() + { + throw new NotImplementedException(); + } + + public override ParameterInfo[] GetParameters() + { + var parameters = new List(); + foreach (IParameterSymbol p in _method.Parameters) + { + parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext)); + } + return parameters.ToArray(); + } + + public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ParameterInfoWrapper.cs new file mode 100644 index 000000000000..343df7ddbcf0 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ParameterInfoWrapper.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + public class ParameterInfoWrapper : ParameterInfo + { + private readonly IParameterSymbol _parameter; + private readonly MetadataLoadContext _metadataLoadContext; + + public ParameterInfoWrapper(IParameterSymbol parameter, MetadataLoadContext metadataLoadContext) + { + _parameter = parameter; + _metadataLoadContext = metadataLoadContext; + } + + public override Type ParameterType => _parameter.Type.AsType(_metadataLoadContext); + public override string Name => _parameter.Name; + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _parameter.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/PropertyInfoWrapper.cs new file mode 100644 index 000000000000..f0bfde948180 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/PropertyInfoWrapper.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class PropertyWrapper : PropertyInfo + { + private readonly IPropertySymbol _property; + private MetadataLoadContext _metadataLoadContext; + + public PropertyWrapper(IPropertySymbol property, MetadataLoadContext metadataLoadContext) + { + _property = property; + _metadataLoadContext = metadataLoadContext; + } + + public override PropertyAttributes Attributes => throw new NotImplementedException(); + + public override bool CanRead => _property.GetMethod != null; + + public override bool CanWrite => _property.SetMethod != null; + + public override Type PropertyType => _property.Type.AsType(_metadataLoadContext); + + public override Type DeclaringType => _property.ContainingType.AsType(_metadataLoadContext); + + public override string Name => _property.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override MethodInfo[] GetAccessors(bool nonPublic) + { + throw new NotImplementedException(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override MethodInfo GetGetMethod(bool nonPublic) + { + return _property.GetMethod!.AsMethodInfo(_metadataLoadContext); + } + + public override ParameterInfo[] GetIndexParameters() + { + var parameters = new List(); + foreach (IParameterSymbol p in _property.Parameters) + { + parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext)); + } + return parameters.ToArray(); + } + + public override MethodInfo GetSetMethod(bool nonPublic) + { + return _property.SetMethod!.AsMethodInfo(_metadataLoadContext); + } + + public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ReflectionExtensions.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ReflectionExtensions.cs new file mode 100644 index 000000000000..8cd153595b36 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/ReflectionExtensions.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Reflection; + +namespace System.Reflection +{ + internal static class ReflectionExtensions + { + public static CustomAttributeData GetCustomAttributeData(this MemberInfo memberInfo, Type type) + { + return memberInfo.CustomAttributes.FirstOrDefault(a => type.IsAssignableFrom(a.AttributeType)); + } + + public static CustomAttributeData GetCustomAttributeData(this ParameterInfo paramterInfo, Type type) + { + return paramterInfo.CustomAttributes.FirstOrDefault(a => type.IsAssignableFrom(a.AttributeType)); + } + + public static TValue GetConstructorArgument(this CustomAttributeData customAttributeData, int index) + { + return index < customAttributeData.ConstructorArguments.Count ? (TValue)customAttributeData.ConstructorArguments[index].Value! : default!; + } + + public static TValue GetNamedArgument(this CustomAttributeData customAttributeData, string name) + { + return (TValue)customAttributeData.NamedArguments.FirstOrDefault(a => a.MemberName == name).TypedValue.Value; + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/RoslynExtensions.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/RoslynExtensions.cs new file mode 100644 index 000000000000..72800e71dd55 --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/RoslynExtensions.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + public static class RoslynExtensions + { + public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContext metadataLoadContext) => (typeSymbol == null ? null : new TypeWrapper(typeSymbol, metadataLoadContext))!; + + public static ParameterInfo AsParameterInfo(this IParameterSymbol parameterSymbol, MetadataLoadContext metadataLoadContext) => (parameterSymbol == null ? null : new ParameterInfoWrapper(parameterSymbol, metadataLoadContext))!; + + public static MethodInfo AsMethodInfo(this IMethodSymbol methodSymbol, MetadataLoadContext metadataLoadContext) => (methodSymbol == null ? null : new MethodInfoWrapper(methodSymbol, metadataLoadContext))!; + + + public static IEnumerable BaseTypes(this INamedTypeSymbol typeSymbol) + { + var t = typeSymbol; + while (t != null) + { + yield return t; + t = t.BaseType; + } + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/TypeWrapper.cs b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/TypeWrapper.cs new file mode 100644 index 000000000000..e7959fcadd6f --- /dev/null +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/ReflectionUtils/TypeWrapper.cs @@ -0,0 +1,296 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class TypeWrapper : Type + { + private readonly ITypeSymbol _typeSymbol; + private readonly MetadataLoadContext _metadataLoadContext; + + public TypeWrapper(ITypeSymbol namedTypeSymbol, MetadataLoadContext metadataLoadContext) + { + _typeSymbol = namedTypeSymbol; + _metadataLoadContext = metadataLoadContext; + } + + public override Assembly Assembly => new AssemblyWrapper(_typeSymbol.ContainingAssembly, _metadataLoadContext); + + public override string AssemblyQualifiedName => throw new NotImplementedException(); + + public override Type BaseType => _typeSymbol.BaseType!.AsType(_metadataLoadContext); + + public override string FullName => Namespace == null ? Name : Namespace + "." + Name; + + public override Guid GUID => Guid.Empty; + + public override Module Module => throw new NotImplementedException(); + + public override string Namespace => _typeSymbol.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining))!; + + public override Type UnderlyingSystemType => this; + + public override string Name => _typeSymbol.MetadataName; + + public override bool IsGenericType => NamedTypeSymbol?.IsGenericType == true; + + private INamedTypeSymbol NamedTypeSymbol => (_typeSymbol as INamedTypeSymbol)!; + + private IArrayTypeSymbol ArrayTypeSymbol => (_typeSymbol as IArrayTypeSymbol)!; + + public override bool IsGenericTypeDefinition => base.IsGenericTypeDefinition; + + public override Type[] GetGenericArguments() + { + var args = new List(); + foreach (ITypeSymbol item in NamedTypeSymbol.TypeArguments) + { + args.Add(item.AsType(_metadataLoadContext)); + } + return args.ToArray(); + } + + public override Type GetGenericTypeDefinition() + { + return NamedTypeSymbol.ConstructedFrom.AsType(_metadataLoadContext); + } + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _typeSymbol.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + { + var ctors = new List(); + foreach (IMethodSymbol c in NamedTypeSymbol.Constructors) + { + ctors.Add(new ConstructorInfoWrapper(c, _metadataLoadContext)); + } + return ctors.ToArray(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override Type GetElementType() + { + return ArrayTypeSymbol?.ElementType.AsType(_metadataLoadContext)!; + } + + public override EventInfo GetEvent(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override FieldInfo GetField(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + { + var fields = new List(); + foreach (ISymbol item in _typeSymbol.GetMembers()) + { + // Associated Symbol checks the field is not a backingfield. + if (item is IFieldSymbol field && field.AssociatedSymbol == null) + { + fields.Add(new FieldInfoWrapper(field, _metadataLoadContext)); + } + } + return fields.ToArray(); + } + + public override Type GetInterface(string name, bool ignoreCase) + { + throw new NotImplementedException(); + } + + public override Type[] GetInterfaces() + { + var interfaces = new List(); + foreach (INamedTypeSymbol i in _typeSymbol.Interfaces) + { + interfaces.Add(i.AsType(_metadataLoadContext)); + } + return interfaces.ToArray(); + } + + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + { + var methods = new List(); + foreach (ISymbol m in _typeSymbol.GetMembers()) + { + // TODO: Efficiency + if (m is IMethodSymbol method && !NamedTypeSymbol.Constructors.Contains(method)) + { + methods.Add(method.AsMethodInfo(_metadataLoadContext)); + } + } + return methods.ToArray(); + } + + public override Type GetNestedType(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + { + var nestedTypes = new List(); + foreach (INamedTypeSymbol type in _typeSymbol.GetTypeMembers()) + { + nestedTypes.Add(type.AsType(_metadataLoadContext)); + } + return nestedTypes.ToArray(); + } + + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + { + var properties = new List(); + foreach (ISymbol item in _typeSymbol.GetMembers()) + { + if (item is IPropertySymbol property) + { + properties.Add(new PropertyWrapper(property, _metadataLoadContext)); + } + } + return properties.ToArray(); + } + + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + protected override TypeAttributes GetAttributeFlagsImpl() + { + throw new NotImplementedException(); + } + + protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + { + throw new NotImplementedException(); + } + + protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + { + throw new NotImplementedException(); + } + + protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) + { + throw new NotImplementedException(); + } + + protected override bool HasElementTypeImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsArrayImpl() + { + return ArrayTypeSymbol != null; + } + + protected override bool IsByRefImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsCOMObjectImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsPointerImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsPrimitiveImpl() + { + throw new NotImplementedException(); + } + + public override bool IsAssignableFrom(Type c) + { + if (c is TypeWrapper tr) + { + return tr._typeSymbol.AllInterfaces.Contains(_typeSymbol) || (tr.NamedTypeSymbol != null && tr.NamedTypeSymbol.BaseTypes().Contains(_typeSymbol)); + } + else if (_metadataLoadContext.Resolve(c) is TypeWrapper trr) + { + return trr._typeSymbol.AllInterfaces.Contains(_typeSymbol) || (trr.NamedTypeSymbol != null && trr.NamedTypeSymbol.BaseTypes().Contains(_typeSymbol)); + } + return false; + } + + public override int GetHashCode() + { + return _typeSymbol.GetHashCode(); + } + + public override bool Equals(object o) + { + if (o is TypeWrapper tw) + { + return _typeSymbol.Equals(tw._typeSymbol, SymbolEqualityComparer.Default); + } + else if (o is Type t && _metadataLoadContext.Resolve(t) is TypeWrapper tww) + { + return _typeSymbol.Equals(tww._typeSymbol, SymbolEqualityComparer.Default); + } + + return base.Equals(o); + } + + public override bool Equals(Type o) + { + if (o is TypeWrapper tw) + { + return _typeSymbol.Equals(tw._typeSymbol, SymbolEqualityComparer.Default); + } + else if (_metadataLoadContext.Resolve(o) is TypeWrapper tww) + { + return _typeSymbol.Equals(tww._typeSymbol, SymbolEqualityComparer.Default); + } + return base.Equals(o); + } + } +} diff --git a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.csproj index d48dde708aeb..da4f4b5f3f33 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/System.Text.Json.SourceGeneration/System.Text.Json.SourceGeneration.csproj @@ -18,7 +18,20 @@ + + + + + + + + + + + + + diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 0de194ebde5b..d91ea3ba056d 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -575,6 +575,12 @@ public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Seriali public JsonPropertyNameAttribute(string name) { } public string Name { get { throw null; } } } + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = false)] + public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute + { + public JsonSerializableAttribute() { } + public JsonSerializableAttribute(System.Type type) { } + } public sealed partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory { public JsonStringEnumConverter() { } diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 64d2b0562b0e..ea41e778182a 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -1,4 +1,4 @@ - + true $(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461;$(NetFrameworkCurrent) @@ -62,6 +62,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs new file mode 100644 index 000000000000..3bd042668dc1 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization +{ + /// + /// When placed on a type and the System.Text.Json.SourceGeneration generator is enabled, the generator will + /// attempt to generate source code to help optimize the start-up and throughput performance when serializing and + /// deserializing instances of the specified type and types in its object graph. + /// + /// + /// Must take into account that type discovery using this attribute is at compile time. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)] + public sealed class JsonSerializableAttribute : JsonAttribute + { + /// + /// Initializes a new instance of . + /// + public JsonSerializableAttribute() { } + + /// + /// Initializes a new instance of with the specified type. + /// + /// The Type of the property. + public JsonSerializableAttribute(Type type) { } + } +}