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 4b34054571aa..63261c68a85d 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);