From 15f968f2691d21dfed453f5e5a34688c124636e5 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 11 Jun 2025 16:57:54 -0400 Subject: [PATCH 1/3] AST: Call setExtendedNominal() instead of cacheOutput(ExtendedNominalRequest...) --- lib/ClangImporter/ImportDecl.cpp | 6 ++---- lib/Frontend/ModuleInterfaceSupport.cpp | 3 +-- lib/Sema/TypeCheckConcurrency.cpp | 3 +-- lib/Serialization/Deserialization.cpp | 3 +-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index f1d33788cd16b..f4fc49bf6cb7a 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -5451,8 +5451,7 @@ namespace { { }, dc, nullptr, decl); Impl.SwiftContext.evaluator.cacheOutput(ExtendedTypeRequest{result}, objcClass->getDeclaredType()); - Impl.SwiftContext.evaluator.cacheOutput(ExtendedNominalRequest{result}, - std::move(objcClass)); + result->setExtendedNominal(objcClass); Identifier categoryName; if (!decl->getName().empty()) @@ -10193,8 +10192,7 @@ ClangImporter::Implementation::importDeclContextOf( getClangModuleForDecl(decl), nullptr); SwiftContext.evaluator.cacheOutput(ExtendedTypeRequest{ext}, nominal->getDeclaredType()); - SwiftContext.evaluator.cacheOutput(ExtendedNominalRequest{ext}, - std::move(nominal)); + ext->setExtendedNominal(nominal); // Record this extension so we can find it later. We do this early because // once we've set the member loader, we don't know when the compiler will use diff --git a/lib/Frontend/ModuleInterfaceSupport.cpp b/lib/Frontend/ModuleInterfaceSupport.cpp index a70d91dc10199..e4d31374e87c1 100644 --- a/lib/Frontend/ModuleInterfaceSupport.cpp +++ b/lib/Frontend/ModuleInterfaceSupport.cpp @@ -799,8 +799,7 @@ class InheritedProtocolCollector { ctx.evaluator.cacheOutput(ExtendedTypeRequest{extension}, nominal->getDeclaredType()); - ctx.evaluator.cacheOutput(ExtendedNominalRequest{extension}, - const_cast(nominal)); + extension->setExtendedNominal(const_cast(nominal)); extension->print(printer, printOptions); printer << "\n"; diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index f2d47dc2509a4..d8e904f55c938 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7175,8 +7175,7 @@ ProtocolConformance *swift::deriveImplicitSendableConformance( ctx.evaluator.cacheOutput(ExtendedTypeRequest{extension}, nominal->getDeclaredType()); - ctx.evaluator.cacheOutput(ExtendedNominalRequest{extension}, - std::move(nominal)); + extension->setExtendedNominal(nominal); nominal->addExtension(extension); // Make it accessible to getTopLevelDecls() diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 724f4af8eae1f..ec822b95bf8c4 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -5420,8 +5420,7 @@ class DeclDeserializer { ctx.evaluator.cacheOutput(ExtendedTypeRequest{extension}, std::move(extendedType)); auto nominal = dyn_cast_or_null(MF.getDecl(extendedNominalID)); - ctx.evaluator.cacheOutput(ExtendedNominalRequest{extension}, - std::move(nominal)); + extension->setExtendedNominal(nominal); if (isImplicit) extension->setImplicit(); From 0466387609eeec56c480028733c365aedc3e017f Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 11 Jun 2025 16:58:49 -0400 Subject: [PATCH 2/3] AST: Add excludeMacroExpansions parameter to computeExtendedNominal() --- include/swift/AST/Decl.h | 3 ++- include/swift/AST/NameLookupRequests.h | 6 +++--- lib/AST/Decl.cpp | 11 +++++++---- lib/AST/NameLookup.cpp | 20 +++++++++++++++++++- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index b3455fab4e2f8..9b70df026fa1c 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2064,7 +2064,8 @@ class ExtensionDecl final : public GenericContext, public Decl, NominalTypeDecl *getExtendedNominal() const; /// Compute the nominal type declaration that is being extended. - NominalTypeDecl *computeExtendedNominal() const; + NominalTypeDecl *computeExtendedNominal( + bool excludeMacroExpansions=false) const; /// \c hasBeenBound means nothing if this extension can never been bound /// because it is not at the top level. diff --git a/include/swift/AST/NameLookupRequests.h b/include/swift/AST/NameLookupRequests.h index 36fe7b6974dbe..26e901e7a1a08 100644 --- a/include/swift/AST/NameLookupRequests.h +++ b/include/swift/AST/NameLookupRequests.h @@ -267,7 +267,7 @@ class HasMissingDesignatedInitializersRequest : /// Request the nominal declaration extended by a given extension declaration. class ExtendedNominalRequest : public SimpleRequest< - ExtendedNominalRequest, NominalTypeDecl *(ExtensionDecl *), + ExtendedNominalRequest, NominalTypeDecl *(ExtensionDecl *, bool), RequestFlags::SeparatelyCached | RequestFlags::DependencySink> { public: using SimpleRequest::SimpleRequest; @@ -276,8 +276,8 @@ class ExtendedNominalRequest friend SimpleRequest; // Evaluation. - NominalTypeDecl * - evaluate(Evaluator &evaluator, ExtensionDecl *ext) const; + NominalTypeDecl * evaluate(Evaluator &evaluator, ExtensionDecl *ext, + bool excludeMacroExpansions) const; public: // Separate caching. diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 4f7873fd47dcf..7480c3fea3884 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2075,11 +2075,14 @@ NominalTypeDecl *ExtensionDecl::getExtendedNominal() const { "Extension must have already been bound (by bindExtensions)"); } -NominalTypeDecl *ExtensionDecl::computeExtendedNominal() const { +NominalTypeDecl *ExtensionDecl::computeExtendedNominal( + bool excludeMacroExpansions) const { ASTContext &ctx = getASTContext(); - return evaluateOrDefault( - ctx.evaluator, ExtendedNominalRequest{const_cast(this)}, - nullptr); + return evaluateOrDefault(ctx.evaluator, + ExtendedNominalRequest{ + const_cast(this), + excludeMacroExpansions}, + nullptr); } bool ExtensionDecl::canNeverBeBound() const { diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index 4163531b4f97b..97c16835ab32d 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -1125,6 +1125,9 @@ enum class DirectlyReferencedTypeLookupFlags { /// Include members that would normally be excluded because they come from /// modules that have not been imported directly. IgnoreMissingImports = 1 << 3, + + /// Whenther we should exclude macro expansions. + ExcludeMacroExpansions = 1 << 4, }; using DirectlyReferencedTypeLookupOptions = @@ -3065,6 +3068,10 @@ static DirectlyReferencedTypeDecls directReferencesForUnqualifiedTypeLookup( DirectlyReferencedTypeLookupFlags::IgnoreMissingImports)) options |= UnqualifiedLookupFlags::IgnoreMissingImports; + if (typeLookupOptions.contains( + DirectlyReferencedTypeLookupFlags::ExcludeMacroExpansions)) + options |= UnqualifiedLookupFlags::ExcludeMacroExpansions; + // Manually exclude macro expansions here since the source location // is overridden below. if (namelookup::isInMacroArgument(dc->getParentSourceFile(), loc)) @@ -3147,6 +3154,10 @@ static llvm::TinyPtrVector directReferencesForQualifiedTypeLookup( DirectlyReferencedTypeLookupFlags::IgnoreMissingImports)) options |= NL_IgnoreMissingImports; + if (typeLookupOptions.contains( + DirectlyReferencedTypeLookupFlags::ExcludeMacroExpansions)) + options |= NL_ExcludeMacroExpansions; + // Look through the type declarations we were given, resolving them down // to nominal type declarations, module declarations, and SmallVector moduleDecls; @@ -3574,7 +3585,8 @@ ProtocolRequirementsRequest::evaluate(Evaluator &evaluator, NominalTypeDecl * ExtendedNominalRequest::evaluate(Evaluator &evaluator, - ExtensionDecl *ext) const { + ExtensionDecl *ext, + bool excludeMacroExpansions) const { auto typeRepr = ext->getExtendedTypeRepr(); if (!typeRepr) { // We must've seen 'extension { ... }' during parsing. @@ -3583,9 +3595,15 @@ ExtendedNominalRequest::evaluate(Evaluator &evaluator, ASTContext &ctx = ext->getASTContext(); auto options = defaultDirectlyReferencedTypeLookupOptions; + if (ext->isInSpecializeExtensionContext()) { options |= DirectlyReferencedTypeLookupFlags::AllowUsableFromInline; } + + if (excludeMacroExpansions) { + options |= DirectlyReferencedTypeLookupFlags::ExcludeMacroExpansions; + } + DirectlyReferencedTypeDecls referenced = directReferencesForTypeRepr( evaluator, ctx, typeRepr, ext->getParent(), options); From a10cc92bf791fea84329b472e6fff7e0d04ca4c5 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 11 Jun 2025 16:59:38 -0400 Subject: [PATCH 3/3] Sema: Try to bind extensions without expanding macros Macro expansion can call typeCheckExpr(), which performs qualified lookups. So if we expand macros while binding extensions, these qualified lookups can fail because they cannot find members of extensions that have not been bound yet. To fix this, try binding extensions without performing macro expansion first. If any extensions remain at the end, we fall back to the old behavior, and try to bind them again, this time performing macro expansion. Fixes rdar://149798059. --- lib/Sema/TypeChecker.cpp | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/Sema/TypeChecker.cpp b/lib/Sema/TypeChecker.cpp index d5c7a33a5d51b..d1a3c2910ab09 100644 --- a/lib/Sema/TypeChecker.cpp +++ b/lib/Sema/TypeChecker.cpp @@ -191,12 +191,14 @@ ModuleDecl *TypeChecker::getStdlibModule(const DeclContext *dc) { } void swift::bindExtensions(ModuleDecl &mod) { + bool excludeMacroExpansions = true; + // Utility function to try and resolve the extended type without diagnosing. // If we succeed, we go ahead and bind the extension. Otherwise, return false. auto tryBindExtension = [&](ExtensionDecl *ext) -> bool { assert(!ext->canNeverBeBound() && "Only extensions that can ever be bound get here."); - if (auto nominal = ext->computeExtendedNominal()) { + if (auto nominal = ext->computeExtendedNominal(excludeMacroExpansions)) { nominal->addExtension(ext); return true; } @@ -228,20 +230,28 @@ void swift::bindExtensions(ModuleDecl &mod) { visitTopLevelDecl(D); } - // Phase 2 - repeatedly go through the worklist and attempt to bind each - // extension there, removing it from the worklist if we succeed. - bool changed; - do { - changed = false; - - auto last = std::remove_if(worklist.begin(), worklist.end(), - tryBindExtension); - if (last != worklist.end()) { - worklist.erase(last, worklist.end()); - changed = true; - } - } while(changed); + auto tryBindExtensions = [&]() { + // Phase 2 - repeatedly go through the worklist and attempt to bind each + // extension there, removing it from the worklist if we succeed. + bool changed; + do { + changed = false; + + auto last = std::remove_if(worklist.begin(), worklist.end(), + tryBindExtension); + if (last != worklist.end()) { + worklist.erase(last, worklist.end()); + changed = true; + } + } while(changed); + }; + + tryBindExtensions(); + // If that fails, try again, but this time expand macros. + excludeMacroExpansions = false; + tryBindExtensions(); + // Any remaining extensions are invalid. They will be diagnosed later by // typeCheckDecl(). }