From 0903772bf38d1c156578e5c8d01f4c4d2bfd9fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Mon, 15 Sep 2025 18:34:28 +0200 Subject: [PATCH 1/3] Proof-of-concept fix for getting validation SG to work in Blazor Wasm SDK projects --- src/Shared/RoslynUtils/SymbolExtensions.cs | 14 +++- src/Shared/RoslynUtils/WellKnownTypes.cs | 25 ++++--- .../gen/Extensions/ISymbolExtensions.cs | 7 +- .../gen/Extensions/ITypeSymbolExtensions.cs | 70 ++++++++++++++++--- .../Microsoft.Extensions.Validation.csproj | 15 +++- 5 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/Shared/RoslynUtils/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs index 8a2f07fcb200..62b08ff6395c 100644 --- a/src/Shared/RoslynUtils/SymbolExtensions.cs +++ b/src/Shared/RoslynUtils/SymbolExtensions.cs @@ -49,8 +49,13 @@ public static IEnumerable GetThisAndBaseTypes(this ITypeSymbol? typ } } - public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) + public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol? attributeType) { + if (attributeType is null) + { + return false; + } + foreach (var attributeData in symbol.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, attributeType)) @@ -67,8 +72,13 @@ public static bool HasAttribute(this ImmutableArray attributes, I return attributes.TryGetAttribute(attributeType, out _); } - public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol attributeSymbol) + public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol? attributeSymbol) { + if (attributeSymbol is null) + { + return false; + } + var current = typeSymbol; while (current is not null) diff --git a/src/Shared/RoslynUtils/WellKnownTypes.cs b/src/Shared/RoslynUtils/WellKnownTypes.cs index a3a5cd3061b7..f3b66246ab22 100644 --- a/src/Shared/RoslynUtils/WellKnownTypes.cs +++ b/src/Shared/RoslynUtils/WellKnownTypes.cs @@ -58,8 +58,13 @@ public INamedTypeSymbol Get(SpecialType type) return _compilation.GetSpecialType(type); } - public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) + public INamedTypeSymbol? Get(WellKnownTypeData.WellKnownType? type) { + if (type is null) + { + return null; + } + var index = (int)type; var symbol = _lazyWellKnownTypes[index]; if (symbol is not null) @@ -72,18 +77,18 @@ public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) return GetAndCache(index); } - private INamedTypeSymbol GetAndCache(int index) + private INamedTypeSymbol? GetAndCache(int index) { var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]); - if (result == null) - { - throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'."); - } + //if (result == null) + //{ + // throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'."); + //} Interlocked.CompareExchange(ref _lazyWellKnownTypes[index], result, null); // GetTypeByMetadataName should always return the same instance for a name. // To ensure we have a consistent value, for thread safety, return symbol set in the array. - return _lazyWellKnownTypes[index]!; + return _lazyWellKnownTypes[index]; } // Filter for types within well-known (framework-owned) assemblies only. @@ -130,7 +135,7 @@ public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnown return false; } - public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType[] interfaceWellKnownTypes) + public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType?[] interfaceWellKnownTypes) { foreach (var wellKnownType in interfaceWellKnownTypes) { @@ -143,9 +148,9 @@ public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType[] inter return false; } - public static bool Implements(ITypeSymbol? type, ITypeSymbol interfaceType) + public static bool Implements(ITypeSymbol? type, ITypeSymbol? interfaceType) { - if (type is null) + if (type is null || interfaceType is null) { return false; } diff --git a/src/Validation/gen/Extensions/ISymbolExtensions.cs b/src/Validation/gen/Extensions/ISymbolExtensions.cs index a770b7260656..293a6a0c7068 100644 --- a/src/Validation/gen/Extensions/ISymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ISymbolExtensions.cs @@ -10,8 +10,13 @@ namespace Microsoft.Extensions.Validation; internal static class ISymbolExtensions { - public static string GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute) + public static string GetDisplayName(this ISymbol property, INamedTypeSymbol? displayAttribute) { + if (displayAttribute is null) + { + return property.Name; + } + var displayNameAttribute = property.GetAttributes() .FirstOrDefault(attribute => attribute.AttributeClass is { } attributeClass && diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs index 3532dbd69ebc..18625b0baf8c 100644 --- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Immutable; using System.Linq; using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; @@ -21,8 +22,13 @@ public static bool IsEnumerable(this ITypeSymbol type, INamedTypeSymbol enumerab return type.ImplementsInterface(enumerable) || SymbolEqualityComparer.Default.Equals(type, enumerable); } - public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol validationAttributeSymbol) + public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol? validationAttributeSymbol) { + if (validationAttributeSymbol is null) + { + return false; + } + var baseType = typeSymbol.BaseType; while (baseType != null) { @@ -36,8 +42,9 @@ public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, IN return false; } - public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enumerable) + public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol? enumerable) { + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T && type is INamedTypeSymbol { TypeArguments.Length: 1 }) { @@ -52,6 +59,11 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enu type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated); } + if (enumerable is null) + { + return type; + } + if (type is INamedTypeSymbol namedType && namedType.IsEnumerable(enumerable) && namedType.TypeArguments.Length == 1) { // Extract the T from an IEnumerable or List @@ -61,8 +73,13 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enu return type; } - internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol interfaceType) + internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol? interfaceType) { + if (interfaceType is null) + { + return false; + } + foreach (var iface in type.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(interfaceType, iface)) @@ -73,8 +90,13 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol inte return false; } - internal static ImmutableArray? GetJsonDerivedTypes(this ITypeSymbol type, INamedTypeSymbol jsonDerivedTypeAttribute) + internal static ImmutableArray? GetJsonDerivedTypes(this ITypeSymbol type, INamedTypeSymbol? jsonDerivedTypeAttribute) { + if (jsonDerivedTypeAttribute is null) + { + return null; + } + var derivedTypes = ImmutableArray.CreateBuilder(); foreach (var attribute in type.GetAttributes()) { @@ -95,7 +117,9 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol inte // types themselves so we short-circuit on them. internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnownTypes) { - return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) + try + { + return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpRequest)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpResponse)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Threading_CancellationToken)) @@ -104,6 +128,12 @@ internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnow || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFile)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Stream)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Pipelines_PipeReader)); + + } + catch (InvalidOperationException) + { + return false; + } } internal static IPropertySymbol? FindPropertyIncludingBaseTypes(this INamedTypeSymbol typeSymbol, string propertyName) @@ -132,8 +162,13 @@ internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnow /// The parameter to check. /// The symbol representing the [FromService] attribute. /// The symbol representing the [FromKeyedService] attribute. - internal static bool IsServiceParameter(this IParameterSymbol parameter, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol) + internal static bool IsServiceParameter(this IParameterSymbol parameter, INamedTypeSymbol? fromServiceMetadataSymbol, INamedTypeSymbol? fromKeyedServiceAttributeSymbol) { + if (fromServiceMetadataSymbol is null || fromKeyedServiceAttributeSymbol is null) + { + return false; + } + return parameter.GetAttributes().Any(attr => attr.AttributeClass is not null && (attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) || @@ -146,8 +181,13 @@ attr.AttributeClass is not null && /// The property to check. /// The symbol representing the [FromServices] attribute. /// The symbol representing the [FromKeyedServices] attribute. - internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol) + internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol? fromServiceMetadataSymbol, INamedTypeSymbol? fromKeyedServiceAttributeSymbol) { + if (fromServiceMetadataSymbol is null || fromKeyedServiceAttributeSymbol is null) + { + return false; + } + return property.GetAttributes().Any(attr => attr.AttributeClass is not null && (attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) || @@ -159,19 +199,29 @@ attr.AttributeClass is not null && /// /// The property to check. /// The symbol representing the [JsonIgnore] attribute. - internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol jsonIgnoreAttributeSymbol) + internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol? jsonIgnoreAttributeSymbol) { + if (jsonIgnoreAttributeSymbol is null) + { + return false; + } + return property.GetAttributes().Any(attr => attr.AttributeClass is not null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol)); } - internal static bool IsSkippedValidationProperty(this IPropertySymbol property, INamedTypeSymbol skipValidationAttributeSymbol) + internal static bool IsSkippedValidationProperty(this IPropertySymbol property, INamedTypeSymbol? skipValidationAttributeSymbol) { + if (skipValidationAttributeSymbol is null) + { + return false; + } + return property.HasAttribute(skipValidationAttributeSymbol) || property.Type.HasAttribute(skipValidationAttributeSymbol); } - internal static bool IsSkippedValidationParameter(this IParameterSymbol parameter, INamedTypeSymbol skipValidationAttributeSymbol) + internal static bool IsSkippedValidationParameter(this IParameterSymbol parameter, INamedTypeSymbol? skipValidationAttributeSymbol) { return parameter.HasAttribute(skipValidationAttributeSymbol) || parameter.Type.HasAttribute(skipValidationAttributeSymbol); } diff --git a/src/Validation/src/Microsoft.Extensions.Validation.csproj b/src/Validation/src/Microsoft.Extensions.Validation.csproj index 72d50e224f42..4ed2a7a471df 100644 --- a/src/Validation/src/Microsoft.Extensions.Validation.csproj +++ b/src/Validation/src/Microsoft.Extensions.Validation.csproj @@ -1,4 +1,4 @@ - + Common validation abstractions and validation infrastructure for .NET applications. @@ -11,6 +11,19 @@ true + + + false + Content + PreserveNewest + + + + + + + + From 1bee48cf45b462bdcb3c67f8bfef6881cb68d2ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Tue, 16 Sep 2025 18:28:44 +0200 Subject: [PATCH 2/3] Use special type symbol for missing type comparisons --- src/Shared/RoslynUtils/SymbolExtensions.cs | 14 +--- src/Shared/RoslynUtils/WellKnownTypes.cs | 27 +++---- .../gen/Extensions/ISymbolExtensions.cs | 7 +- .../gen/Extensions/ITypeSymbolExtensions.cs | 70 +++---------------- .../Microsoft.Extensions.Validation.csproj | 2 +- 5 files changed, 25 insertions(+), 95 deletions(-) diff --git a/src/Shared/RoslynUtils/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs index 62b08ff6395c..8a2f07fcb200 100644 --- a/src/Shared/RoslynUtils/SymbolExtensions.cs +++ b/src/Shared/RoslynUtils/SymbolExtensions.cs @@ -49,13 +49,8 @@ public static IEnumerable GetThisAndBaseTypes(this ITypeSymbol? typ } } - public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol? attributeType) + public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) { - if (attributeType is null) - { - return false; - } - foreach (var attributeData in symbol.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, attributeType)) @@ -72,13 +67,8 @@ public static bool HasAttribute(this ImmutableArray attributes, I return attributes.TryGetAttribute(attributeType, out _); } - public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol? attributeSymbol) + public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol attributeSymbol) { - if (attributeSymbol is null) - { - return false; - } - var current = typeSymbol; while (current is not null) diff --git a/src/Shared/RoslynUtils/WellKnownTypes.cs b/src/Shared/RoslynUtils/WellKnownTypes.cs index f3b66246ab22..deb2def60b60 100644 --- a/src/Shared/RoslynUtils/WellKnownTypes.cs +++ b/src/Shared/RoslynUtils/WellKnownTypes.cs @@ -19,6 +19,7 @@ public static WellKnownTypes GetOrCreate(Compilation compilation) => private readonly INamedTypeSymbol?[] _lazyWellKnownTypes; private readonly Compilation _compilation; + private readonly INamedTypeSymbol _missingTypeSymbol; static WellKnownTypes() { @@ -51,6 +52,7 @@ private WellKnownTypes(Compilation compilation) { _lazyWellKnownTypes = new INamedTypeSymbol?[WellKnownTypeData.WellKnownTypeNames.Length]; _compilation = compilation; + _missingTypeSymbol = compilation.GetTypeByMetadataName(typeof(MissingType).FullName!)!; } public INamedTypeSymbol Get(SpecialType type) @@ -58,13 +60,8 @@ public INamedTypeSymbol Get(SpecialType type) return _compilation.GetSpecialType(type); } - public INamedTypeSymbol? Get(WellKnownTypeData.WellKnownType? type) + public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) { - if (type is null) - { - return null; - } - var index = (int)type; var symbol = _lazyWellKnownTypes[index]; if (symbol is not null) @@ -77,18 +74,14 @@ public INamedTypeSymbol Get(SpecialType type) return GetAndCache(index); } - private INamedTypeSymbol? GetAndCache(int index) + private INamedTypeSymbol GetAndCache(int index) { - var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]); - //if (result == null) - //{ - // throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'."); - //} + var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]) ?? _missingTypeSymbol; Interlocked.CompareExchange(ref _lazyWellKnownTypes[index], result, null); // GetTypeByMetadataName should always return the same instance for a name. // To ensure we have a consistent value, for thread safety, return symbol set in the array. - return _lazyWellKnownTypes[index]; + return _lazyWellKnownTypes[index]!; } // Filter for types within well-known (framework-owned) assemblies only. @@ -135,7 +128,7 @@ public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnown return false; } - public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType?[] interfaceWellKnownTypes) + public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType[] interfaceWellKnownTypes) { foreach (var wellKnownType in interfaceWellKnownTypes) { @@ -148,9 +141,9 @@ public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType?[] inte return false; } - public static bool Implements(ITypeSymbol? type, ITypeSymbol? interfaceType) + public static bool Implements(ITypeSymbol? type, ITypeSymbol interfaceType) { - if (type is null || interfaceType is null) + if (type is null) { return false; } @@ -164,4 +157,6 @@ public static bool Implements(ITypeSymbol? type, ITypeSymbol? interfaceType) } return false; } + + internal class MissingType { } } diff --git a/src/Validation/gen/Extensions/ISymbolExtensions.cs b/src/Validation/gen/Extensions/ISymbolExtensions.cs index 293a6a0c7068..a770b7260656 100644 --- a/src/Validation/gen/Extensions/ISymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ISymbolExtensions.cs @@ -10,13 +10,8 @@ namespace Microsoft.Extensions.Validation; internal static class ISymbolExtensions { - public static string GetDisplayName(this ISymbol property, INamedTypeSymbol? displayAttribute) + public static string GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute) { - if (displayAttribute is null) - { - return property.Name; - } - var displayNameAttribute = property.GetAttributes() .FirstOrDefault(attribute => attribute.AttributeClass is { } attributeClass && diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs index 18625b0baf8c..3532dbd69ebc 100644 --- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Immutable; using System.Linq; using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; @@ -22,13 +21,8 @@ public static bool IsEnumerable(this ITypeSymbol type, INamedTypeSymbol enumerab return type.ImplementsInterface(enumerable) || SymbolEqualityComparer.Default.Equals(type, enumerable); } - public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol? validationAttributeSymbol) + public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol validationAttributeSymbol) { - if (validationAttributeSymbol is null) - { - return false; - } - var baseType = typeSymbol.BaseType; while (baseType != null) { @@ -42,9 +36,8 @@ public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, IN return false; } - public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol? enumerable) + public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enumerable) { - if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T && type is INamedTypeSymbol { TypeArguments.Length: 1 }) { @@ -59,11 +52,6 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol? en type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated); } - if (enumerable is null) - { - return type; - } - if (type is INamedTypeSymbol namedType && namedType.IsEnumerable(enumerable) && namedType.TypeArguments.Length == 1) { // Extract the T from an IEnumerable or List @@ -73,13 +61,8 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol? en return type; } - internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol? interfaceType) + internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol interfaceType) { - if (interfaceType is null) - { - return false; - } - foreach (var iface in type.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(interfaceType, iface)) @@ -90,13 +73,8 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol? int return false; } - internal static ImmutableArray? GetJsonDerivedTypes(this ITypeSymbol type, INamedTypeSymbol? jsonDerivedTypeAttribute) + internal static ImmutableArray? GetJsonDerivedTypes(this ITypeSymbol type, INamedTypeSymbol jsonDerivedTypeAttribute) { - if (jsonDerivedTypeAttribute is null) - { - return null; - } - var derivedTypes = ImmutableArray.CreateBuilder(); foreach (var attribute in type.GetAttributes()) { @@ -117,9 +95,7 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol? int // types themselves so we short-circuit on them. internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnownTypes) { - try - { - return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) + return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpRequest)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpResponse)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Threading_CancellationToken)) @@ -128,12 +104,6 @@ internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnow || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFile)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Stream)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Pipelines_PipeReader)); - - } - catch (InvalidOperationException) - { - return false; - } } internal static IPropertySymbol? FindPropertyIncludingBaseTypes(this INamedTypeSymbol typeSymbol, string propertyName) @@ -162,13 +132,8 @@ internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnow /// The parameter to check. /// The symbol representing the [FromService] attribute. /// The symbol representing the [FromKeyedService] attribute. - internal static bool IsServiceParameter(this IParameterSymbol parameter, INamedTypeSymbol? fromServiceMetadataSymbol, INamedTypeSymbol? fromKeyedServiceAttributeSymbol) + internal static bool IsServiceParameter(this IParameterSymbol parameter, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol) { - if (fromServiceMetadataSymbol is null || fromKeyedServiceAttributeSymbol is null) - { - return false; - } - return parameter.GetAttributes().Any(attr => attr.AttributeClass is not null && (attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) || @@ -181,13 +146,8 @@ attr.AttributeClass is not null && /// The property to check. /// The symbol representing the [FromServices] attribute. /// The symbol representing the [FromKeyedServices] attribute. - internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol? fromServiceMetadataSymbol, INamedTypeSymbol? fromKeyedServiceAttributeSymbol) + internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol) { - if (fromServiceMetadataSymbol is null || fromKeyedServiceAttributeSymbol is null) - { - return false; - } - return property.GetAttributes().Any(attr => attr.AttributeClass is not null && (attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) || @@ -199,29 +159,19 @@ attr.AttributeClass is not null && /// /// The property to check. /// The symbol representing the [JsonIgnore] attribute. - internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol? jsonIgnoreAttributeSymbol) + internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol jsonIgnoreAttributeSymbol) { - if (jsonIgnoreAttributeSymbol is null) - { - return false; - } - return property.GetAttributes().Any(attr => attr.AttributeClass is not null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol)); } - internal static bool IsSkippedValidationProperty(this IPropertySymbol property, INamedTypeSymbol? skipValidationAttributeSymbol) + internal static bool IsSkippedValidationProperty(this IPropertySymbol property, INamedTypeSymbol skipValidationAttributeSymbol) { - if (skipValidationAttributeSymbol is null) - { - return false; - } - return property.HasAttribute(skipValidationAttributeSymbol) || property.Type.HasAttribute(skipValidationAttributeSymbol); } - internal static bool IsSkippedValidationParameter(this IParameterSymbol parameter, INamedTypeSymbol? skipValidationAttributeSymbol) + internal static bool IsSkippedValidationParameter(this IParameterSymbol parameter, INamedTypeSymbol skipValidationAttributeSymbol) { return parameter.HasAttribute(skipValidationAttributeSymbol) || parameter.Type.HasAttribute(skipValidationAttributeSymbol); } diff --git a/src/Validation/src/Microsoft.Extensions.Validation.csproj b/src/Validation/src/Microsoft.Extensions.Validation.csproj index 4ed2a7a471df..35405452fdd3 100644 --- a/src/Validation/src/Microsoft.Extensions.Validation.csproj +++ b/src/Validation/src/Microsoft.Extensions.Validation.csproj @@ -1,4 +1,4 @@ - + Common validation abstractions and validation infrastructure for .NET applications. From b3e83703835f9254b1c970834b1aefb6161d6100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Thu, 18 Sep 2025 18:47:05 +0200 Subject: [PATCH 3/3] Revert throwing behavior for type symbol retrieval for other analyzers --- src/Shared/RoslynUtils/WellKnownTypes.cs | 39 ++++++++++++++++--- .../gen/Extensions/ITypeSymbolExtensions.cs | 12 +++--- .../ValidationsGenerator.TypesParser.cs | 8 ++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Shared/RoslynUtils/WellKnownTypes.cs b/src/Shared/RoslynUtils/WellKnownTypes.cs index deb2def60b60..05925ddf68bc 100644 --- a/src/Shared/RoslynUtils/WellKnownTypes.cs +++ b/src/Shared/RoslynUtils/WellKnownTypes.cs @@ -19,7 +19,6 @@ public static WellKnownTypes GetOrCreate(Compilation compilation) => private readonly INamedTypeSymbol?[] _lazyWellKnownTypes; private readonly Compilation _compilation; - private readonly INamedTypeSymbol _missingTypeSymbol; static WellKnownTypes() { @@ -52,7 +51,6 @@ private WellKnownTypes(Compilation compilation) { _lazyWellKnownTypes = new INamedTypeSymbol?[WellKnownTypeData.WellKnownTypeNames.Length]; _compilation = compilation; - _missingTypeSymbol = compilation.GetTypeByMetadataName(typeof(MissingType).FullName!)!; } public INamedTypeSymbol Get(SpecialType type) @@ -60,7 +58,28 @@ public INamedTypeSymbol Get(SpecialType type) return _compilation.GetSpecialType(type); } + /// + /// Returns the type symbol for the specified well-known type, or throws if the type cannot be found. + /// public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) + { + return Get(type, throwOnNotFound: true); + } + + /// + /// Returns the type symbol for the specified well-known type, or a special marker type symbol if the type cannot be found. + /// + /// + /// We use a special marker type for cases where some types can be legitimately missing. + /// E.g. The Microsoft.Extensions.Validation source generator checks against some types + /// from the shared framework which are missing in Blazor WebAssembly SDK projects. + /// + public INamedTypeSymbol GetOptional(WellKnownTypeData.WellKnownType type) + { + return Get(type, throwOnNotFound: false); + } + + private INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type, bool throwOnNotFound) { var index = (int)type; var symbol = _lazyWellKnownTypes[index]; @@ -71,12 +90,22 @@ public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) // Symbol hasn't been added to the cache yet. // Resolve symbol from name, cache, and return. - return GetAndCache(index); + return GetAndCache(index, throwOnNotFound); } - private INamedTypeSymbol GetAndCache(int index) + private INamedTypeSymbol GetAndCache(int index, bool throwOnNotFound) { - var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]) ?? _missingTypeSymbol; + var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]); + + if (result == null && throwOnNotFound) + { + throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'."); + } + else + { + result ??= _compilation.GetTypeByMetadataName(typeof(MissingType).FullName!)!; + } + Interlocked.CompareExchange(ref _lazyWellKnownTypes[index], result, null); // GetTypeByMetadataName should always return the same instance for a name. diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs index 3532dbd69ebc..df12e81cc6e0 100644 --- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs @@ -95,13 +95,13 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol inte // types themselves so we short-circuit on them. internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnownTypes) { - return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) - || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpRequest)) - || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpResponse)) + return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.GetOptional(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) + || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.GetOptional(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpRequest)) + || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.GetOptional(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpResponse)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Threading_CancellationToken)) - || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormCollection)) - || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFileCollection)) - || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFile)) + || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.GetOptional(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormCollection)) + || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.GetOptional(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFileCollection)) + || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.GetOptional(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFile)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Stream)) || SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Pipelines_PipeReader)); } diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs index cc0624fdfb93..71466a8a547b 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs @@ -27,9 +27,9 @@ internal ImmutableArray ExtractValidatableTypes(IInvocationOper ? method.Parameters : []; - var fromServiceMetadataSymbol = wellKnownTypes.Get( + var fromServiceMetadataSymbol = wellKnownTypes.GetOptional( WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata); - var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get( + var fromKeyedServiceAttributeSymbol = wellKnownTypes.GetOptional( WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute); var skipValidationAttributeSymbol = wellKnownTypes.Get( WellKnownTypeData.WellKnownType.Microsoft_Extensions_Validation_SkipValidationAttribute); @@ -127,9 +127,9 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb var members = new List(); var resolvedRecordProperty = new List(); - var fromServiceMetadataSymbol = wellKnownTypes.Get( + var fromServiceMetadataSymbol = wellKnownTypes.GetOptional( WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata); - var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get( + var fromKeyedServiceAttributeSymbol = wellKnownTypes.GetOptional( WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute); var jsonIgnoreAttributeSymbol = wellKnownTypes.Get( WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonIgnoreAttribute);