From 74461db3920b52aa39b2f08e34c626b112ab1b8c Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 1 May 2025 14:51:25 -0700 Subject: [PATCH 1/2] Fix handling of nullable types in validations generator --- .../Extensions/ITypeSymbolExtensions.cs | 5 +++-- .../Parsers/ValidationsGenerator.TypesParser.cs | 9 +++++---- .../ValidationsGenerator.ComplexType.cs | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs index a5a580a33a53..89392a93849f 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs @@ -46,8 +46,9 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enu if (type.NullableAnnotation == NullableAnnotation.Annotated) { - // Extract the underlying type from a reference type - type = type.OriginalDefinition; + // Remove the nullable annotation but keep any generic arguments, e.g. List? → List + // so we can retain them in future steps. + type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated); } if (type is INamedTypeSymbol namedType && namedType.IsEnumerable(enumerable) && namedType.TypeArguments.Length == 1) diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs index 79bbc82e45a7..dd0092f31fd0 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs @@ -29,13 +29,14 @@ internal ImmutableArray ExtractValidatableTypes(IInvocationOper List visitedTypes = []; foreach (var parameter in parameters) { - _ = TryExtractValidatableType(parameter.Type.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)), wellKnownTypes, ref validatableTypes, ref visitedTypes); + _ = TryExtractValidatableType(parameter.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); } return [.. validatableTypes]; } - internal bool TryExtractValidatableType(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, ref HashSet validatableTypes, ref List visitedTypes) + internal bool TryExtractValidatableType(ITypeSymbol incomingTypeSymbol, WellKnownTypes wellKnownTypes, ref HashSet validatableTypes, ref List visitedTypes) { + var typeSymbol = incomingTypeSymbol.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)); if (typeSymbol.SpecialType != SpecialType.None) { return false; @@ -126,7 +127,7 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb // Check if the property's type is validatable, this resolves // validatable types in the inheritance hierarchy var hasValidatableType = TryExtractValidatableType( - correspondingProperty.Type.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)), + correspondingProperty.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); @@ -153,7 +154,7 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb continue; } - var hasValidatableType = TryExtractValidatableType(member.Type.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)), wellKnownTypes, ref validatableTypes, ref visitedTypes); + var hasValidatableType = TryExtractValidatableType(member.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); var attributes = ExtractValidationAttributes(member, wellKnownTypes, out var isRequired); // If the member has no validation attributes or validatable types and is not required, skip it. diff --git a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs index 75ebb1c81652..50cd7eca1769 100644 --- a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs +++ b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs @@ -45,7 +45,8 @@ public class ComplexType public SubTypeWithInheritance PropertyWithInheritance { get; set; } = new SubTypeWithInheritance("some-value", default); - public List ListOfSubTypes { get; set; } = []; + // Nullable to validate https://github.com/dotnet/aspnetcore/issues/61737 + public List? ListOfSubTypes { get; set; } = []; [DerivedValidation(ErrorMessage = "Value must be an even number")] public int IntegerWithDerivedValidationAttribute { get; set; } From 1b7b2efe9f18c59ab4b88781480d69006798a12a Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 1 May 2025 23:07:54 -0700 Subject: [PATCH 2/2] Fix property type in parsable test --- .../test/ValidationsGenerator/ValidationsGenerator.Parsable.cs | 2 +- ...WithParsableProperties#ValidatableInfoResolver.g.verified.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs index 79bd2916ae12..6cebd6df8584 100644 --- a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs +++ b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs @@ -39,7 +39,7 @@ public class ComplexTypeWithParsableProperties public TimeOnly? TimeOnlyWithRequiredValue { get; set; } = TimeOnly.FromDateTime(DateTime.UtcNow); [Url(ErrorMessage = "The field Url must be a valid URL.")] - public Uri? Url { get; set; } = new Uri("https://example.com"); + public string? Url { get; set; } = "https://example.com"; [Required] [Range(typeof(DateOnly), "2023-01-01", "2025-12-31", ErrorMessage = "Date must be between 2023-01-01 and 2025-12-31")] diff --git a/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs b/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs index ef6057388d2a..7eea5401c9c1 100644 --- a/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs +++ b/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs @@ -95,7 +95,7 @@ private ValidatableTypeInfo CreateComplexTypeWithParsableProperties() ), new GeneratedValidatablePropertyInfo( containingType: typeof(global::ComplexTypeWithParsableProperties), - propertyType: typeof(global::System.Uri), + propertyType: typeof(string), name: "Url", displayName: "Url" ),