private const string DefaultOptionsStaticVarName = "s_defaultOptions";
private const string DefaultContextBackingStaticVarName = "s_defaultContext";
internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory";
- private const string MakeReadOnlyMethodName = "MakeReadOnly";
private const string InfoVarName = "info";
private const string PropertyInfoVarName = "propertyInfo";
internal const string JsonContextVarName = "jsonContext";
string? @namespace = _currentContext.ContextType.Namespace;
bool isInGlobalNamespace = @namespace == JsonConstants.GlobalNamespaceValue;
- StringBuilder sb = new(@"// <auto-generated/>
+ StringBuilder sb = new("""
+// <auto-generated/>
#nullable enable annotations
#nullable disable warnings
// Suppress warnings about [Obsolete] member usage in generated code.
-#pragma warning disable CS0618");
+#pragma warning disable CS0612, CS0618
+
+
+""");
+ int indentation = 0;
if (!isInGlobalNamespace)
{
- sb.Append(@$"
-
-namespace {@namespace}
-{{");
+ sb.AppendLine($$"""
+namespace {{@namespace}}
+{
+""");
+ indentation++;
}
for (int i = 0; i < declarationCount - 1; i++)
{
- string declarationSource = $@"
-{declarationList[declarationCount - 1 - i]}
-{{";
- sb.Append($@"
-{IndentSource(declarationSource, numIndentations: i + 1)}
-");
+ string declarationSource = $$"""
+{{declarationList[declarationCount - 1 - i]}}
+{
+""";
+ sb.AppendLine(IndentSource(declarationSource, numIndentations: indentation++));
}
// Add the core implementation for the derived context class.
- string partialContextImplementation = $@"
-{generatedCodeAttributeSource}{declarationList[0]}{(interfaceImplementation is null ? "" : ": " + interfaceImplementation)}
-{{
- {IndentSource(source, Math.Max(1, declarationCount - 1))}
-}}";
- sb.AppendLine(IndentSource(partialContextImplementation, numIndentations: declarationCount));
-
- // Match curly brace for each containing type.
- for (int i = 0; i < declarationCount - 1; i++)
- {
- sb.AppendLine(IndentSource("}", numIndentations: declarationCount + i + 1));
- }
+ string partialContextImplementation = $$"""
+{{generatedCodeAttributeSource}}{{declarationList[0]}}{{(interfaceImplementation is null ? "" : ": " + interfaceImplementation)}}
+{
+{{IndentSource(source, 1)}}
+}
+""";
+ sb.AppendLine(IndentSource(partialContextImplementation, numIndentations: indentation--));
- if (!isInGlobalNamespace)
+ // Match curly braces for each containing type/namespace.
+ for (; indentation >= 0; indentation--)
{
- sb.AppendLine("}");
+ sb.AppendLine(IndentSource("}", numIndentations: indentation));
}
_sourceGenerationContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8));
string typeInfoPropertyTypeRef = $"{JsonTypeInfoTypeRef}<{typeCompilableName}>";
return @$"private {typeInfoPropertyTypeRef}? _{typeFriendlyName};
+
/// <summary>
/// Defines the source generated JSON serialization contract metadata for a given type.
/// </summary>
public {typeInfoPropertyTypeRef} {typeFriendlyName}
{{
- get => _{typeFriendlyName} ??= {typeMetadata.CreateTypeInfoMethodName}({OptionsInstanceVariableName}, makeReadOnly: true);
+ get => _{typeFriendlyName} ??= ({typeInfoPropertyTypeRef}){OptionsInstanceVariableName}.GetTypeInfo(typeof({typeCompilableName}));
}}
-private {typeInfoPropertyTypeRef} {typeMetadata.CreateTypeInfoMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}, bool makeReadOnly)
+private {typeInfoPropertyTypeRef} {typeMetadata.CreateTypeInfoMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})
{{
{typeInfoPropertyTypeRef}? {JsonTypeInfoReturnValueLocalVariableName} = null;
{WrapWithCheckForCustomConverter(metadataInitSource, typeCompilableName)}
- if (makeReadOnly)
- {{
- {JsonTypeInfoReturnValueLocalVariableName}.{MakeReadOnlyMethodName}();
- }}
-
return {JsonTypeInfoReturnValueLocalVariableName};
}}
{additionalSource}";
sb.Append(
@$"/// <inheritdoc/>
-public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type)
+public override {JsonTypeInfoTypeRef}? GetTypeInfo({TypeTypeRef} type)
{{");
-
- HashSet<TypeGenerationSpec> types = new(_currentContext.TypesWithMetadataGenerated);
-
- // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64.
- foreach (TypeGenerationSpec metadata in types)
+ // This method body grows linearly over the number of generated types.
+ // In line with https://github.com/dotnet/runtime/issues/77897 we should
+ // eventually replace this method with a direct call to Options.GetTypeInfo().
+ // We can't do this currently because Options.GetTypeInfo throws whereas
+ // this GetTypeInfo returns null for unsupported types, so we need new API to support it.
+ foreach (TypeGenerationSpec metadata in _currentContext.TypesWithMetadataGenerated)
{
if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
{
}
sb.AppendLine(@"
- return null!;
+ return null;
}");
// Explicit IJsonTypeInfoResolver implementation
sb.AppendLine();
sb.Append(@$"{JsonTypeInfoTypeRef}? {JsonTypeInfoResolverTypeRef}.GetTypeInfo({TypeTypeRef} type, {JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})
{{");
- // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64.
- foreach (TypeGenerationSpec metadata in types)
+ foreach (TypeGenerationSpec metadata in _currentContext.TypesWithMetadataGenerated)
{
if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
{
sb.Append($@"
if (type == typeof({metadata.TypeRef}))
{{
- return {metadata.CreateTypeInfoMethodName}({OptionsLocalVariableName}, makeReadOnly: false);
+ return {metadata.CreateTypeInfoMethodName}({OptionsLocalVariableName});
}}
");
}
private static string IndentSource(string source, int numIndentations)
{
- Debug.Assert(numIndentations >= 1);
- return source.Replace(Environment.NewLine, $"{Environment.NewLine}{new string(' ', 4 * numIndentations)}"); // 4 spaces per indentation.
+ if (numIndentations == 0)
+ {
+ return source;
+ }
+
+ string indentation = new string(' ', 4 * numIndentations); // 4 spaces per indentation.
+ return indentation + source.Replace(Environment.NewLine, Environment.NewLine + indentation);
}
private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) =>