diff --git a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs index 34b981448446d7..6497556d8c2510 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs @@ -104,12 +104,6 @@ public static bool ContainsAttribute(this ISymbol memberInfo, string attributeFu public static bool IsVirtual(this ISymbol symbol) => symbol.IsVirtual || symbol.IsOverride || symbol.IsAbstract; - public static bool IsNestedPrivate(this ISymbol symbol) - => symbol.DeclaredAccessibility is Accessibility.Private && symbol.ContainingType != null; - - public static bool IsPublic(this ITypeSymbol symbol) - => symbol.DeclaredAccessibility is Accessibility.Public && symbol.ContainingType is null; - public static bool IsAssignableFrom(this ITypeSymbol? baseType, ITypeSymbol? type) { if (baseType is null || type is null) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 0fff894391db77..d94b13edd6087d 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -709,11 +709,7 @@ private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGeneratio { string typeRef = typeGenSpec.TypeRef.FullyQualifiedName; - if (!TryFilterSerializableProps( - typeGenSpec, - contextSpec, - out Dictionary? serializableProperties, - out bool castingRequiredForProps)) + if (!TryFilterSerializableProps(typeGenSpec, contextSpec, out Dictionary? serializableProperties)) { // Type uses configuration that doesn't support fast-path: emit a stub that just throws. GenerateFastPathFuncHeader(writer, typeGenSpec, serializeMethodName, skipNullCheck: true); @@ -754,7 +750,13 @@ private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGeneratio _propertyNames.TryAdd(runtimePropName, propNameVarName); DefaultCheckType defaultCheckType = GetDefaultCheckType(contextSpec, propertyGenSpec); - string? objectExpr = castingRequiredForProps ? $"(({propertyGenSpec.DeclaringType.FullyQualifiedName}){ValueVarName})" : ValueVarName; + + // For properties whose declared type differs from that of the serialized type + // perform an explicit cast -- this is to account for hidden properties or diamond ambiguity. + string? objectExpr = propertyGenSpec.DeclaringType != typeGenSpec.TypeRef + ? $"(({propertyGenSpec.DeclaringType.FullyQualifiedName}){ValueVarName})" + : ValueVarName; + string propValueExpr = $"{objectExpr}.{propertyGenSpec.NameSpecifiedInSourceCode}"; switch (defaultCheckType) @@ -1353,12 +1355,10 @@ private static bool IsGenerationModeSpecified(TypeGenerationSpec typeSpec, JsonS public static bool TryFilterSerializableProps( TypeGenerationSpec typeSpec, ContextGenerationSpec contextSpec, - [NotNullWhen(true)] out Dictionary? serializableProperties, - out bool castingRequiredForProps) + [NotNullWhen(true)] out Dictionary? serializableProperties) { Debug.Assert(typeSpec.PropertyGenSpecs != null); - castingRequiredForProps = false; serializableProperties = new Dictionary(); HashSet? ignoredMembers = null; @@ -1391,10 +1391,6 @@ public static bool TryFilterSerializableProps( continue; } - // Using properties from an interface hierarchy -- require explicit casting when - // getting properties in the fast path to account for possible diamond ambiguities. - castingRequiredForProps |= typeSpec.TypeRef.TypeKind is TypeKind.Interface && propGenSpec.PropertyType != typeSpec.TypeRef; - string memberName = propGenSpec.MemberName!; // The JsonPropertyNameAttribute or naming policy resulted in a collision. @@ -1452,12 +1448,10 @@ public static bool TryFilterSerializableProps( } Debug.Assert(typeSpec.PropertyGenSpecs.Count >= serializableProperties.Count); - castingRequiredForProps |= typeSpec.PropertyGenSpecs.Count > serializableProperties.Count; return true; ReturnFalse: serializableProperties = null; - castingRequiredForProps = false; return false; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 73d5355d9351f3..38b1fc41a044a2 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -131,7 +131,7 @@ public Parser(KnownTypeSymbols knownSymbols) TypeToGenerate typeToGenerate = _typesToGenerate.Dequeue(); if (!_generatedTypes.ContainsKey(typeToGenerate.Type)) { - TypeGenerationSpec spec = ParseTypeGenerationSpec(typeToGenerate, contextName: contextTypeSymbol.Name, contextLocation, options); + TypeGenerationSpec spec = ParseTypeGenerationSpec(typeToGenerate, contextTypeSymbol, contextLocation, options); _generatedTypes.Add(typeToGenerate.Type, spec); } } @@ -494,8 +494,10 @@ private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOpt }; } - private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate, string contextName, Location contextLocation, JsonSourceGenerationOptionsAttribute options) + private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate, INamedTypeSymbol contextType, Location contextLocation, JsonSourceGenerationOptionsAttribute options) { + Debug.Assert(IsSymbolAccessibleWithin(typeToGenerate.Type, within: contextType), "should not generate metadata for inaccessible types."); + ITypeSymbol type = typeToGenerate.Type; Location typeLocation = type.GetDiagnosticLocation() ?? typeToGenerate.AttributeLocation ?? contextLocation; @@ -547,7 +549,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate } else if (!foundDesignTimeCustomConverter && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) { - converterType = GetConverterTypeFromAttribute(attributeData); + converterType = GetConverterTypeFromAttribute(contextType, attributeData); foundDesignTimeCustomConverter = true; } @@ -589,179 +591,41 @@ private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate { classType = ClassType.Enum; } - else if (type.GetCompatibleGenericBaseType(_knownSymbols.IAsyncEnumerableOfTType) is INamedTypeSymbol iasyncEnumerableType) + else if (TryResolveCollectionType(type, + out ITypeSymbol? valueType, + out ITypeSymbol? keyType, + out collectionType, + out immutableCollectionFactoryTypeFullName, + out bool needsRuntimeType)) { - if (type.CanUseDefaultConstructorForDeserialization(out IMethodSymbol? defaultCtor)) - { - constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; - constructorSetsRequiredMembers = defaultCtor?.ContainsAttribute(SetsRequiredMembersAttributeFullName) == true; - } - - ITypeSymbol elementType = iasyncEnumerableType.TypeArguments[0]; - collectionValueType = EnqueueType(elementType, typeToGenerate.Mode); - collectionType = CollectionType.IAsyncEnumerableOfT; - classType = ClassType.Enumerable; - } - else if (_knownSymbols.IEnumerableType.IsAssignableFrom(type)) - { - if (type.CanUseDefaultConstructorForDeserialization(out IMethodSymbol? defaultCtor)) - { - constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; - constructorSetsRequiredMembers = defaultCtor?.ContainsAttribute(SetsRequiredMembersAttributeFullName) == true; - } - - INamedTypeSymbol? actualTypeToConvert; - ITypeSymbol? keyType = null; - ITypeSymbol valueType; - bool needsRuntimeType = false; - - if (type is IArrayTypeSymbol arraySymbol) - { - Debug.Assert(arraySymbol.Rank == 1, "multi-dimensional arrays should have been handled earlier."); - classType = ClassType.Enumerable; - collectionType = CollectionType.Array; - valueType = arraySymbol.ElementType; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ListOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.List; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.DictionaryOfTKeyTValueType)) != null) - { - classType = ClassType.Dictionary; - collectionType = CollectionType.Dictionary; - - keyType = actualTypeToConvert.TypeArguments[0]; - valueType = actualTypeToConvert.TypeArguments[1]; - } - else if (_knownSymbols.IsImmutableDictionaryType(type, out immutableCollectionFactoryTypeFullName)) - { - classType = ClassType.Dictionary; - collectionType = CollectionType.ImmutableDictionary; - - ImmutableArray genericArgs = ((INamedTypeSymbol)type).TypeArguments; - keyType = genericArgs[0]; - valueType = genericArgs[1]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IDictionaryOfTKeyTValueType)) != null) - { - classType = ClassType.Dictionary; - collectionType = CollectionType.IDictionaryOfTKeyTValue; - - keyType = actualTypeToConvert.TypeArguments[0]; - valueType = actualTypeToConvert.TypeArguments[1]; - - needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IReadonlyDictionaryOfTKeyTValueType)) != null) - { - classType = ClassType.Dictionary; - collectionType = CollectionType.IReadOnlyDictionary; - - keyType = actualTypeToConvert.TypeArguments[0]; - valueType = actualTypeToConvert.TypeArguments[1]; - - needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); - } - else if (_knownSymbols.IsImmutableEnumerableType(type, out immutableCollectionFactoryTypeFullName)) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.ImmutableEnumerable; - valueType = ((INamedTypeSymbol)type).TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IListOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.IListOfT; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ISetOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.ISet; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ICollectionOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.ICollectionOfT; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.StackOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.StackOfT; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.QueueOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.QueueOfT; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ConcurrentStackType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.ConcurrentStack; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ConcurrentQueueType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.ConcurrentQueue; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IEnumerableOfTType)) != null) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.IEnumerableOfT; - valueType = actualTypeToConvert.TypeArguments[0]; - } - else if (_knownSymbols.IDictionaryType.IsAssignableFrom(type)) - { - classType = ClassType.Dictionary; - collectionType = CollectionType.IDictionary; - keyType = _knownSymbols.StringType; - valueType = _knownSymbols.ObjectType; - - needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); - } - else if (_knownSymbols.IListType.IsAssignableFrom(type)) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.IList; - valueType = _knownSymbols.ObjectType; - } - else if (_knownSymbols.StackType.IsAssignableFrom(type)) - { - classType = ClassType.Enumerable; - collectionType = CollectionType.Stack; - valueType = _knownSymbols.ObjectType; - } - else if (_knownSymbols.QueueType.IsAssignableFrom(type)) + if (!IsSymbolAccessibleWithin(valueType, within: contextType) || + (keyType != null && !IsSymbolAccessibleWithin(keyType, within: contextType))) { - classType = ClassType.Enumerable; - collectionType = CollectionType.Queue; - valueType = _knownSymbols.ObjectType; + classType = ClassType.UnsupportedType; + keyType = valueType = null; + immutableCollectionFactoryTypeFullName = null; + collectionType = default; + needsRuntimeType = false; } else { - classType = ClassType.Enumerable; - collectionType = CollectionType.IEnumerable; - valueType = _knownSymbols.ObjectType; - } - - collectionValueType = EnqueueType(valueType, typeToGenerate.Mode); + if (type.CanUseDefaultConstructorForDeserialization(out IMethodSymbol? defaultCtor)) + { + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; + constructorSetsRequiredMembers = defaultCtor?.ContainsAttribute(SetsRequiredMembersAttributeFullName) == true; + } - if (keyType != null) - { - collectionKeyType = EnqueueType(keyType, typeToGenerate.Mode); + classType = keyType != null ? ClassType.Dictionary : ClassType.Enumerable; + collectionValueType = EnqueueType(valueType, typeToGenerate.Mode); - if (needsRuntimeType) + if (keyType != null) { - runtimeTypeRef = GetDictionaryTypeRef(keyType, valueType); + collectionKeyType = EnqueueType(keyType, typeToGenerate.Mode); + + if (needsRuntimeType) + { + runtimeTypeRef = GetDictionaryTypeRef(keyType, valueType); + } } } } @@ -825,8 +689,9 @@ private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate foreach (INamedTypeSymbol currentType in type.GetSortedTypeHierarchy()) { var declaringTypeRef = new TypeRef(currentType); + ImmutableArray members = currentType.GetMembers(); - foreach (IPropertySymbol propertyInfo in currentType.GetMembers().OfType()) + foreach (IPropertySymbol propertyInfo in members.OfType()) { bool isVirtual = propertyInfo.IsVirtual(); @@ -840,7 +705,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate continue; } - PropertyGenerationSpec? spec = ParsePropertyGenerationSpec(declaringTypeRef, propertyInfo.Type, propertyInfo, isVirtual, typeToGenerate.Mode, options); + PropertyGenerationSpec? spec = ParsePropertyGenerationSpec(contextType, declaringTypeRef, propertyInfo.Type, propertyInfo, isVirtual, typeToGenerate.Mode, options); if (spec is null) { continue; @@ -849,7 +714,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate CacheMemberHelper(propertyInfo.Type, propertyInfo, spec); } - foreach (IFieldSymbol fieldInfo in currentType.GetMembers().OfType()) + foreach (IFieldSymbol fieldInfo in members.OfType()) { // Skip if : if ( @@ -865,7 +730,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate continue; } - PropertyGenerationSpec? spec = ParsePropertyGenerationSpec(declaringTypeRef, fieldInfo.Type, fieldInfo, isVirtual: false, typeToGenerate.Mode, options); + PropertyGenerationSpec? spec = ParsePropertyGenerationSpec(contextType, declaringTypeRef, fieldInfo.Type, fieldInfo, isVirtual: false, typeToGenerate.Mode, options); if (spec is null) { continue; @@ -915,11 +780,6 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener (propertyInitializers ??= new()).Add(propInitializerSpec); } } - - if (spec.HasJsonInclude && (!spec.CanUseGetter || !spec.CanUseSetter || !spec.IsPublic)) - { - ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), new string[] { type.Name, spec.MemberName }); - } } } @@ -938,7 +798,7 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener ReportDiagnostic(DiagnosticDescriptors.TypeNotSupported, typeLocation, new string[] { typeRef.FullyQualifiedName }); } - if (!_generatedContextAndTypeNames.Add((contextName, typeInfoPropertyName))) + 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. @@ -976,6 +836,149 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener }; } + private bool TryResolveCollectionType( + ITypeSymbol type, + [NotNullWhen(true)] out ITypeSymbol? valueType, + out ITypeSymbol? keyType, + out CollectionType collectionType, + out string? immutableCollectionFactoryTypeFullName, + out bool needsRuntimeType) + { + INamedTypeSymbol? actualTypeToConvert; + valueType = null; + keyType = null; + collectionType = default; + immutableCollectionFactoryTypeFullName = null; + needsRuntimeType = false; + + // IAsyncEnumerable takes precedence over IEnumerable. + if (type.GetCompatibleGenericBaseType(_knownSymbols.IAsyncEnumerableOfTType) is INamedTypeSymbol iAsyncEnumerableType) + { + valueType = iAsyncEnumerableType.TypeArguments[0]; + collectionType = CollectionType.IAsyncEnumerableOfT; + return true; + } + + if (!_knownSymbols.IEnumerableType.IsAssignableFrom(type)) + { + // Type is not IEnumerable and therefore not a collection type + return false; + } + + if (type is IArrayTypeSymbol arraySymbol) + { + Debug.Assert(arraySymbol.Rank == 1, "multi-dimensional arrays should have been handled earlier."); + collectionType = CollectionType.Array; + valueType = arraySymbol.ElementType; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ListOfTType)) != null) + { + collectionType = CollectionType.List; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.DictionaryOfTKeyTValueType)) != null) + { + collectionType = CollectionType.Dictionary; + keyType = actualTypeToConvert.TypeArguments[0]; + valueType = actualTypeToConvert.TypeArguments[1]; + } + else if (_knownSymbols.IsImmutableDictionaryType(type, out immutableCollectionFactoryTypeFullName)) + { + collectionType = CollectionType.ImmutableDictionary; + ImmutableArray genericArgs = ((INamedTypeSymbol)type).TypeArguments; + keyType = genericArgs[0]; + valueType = genericArgs[1]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IDictionaryOfTKeyTValueType)) != null) + { + collectionType = CollectionType.IDictionaryOfTKeyTValue; + keyType = actualTypeToConvert.TypeArguments[0]; + valueType = actualTypeToConvert.TypeArguments[1]; + needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IReadonlyDictionaryOfTKeyTValueType)) != null) + { + collectionType = CollectionType.IReadOnlyDictionary; + keyType = actualTypeToConvert.TypeArguments[0]; + valueType = actualTypeToConvert.TypeArguments[1]; + needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); + } + else if (_knownSymbols.IsImmutableEnumerableType(type, out immutableCollectionFactoryTypeFullName)) + { + collectionType = CollectionType.ImmutableEnumerable; + valueType = ((INamedTypeSymbol)type).TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IListOfTType)) != null) + { + collectionType = CollectionType.IListOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ISetOfTType)) != null) + { + collectionType = CollectionType.ISet; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ICollectionOfTType)) != null) + { + collectionType = CollectionType.ICollectionOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.StackOfTType)) != null) + { + collectionType = CollectionType.StackOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.QueueOfTType)) != null) + { + collectionType = CollectionType.QueueOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ConcurrentStackType)) != null) + { + collectionType = CollectionType.ConcurrentStack; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ConcurrentQueueType)) != null) + { + collectionType = CollectionType.ConcurrentQueue; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IEnumerableOfTType)) != null) + { + collectionType = CollectionType.IEnumerableOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } + else if (_knownSymbols.IDictionaryType.IsAssignableFrom(type)) + { + collectionType = CollectionType.IDictionary; + keyType = _knownSymbols.StringType; + valueType = _knownSymbols.ObjectType; + needsRuntimeType = SymbolEqualityComparer.Default.Equals(type, actualTypeToConvert); + } + else if (_knownSymbols.IListType.IsAssignableFrom(type)) + { + collectionType = CollectionType.IList; + valueType = _knownSymbols.ObjectType; + } + else if (_knownSymbols.StackType.IsAssignableFrom(type)) + { + collectionType = CollectionType.Stack; + valueType = _knownSymbols.ObjectType; + } + else if (_knownSymbols.QueueType.IsAssignableFrom(type)) + { + collectionType = CollectionType.Queue; + valueType = _knownSymbols.ObjectType; + } + else + { + collectionType = CollectionType.IEnumerable; + valueType = _knownSymbols.ObjectType; + } + + return true; + } + private TypeRef? GetDictionaryTypeRef(ITypeSymbol keyType, ITypeSymbol valueType) { INamedTypeSymbol? dictionary = _knownSymbols.DictionaryOfTKeyTValueType?.Construct(keyType, valueType); @@ -1040,6 +1043,7 @@ private static bool PropertyIsOverriddenAndIgnored( } private PropertyGenerationSpec? ParsePropertyGenerationSpec( + INamedTypeSymbol contextType, TypeRef declaringType, ITypeSymbol memberType, ISymbol memberInfo, @@ -1050,6 +1054,7 @@ private static bool PropertyIsOverriddenAndIgnored( Debug.Assert(memberInfo is IFieldSymbol or IPropertySymbol); ProcessMemberCustomAttributes( + contextType, memberInfo, out bool hasJsonInclude, out string? jsonPropertyName, @@ -1062,6 +1067,7 @@ private static bool PropertyIsOverriddenAndIgnored( out bool hasJsonRequiredAttribute); ProcessMember( + contextType, memberInfo, hasJsonInclude, out bool isReadOnly, @@ -1069,14 +1075,22 @@ private static bool PropertyIsOverriddenAndIgnored( out bool isRequired, out bool canUseGetter, out bool canUseSetter, + out bool isJsonIncludeInaccessible, out bool setterIsInitOnly); - if (!isPublic && !memberType.IsPublic()) + if (isJsonIncludeInaccessible) { - return null; + ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), new string[] { declaringType.Name, memberInfo.Name }); } - bool needsAtSign = memberInfo.MemberNameNeedsAtSign(); + if ((!canUseGetter && !canUseSetter && !isJsonIncludeInaccessible) || + !IsSymbolAccessibleWithin(memberType, within: contextType)) + { + // Skip the member if either of the two conditions hold + // 1. Member has no accessible getters or setters (but is not marked with JsonIncludeAttribute since we need to throw a runtime exception) OR + // 2. The member type is not accessible within the generated context. + return null; + } string clrName = memberInfo.Name; string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, options.PropertyNamingPolicy); @@ -1084,7 +1098,7 @@ private static bool PropertyIsOverriddenAndIgnored( return new PropertyGenerationSpec { - NameSpecifiedInSourceCode = needsAtSign ? "@" + memberInfo.Name : memberInfo.Name, + NameSpecifiedInSourceCode = memberInfo.MemberNameNeedsAtSign() ? "@" + memberInfo.Name : memberInfo.Name, MemberName = memberInfo.Name, IsProperty = memberInfo is IPropertySymbol, IsPublic = isPublic, @@ -1111,6 +1125,7 @@ private static bool PropertyIsOverriddenAndIgnored( } private void ProcessMemberCustomAttributes( + INamedTypeSymbol contextType, ISymbol memberInfo, out bool hasJsonInclude, out string? jsonPropertyName, @@ -1145,7 +1160,7 @@ private void ProcessMemberCustomAttributes( if (converterType is null && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) { - converterType = GetConverterTypeFromAttribute(attributeData); + converterType = GetConverterTypeFromAttribute(contextType, attributeData); } else if (attributeType.ContainingAssembly.Name == SystemTextJsonNamespace) { @@ -1213,7 +1228,8 @@ private void ProcessMemberCustomAttributes( } } - private static void ProcessMember( + private void ProcessMember( + INamedTypeSymbol contextType, ISymbol memberInfo, bool hasJsonInclude, out bool isReadOnly, @@ -1221,13 +1237,16 @@ private static void ProcessMember( out bool isRequired, out bool canUseGetter, out bool canUseSetter, - out bool setterIsInitOnly) + out bool isJsonIncludeInaccessible, + out bool isSetterInitOnly) { isPublic = false; + isReadOnly = false; isRequired = false; canUseGetter = false; canUseSetter = false; - setterIsInitOnly = false; + isJsonIncludeInaccessible = false; + isSetterInitOnly = false; switch (memberInfo) { @@ -1246,26 +1265,33 @@ private static void ProcessMember( isPublic = true; canUseGetter = true; } - else if (getMethod.DeclaredAccessibility is Accessibility.Internal) + else if (IsSymbolAccessibleWithin(getMethod, within: contextType)) { canUseGetter = hasJsonInclude; } + else + { + isJsonIncludeInaccessible = hasJsonInclude; + } } if (setMethod != null) { - isReadOnly = false; - setterIsInitOnly = setMethod.IsInitOnly; + isSetterInitOnly = setMethod.IsInitOnly; if (setMethod.DeclaredAccessibility is Accessibility.Public) { isPublic = true; canUseSetter = true; } - else if (setMethod.DeclaredAccessibility is Accessibility.Internal) + else if (IsSymbolAccessibleWithin(setMethod, within: contextType)) { canUseSetter = hasJsonInclude; } + else + { + isJsonIncludeInaccessible = hasJsonInclude; + } } else { @@ -1275,35 +1301,37 @@ private static void ProcessMember( break; case IFieldSymbol fieldInfo: { - isPublic = fieldInfo.DeclaredAccessibility is Accessibility.Public; isReadOnly = fieldInfo.IsReadOnly; #if ROSLYN4_4_OR_GREATER isRequired = fieldInfo.IsRequired; #endif - if (fieldInfo.DeclaredAccessibility is not (Accessibility.Private or Accessibility.Protected)) + if (fieldInfo.DeclaredAccessibility is Accessibility.Public) { + isPublic = true; canUseGetter = true; canUseSetter = !isReadOnly; } + else + { + // Unlike properties JsonIncludeAttribute is not supported for internal fields. + isJsonIncludeInaccessible = hasJsonInclude; + } } break; default: - throw new InvalidOperationException(); + Debug.Fail("Method given an invalid symbol type."); + break; } } - private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) - => accessor != null && (accessor.IsPublic || accessor.IsAssembly); - - private TypeRef? GetConverterTypeFromAttribute(AttributeData attributeData) + private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, AttributeData attributeData) { Debug.Assert(_knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeData.AttributeClass)); var converterType = (INamedTypeSymbol?)attributeData.ConstructorArguments[0].Value; if (converterType == null || !_knownSymbols.JsonConverterType.IsAssignableFrom(converterType) || - !converterType.Constructors.Any(c => c.Parameters.Length == 0) || - converterType.IsNestedPrivate()) + !converterType.Constructors.Any(c => c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType))) { return null; } @@ -1502,6 +1530,9 @@ private static bool TryGetDeserializationConstructor( return true; } + private bool IsSymbolAccessibleWithin(ISymbol symbol, INamedTypeSymbol within) + => _knownSymbols.Compilation.IsSymbolAccessibleWithin(symbol, within); + private bool IsUnsupportedType(ITypeSymbol type) { return diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs index 60bb9ab11a3f24..72dccfa17c3f53 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; +using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Numerics; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Xunit; @@ -1089,6 +1091,138 @@ ClassWithPublicGetterAndPrivateSetter obj Assert.Null(obj.Class); } + [Fact] + public async Task ProtectedMembers() + { + var options = Serializer.CreateOptions(includeFields: true); + JsonTypeInfo typeInfo = options.GetTypeInfo(typeof(ClassWithProtectedMembers)); + Assert.Empty(typeInfo.Properties); + + var value = new ClassWithProtectedMembers(); + value.SetValues(field: 10, property: 20); + + string json = await Serializer.SerializeWrapper(value, options); + Assert.Equal("{}", json); + + value = await Serializer.DeserializeWrapper("""{"_field":10,"Property":20}"""); + value.GetValues(out int field, out int property); + Assert.Equal(0, field); + Assert.Equal(0, property); + } + + [Fact] + public async Task ProtectedGetter() + { + var value = new ClassWithProtectedGetter { Property = 42 }; + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("{}", json); + + value = await Serializer.DeserializeWrapper("""{"Property":42}"""); + Assert.Equal(42, value.GetValue()); + } + + [Fact] + public async Task ProtectedSetter() + { + var value = new ClassWithProtectedSetter(); + value.SetValue(42); + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"Property":42}""", json); + + value = await Serializer.DeserializeWrapper(json); + Assert.Equal(0, value.Property); + } + + [Fact] + public async Task PrivateProtectedMembers() + { + var options = Serializer.CreateOptions(includeFields: true); + JsonTypeInfo typeInfo = options.GetTypeInfo(typeof(ClassWithPrivateProtectedMembers)); + Assert.Empty(typeInfo.Properties); + + var value = new ClassWithPrivateProtectedMembers(); + value.SetValues(field: 10, property: 20); + + string json = await Serializer.SerializeWrapper(value, options); + Assert.Equal("{}", json); + + value = await Serializer.DeserializeWrapper("""{"_field":10,"Property":20}"""); + value.GetValues(out int field, out int property); + Assert.Equal(0, field); + Assert.Equal(0, property); + } + + [Fact] + public async Task PrivateProtectedGetter() + { + var value = new ClassWithPrivateProtectedGetter { Property = 42 }; + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("{}", json); + + value = await Serializer.DeserializeWrapper("""{"Property":42}"""); + Assert.Equal(42, value.GetValue()); + } + + [Fact] + public async Task PrivateProtectedSetter() + { + var value = new ClassWithPrivateProtectedSetter(); + value.SetValue(42); + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"Property":42}""", json); + + value = await Serializer.DeserializeWrapper(json); + Assert.Equal(0, value.Property); + } + + [Fact] + public async Task InternalProtectedMembers() + { + var options = Serializer.CreateOptions(includeFields: true); + JsonTypeInfo typeInfo = options.GetTypeInfo(typeof(ClassWithInternalProtectedMembers)); + Assert.Empty(typeInfo.Properties); + + var value = new ClassWithInternalProtectedMembers(); + value.SetValues(field: 10, property: 20); + + string json = await Serializer.SerializeWrapper(value, options); + Assert.Equal("{}", json); + + value = await Serializer.DeserializeWrapper("""{"_field":10,"Property":20}"""); + value.GetValues(out int field, out int property); + Assert.Equal(0, field); + Assert.Equal(0, property); + } + + [Fact] + public async Task InternalProtectedGetter() + { + var value = new ClassWithInternalProtectedGetter { Property = 42 }; + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("{}", json); + + value = await Serializer.DeserializeWrapper("""{"Property":42}"""); + Assert.Equal(42, value.GetValue()); + } + + [Fact] + public async Task InternalProtectedSetter() + { + var value = new ClassWithInternalProtectedSetter(); + value.SetValue(42); + + string json = await Serializer.SerializeWrapper(value); + Assert.Equal("""{"Property":42}""", json); + + value = await Serializer.DeserializeWrapper(json); + Assert.Equal(0, value.Property); + } + [Fact] public async Task MissingObjectProperty() { @@ -1355,6 +1489,69 @@ public void SetMyString(string value) } } + public class ClassWithProtectedMembers + { + protected int _field; + protected int Property { get; set; } + + public void SetValues(int field, int property) => (_field, Property) = (field, property); + public void GetValues(out int field, out int property) => (field, property) = (_field, Property); + } + + public class ClassWithProtectedGetter + { + public int Property { protected get; set; } + public int GetValue() => Property; + } + + public class ClassWithProtectedSetter + { + public int Property { get; protected set; } + public void SetValue(int value) => Property = value; + } + + public class ClassWithPrivateProtectedMembers + { + private protected int _field; + private protected int Property { get; set; } + + public void SetValues(int field, int property) => (_field, Property) = (field, property); + public void GetValues(out int field, out int property) => (field, property) = (_field, Property); + } + + public class ClassWithPrivateProtectedGetter + { + public int Property { private protected get; set; } + public int GetValue() => Property; + } + + public class ClassWithPrivateProtectedSetter + { + public int Property { get; private protected set; } + public void SetValue(int value) => Property = value; + } + + public class ClassWithInternalProtectedMembers + { + protected internal int _field; + protected internal int Property { get; set; } + + public void SetValues(int field, int property) => (_field, Property) = (field, property); + public void GetValues(out int field, out int property) => (field, property) = (_field, Property); + } + + public class ClassWithInternalProtectedGetter + { + public int Property { internal protected get; set; } + public int GetValue() => Property; + } + + public class ClassWithInternalProtectedSetter + { + public int Property { get; internal protected set; } + public void SetValue(int value) => Property = value; + } + public class ClassWithReadOnlyFields { public ClassWithReadOnlyFields() @@ -3028,5 +3225,84 @@ public class Implementation : IJoinInterface public int DerivedProperty { get; set; } } } + + [Fact] + public async virtual Task TestCollectionWithPrivateElementType() + { + // The reflection-based serializer supports enumerables whose element type is private. + + CollectionWithPrivateElementType collection = CollectionWithPrivateElementType.CreatePopulatedInstance(); + + string json = await Serializer.SerializeWrapper(collection); + Assert.Equal(collection.GetExpectedJson(), json); + + collection = await Serializer.DeserializeWrapper(json); + collection.Validate(); + } + + public class CollectionWithPrivateElementType : ICollection + { + private readonly ICollection _values = new List(); + + public static CollectionWithPrivateElementType CreatePopulatedInstance() + => new CollectionWithPrivateElementType { _values = { PrivateEnum.A, PrivateEnum.B, PrivateEnum.C } }; + + public void Validate() => Assert.Equal(new[] { PrivateEnum.A, PrivateEnum.B, PrivateEnum.C }, this); + public string GetExpectedJson() => "[0,1,2]"; + + int ICollection.Count => _values.Count; + bool ICollection.IsReadOnly => _values.IsReadOnly; + void ICollection.Add(PrivateEnum item) => _values.Add(item); + void ICollection.Clear() => _values.Clear(); + bool ICollection.Contains(PrivateEnum item) => _values.Contains(item); + void ICollection.CopyTo(PrivateEnum[] array, int arrayIndex) => _values.CopyTo(array, arrayIndex); + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + bool ICollection.Remove(PrivateEnum item) => _values.Remove(item); + } + + [Fact] + public async virtual Task TestDictionaryWithPrivateKeyAndValueType() + { + // The reflection-based serializer supports dictionaries whose key/value types are private + + DictionaryWithPrivateKeyAndValueType collection = DictionaryWithPrivateKeyAndValueType.CreatePopulatedInstance(); + + string json = await Serializer.SerializeWrapper(collection); + Assert.Equal(collection.GetExpectedJson(), json); + + collection = await Serializer.DeserializeWrapper(json); + collection.Validate(); + } + + public class DictionaryWithPrivateKeyAndValueType : IDictionary + { + private readonly IDictionary _values = new Dictionary(); + + public static DictionaryWithPrivateKeyAndValueType CreatePopulatedInstance() + => new DictionaryWithPrivateKeyAndValueType { _values = { [PrivateEnum.A] = PrivateEnum.B, [PrivateEnum.B] = PrivateEnum.C } }; + + public void Validate() => Assert.Equal(new KeyValuePair[] { new(PrivateEnum.A, PrivateEnum.B), new(PrivateEnum.B, PrivateEnum.C) }, this); + public string GetExpectedJson() => """{"A":1,"B":2}"""; // cf. https://github.com/dotnet/runtime/issues/87129 + + PrivateEnum IDictionary.this[PrivateEnum key] { get => _values[key]; set => _values[key] = value; } + ICollection IDictionary.Keys => _values.Keys; + ICollection IDictionary.Values => _values.Values; + int ICollection>.Count => _values.Count; + bool ICollection>.IsReadOnly => _values.IsReadOnly; + void IDictionary.Add(PrivateEnum key, PrivateEnum value) => _values.Add(key, value); + void ICollection>.Add(KeyValuePair item) => _values.Add(item); + void ICollection>.Clear() => _values.Clear(); + bool ICollection>.Contains(KeyValuePair item) => _values.Contains(item); + bool IDictionary.ContainsKey(PrivateEnum key) => _values.ContainsKey(key); + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => _values.CopyTo(array, arrayIndex); + IEnumerator> IEnumerable>.GetEnumerator() => _values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + bool IDictionary.Remove(PrivateEnum key) => _values.Remove(key); + bool ICollection>.Remove(KeyValuePair item) => _values.Remove(item); + bool IDictionary.TryGetValue(PrivateEnum key, out PrivateEnum value) => _values.TryGetValue(key, out value); + } + + private enum PrivateEnum { A = 0, B = 1, C = 2 } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/TestClasses.cs index a0eebf2e4fd383..4879565121d9ce 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/TestClasses.cs @@ -10,6 +10,16 @@ public class MyPoco public string Value { get; set; } } + public class ClassFromOtherAssemblyWithNonPublicMembers + { + public int PublicValue { get; set; } = 1; + internal int InternalValue { get; set; } = 2; + private int PrivateValue { get; set; } = 4; + protected int ProtectedValue { get; set; } = 8; + private protected int PrivateProtectedValue { get; set; } = 16; + internal protected int InternalProtectedValue { get; set; } = 32; + } + [JsonSerializable(typeof(MyPoco))] public partial class NETStandardSerializerContext : JsonSerializerContext { 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 741754dd251f1e..2ac6a125cdfd50 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 @@ -6,6 +6,7 @@ using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Tests; +using System.Text.Json.SourceGeneration.Tests.NETStandard; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -765,6 +766,26 @@ internal partial class ClassWithDictionaryPropertyContext : JsonSerializerContex { } + [Fact] + public static void DoesNotReferenceInternalMembersFromOtherAssemblies() + { + // Regression test for https://github.com/dotnet/runtime/issues/66679 + + Assert.Equal(1, ContextForClassesFromAnotherAssembly.Default.ClassFromOtherAssemblyWithNonPublicMembers.Properties.Count); + Assert.Equal("PublicValue", ContextForClassesFromAnotherAssembly.Default.ClassFromOtherAssemblyWithNonPublicMembers.Properties[0].Name); + + var value = new ClassFromOtherAssemblyWithNonPublicMembers(); + string json = JsonSerializer.Serialize(value, ContextForClassesFromAnotherAssembly.Default.ClassFromOtherAssemblyWithNonPublicMembers); + Assert.Equal("""{"PublicValue":1}""", json); + + JsonSerializer.Deserialize(json, ContextForClassesFromAnotherAssembly.Default.ClassFromOtherAssemblyWithNonPublicMembers); + } + + [JsonSerializable(typeof(ClassFromOtherAssemblyWithNonPublicMembers))] + internal partial class ContextForClassesFromAnotherAssembly : JsonSerializerContext + { + } + internal class ClassWithPocoListDictionaryAndNullable { public uint UIntProperty { get; set; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs index 4cbed0897b1840..aae3ab143948ad 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -145,6 +145,32 @@ public override async Task HonorJsonPropertyName_PrivateSetter() Assert.Equal(json, await Serializer.SerializeWrapper(obj)); } + [Fact] + public override async Task TestCollectionWithPrivateElementType() + { + // The source generator cannot support enumerables whose element type is private. + CollectionWithPrivateElementType collection = CollectionWithPrivateElementType.CreatePopulatedInstance(); + string json = collection.GetExpectedJson(); + + Assert.True(Serializer.DefaultOptions.TryGetTypeInfo(typeof(CollectionWithPrivateElementType), out _)); + + await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(collection)); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json)); + } + + [Fact] + public override async Task TestDictionaryWithPrivateKeyAndValueType() + { + // The source generator cannot support dictionaries whose key/value types are private. + DictionaryWithPrivateKeyAndValueType dictionary = DictionaryWithPrivateKeyAndValueType.CreatePopulatedInstance(); + string json = dictionary.GetExpectedJson(); + + Assert.True(Serializer.DefaultOptions.TryGetTypeInfo(typeof(DictionaryWithPrivateKeyAndValueType), out _)); + + await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(dictionary)); + await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json)); + } + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithNewSlotField))] [JsonSerializable(typeof(int))] @@ -184,6 +210,15 @@ public override async Task HonorJsonPropertyName_PrivateSetter() [JsonSerializable(typeof(ClassWithNewSlotInternalProperty))] [JsonSerializable(typeof(ClassWithPropertyPolicyConflict))] [JsonSerializable(typeof(ClassWithPrivateSetterAndGetter))] + [JsonSerializable(typeof(ClassWithProtectedMembers))] + [JsonSerializable(typeof(ClassWithProtectedGetter))] + [JsonSerializable(typeof(ClassWithProtectedSetter))] + [JsonSerializable(typeof(ClassWithPrivateProtectedMembers))] + [JsonSerializable(typeof(ClassWithPrivateProtectedGetter))] + [JsonSerializable(typeof(ClassWithPrivateProtectedSetter))] + [JsonSerializable(typeof(ClassWithInternalProtectedMembers))] + [JsonSerializable(typeof(ClassWithInternalProtectedGetter))] + [JsonSerializable(typeof(ClassWithInternalProtectedSetter))] [JsonSerializable(typeof(ClassWithIgnoreAttributeProperty))] [JsonSerializable(typeof(ClassWithIgnoredNewSlotField))] [JsonSerializable(typeof(MyStruct_WithNonPublicAccessors_WithTypeAttribute))] @@ -284,6 +319,8 @@ public override async Task HonorJsonPropertyName_PrivateSetter() [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IJoinInterface))] [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflict.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictIJoinInterface")] [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictUsingAttributeIJoinInterface")] + [JsonSerializable(typeof(CollectionWithPrivateElementType))] + [JsonSerializable(typeof(DictionaryWithPrivateKeyAndValueType))] internal sealed partial class PropertyVisibilityTestsContext_Metadata : JsonSerializerContext { } @@ -430,6 +467,15 @@ public void PublicContextAndJsonSerializerOptions() [JsonSerializable(typeof(ClassWithNewSlotInternalProperty))] [JsonSerializable(typeof(ClassWithPropertyPolicyConflict))] [JsonSerializable(typeof(ClassWithPrivateSetterAndGetter))] + [JsonSerializable(typeof(ClassWithProtectedMembers))] + [JsonSerializable(typeof(ClassWithProtectedGetter))] + [JsonSerializable(typeof(ClassWithProtectedSetter))] + [JsonSerializable(typeof(ClassWithPrivateProtectedMembers))] + [JsonSerializable(typeof(ClassWithPrivateProtectedGetter))] + [JsonSerializable(typeof(ClassWithPrivateProtectedSetter))] + [JsonSerializable(typeof(ClassWithInternalProtectedMembers))] + [JsonSerializable(typeof(ClassWithInternalProtectedGetter))] + [JsonSerializable(typeof(ClassWithInternalProtectedSetter))] [JsonSerializable(typeof(ClassWithIgnoreAttributeProperty))] [JsonSerializable(typeof(ClassWithIgnoredNewSlotField))] [JsonSerializable(typeof(MyStruct_WithNonPublicAccessors_WithTypeAttribute))] @@ -530,6 +576,8 @@ public void PublicContextAndJsonSerializerOptions() [JsonSerializable(typeof(IDiamondInterfaceHierarchy.IJoinInterface))] [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflict.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictIJoinInterface")] [JsonSerializable(typeof(IDiamondInterfaceHierarchyWithNamingConflictUsingAttribute.IJoinInterface), TypeInfoPropertyName = "IDiamondInterfaceHierarchyWithNamingConflictUsingAttributeIJoinInterface")] + [JsonSerializable(typeof(CollectionWithPrivateElementType))] + [JsonSerializable(typeof(DictionaryWithPrivateKeyAndValueType))] internal sealed partial class PropertyVisibilityTestsContext_Default : JsonSerializerContext { } 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 66a55a42ea3a25..922311a1c80bb2 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 @@ -483,6 +483,13 @@ namespace HelloWorld { public class Location { + [JsonInclude] + public int publicField; + [JsonInclude] + internal int internalField; + [JsonInclude] + private int privateField; + [JsonInclude] public int Id { get; private set; } [JsonInclude] 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 785906bb29c240..4f055c5d0ed8c4 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 @@ -344,12 +344,16 @@ public void WarnOnClassesWithInaccessibleJsonIncludeProperties() Location idLocation = compilation.GetSymbolsWithName("Id").First().Locations[0]; Location address2Location = compilation.GetSymbolsWithName("Address2").First().Locations[0]; Location countryLocation = compilation.GetSymbolsWithName("Country").First().Locations[0]; + Location internalFieldLocation = compilation.GetSymbolsWithName("internalField").First().Locations[0]; + Location privateFieldLocation = compilation.GetSymbolsWithName("privateField").First().Locations[0]; (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] { (idLocation, "The member 'Location.Id' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), (address2Location, "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), - (countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator.") + (countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + (internalFieldLocation, "The member 'Location.internalField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + (privateFieldLocation, "The member 'Location.privateField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), }; CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs index 4287e161ca048e..749673cd4a5ce4 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs @@ -426,8 +426,10 @@ public record AppRecord(int Id) CheckCompilationDiagnosticsErrors(result.Diagnostics); CheckCompilationDiagnosticsErrors(result.NewCompilation.GetDiagnostics()); - Assert.Equal(4, result.AllGeneratedTypes.Count()); + Assert.Equal(3, result.AllGeneratedTypes.Count()); result.AssertContainsType("global::HelloWorld.AppRecord"); + result.AssertContainsType("string"); + result.AssertContainsType("int"); } [Fact] @@ -463,8 +465,10 @@ internal partial class JsonContext : JsonSerializerContext CheckCompilationDiagnosticsErrors(result.Diagnostics); CheckCompilationDiagnosticsErrors(result.NewCompilation.GetDiagnostics()); - Assert.Equal(4, result.AllGeneratedTypes.Count()); + Assert.Equal(3, result.AllGeneratedTypes.Count()); result.AssertContainsType("global::ReferencedAssembly.LibRecord"); + result.AssertContainsType("string"); + result.AssertContainsType("int"); } [Fact] @@ -504,8 +508,10 @@ internal record AppRecord : LibRecord CheckCompilationDiagnosticsErrors(result.Diagnostics); CheckCompilationDiagnosticsErrors(result.NewCompilation.GetDiagnostics()); - Assert.Equal(4, result.AllGeneratedTypes.Count()); + Assert.Equal(3, result.AllGeneratedTypes.Count()); result.AssertContainsType("global::HelloWorld.AppRecord"); + result.AssertContainsType("string"); + result.AssertContainsType("int"); } private void CheckCompilationDiagnosticsErrors(ImmutableArray diagnostics)