Skip to content
Draft
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
49 changes: 49 additions & 0 deletions src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,55 @@ public bool IsImmutableDictionaryType(ITypeSymbol type, out string? factoryTypeF
return false;
}

/// <summary>
/// Determines whether the specified type symbol is a System.Tuple generic type.
/// </summary>
public bool IsReferenceTupleType(INamedTypeSymbol type)
{
if (!type.IsGenericType)
{
return false;
}

INamedTypeSymbol def = type.ConstructedFrom;
return def.Arity switch
{
1 => SymbolEqualityComparer.Default.Equals(def, TupleOfT1Type),
2 => SymbolEqualityComparer.Default.Equals(def, TupleOfT2Type),
3 => SymbolEqualityComparer.Default.Equals(def, TupleOfT3Type),
4 => SymbolEqualityComparer.Default.Equals(def, TupleOfT4Type),
5 => SymbolEqualityComparer.Default.Equals(def, TupleOfT5Type),
6 => SymbolEqualityComparer.Default.Equals(def, TupleOfT6Type),
7 => SymbolEqualityComparer.Default.Equals(def, TupleOfT7Type),
8 => SymbolEqualityComparer.Default.Equals(def, TupleOfT8Type),
_ => false,
};
}

public INamedTypeSymbol? TupleOfT1Type => GetOrResolveType("System.Tuple`1", ref _TupleOfT1Type);
private Option<INamedTypeSymbol?> _TupleOfT1Type;

public INamedTypeSymbol? TupleOfT2Type => GetOrResolveType("System.Tuple`2", ref _TupleOfT2Type);
private Option<INamedTypeSymbol?> _TupleOfT2Type;

public INamedTypeSymbol? TupleOfT3Type => GetOrResolveType("System.Tuple`3", ref _TupleOfT3Type);
private Option<INamedTypeSymbol?> _TupleOfT3Type;

public INamedTypeSymbol? TupleOfT4Type => GetOrResolveType("System.Tuple`4", ref _TupleOfT4Type);
private Option<INamedTypeSymbol?> _TupleOfT4Type;

public INamedTypeSymbol? TupleOfT5Type => GetOrResolveType("System.Tuple`5", ref _TupleOfT5Type);
private Option<INamedTypeSymbol?> _TupleOfT5Type;

public INamedTypeSymbol? TupleOfT6Type => GetOrResolveType("System.Tuple`6", ref _TupleOfT6Type);
private Option<INamedTypeSymbol?> _TupleOfT6Type;

public INamedTypeSymbol? TupleOfT7Type => GetOrResolveType("System.Tuple`7", ref _TupleOfT7Type);
private Option<INamedTypeSymbol?> _TupleOfT7Type;

public INamedTypeSymbol? TupleOfT8Type => GetOrResolveType("System.Tuple`8", ref _TupleOfT8Type);
private Option<INamedTypeSymbol?> _TupleOfT8Type;

private INamedTypeSymbol? GetOrResolveType(Type type, ref Option<INamedTypeSymbol?> field)
=> GetOrResolveType(type.FullName!, ref field);

Expand Down
108 changes: 107 additions & 1 deletion src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGeneratio
if (defaultCheckType != SerializedValueCheckType.None)
{
// Use temporary variable to evaluate property value only once
string localVariableName = $"__value_{propertyGenSpec.NameSpecifiedInSourceCode.TrimStart('@')}";
string localVariableName = $"__value_{propertyGenSpec.NameSpecifiedInSourceCode.TrimStart('@').Replace('.', '_')}";
writer.WriteLine($"{propertyGenSpec.PropertyType.FullyQualifiedName} {localVariableName} = {objectExpr}.{propertyGenSpec.NameSpecifiedInSourceCode};");
propValueExpr = localVariableName;
}
Expand Down Expand Up @@ -944,6 +944,11 @@ static void ThrowPropertyNullException(string propertyName)

private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec typeGenerationSpec)
{
if (typeGenerationSpec.HasNestedTupleElements)
{
return GetTupleCtorInvocationFunc(typeGenerationSpec);
}

ImmutableEquatableArray<ParameterGenerationSpec> parameters = typeGenerationSpec.CtorParamGenSpecs;
ImmutableEquatableArray<PropertyInitializerGenerationSpec> propertyInitializers = typeGenerationSpec.PropertyInitializerSpecs;

Expand Down Expand Up @@ -1019,6 +1024,107 @@ static string GetParamExpression(ParameterGenerationSpec param, string argsVarNa
}
}

/// <summary>
/// Generates nested constructor invocation for tuple types.
/// For a 10-element tuple, generates:
/// static args => new ValueTuple&lt;...&gt;((T)args[0], ..., new ValueTuple&lt;...&gt;((T)args[7], ...))
/// </summary>
private static string GetTupleCtorInvocationFunc(TypeGenerationSpec typeGenerationSpec)
{
ImmutableEquatableArray<ParameterGenerationSpec> parameters = typeGenerationSpec.CtorParamGenSpecs;
const string ArgsVarName = "args";

StringBuilder sb = new($"static {ArgsVarName} => ");
AppendNestedTupleConstructor(sb, typeGenerationSpec, parameters, 0, ArgsVarName);
return sb.ToString();
}

/// <summary>
/// Recursively appends nested tuple constructor expressions.
/// Groups every 7 elements and wraps the remainder in a nested ValueTuple/Tuple constructor.
/// </summary>
private static void AppendNestedTupleConstructor(
StringBuilder sb,
TypeGenerationSpec typeSpec,
ImmutableEquatableArray<ParameterGenerationSpec> parameters,
int startIndex,
string argsVarName)
{
int remaining = parameters.Count - startIndex;
int directArgs = remaining > 7 ? 7 : remaining;

// Build the constructor type name from the parameter types at this nesting level.
// We must use the CLR type name (e.g. global::System.ValueTuple<int, int, ...>)
// rather than the C# tuple syntax (int, int, ...) because `new` doesn't work with tuple syntax.
string tuplePrefix = typeSpec.IsValueTuple ? "global::System.ValueTuple" : "global::System.Tuple";

sb.Append($"new {tuplePrefix}<");
for (int i = 0; i < directArgs; i++)
{
sb.Append(parameters[startIndex + i].ParameterType.FullyQualifiedName);
sb.Append(", ");
}

if (remaining > 7)
{
// The last type argument is the nested tuple type — build it recursively.
AppendNestedTupleTypeName(sb, typeSpec, parameters, startIndex + 7);
}
else
{
sb.Length -= 2; // remove last ", "
}

sb.Append(">(");

for (int i = 0; i < directArgs; i++)
{
ParameterGenerationSpec param = parameters[startIndex + i];
sb.Append($"({param.ParameterType.FullyQualifiedName}){argsVarName}[{param.ArgsIndex}]");

if (i < directArgs - 1 || remaining > 7)
{
sb.Append(", ");
}
}

if (remaining > 7)
{
AppendNestedTupleConstructor(sb, typeSpec, parameters, startIndex + 7, argsVarName);
}

sb.Append(')');
}

private static void AppendNestedTupleTypeName(
StringBuilder sb,
TypeGenerationSpec typeSpec,
ImmutableEquatableArray<ParameterGenerationSpec> parameters,
int startIndex)
{
int remaining = parameters.Count - startIndex;
int directArgs = remaining > 7 ? 7 : remaining;
string tuplePrefix = typeSpec.IsValueTuple ? "global::System.ValueTuple" : "global::System.Tuple";

sb.Append($"{tuplePrefix}<");
for (int i = 0; i < directArgs; i++)
{
sb.Append(parameters[startIndex + i].ParameterType.FullyQualifiedName);
sb.Append(", ");
}

if (remaining > 7)
{
AppendNestedTupleTypeName(sb, typeSpec, parameters, startIndex + 7);
}
else
{
sb.Length -= 2;
}

sb.Append('>');
}

private static string? GetPrimitiveWriterMethod(TypeGenerationSpec type)
{
return type.PrimitiveTypeKind switch
Expand Down
Loading
Loading