Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
| __`SYSLIB1237`__ | _`SYSLIB1230`-`SYSLIB1239` reserved for Microsoft.Interop.ComInterfaceGenerator._ |
| __`SYSLIB1238`__ | _`SYSLIB1230`-`SYSLIB1239` reserved for Microsoft.Interop.ComInterfaceGenerator._ |
| __`SYSLIB1239`__ | _`SYSLIB1230`-`SYSLIB1239` reserved for Microsoft.Interop.ComInterfaceGenerator._ |
| __`SYSLIB1240`__ | Derived type is not a supported polymorphic derived type. |
| __`SYSLIB1241`__ | Inferred derived type is less accessible than the polymorphic base type. |
| __`SYSLIB1242`__ | Derived types produce a duplicate type discriminator. |
| __`SYSLIB1243`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1244`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1245`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1246`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1247`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1248`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1249`__ | _`SYSLIB1240`-`SYSLIB1249` reserved for System.Text.Json.SourceGeneration._ |

### Diagnostic Suppressions (`SYSLIBSUPPRESS****`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,10 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
/// Specifies the default value of <see cref="JsonSerializerOptions.AllowDuplicateProperties"/> when set.
/// </summary>
public bool AllowDuplicateProperties { get; set; }

/// <summary>
/// Specifies the default value of <see cref="JsonSerializerOptions.InferClosedTypePolymorphism"/> when set.
/// </summary>
public bool InferClosedTypePolymorphism { get; set; }
}
}
383 changes: 383 additions & 0 deletions src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,30 @@ internal static class DiagnosticDescriptors
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static DiagnosticDescriptor DerivedTypeIsNotSupported { get; } = DiagnosticDescriptorHelper.Create(
id: "SYSLIB1240",
title: new LocalizableResourceString(nameof(SR.DerivedTypeIsNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
messageFormat: new LocalizableResourceString(nameof(SR.DerivedTypeIsNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static DiagnosticDescriptor InferredDerivedTypeIsNotAccessible { get; } = DiagnosticDescriptorHelper.Create(
id: "SYSLIB1241",
title: new LocalizableResourceString(nameof(SR.InferredDerivedTypeIsNotAccessibleTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
messageFormat: new LocalizableResourceString(nameof(SR.InferredDerivedTypeIsNotAccessibleMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);

public static DiagnosticDescriptor DerivedTypeDiscriminatorCollision { get; } = DiagnosticDescriptorHelper.Create(
id: "SYSLIB1242",
title: new LocalizableResourceString(nameof(SR.DerivedTypeDiscriminatorCollisionTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
messageFormat: new LocalizableResourceString(nameof(SR.DerivedTypeDiscriminatorCollisionMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ private sealed partial class Emitter
/// </summary>
private static class ExceptionMessages
{
public const string ClosedTypeInferenceRequiresCompileTimeOptIn =
"The 'JsonSerializerOptions.InferClosedTypePolymorphism' setting is enabled for the closed type '{0}', but its derived type metadata was not generated by the source generator. Enable 'JsonSourceGenerationOptionsAttribute.InferClosedTypePolymorphism' when generating the metadata, or register the derived types explicitly using 'JsonDerivedTypeAttribute'.";

public const string IncompatibleConverterType =
"The converter '{0}' is not compatible with the type '{1}'.";

Expand Down
64 changes: 29 additions & 35 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ private SourceText GenerateForCollection(ContextGenerationSpec contextSpec, Type
jsonTypeInfo.NumberHandling = {{FormatNumberHandling(typeGenerationSpec.NumberHandling)}};
""");

GenerateClosedTypeInferenceGuard(writer, typeGenerationSpec);

GenerateTypeInfoFactoryFooter(writer);

if (serializeMethodName != null)
Expand Down Expand Up @@ -634,6 +636,8 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
}
}

GenerateClosedTypeInferenceGuard(writer, typeMetadata);

GenerateTypeInfoFactoryFooter(writer);

if (propInitMethodName != null)
Expand Down Expand Up @@ -686,20 +690,6 @@ private static SourceText GenerateForUnion(ContextGenerationSpec contextSpec, Ty
// the canonical switch arm (preferring the non-Nullable<T> sibling so
// most-derived dispatch reports typeof(T)). The null payload is handled
// separately by the `null =>` arm via nullCase.
int switchArmCount = 0;
bool armsMergeDeclaredCases = false;
foreach (UnionCaseSpec caseSpec in unionCases)
{
if (caseSpec.IsSwitchArm)
{
switchArmCount++;
}
else
{
armsMergeDeclaredCases = true;
}
}

string unionCasesExpr = unionCases.Count == 0
? $"global::System.Array.Empty<{JsonUnionCaseInfoTypeRef}>()"
: $$"""new {{JsonUnionCaseInfoTypeRef}}[] { {{string.Join(", ", unionCases.Select(c => $"new {JsonUnionCaseInfoTypeRef}(typeof({c.CaseType.FullyQualifiedName})) {{ IsNullable = {(c.IsNullable ? "true" : "false")} }}"))}} }""";
Expand Down Expand Up @@ -748,22 +738,7 @@ private static SourceText GenerateForUnion(ContextGenerationSpec contextSpec, Ty
writer.WriteLine("},");

// The deconstructor switch has no `_` arm — it relies on the union's
// declared case set being exhaustively covered by its arms. Roslyn's
// union exhaustiveness analyzer fails to recognize coverage in two
// shapes today: (a) when switchArmCount == 1 the switch looks
// non-exhaustive on `object?`-shaped surface area, and (b) when
// Foo(T)+Foo(Nullable<T>) overloads merge into a single `T` arm the
// Nullable<T> declared case isn't seen as covered. Tracked by
// https://github.com/dotnet/roslyn/issues/83666; the fix is present
// in Roslyn 5.9.0-1.26279.1 and later. Once the compiler bundled by
// this repo's SDK reaches that version this pragma and the
// `armsMergeDeclaredCases` plumbing can be removed.
bool needsExhaustivenessPragma = switchArmCount == 1 || armsMergeDeclaredCases;
if (needsExhaustivenessPragma)
{
writer.WriteLine("#pragma warning disable CS8509 // https://github.com/dotnet/roslyn/issues/83666");
}

// declared case set being exhaustively covered by its arms.
writer.WriteLine($"UnionDeconstructor = static ({genericArg} value) =>");
writer.WriteLine('{');
writer.Indentation++;
Expand Down Expand Up @@ -816,11 +791,6 @@ private static SourceText GenerateForUnion(ContextGenerationSpec contextSpec, Ty
writer.WriteLine("};");
writer.Indentation--;
writer.WriteLine("},");

if (needsExhaustivenessPragma)
{
writer.WriteLine("#pragma warning restore CS8509");
}
}

writer.WriteLine("TypeClassifier = null,");
Expand Down Expand Up @@ -1959,6 +1929,27 @@ private static void GenerateTypeInfoFactoryFooter(SourceWriter writer)
""");
}

/// <summary>
/// Emits a runtime guard for closed hierarchies whose derived-type polymorphism metadata was not
/// generated because <c>JsonSourceGenerationOptionsAttribute.InferClosedTypePolymorphism</c> was
/// disabled at compile time. Enabling the setting only on the runtime <c>JsonSerializerOptions</c>
/// cannot recover that metadata, so we fail explicitly rather than silently serializing the base
/// type non-polymorphically.
/// </summary>
private static void GenerateClosedTypeInferenceGuard(SourceWriter writer, TypeGenerationSpec typeSpec)
{
if (typeSpec.IsClosedTypeWithoutInferredPolymorphism)
{
writer.WriteLine($$"""

if (options.InferClosedTypePolymorphism)
{
throw new {{InvalidOperationExceptionTypeRef}}(string.Format("{{ExceptionMessages.ClosedTypeInferenceRequiresCompileTimeOptIn}}", typeof({{typeSpec.TypeRef.FullyQualifiedName}})));
}
""");
}
}

private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec contextSpec, bool emitGetConverterForNullablePropertyMethod, bool emitValueTypeSetterDelegate, bool emitByteArrayValueHelper)
{
string contextTypeRef = contextSpec.ContextType.FullyQualifiedName;
Expand Down Expand Up @@ -2119,6 +2110,9 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti
if (optionsSpec.IncludeFields is bool includeFields)
writer.WriteLine($"IncludeFields = {FormatBoolLiteral(includeFields)},");

if (optionsSpec.InferClosedTypePolymorphism is bool inferClosedTypePolymorphism)
writer.WriteLine($"InferClosedTypePolymorphism = {FormatBoolLiteral(inferClosedTypePolymorphism)},");

if (optionsSpec.MaxDepth is int maxDepth)
writer.WriteLine($"MaxDepth = {maxDepth},");

Expand Down
Loading
Loading