From 139826b2b0ed6072fc9a64bb228b0684c1fd1e75 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 20 Jun 2023 18:43:29 +0100 Subject: [PATCH] Fix a number of source generator bugs. (#87796) * Fix a number of source generator bugs. * Extract type attribute processing to a standalone method. --- .../gen/Helpers/RoslynExtensions.cs | 11 + ...onSourceGenerator.DiagnosticDescriptors.cs | 8 + .../gen/JsonSourceGenerator.Parser.cs | 322 +++++++----------- .../System.Text.Json/gen/Model/ClassType.cs | 4 +- .../gen/Resources/Strings.resx | 6 + .../gen/Resources/xlf/Strings.cs.xlf | 10 + .../gen/Resources/xlf/Strings.de.xlf | 10 + .../gen/Resources/xlf/Strings.es.xlf | 10 + .../gen/Resources/xlf/Strings.fr.xlf | 10 + .../gen/Resources/xlf/Strings.it.xlf | 10 + .../gen/Resources/xlf/Strings.ja.xlf | 10 + .../gen/Resources/xlf/Strings.ko.xlf | 10 + .../gen/Resources/xlf/Strings.pl.xlf | 10 + .../gen/Resources/xlf/Strings.pt-BR.xlf | 10 + .../gen/Resources/xlf/Strings.ru.xlf | 10 + .../gen/Resources/xlf/Strings.tr.xlf | 10 + .../gen/Resources/xlf/Strings.zh-Hans.xlf | 10 + .../gen/Resources/xlf/Strings.zh-Hant.xlf | 10 + .../JsonSerializerContextTests.cs | 12 + ...m.Text.Json.SourceGeneration.Tests.targets | 3 +- .../CompilationHelper.cs | 96 +++++- .../JsonSourceGeneratorDiagnosticsTests.cs | 146 +++++--- 22 files changed, 489 insertions(+), 249 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs index a05c577af3206..6dbfe0ca5fe8f 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs @@ -26,6 +26,12 @@ internal static class RoslynExtensions public static Location? GetDiagnosticLocation(this ISymbol typeSymbol) => typeSymbol.Locations.Length > 0 ? typeSymbol.Locations[0] : null; + public static Location? GetDiagnosticLocation(this AttributeData attributeData) + { + SyntaxReference? reference = attributeData.ApplicationSyntaxReference; + return reference?.SyntaxTree.GetLocation(reference.Span); + } + /// /// Creates a copy of the Location instance that does not capture a reference to Compilation. /// @@ -60,6 +66,11 @@ public static ITypeSymbol EraseCompileTimeMetadata(this Compilation compilation, } else if (namedType.IsGenericType) { + if (namedType.IsUnboundGenericType) + { + return namedType; + } + ImmutableArray typeArguments = namedType.TypeArguments; INamedTypeSymbol? containingType = namedType.ContainingType; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs index bd0128b36dceb..fb17dadbdee71 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -80,6 +80,14 @@ internal static class DiagnosticDescriptors category: JsonConstants.SystemTextJsonSourceGenerationName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonConverterAttributeInvalidType { get; } = new DiagnosticDescriptor( + id: "SYSLIB1041", + title: new LocalizableResourceString(nameof(SR.JsonConverterAttributeInvalidTypeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.JsonConverterAttributeInvalidTypeMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 806204f747841..29c20bc47a60f 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -86,10 +86,11 @@ public Parser(KnownTypeSymbols knownSymbols) return null; } + INamedTypeSymbol? contextTypeSymbol = semanticModel.GetDeclaredSymbol(contextClassDeclaration, cancellationToken); + Debug.Assert(contextTypeSymbol != null); + if (!TryParseJsonSerializerContextAttributes( - contextClassDeclaration, - semanticModel, - cancellationToken, + contextTypeSymbol, out List? rootSerializableTypes, out JsonSourceGenerationOptionsAttribute? options)) { @@ -103,14 +104,11 @@ public Parser(KnownTypeSymbols knownSymbols) return null; } - INamedTypeSymbol? contextTypeSymbol = semanticModel.GetDeclaredSymbol(contextClassDeclaration, cancellationToken); - Debug.Assert(contextTypeSymbol != null); - Location contextLocation = contextClassDeclaration.GetLocation(); if (!TryGetClassDeclarationList(contextTypeSymbol, out List? classDeclarationList)) { // Class or one of its containing types is not partial so we can't add to it. - ReportDiagnostic(DiagnosticDescriptors.ContextClassesMustBePartial, contextLocation, new string[] { contextTypeSymbol.Name }); + ReportDiagnostic(DiagnosticDescriptors.ContextClassesMustBePartial, contextLocation, contextTypeSymbol.Name); return null; } @@ -285,52 +283,24 @@ private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode? generati return new TypeRef(type); } - private static JsonSourceGenerationMode? GetJsonSourceGenerationModeEnumVal(SyntaxNode propertyValueMode) - { - IEnumerable enumTokens = propertyValueMode - .DescendantTokens() - .Where(token => IsValidEnumIdentifier(token.ValueText)) - .Select(token => token.ValueText); - string enumAsStr = string.Join(",", enumTokens); - - if (Enum.TryParse(enumAsStr, out JsonSourceGenerationMode value)) - { - return value; - } - - return null; - - static bool IsValidEnumIdentifier(string token) => token != nameof(JsonSourceGenerationMode) && token != "." && token != "|"; - } - private bool TryParseJsonSerializerContextAttributes( - ClassDeclarationSyntax classDeclarationSyntax, - SemanticModel semanticModel, - CancellationToken cancellationToken, + ITypeSymbol contextClassSymbol, out List? rootSerializableTypes, out JsonSourceGenerationOptionsAttribute? options) { Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null); Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null); - bool foundSourceGenAttributes = false; rootSerializableTypes = null; options = null; - foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) + foreach (AttributeData attributeData in contextClassSymbol.GetAttributes()) { - AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); - if (semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol is not IMethodSymbol attributeSymbol) - { - continue; - } - - INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; + INamedTypeSymbol? attributeClass = attributeData.AttributeClass; - if (_knownSymbols.JsonSerializableAttributeType.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) + if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSerializableAttributeType)) { - foundSourceGenAttributes = true; - TypeToGenerate? typeToGenerate = ParseJsonSerializableAttribute(semanticModel, attributeSyntax, cancellationToken); + TypeToGenerate? typeToGenerate = ParseJsonSerializableAttribute(attributeData); if (typeToGenerate is null) { continue; @@ -338,91 +308,51 @@ private bool TryParseJsonSerializerContextAttributes( (rootSerializableTypes ??= new()).Add(typeToGenerate.Value); } - else if (_knownSymbols.JsonSourceGenerationOptionsAttributeType.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) + else if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSourceGenerationOptionsAttributeType)) { - foundSourceGenAttributes = true; - options = ParseJsonSourceGenerationOptionsAttribute(attributeSyntax); + options = ParseJsonSourceGenerationOptionsAttribute(attributeData); } } - return foundSourceGenAttributes; + return rootSerializableTypes != null || options != null; } - private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOptionsAttribute(AttributeSyntax attributeSyntax) + private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOptionsAttribute(AttributeData attributeData) { - IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); - JsonSourceGenerationOptionsAttribute options = new(); - foreach (AttributeArgumentSyntax node in attributeArguments) + foreach (KeyValuePair namedArg in attributeData.NamedArguments) { - IEnumerable childNodes = node.ChildNodes(); - - NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; - Debug.Assert(propertyNameNode != null); - - SyntaxNode propertyValueNode = childNodes.ElementAt(1); - string propertyValueStr = propertyValueNode.GetLastToken().ValueText; - - switch (propertyNameNode.Name.Identifier.ValueText) + switch (namedArg.Key) { case nameof(JsonSourceGenerationOptionsAttribute.DefaultIgnoreCondition): - { - if (Enum.TryParse(propertyValueStr, out JsonIgnoreCondition value)) - { - options.DefaultIgnoreCondition = value; - } - } + options.DefaultIgnoreCondition = (JsonIgnoreCondition)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields): - { - if (bool.TryParse(propertyValueStr, out bool value)) - { - options.IgnoreReadOnlyFields = value; - } - } + options.IgnoreReadOnlyFields = (bool)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyProperties): - { - if (bool.TryParse(propertyValueStr, out bool value)) - { - options.IgnoreReadOnlyProperties = value; - } - } + options.IgnoreReadOnlyProperties = (bool)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.IncludeFields): - { - if (bool.TryParse(propertyValueStr, out bool value)) - { - options.IncludeFields = value; - } - } + options.IncludeFields = (bool)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.PropertyNamingPolicy): - { - if (Enum.TryParse(propertyValueStr, out JsonKnownNamingPolicy value)) - { - options.PropertyNamingPolicy = value; - } - } + options.PropertyNamingPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.WriteIndented): - { - if (bool.TryParse(propertyValueStr, out bool value)) - { - options.WriteIndented = value; - } - } + options.WriteIndented = (bool)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode): - { - JsonSourceGenerationMode? mode = GetJsonSourceGenerationModeEnumVal(propertyValueNode); - if (mode.HasValue) - { - options.GenerationMode = mode.Value; - } - } + options.GenerationMode = (JsonSourceGenerationMode)namedArg.Value.Value!; break; + default: throw new InvalidOperationException(); } @@ -431,50 +361,30 @@ private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOpt return options; } - private static TypeToGenerate? ParseJsonSerializableAttribute(SemanticModel semanticModel, AttributeSyntax attributeSyntax, CancellationToken cancellationToken) + private static TypeToGenerate? ParseJsonSerializableAttribute(AttributeData attributeData) { - IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); - ITypeSymbol? typeSymbol = null; string? typeInfoPropertyName = null; JsonSourceGenerationMode? generationMode = null; - bool seenFirstArg = false; - foreach (AttributeArgumentSyntax node in attributeArguments) + Debug.Assert(attributeData.ConstructorArguments.Length == 1); + foreach (TypedConstant value in attributeData.ConstructorArguments) { - if (!seenFirstArg) - { - TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax; - if (typeNode != null) - { - ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); - typeSymbol = semanticModel.GetTypeInfo(typeNameSyntax, cancellationToken).ConvertedType; - } + typeSymbol = value.Value as ITypeSymbol; + } - seenFirstArg = true; - } - else + foreach (KeyValuePair namedArg in attributeData.NamedArguments) + { + switch (namedArg.Key) { - IEnumerable childNodes = node.ChildNodes(); - - NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; - Debug.Assert(propertyNameNode != null); - - SyntaxNode propertyValueNode = childNodes.ElementAt(1); - string optionName = propertyNameNode.Name.Identifier.ValueText; - - if (optionName == nameof(JsonSerializableAttribute.TypeInfoPropertyName)) - { - typeInfoPropertyName = propertyValueNode.GetFirstToken().ValueText; - } - else if (optionName == nameof(JsonSerializableAttribute.GenerationMode)) - { - JsonSourceGenerationMode? mode = GetJsonSourceGenerationModeEnumVal(propertyValueNode); - if (mode.HasValue) - { - generationMode = mode.Value; - } - } + case nameof(JsonSerializableAttribute.TypeInfoPropertyName): + typeInfoPropertyName = (string)namedArg.Value.Value!; + break; + case nameof(JsonSerializableAttribute.GenerationMode): + generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!; + break; + default: + throw new InvalidOperationException(); } } @@ -488,7 +398,7 @@ private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOpt Type = typeSymbol, Mode = generationMode, TypeInfoPropertyName = typeInfoPropertyName, - AttributeLocation = attributeSyntax.GetLocation(), + AttributeLocation = attributeData.GetDiagnosticLocation(), }; } @@ -512,63 +422,25 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener ParameterGenerationSpec[]? ctorParamSpecs = null; List? propertyInitializerSpecs = null; CollectionType collectionType = CollectionType.NotApplicable; - JsonNumberHandling? numberHandling = null; - JsonUnmappedMemberHandling? unmappedMemberHandling = null; - JsonObjectCreationHandling? preferredPropertyObjectCreationHandling = null; string? immutableCollectionFactoryTypeFullName = null; - bool foundDesignTimeCustomConverter = false; - TypeRef? converterType = null; bool implementsIJsonOnSerialized = false; bool implementsIJsonOnSerializing = false; - bool isPolymorphic = false; - - IList attributeDataList = type.GetAttributes(); - foreach (AttributeData attributeData in attributeDataList) - { - INamedTypeSymbol? attributeType = attributeData.AttributeClass; - if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonNumberHandlingAttributeType)) - { - IList ctorArgs = attributeData.ConstructorArguments; - numberHandling = (JsonNumberHandling)ctorArgs[0].Value!; - continue; - } - else if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonUnmappedMemberHandlingAttributeType)) - { - IList ctorArgs = attributeData.ConstructorArguments; - unmappedMemberHandling = (JsonUnmappedMemberHandling)ctorArgs[0].Value!; - continue; - } - else if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonObjectCreationHandlingAttributeType)) - { - IList ctorArgs = attributeData.ConstructorArguments; - preferredPropertyObjectCreationHandling = (JsonObjectCreationHandling)ctorArgs[0].Value!; - continue; - } - else if (!foundDesignTimeCustomConverter && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) - { - converterType = GetConverterTypeFromAttribute(contextType, type, attributeData); - foundDesignTimeCustomConverter = true; - } - - if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonDerivedTypeAttributeType)) - { - Debug.Assert(attributeData.ConstructorArguments.Length > 0); - var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; - EnqueueType(derivedType, typeToGenerate.Mode); - - if (!isPolymorphic && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) - { - ReportDiagnostic(DiagnosticDescriptors.PolymorphismNotSupported, typeLocation, new string[] { type.ToDisplayString() }); - } + ProcessTypeCustomAttributes(typeToGenerate, contextType, typeLocation, + out JsonNumberHandling? numberHandling, + out JsonUnmappedMemberHandling? unmappedMemberHandling, + out JsonObjectCreationHandling? preferredPropertyObjectCreationHandling, + out bool foundJsonConverterAttribute, + out TypeRef? customConverterType, + out bool isPolymorphic); - isPolymorphic = true; - } + if (type is INamedTypeSymbol { IsUnboundGenericType: true } or IErrorTypeSymbol) + { + classType = ClassType.TypeUnsupportedBySourceGen; } - - if (foundDesignTimeCustomConverter) + else if (foundJsonConverterAttribute) { - classType = converterType != null + classType = customConverterType != null ? ClassType.TypeWithDesignTimeProvidedCustomConverter : ClassType.TypeUnsupportedBySourceGen; } @@ -632,7 +504,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener if (!TryGetDeserializationConstructor(type, useDefaultCtorInAnnotatedStructs, out IMethodSymbol? constructor)) { classType = ClassType.TypeUnsupportedBySourceGen; - ReportDiagnostic(DiagnosticDescriptors.MultipleJsonConstructorAttribute, typeLocation, new string[] { type.ToDisplayString() }); + ReportDiagnostic(DiagnosticDescriptors.MultipleJsonConstructorAttribute, typeLocation, type.ToDisplayString()); } else { @@ -652,14 +524,14 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener if (classType is ClassType.TypeUnsupportedBySourceGen) { - ReportDiagnostic(DiagnosticDescriptors.TypeNotSupported, typeLocation, new string[] { typeRef.FullyQualifiedName }); + ReportDiagnostic(DiagnosticDescriptors.TypeNotSupported, typeToGenerate.AttributeLocation ?? typeLocation, type.ToDisplayString()); } if (!_generatedContextAndTypeNames.Add((contextType.Name, typeInfoPropertyName))) { // The context name/property name combination will result in a conflict in generated types. // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. - ReportDiagnostic(DiagnosticDescriptors.DuplicateTypeName, typeToGenerate.AttributeLocation ?? contextLocation, new string[] { typeInfoPropertyName }); + ReportDiagnostic(DiagnosticDescriptors.DuplicateTypeName, typeToGenerate.AttributeLocation ?? contextLocation, typeInfoPropertyName); classType = ClassType.TypeUnsupportedBySourceGen; } @@ -686,13 +558,72 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener RuntimeTypeRef = runtimeTypeRef, IsValueTuple = type.IsTupleType, HasExtensionDataPropertyType = hasExtensionDataProperty, - ConverterType = converterType, + ConverterType = customConverterType, ImplementsIJsonOnSerialized = implementsIJsonOnSerialized, ImplementsIJsonOnSerializing = implementsIJsonOnSerializing, ImmutableCollectionFactoryMethod = DetermineImmutableCollectionFactoryMethod(immutableCollectionFactoryTypeFullName), }; } + private void ProcessTypeCustomAttributes( + in TypeToGenerate typeToGenerate, + INamedTypeSymbol contextType, + Location typeLocation, + out JsonNumberHandling? numberHandling, + out JsonUnmappedMemberHandling? unmappedMemberHandling, + out JsonObjectCreationHandling? objectCreationHandling, + out bool foundJsonConverterAttribute, + out TypeRef? customConverterType, + out bool isPolymorphic) + { + numberHandling = null; + unmappedMemberHandling = null; + objectCreationHandling = null; + customConverterType = null; + foundJsonConverterAttribute = false; + isPolymorphic = false; + + foreach (AttributeData attributeData in typeToGenerate.Type.GetAttributes()) + { + INamedTypeSymbol? attributeType = attributeData.AttributeClass; + + if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonNumberHandlingAttributeType)) + { + numberHandling = (JsonNumberHandling)attributeData.ConstructorArguments[0].Value!; + continue; + } + else if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonUnmappedMemberHandlingAttributeType)) + { + unmappedMemberHandling = (JsonUnmappedMemberHandling)attributeData.ConstructorArguments[0].Value!; + continue; + } + else if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonObjectCreationHandlingAttributeType)) + { + objectCreationHandling = (JsonObjectCreationHandling)attributeData.ConstructorArguments[0].Value!; + continue; + } + else if (!foundJsonConverterAttribute && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) + { + customConverterType = GetConverterTypeFromAttribute(contextType, typeToGenerate.Type, attributeData); + foundJsonConverterAttribute = true; + } + + if (SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonDerivedTypeAttributeType)) + { + Debug.Assert(attributeData.ConstructorArguments.Length > 0); + var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; + EnqueueType(derivedType, typeToGenerate.Mode); + + if (!isPolymorphic && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) + { + ReportDiagnostic(DiagnosticDescriptors.PolymorphismNotSupported, typeLocation, typeToGenerate.Type.ToDisplayString()); + } + + isPolymorphic = true; + } + } + } + private bool TryResolveCollectionType( ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? valueType, @@ -987,19 +918,19 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type) if (hasJsonIncludeButIsInaccessible) { - ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), new string[] { declaringType.Name, memberInfo.Name }); + ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), declaringType.Name, memberInfo.Name); } if (isExtensionData) { if (typeHasExtensionDataProperty) { - ReportDiagnostic(DiagnosticDescriptors.MultipleJsonExtensionDataAttribute, typeLocation, new string[] { declaringType.Name }); + ReportDiagnostic(DiagnosticDescriptors.MultipleJsonExtensionDataAttribute, typeLocation, declaringType.Name); } if (!IsValidDataExtensionPropertyType(memberType)) { - ReportDiagnostic(DiagnosticDescriptors.DataExtensionPropertyInvalid, memberInfo.GetDiagnosticLocation(), new string[] { declaringType.Name, memberInfo.Name }); + ReportDiagnostic(DiagnosticDescriptors.DataExtensionPropertyInvalid, memberInfo.GetDiagnosticLocation(), declaringType.Name, memberInfo.Name); } typeHasExtensionDataProperty = true; @@ -1364,12 +1295,13 @@ bool MatchesConstructorParameter(ParameterGenerationSpec paramSpec) !_knownSymbols.JsonConverterType.IsAssignableFrom(converterType) || !converterType.Constructors.Any(c => c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType))) { + ReportDiagnostic(DiagnosticDescriptors.JsonConverterAttributeInvalidType, attributeData.GetDiagnosticLocation(), converterType?.ToDisplayString() ?? "null", declaringSymbol.ToDisplayString()); return null; } if (_knownSymbols.JsonStringEnumConverterType.IsAssignableFrom(converterType)) { - ReportDiagnostic(DiagnosticDescriptors.JsonStringEnumConverterNotSupportedInAot, declaringSymbol.GetDiagnosticLocation(), declaringSymbol.ToDisplayString()); + ReportDiagnostic(DiagnosticDescriptors.JsonStringEnumConverterNotSupportedInAot, attributeData.GetDiagnosticLocation(), declaringSymbol.ToDisplayString()); } return new TypeRef(converterType); diff --git a/src/libraries/System.Text.Json/gen/Model/ClassType.cs b/src/libraries/System.Text.Json/gen/Model/ClassType.cs index 0997fdcb45d5e..c283039249f57 100644 --- a/src/libraries/System.Text.Json/gen/Model/ClassType.cs +++ b/src/libraries/System.Text.Json/gen/Model/ClassType.cs @@ -6,13 +6,13 @@ namespace System.Text.Json.SourceGeneration public enum ClassType { /// - /// Types that are not supported yet by source gen including types with constructor parameters. + /// Types that are not supported at all and will be warned on/skipped by the source generator. /// TypeUnsupportedBySourceGen = 0, Object = 1, BuiltInSupportType = 2, /// - /// Known types such as System.Type and System.IntPtr that throw NotSupportedException. + /// Known types such as System.Type and System.IntPtr that throw NotSupportedException at runtime. /// UnsupportedType = 3, TypeWithDesignTimeProvidedCustomConverter = 4, diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 0af8900f49967..7404cad9ccbee 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -177,4 +177,10 @@ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 877b0cc444f4b..1e6e5354bb563 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -62,6 +62,16 @@ Deserializace vlastností pouze pro inicializaci se v současnosti v režimu generování zdroje nepodporuje. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. Člen {0} byl opatřen poznámkou jsonStringEnumConverter, což není v nativním AOT podporováno. Zvažte použití obecného objektu JsonStringEnumConverter<TEnum>. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index 26e7032ad8728..8f49e4e56e215 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -62,6 +62,16 @@ Die Deserialisierung von reinen init-Eigenschaften wird im Quellgenerierungsmodus derzeit nicht unterstützt. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. Der Member "{0}" wurde mit "JsonStringEnumConverter" kommentiert, was in nativem AOT nicht unterstützt wird. Erwägen Sie stattdessen die Verwendung des generischen "JsonStringEnumConverter<TEnum>". diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index 9f297c5ef1e29..2eabf459872da 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -62,6 +62,16 @@ Actualmente no se admite la deserialización de propiedades de solo inicialización en el modo de generación de origen. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. El miembro '{0}' se ha anotado con 'JsonStringEnumConverter', que no se admite en AOT nativo. Considere la posibilidad de usar el elemento genérico "JsonStringEnumConverter<TEnum>" en su lugar. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index 839ad7f451fd8..6f1a66a53552e 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -62,6 +62,16 @@ La désérialisation des propriétés d’initialisation uniquement n’est actuellement pas prise en charge en mode de génération de source. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. Le membre '{0}' a été annoté avec 'JsonStringEnumConverter', ce qui n’est pas pris en charge dans AOT natif. Utilisez plutôt le générique 'JsonStringEnumConverter<TEnum>'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index a7993e2b7075f..ba1aa5b439da0 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -62,6 +62,16 @@ La deserializzazione delle proprietà di sola inizializzazione al momento non è supportata nella modalità di generazione di origine. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. Il membro '{0}' è stato annotato con 'JsonStringEnumConverter' che non è supportato in AOT nativo. Provare a usare il generico 'JsonStringEnumConverter<TEnum>'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index 0d7c7c2ae17a8..c37b0a0c98bf2 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -62,6 +62,16 @@ 現在、ソース生成モードでは init-only プロパティの逆シリアル化はサポートされていません。 + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. メンバー '{0}' には、ネイティブ AOT ではサポートされていない 'JsonStringEnumConverter' の注釈が付けられています。 代わりに汎用の 'JsonStringEnumConverter<TEnum>' を使用することを検討してください。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index 51206e69b2913..12ab8a5571d1e 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -62,6 +62,16 @@ 초기화 전용 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. '{0}' 멤버에 네이티브 AOT에서 지원되지 않는 'JsonStringEnumConverter'로 주석이 달렸습니다. 대신 제네릭 'JsonStringEnumConverter<TEnum>'을 사용해 보세요. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index b6676f36074a9..d70419eb349ea 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -62,6 +62,16 @@ Deserializacja właściwości tylko do inicjowania nie jest obecnie obsługiwana w trybie generowania źródła. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. Element członkowski '{0}' został opatrzony adnotacją 'JsonStringEnumConverter', która nie jest obsługiwana w natywnym AOT. Zamiast tego należy rozważyć użycie ogólnego konwertera „JsonStringEnumConverter<TEnum>”. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index 5f1f3dee6a5d3..978cfcde17f78 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -62,6 +62,16 @@ A desserialização de propriedades apenas de inicialização não é atualmente suportada no modo de geração de origem. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. O membro "{0}" foi anotado com "JsonStringEnumConverter" que não tem suporte na AOT nativa. Considere usar o genérico "JsonStringEnumConverter<TEnum>". diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index ed8903e7d64ab..af4e072d09d75 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -62,6 +62,16 @@ Десериализация свойств, предназначенных только для инициализации, сейчас не поддерживается в режиме создания исходного кода. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. Элемент "{0}" содержит примечание JsonStringEnumConverter, что не поддерживается в собственном AOT. Вместо этого рассмотрите возможность использовать универсальный параметр JsonStringEnumConverter<TEnum>. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 1bf6244698f78..12b746e205326 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -62,6 +62,16 @@ Yalnızca başlangıç özelliklerini seri durumdan çıkarma şu anda kaynak oluşturma modunda desteklenmiyor. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. '{0}' adlı üyeye yerel AOT’de desteklenmeyen 'JsonStringEnumConverter' parametresi eklendi. bunun yerine genel 'JsonStringEnumConverter<TEnum>' parametresini kullanmayı deneyin. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index 11a409273a671..af23644d1a1a7 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -62,6 +62,16 @@ 源生成模式当前不支持仅初始化属性的反序列化。 + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. 成员“{0}”已使用本机 AOT 中不支持的 "JsonStringEnumConverter" 进行批注。请改为考虑使用泛型 "JsonStringEnumConverter<TEnum>"。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 542bf488304c8..9c24bb23ed783 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -62,6 +62,16 @@ 來源產生模式目前不支援 init-only 屬性的還原序列化。 + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. + + + + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + The 'JsonConverterAttribute.Type' contains an invalid or inaccessible argument. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. 成員 '{0}' 已使用原生 AOT 不支援的 'JsonStringEnumConverter' 加上標註。請考慮改用一般 'JsonStringEnumConverter<TEnum>'。 diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index 79403e2cb935f..b7121420ee1bd 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -834,5 +834,17 @@ public static void FastPathSerialization_EvaluatePropertyOnlyOnceWhenIgnoreNullO Assert.Equal(1, value.WhenWritingNullAccessCounter); Assert.Equal(1, value.WhenWritingDefaultAccessCounter); } + + [Fact] + public static void ContextWithInterpolatedAnnotations_WorksAsExpected() + { + // Regression test for https://github.com/dotnet/runtime/issues/82997 and https://github.com/dotnet/runtime/issues/69207 + Assert.IsAssignableFrom>(ContextWithInterpolatedAnnotations.Default.TestPocoSomeUniqueSuffixSuffix2); + } + + [JsonSerializable(type: typeof(TestPoco), TypeInfoPropertyName = $"{nameof(TestPoco)}SomeUniqueSuffix" + "Suffix2")] + internal partial class ContextWithInterpolatedAnnotations : JsonSerializerContext + { + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index dd286c5e31e0f..5f9aa111d37db 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -9,7 +9,8 @@ - $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1037;SYSLIB1038;SYSLIB1039;SYSLIB1040 + + $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1037;SYSLIB1038;SYSLIB1039;SYSLIB1040;SYSLIB1041 true diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index dca62396a2598..2be1a41163e70 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -32,7 +32,7 @@ public void AssertContainsType(string fullyQualifiedName) spec => spec.TypeRef.FullyQualifiedName == fullyQualifiedName); } - public class CompilationHelper + public static class CompilationHelper { private static readonly CSharpParseOptions s_parseOptions = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse); @@ -53,7 +53,7 @@ public static Compilation CreateCompilation( Func configureParseOptions = null) { - List references = new List { + List references = new() { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), @@ -88,7 +88,7 @@ public static Compilation CreateCompilation( return CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, parseOptions) }, - references: references.ToArray(), + references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) ); } @@ -183,7 +183,7 @@ public class CampaignSummaryViewModel } """; - return CreateCompilation(source); + return CreateCompilation(source, assemblyName: "CampaignSummaryAssembly"); } public static Compilation CreateActiveOrUpcomingEventCompilation() @@ -647,6 +647,88 @@ public enum Enum2 { A, B, C }; return CreateCompilation(source); } + public static Compilation CreateTypesWithInvalidJsonConverterAttributeType() + { + string source = """ + using System.Text.Json.Serialization; + + namespace HelloWorld + { + [JsonSerializable(typeof(MyClass))] + internal partial class JsonContext : JsonSerializerContext + { + } + + public class MyClass + { + [JsonConverter(null)] + public int Value1 { get; set; } + + [JsonConverter(typeof(int)] + public int Value2 { get; set; } + + [JsonConverter(typeof(InacessibleConverter))] + public int Value3 { get; set; } + } + + public class InacessibleConverter : JsonConverter + { + private InacessibleConverter() + { } + + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } + } + """; + + return CreateCompilation(source); + } + + public static Compilation CreateContextWithUnboundGenericTypeDeclarations() + { + string source = """ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + namespace HelloWorld + { + [JsonSerializable(typeof(List<>))] + [JsonSerializable(typeof(Dictionary<,>))] + internal partial class JsonContext : JsonSerializerContext + { + } + } + """; + + return CreateCompilation(source); + } + + public static Compilation CreateContextWithErrorTypeDeclarations() + { + string source = """ + using System.Text.Json.Serialization; + + namespace HelloWorld + { + [JsonSerializable(typeof(BogusType))] + [JsonSerializable(typeof(BogusType))] + internal partial class JsonContext : JsonSerializerContext + { + } + } + """; + + return CreateCompilation(source); + } + internal static void AssertEqualDiagnosticMessages( IEnumerable expectedDiags, IEnumerable actualDiags) @@ -655,6 +737,12 @@ internal static void AssertEqualDiagnosticMessages( HashSet actualSet = new(actualDiags.Select(d => new DiagnosticData(d.Severity, d.Location, d.GetMessage()))); AssertExtensions.Equal(expectedSet, actualSet); } + + internal static Location? GetLocation(this AttributeData attributeData) + { + SyntaxReference? reference = attributeData.ApplicationSyntaxReference; + return reference?.SyntaxTree.GetLocation(reference.Span); + } } public record struct DiagnosticData( diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs index fcc219564e520..5a64762a9c9d5 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -102,7 +102,7 @@ public void SuccessfulSourceGeneration() namespace JsonSourceGenerator { - [JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] + [JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel))] public partial class JsonContext : JsonSerializerContext { } @@ -126,60 +126,58 @@ public class IndexViewModel JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); + Assert.Empty(result.NewCompilation.GetDiagnostics()); Assert.Empty(result.Diagnostics); } - [Fact] - public void MultiDimensionArrayDoesNotProduceWarnings() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void MultiDimensionArrayDoesNotProduceWarnings(bool explicitRef) { - static void RunTest(bool explicitRef) - { - // Compile the referenced assembly first. - Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); - Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); + // Compile the referenced assembly first. + Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); + Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); - // Emit the image of the referenced assembly. - byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); - byte[] eventImage = CompilationHelper.CreateAssemblyImage(eventCompilation); + // Emit the image of the referenced assembly. + byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); + byte[] eventImage = CompilationHelper.CreateAssemblyImage(eventCompilation); - string optionalAttribute = explicitRef ? "[JsonSerializable(typeof(ActiveOrUpcomingEvent[,])]" : null; + string optionalAttribute = explicitRef ? "[JsonSerializable(typeof(ActiveOrUpcomingEvent[,]))]" : null; - // Main source for current compilation. - string source = $$""" - using System.Collections.Generic; - using System.Text.Json.Serialization; - using ReferencedAssembly; + // Main source for current compilation. + string source = $$""" + using System.Text.Json.Serialization; + using ReferencedAssembly; + + namespace JsonSourceGenerator + { + {{optionalAttribute}} + [JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel))] + public partial class JsonContext : JsonSerializerContext + { + } - namespace JsonSourceGenerator + public class IndexViewModel { - {{optionalAttribute}} - [JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] - public partial class JsonContext : JsonSerializerContext - { - } - - public class IndexViewModel - { - public ActiveOrUpcomingEvent[,] ActiveOrUpcomingEvents { get; set; } - public CampaignSummaryViewModel FeaturedCampaign { get; set; } - public bool IsNewAccount { get; set; } - public bool HasFeaturedCampaign => FeaturedCampaign != null; - } + public ActiveOrUpcomingEvent[,] ActiveOrUpcomingEvents { get; set; } + public CampaignSummaryViewModel FeaturedCampaign { get; set; } + public bool IsNewAccount { get; set; } + public bool HasFeaturedCampaign => FeaturedCampaign != null; } - """; + } + """; - MetadataReference[] additionalReferences = { - MetadataReference.CreateFromImage(campaignImage), - MetadataReference.CreateFromImage(eventImage), - }; + MetadataReference[] additionalReferences = { + MetadataReference.CreateFromImage(campaignImage), + MetadataReference.CreateFromImage(eventImage), + }; - Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); - JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - Assert.Empty(result.Diagnostics); - } + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - RunTest(explicitRef: true); - RunTest(false); + Assert.Empty(result.NewCompilation.GetDiagnostics()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -190,9 +188,7 @@ public void NameClashSourceGeneration() JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); INamedTypeSymbol symbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("JsonContext").FirstOrDefault(); - SyntaxReference syntaxReference = new List(symbol.GetAttributes())[1].ApplicationSyntaxReference; - TextSpan textSpan = syntaxReference.Span; - Location location = syntaxReference.SyntaxTree.GetLocation(textSpan)!; + Location location = symbol.GetAttributes()[1].GetLocation(); var expectedDiagnostics = new DiagnosticData[] { @@ -339,8 +335,8 @@ public void JsonStringEnumConverterWarns() Compilation compilation = CompilationHelper.CreateTypesAnnotatedWithJsonStringEnumConverter(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - Location enum2PropLocation = compilation.GetSymbolsWithName("Enum2Prop").First().Locations[0]; - Location enum1TypeLocation = compilation.GetSymbolsWithName("Enum1").First().Locations[0]; + Location enum2PropLocation = compilation.GetSymbolsWithName("Enum2Prop").First().GetAttributes()[0].GetLocation(); + Location enum1TypeLocation = compilation.GetSymbolsWithName("Enum1").First().GetAttributes()[0].GetLocation(); var expectedDiagnostics = new DiagnosticData[] { @@ -350,5 +346,61 @@ public void JsonStringEnumConverterWarns() CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); } + + [Fact] + public void InvalidJsonConverterAttributeTypeWarns() + { + Compilation compilation = CompilationHelper.CreateTypesWithInvalidJsonConverterAttributeType(); + JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); + + Location value1PropLocation = compilation.GetSymbolsWithName("Value1").First().GetAttributes()[0].GetLocation(); + Location value2PropLocation = compilation.GetSymbolsWithName("Value2").First().GetAttributes()[0].GetLocation(); + Location value3PropLocation = compilation.GetSymbolsWithName("Value3").First().GetAttributes()[0].GetLocation(); + + var expectedDiagnostics = new DiagnosticData[] + { + new(DiagnosticSeverity.Warning, value1PropLocation, "The 'JsonConverterAttribute' type 'null' specified on member 'HelloWorld.MyClass.Value1' is not a converter type or does not contain an accessible parameterless constructor."), + new(DiagnosticSeverity.Warning, value2PropLocation, "The 'JsonConverterAttribute' type 'int' specified on member 'HelloWorld.MyClass.Value2' is not a converter type or does not contain an accessible parameterless constructor."), + new(DiagnosticSeverity.Warning, value3PropLocation, "The 'JsonConverterAttribute' type 'HelloWorld.InacessibleConverter' specified on member 'HelloWorld.MyClass.Value3' is not a converter type or does not contain an accessible parameterless constructor."), + }; + + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + } + + [Fact] + public void UnboundGenericTypeDeclarationWarns() + { + Compilation compilation = CompilationHelper.CreateContextWithUnboundGenericTypeDeclarations(); + JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); + + INamedTypeSymbol symbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("JsonContext").FirstOrDefault(); + Collections.Immutable.ImmutableArray attributes = symbol.GetAttributes(); + + var expectedDiagnostics = new DiagnosticData[] + { + new(DiagnosticSeverity.Warning, attributes[0].GetLocation(), "Did not generate serialization metadata for type 'System.Collections.Generic.List<>'."), + new(DiagnosticSeverity.Warning, attributes[1].GetLocation(), "Did not generate serialization metadata for type 'System.Collections.Generic.Dictionary<,>'."), + }; + + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + } + + [Fact] + public void ErrorTypeDeclarationWarns() + { + Compilation compilation = CompilationHelper.CreateContextWithErrorTypeDeclarations(); + JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); + + INamedTypeSymbol symbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("JsonContext").FirstOrDefault(); + Collections.Immutable.ImmutableArray attributes = symbol.GetAttributes(); + + var expectedDiagnostics = new DiagnosticData[] + { + new(DiagnosticSeverity.Warning, attributes[0].GetLocation(), "Did not generate serialization metadata for type 'BogusType'."), + new(DiagnosticSeverity.Warning, attributes[1].GetLocation(), "Did not generate serialization metadata for type 'BogusType'."), + }; + + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + } } }