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
22 changes: 22 additions & 0 deletions src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,32 @@ namespace System.Text.Json.SourceGeneration
internal static class RoslynExtensions
{
private static readonly Func<ITypeSymbol, bool>? s_isClosedTypeAccessor = CreateIsClosedTypeAccessor();
private static readonly Func<IModuleSymbol, int>? s_memorySafetyRulesVersionAccessor = CreateMemorySafetyRulesVersionAccessor();

public static LanguageVersion? GetLanguageVersion(this Compilation compilation)
=> compilation is CSharpCompilation csc ? csc.LanguageVersion : null;

public static bool UsesUpdatedMemorySafetyRules(this SemanticModel semanticModel)
{
const int UpdatedMemorySafetyRulesVersion = 2;

// The module API includes both the compilation option and the legacy feature flag.
// Older compiler hosts expose only the temporary feature-flag opt-in.
return s_memorySafetyRulesVersionAccessor is { } getVersion
? getVersion(semanticModel.Compilation.SourceModule) >= UpdatedMemorySafetyRulesVersion
: semanticModel.SyntaxTree.Options.Features.ContainsKey("updated-memory-safety-rules");
}

private static Func<IModuleSymbol, int>? CreateMemorySafetyRulesVersionAccessor()
{
// The enum-valued API is unavailable in Roslyn 3.11; delegate binding accepts its underlying int type.
// https://github.com/dotnet/roslyn/pull/84860
MethodInfo? getter = typeof(IModuleSymbol).GetProperty("MemorySafetyRulesVersion")?.GetMethod;
return getter is null
? null
: (Func<IModuleSymbol, int>)getter.CreateDelegate(typeof(Func<IModuleSymbol, int>));
}

public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, Type type)
{
Debug.Assert(!type.IsArray, "Resolution logic only capable of handling named types.");
Expand Down
156 changes: 119 additions & 37 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Large diffs are not rendered by default.

64 changes: 54 additions & 10 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SourceGenerators;
using GenericTypeMetadata = (string? OpenDeclaringTypeFQN, SourceGenerators.ImmutableEquatableArray<string>? DeclaringTypeParameterNames, string? DeclaringTypeParameterConstraintClauses);

namespace System.Text.Json.SourceGeneration
{
Expand Down Expand Up @@ -57,6 +58,7 @@ private sealed class Parser
private readonly Queue<TypeToGenerate> _typesToGenerate = new();
#pragma warning disable RS1024 // Compare symbols correctly https://github.com/dotnet/roslyn-analyzers/issues/5804
private readonly Dictionary<ITypeSymbol, TypeGenerationSpec> _generatedTypes = new(SymbolEqualityComparer.Default);
private readonly Dictionary<INamedTypeSymbol, GenericTypeMetadata> _genericTypeDefinitions = new(SymbolEqualityComparer.Default);
#pragma warning restore

public List<Diagnostic> Diagnostics { get; } = new();
Expand Down Expand Up @@ -99,6 +101,7 @@ public Parser(KnownTypeSymbols knownSymbols)
// Ensure context-scoped metadata caches are empty.
Debug.Assert(_typesToGenerate.Count == 0);
Debug.Assert(_generatedTypes.Count == 0);
Debug.Assert(_genericTypeDefinitions.Count == 0);
Debug.Assert(_contextClassLocation is null);

INamedTypeSymbol? contextTypeSymbol = semanticModel.GetDeclaredSymbol(contextClassDeclaration, cancellationToken);
Expand Down Expand Up @@ -178,10 +181,12 @@ public Parser(KnownTypeSymbols knownSymbols)
Namespace = contextTypeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns ? ns.ToDisplayString() : null,
ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(),
GeneratedOptionsSpec = options,
UseUpdatedMemorySafetyRules = semanticModel.UsesUpdatedMemorySafetyRules(),
};

// Clear the caches of generated metadata between the processing of context classes.
_generatedTypes.Clear();
_genericTypeDefinitions.Clear();
_typesToGenerate.Clear();
_contextClassLocation = null;
return contextGenSpec;
Expand Down Expand Up @@ -909,9 +914,18 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener
classType = ClassType.TypeUnsupportedBySourceGen;
}

bool canUseGenericUnsafeAccessors = type is INamedTypeSymbol { IsGenericType: true } namedConstructorType
&& _knownSymbols.SupportsGenericUnsafeAccessors
&& !contextType.IsGenericType
&& namedConstructorType.ContainingType is not { IsGenericType: true };
Comment thread
svick marked this conversation as resolved.
GenericTypeMetadata genericTypeDefinition = GetGenericTypeDefinition(type);

return new TypeGenerationSpec
{
TypeRef = typeRef,
OpenDeclaringTypeFQN = genericTypeDefinition.OpenDeclaringTypeFQN,
DeclaringTypeParameterNames = genericTypeDefinition.DeclaringTypeParameterNames,
DeclaringTypeParameterConstraintClauses = genericTypeDefinition.DeclaringTypeParameterConstraintClauses,
TypeInfoPropertyName = typeInfoPropertyName,
GenerationMode = typeToGenerate.Mode ?? options?.GenerationMode ?? JsonSourceGenerationMode.Default,
ClassType = classType,
Expand Down Expand Up @@ -939,8 +953,7 @@ polymorphismOptions is null &&
ConstructorIsInaccessible = constructorIsInaccessible,
CanUseUnsafeAccessorForConstructor = constructorIsInaccessible
&& _knownSymbols.UnsafeAccessorAttributeType is not null
&& (type is not INamedTypeSymbol { IsGenericType: true }
|| _knownSymbols.SupportsGenericUnsafeAccessors),
&& (type is not INamedTypeSymbol { IsGenericType: true } || canUseGenericUnsafeAccessors),
NullableUnderlyingType = nullableUnderlyingType,
RuntimeTypeRef = runtimeTypeRef,
IsValueTuple = type.IsTupleType,
Expand Down Expand Up @@ -2204,10 +2217,12 @@ private List<PropertyGenerationSpec> ParsePropertyGenerationSpecs(
List<PropertyGenerationSpec> properties = new();
PropertyHierarchyResolutionState state = new(options);
hasExtensionDataProperty = false;
int declaringTypeIndex = -1;

// Walk the type hierarchy starting from the current type up to the base type(s)
foreach (INamedTypeSymbol currentType in typeToGenerate.Type.GetSortedTypeHierarchy())
{
declaringTypeIndex++;
var declaringTypeRef = new TypeRef(currentType);
ImmutableArray<ISymbol> members = currentType.GetMembers();

Expand Down Expand Up @@ -2278,6 +2293,7 @@ void AddMember(
PropertyGenerationSpec? propertySpec = ParsePropertyGenerationSpec(
contextType,
declaringTypeRef,
declaringTypeIndex,
typeLocation,
memberType,
memberInfo,
Expand Down Expand Up @@ -2421,6 +2437,7 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type)
private PropertyGenerationSpec? ParsePropertyGenerationSpec(
INamedTypeSymbol contextType,
TypeRef declaringType,
int declaringTypeIndex,
Location? typeLocation,
ITypeSymbol memberType,
ISymbol memberInfo,
Expand Down Expand Up @@ -2549,6 +2566,12 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type)
}
}

bool canUseGenericUnsafeAccessors = memberInfo.ContainingType.IsGenericType
&& _knownSymbols.SupportsGenericUnsafeAccessors
&& !contextType.IsGenericType
&& memberInfo.ContainingType.ContainingType is not { IsGenericType: true };
GenericTypeMetadata genericTypeDefinition = GetGenericTypeDefinition(memberInfo.ContainingType);

return new PropertyGenerationSpec
{
NameSpecifiedInSourceCode = memberInfo.MemberNameNeedsAtSign() ? "@" + memberInfo.Name : memberInfo.Name,
Expand All @@ -2572,18 +2595,17 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type)
HasJsonInclude = hasJsonInclude,
CanUseUnsafeAccessors = _knownSymbols.UnsafeAccessorAttributeType is not null
&& (memberInfo.ContainingType is not INamedTypeSymbol { IsGenericType: true }
|| _knownSymbols.SupportsGenericUnsafeAccessors),
OpenDeclaringTypeFQN = memberInfo.ContainingType is INamedTypeSymbol { IsGenericType: true } && _knownSymbols.SupportsGenericUnsafeAccessors
? memberInfo.ContainingType.OriginalDefinition.GetFullyQualifiedName() : null,
OpenPropertyTypeFQN = memberInfo.ContainingType is INamedTypeSymbol { IsGenericType: true } && _knownSymbols.SupportsGenericUnsafeAccessors
|| canUseGenericUnsafeAccessors),
OpenDeclaringTypeFQN = genericTypeDefinition.OpenDeclaringTypeFQN,
OpenPropertyTypeFQN = canUseGenericUnsafeAccessors
&& !SymbolEqualityComparer.Default.Equals(memberType, memberInfo.OriginalDefinition.GetMemberType())
? memberInfo.OriginalDefinition.GetMemberType().GetFullyQualifiedName() : null,
DeclaringTypeParameterNames = memberInfo.ContainingType is INamedTypeSymbol { IsGenericType: true } namedType && _knownSymbols.SupportsGenericUnsafeAccessors
? namedType.OriginalDefinition.TypeParameters.Select(tp => tp.Name).ToImmutableEquatableArray() : null,
DeclaringTypeParameterConstraintClauses = memberInfo.ContainingType is INamedTypeSymbol { IsGenericType: true } namedType2 && _knownSymbols.SupportsGenericUnsafeAccessors
? GetTypeParameterConstraintClauses(namedType2.OriginalDefinition) : null,
DeclaringTypeParameterNames = genericTypeDefinition.DeclaringTypeParameterNames,
DeclaringTypeParameterConstraintClauses = genericTypeDefinition.DeclaringTypeParameterConstraintClauses,
IsExtensionData = isExtensionData,
PropertyType = propertyTypeRef,
DeclaringType = declaringType,
DeclaringTypeIndex = declaringTypeIndex,
ConverterType = converterType,
IsGetterNonNullableAnnotation = isGetterNonNullable,
IsSetterNonNullableAnnotation = isSetterNonNullable,
Expand Down Expand Up @@ -2871,6 +2893,8 @@ private void ProcessMember(
constructorParameters[i] = new ParameterGenerationSpec
{
ParameterType = parameterTypeRef,
OpenParameterTypeFQN = !SymbolEqualityComparer.Default.Equals(parameterInfo.Type, parameterInfo.OriginalDefinition.Type)
? parameterInfo.OriginalDefinition.Type.GetFullyQualifiedName() : null,
Name = parameterInfo.Name,
HasDefaultValue = parameterInfo.HasExplicitDefaultValue,
DefaultValue = parameterInfo.HasExplicitDefaultValue ? parameterInfo.ExplicitDefaultValue : null,
Expand Down Expand Up @@ -3406,6 +3430,26 @@ void AddTypeIfNotNull(ITypeSymbol? type)
}
}

private GenericTypeMetadata GetGenericTypeDefinition(ITypeSymbol type)
{
if (type is not INamedTypeSymbol { IsGenericType: true } namedType)
{
return default;
}

namedType = namedType.OriginalDefinition;
if (!_genericTypeDefinitions.TryGetValue(namedType, out GenericTypeMetadata result))
{
result = (
namedType.GetFullyQualifiedName(),
namedType.TypeParameters.Select(tp => tp.MemberNameNeedsAtSign() ? "@" + tp.Name : tp.Name).ToImmutableEquatableArray(),
GetTypeParameterConstraintClauses(namedType));
_genericTypeDefinitions.Add(namedType, result);
}

return result;
}

/// <summary>
/// Extracts the type parameter constraint clauses from a generic type using
/// Roslyn's <see cref="SymbolDisplayGenericsOptions.IncludeTypeConstraints"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,7 @@ public sealed record ContextGenerationSpec
public required ImmutableEquatableArray<string> ContextClassDeclarations { get; init; }

public required SourceGenerationOptionsSpec? GeneratedOptionsSpec { get; init; }

public required bool UseUpdatedMemorySafetyRules { get; init; }
Comment thread
eiriktsarpalis marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,13 @@ public sealed record ParameterGenerationSpec
/// The ref kind of the parameter (None, Ref, Out, In, or RefReadOnlyParameter).
/// </summary>
public required RefKind RefKind { get; init; }

/// <summary>
/// The FQN of the parameter type using the open type parameters described by
/// <see cref="TypeGenerationSpec.DeclaringTypeParameterNames"/>
/// (e.g., "global::System.Collections.Generic.List&lt;T[]&gt;").
/// Null when the parameter type does not contain generic parameters.
/// </summary>
public string? OpenParameterTypeFQN { get; init; }
}
}
23 changes: 16 additions & 7 deletions src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,6 @@ public sealed record PropertyGenerationSpec
/// </summary>
public string? OpenDeclaringTypeFQN { get; init; }

/// <summary>
/// When <see cref="CanUseUnsafeAccessors"/> is true and the declaring type is generic,
/// contains the FQN of the property type using open type parameters
/// (e.g., "T" for a generic property, "string" for a concrete one).
/// </summary>
public string? OpenPropertyTypeFQN { get; init; }

/// <summary>
/// The type parameter names of the generic declaring type (e.g., ["T"]).
/// Null when the declaring type is not generic.
Expand Down Expand Up @@ -181,11 +174,27 @@ public sealed record PropertyGenerationSpec
/// </summary>
public required TypeRef DeclaringType { get; init; }

/// <summary>
/// The zero-based position of <see cref="DeclaringType"/> in the inheritance hierarchy
/// of the containing <see cref="TypeGenerationSpec"/>.
/// The contract type comes first, followed by its base types in most-derived-first order.
/// </summary>
public required int DeclaringTypeIndex { get; init; }

/// <summary>
/// Design-time specified custom converter type.
/// </summary>
public required TypeRef? ConverterType { get; init; }

/// <summary>
/// When <see cref="CanUseUnsafeAccessors"/> is true and the declaring type is generic,
/// contains the FQN of the property or field type using the open type parameters
/// described by <see cref="DeclaringTypeParameterNames"/>
/// (e.g., "global::System.Collections.Generic.List&lt;T[]&gt;").
/// Null when the member type does not contain generic parameters.
/// </summary>
public string? OpenPropertyTypeFQN { get; init; }

/// <summary>
/// Determines if the specified property should be included in the fast-path method body.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ public sealed record TypeGenerationSpec
/// </summary>
public required TypeRef TypeRef { get; init; }

/// <summary>
/// When the type is generic, contains the FQN of the declaring type using open type parameters
/// (e.g., "global::TestApp.MyGenericType&lt;T&gt;").
/// </summary>
public string? OpenDeclaringTypeFQN { get; init; }

/// <summary>
/// The type parameter names of the generic declaring type (e.g., ["T"]).
/// Null when the declaring type is not generic.
/// </summary>
public ImmutableEquatableArray<string>? DeclaringTypeParameterNames { get; init; }

/// <summary>
/// The combined type parameter constraint clauses of the generic declaring type
/// (e.g., "where T : notnull, global::MyNamespace.MyBase where U : struct").
/// Null when the declaring type is not generic or has no constraints.
/// </summary>
public string? DeclaringTypeParameterConstraintClauses { get; init; }

/// <summary>
/// The name of the public <c>JsonTypeInfo&lt;T&gt;</c> property for this type on the generated context class.
/// For example, if the context class is named MyJsonContext, and the value of this property is JsonMessage;
Expand Down
Loading
Loading