diff --git a/src/Shared/RoslynUtils/WellKnownTypes.cs b/src/Shared/RoslynUtils/WellKnownTypes.cs index a3a5cd3061b7..05925ddf68bc 100644 --- a/src/Shared/RoslynUtils/WellKnownTypes.cs +++ b/src/Shared/RoslynUtils/WellKnownTypes.cs @@ -58,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]; @@ -69,16 +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]); - if (result == null) + + 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. @@ -159,4 +186,6 @@ public static bool Implements(ITypeSymbol? type, ITypeSymbol interfaceType) } return false; } + + internal class MissingType { } } 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); diff --git a/src/Validation/src/Microsoft.Extensions.Validation.csproj b/src/Validation/src/Microsoft.Extensions.Validation.csproj index 72d50e224f42..35405452fdd3 100644 --- a/src/Validation/src/Microsoft.Extensions.Validation.csproj +++ b/src/Validation/src/Microsoft.Extensions.Validation.csproj @@ -11,6 +11,19 @@ true + + + false + Content + PreserveNewest + + + + + + + +