From 232a8d3a6b7eb0ba2d8cda7eb67a968554e8074c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 3 Jul 2023 21:00:51 -0700 Subject: [PATCH] [Macros] Enable freestanding macros at module scope in script mode Eliminate the error message error: global freestanding macros not yet supported in script mode by implementing name lookup, type checking, and code emission for freestanding macros. The key problem here is that, in script mode, it is ambiguous whether a use of a freestanding macro is an expression or a declaration. We parse as an expression (as we do within a function body), which then gets wrapped in a top-level code declaration. Teach various parts of the compiler to look through a top-level code declaration wrapping a macro expansion expression that is for a declaration or code-item macro, e.g., by recording these for global name lookup and treating their expansions as "auxiliary" declarations. Fixes rdar://109699501. --- include/swift/AST/Decl.h | 4 +- include/swift/AST/DiagnosticsSema.def | 3 - lib/AST/Decl.cpp | 68 +++++++++- lib/AST/Module.cpp | 174 +++++++++++++++++--------- lib/Sema/TypeCheckDeclPrimary.cpp | 6 - lib/Sema/TypeCheckMacros.cpp | 3 +- lib/Sema/TypeCheckStmt.cpp | 22 ++++ test/Macros/macro_expand.swift | 3 - 8 files changed, 207 insertions(+), 76 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 69730acc041fd..730db2d8f96d1 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -8446,7 +8446,7 @@ class MissingDecl : public Decl { /// \c unexpandedMacro contains the macro reference and the base declaration /// where the macro expansion applies. struct { - llvm::PointerUnion macroRef; + llvm::PointerUnion macroRef; Decl *baseDecl; } unexpandedMacro; @@ -8470,7 +8470,7 @@ class MissingDecl : public Decl { static MissingDecl * forUnexpandedMacro( - llvm::PointerUnion macroRef, + llvm::PointerUnion macroRef, Decl *baseDecl) { auto &ctx = baseDecl->getASTContext(); auto *dc = baseDecl->getDeclContext(); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index dda6cd2a13019..e57fa6db560fe 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7173,9 +7173,6 @@ ERROR(literal_type_in_macro_expansion,none, ERROR(invalid_macro_introduced_name,none, "declaration name %0 is not covered by macro %1", (DeclName, DeclName)) -ERROR(global_freestanding_macro_script,none, - "global freestanding macros not yet supported in script mode", - ()) ERROR(invalid_macro_role_for_macro_syntax,none, "invalid macro role for %{a freestanding|an attached}0 macro", (unsigned)) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 07d123518e76d..5e892129c377b 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -408,7 +408,25 @@ void Decl::visitAuxiliaryDecls( } if (visitFreestandingExpanded) { - if (auto *med = dyn_cast(mutableThis)) { + Decl *thisDecl = mutableThis; + + // If this is a top-level code decl consisting of a macro expansion + // expression that substituted with a macro expansion declaration, use + // that instead. + if (auto *tlcd = dyn_cast(thisDecl)) { + if (auto body = tlcd->getBody()) { + if (body->getNumElements() == 1) { + if (auto expr = body->getFirstElement().dyn_cast()) { + if (auto expansion = dyn_cast(expr)) { + if (auto substitute = expansion->getSubstituteDecl()) + thisDecl = substitute; + } + } + } + } + } + + if (auto *med = dyn_cast(thisDecl)) { if (auto bufferID = evaluateOrDefault( ctx.evaluator, ExpandMacroExpansionDeclRequest{med}, {})) { auto startLoc = sourceMgr.getLocForBufferStart(*bufferID); @@ -10482,6 +10500,45 @@ bool swift::isMacroSupported(MacroRole role, ASTContext &ctx) { void MissingDecl::forEachMacroExpandedDecl(MacroExpandedDeclCallback callback) { auto macroRef = unexpandedMacro.macroRef; auto *baseDecl = unexpandedMacro.baseDecl; + + // If the macro itself is a macro expansion expression, it should come with + // a top-level code declaration that we can use for resolution. For such + // cases, resolve the macro to determine whether it is a declaration or + // code-item macro, meaning that it can produce declarations. In such cases, + // expand the macro and use its substituted declaration (a MacroExpansionDecl) + // instead. + if (auto freestanding = macroRef.dyn_cast()) { + if (auto expr = dyn_cast(freestanding)) { + bool replacedWithDecl = false; + if (auto tlcd = dyn_cast_or_null(baseDecl)) { + ASTContext &ctx = tlcd->getASTContext(); + if (auto macro = evaluateOrDefault( + ctx.evaluator, + ResolveMacroRequest{macroRef, tlcd->getDeclContext()}, + nullptr)) { + auto macroDecl = cast(macro.getDecl()); + auto roles = macroDecl->getMacroRoles(); + if (roles.contains(MacroRole::Declaration) || + roles.contains(MacroRole::CodeItem)) { + (void)evaluateOrDefault(ctx.evaluator, + ExpandMacroExpansionExprRequest{expr}, + llvm::None); + if (auto substituted = expr->getSubstituteDecl()) { + macroRef = substituted; + baseDecl = substituted; + replacedWithDecl = true; + } + } + } + } + + // If we didn't end up replacing the macro expansion expression with + // a declaration, we're done. + if (!replacedWithDecl) + return; + } + } + if (!macroRef || !baseDecl) return; auto *module = getModuleContext(); @@ -10492,9 +10549,12 @@ void MissingDecl::forEachMacroExpandedDecl(MacroExpandedDeclCallback callback) { : auxiliaryDecl->getInnermostDeclContext()->getParentSourceFile(); // We only visit auxiliary decls that are macro expansions associated with // this macro reference. - if (auto *med = macroRef.dyn_cast()) { - if (med != sf->getMacroExpansion().dyn_cast()) - return; + if (auto *med = macroRef.dyn_cast()) { + auto medAsDecl = dyn_cast(med); + auto medAsExpr = dyn_cast(med); + if ((!medAsDecl || medAsDecl != sf->getMacroExpansion().dyn_cast()) && + (!medAsExpr || medAsExpr != sf->getMacroExpansion().dyn_cast())) + return; } else if (auto *attr = macroRef.dyn_cast()) { if (attr != sf->getAttachedMacroAttribute()) return; diff --git a/lib/AST/Module.cpp b/lib/AST/Module.cpp index c2f83254d48f1..00074943f9ebc 100644 --- a/lib/AST/Module.cpp +++ b/lib/AST/Module.cpp @@ -179,9 +179,9 @@ class swift::SourceLookupCache { /// Top-level macros that produce arbitrary names. SmallVector TopLevelArbitraryMacros; - SmallVector MayHaveAuxiliaryDecls; + SmallVector, 4> + MayHaveAuxiliaryDecls; void populateAuxiliaryDeclCache(); - SourceLookupCache(ASTContext &ctx); public: @@ -238,10 +238,30 @@ SourceLookupCache &SourceFile::getCache() const { return *Cache; } +static Expr *getAsExpr(Decl *decl) { return nullptr; } +static Decl *getAsDecl(Decl *decl) { return decl; } + +static Expr *getAsExpr(ASTNode node) { return node.dyn_cast(); } +static Decl *getAsDecl(ASTNode node) { return node.dyn_cast(); } + template -void SourceLookupCache::addToUnqualifiedLookupCache(Range decls, +void SourceLookupCache::addToUnqualifiedLookupCache(Range items, bool onlyOperators) { - for (Decl *D : decls) { + for (auto item : items) { + // In script mode, we'll see macro expansion expressions for freestanding + // macros. + if (Expr *E = getAsExpr(item)) { + if (auto MEE = dyn_cast(E)) { + if (!onlyOperators) + MayHaveAuxiliaryDecls.push_back(MEE); + } + continue; + } + + Decl *D = getAsDecl(item); + if (!D) + continue; + if (auto *VD = dyn_cast(D)) { if (onlyOperators ? VD->isOperator() : VD->hasName()) { // Cache the value under both its compound name and its full name. @@ -280,6 +300,10 @@ void SourceLookupCache::addToUnqualifiedLookupCache(Range decls, else if (auto *MED = dyn_cast(D)) { if (!onlyOperators) MayHaveAuxiliaryDecls.push_back(MED); + } else if (auto TLCD = dyn_cast(D)) { + if (auto body = TLCD->getBody()){ + addToUnqualifiedLookupCache(body->getElements(), onlyOperators); + } } } } @@ -335,75 +359,113 @@ void SourceLookupCache::addToMemberCache(Range decls) { } void SourceLookupCache::populateAuxiliaryDeclCache() { - using MacroRef = llvm::PointerUnion; - for (auto *decl : MayHaveAuxiliaryDecls) { + using MacroRef = llvm::PointerUnion; + for (auto item : MayHaveAuxiliaryDecls) { + TopLevelCodeDecl *topLevelCodeDecl = nullptr; + // Gather macro-introduced peer names. llvm::SmallDenseMap> introducedNames; - // This code deliberately avoids `forEachAttachedMacro`, because it - // will perform overload resolution and possibly invoke unqualified - // lookup for macro arguments, which will recursively populate the - // auxiliary decl cache and cause request cycles. - // - // We do not need a fully resolved macro until expansion. Instead, we - // conservatively consider peer names for all macro declarations with a - // custom attribute name. Unqualified lookup for that name will later - // invoke expansion of the macro, and will yield no results if the resolved - // macro does not produce the requested name, so the only impact is possibly - // expanding earlier than needed / unnecessarily looking in the top-level - // auxiliary decl cache. - for (auto attrConst : decl->getAttrs().getAttributes()) { - auto *attr = const_cast(attrConst); - UnresolvedMacroReference macroRef(attr); - bool introducesArbitraryNames = false; - namelookup::forEachPotentialResolvedMacro( - decl->getDeclContext()->getModuleScopeContext(), - macroRef.getMacroName(), MacroRole::Peer, - [&](MacroDecl *macro, const MacroRoleAttr *roleAttr) { - // First check for arbitrary names. - if (roleAttr->hasNameKind(MacroIntroducedDeclNameKind::Arbitrary)) { - introducesArbitraryNames = true; - } + /// Introduce names for a freestanding macro. + auto introduceNamesForFreestandingMacro = + [&](FreestandingMacroExpansion *macroRef, Decl *decl, MacroRole role) { + bool introducesArbitraryNames = false; + namelookup::forEachPotentialResolvedMacro( + decl->getDeclContext()->getModuleScopeContext(), + macroRef->getMacroName(), role, + [&](MacroDecl *macro, const MacroRoleAttr *roleAttr) { + // First check for arbitrary names. + if (roleAttr->hasNameKind(MacroIntroducedDeclNameKind::Arbitrary)) { + introducesArbitraryNames = true; + } - macro->getIntroducedNames(MacroRole::Peer, - dyn_cast(decl), - introducedNames[attr]); - }); + macro->getIntroducedNames(role, + /*attachedTo*/ nullptr, + introducedNames[macroRef]); + }); - // Record this macro where appropriate. - if (introducesArbitraryNames) - TopLevelArbitraryMacros.push_back(MissingDecl::forUnexpandedMacro(attr, decl)); + return introducesArbitraryNames; + }; + + // Handle macro expansion expressions, which show up in when we have + // freestanding macros in "script" mode. + if (auto expr = item.dyn_cast()) { + topLevelCodeDecl = dyn_cast(expr->getDeclContext()); + if (topLevelCodeDecl) { + bool introducesArbitraryNames = false; + if (introduceNamesForFreestandingMacro( + expr, topLevelCodeDecl, MacroRole::Declaration)) + introducesArbitraryNames = true; + + if (introduceNamesForFreestandingMacro( + expr, topLevelCodeDecl, MacroRole::CodeItem)) + introducesArbitraryNames = true; + + // Record this macro if it introduces arbitrary names. + if (introducesArbitraryNames) { + TopLevelArbitraryMacros.push_back( + MissingDecl::forUnexpandedMacro(expr, topLevelCodeDecl)); + } + } } - if (auto *med = dyn_cast(decl)) { - UnresolvedMacroReference macroRef(med); - bool introducesArbitraryNames = false; - namelookup::forEachPotentialResolvedMacro( - decl->getDeclContext()->getModuleScopeContext(), - macroRef.getMacroName(), MacroRole::Declaration, - [&](MacroDecl *macro, const MacroRoleAttr *roleAttr) { - // First check for arbitrary names. - if (roleAttr->hasNameKind(MacroIntroducedDeclNameKind::Arbitrary)) { - introducesArbitraryNames = true; - } + auto *decl = item.dyn_cast(); + if (decl) { + // This code deliberately avoids `forEachAttachedMacro`, because it + // will perform overload resolution and possibly invoke unqualified + // lookup for macro arguments, which will recursively populate the + // auxiliary decl cache and cause request cycles. + // + // We do not need a fully resolved macro until expansion. Instead, we + // conservatively consider peer names for all macro declarations with a + // custom attribute name. Unqualified lookup for that name will later + // invoke expansion of the macro, and will yield no results if the resolved + // macro does not produce the requested name, so the only impact is possibly + // expanding earlier than needed / unnecessarily looking in the top-level + // auxiliary decl cache. + for (auto attrConst : decl->getAttrs().getAttributes()) { + auto *attr = const_cast(attrConst); + UnresolvedMacroReference macroRef(attr); + bool introducesArbitraryNames = false; + namelookup::forEachPotentialResolvedMacro( + decl->getDeclContext()->getModuleScopeContext(), + macroRef.getMacroName(), MacroRole::Peer, + [&](MacroDecl *macro, const MacroRoleAttr *roleAttr) { + // First check for arbitrary names. + if (roleAttr->hasNameKind( + MacroIntroducedDeclNameKind::Arbitrary)) { + introducesArbitraryNames = true; + } + + macro->getIntroducedNames(MacroRole::Peer, + dyn_cast(decl), + introducedNames[attr]); + }); + + // Record this macro where appropriate. + if (introducesArbitraryNames) + TopLevelArbitraryMacros.push_back( + MissingDecl::forUnexpandedMacro(attr, decl)); + } + } - macro->getIntroducedNames(MacroRole::Declaration, - /*attachedTo*/ nullptr, - introducedNames[med]); - }); + if (auto *med = dyn_cast_or_null(decl)) { + bool introducesArbitraryNames = + introduceNamesForFreestandingMacro(med, decl, MacroRole::Declaration); - // Record this macro where appropriate. + // Note whether this macro produces arbitrary names. if (introducesArbitraryNames) TopLevelArbitraryMacros.push_back(MissingDecl::forUnexpandedMacro(med, decl)); } // Add macro-introduced names to the top-level auxiliary decl cache as // unexpanded decls represented by a MissingDecl. + auto anchorDecl = decl ? decl : topLevelCodeDecl; for (auto macroNames : introducedNames) { auto macroRef = macroNames.getFirst(); for (auto name : macroNames.getSecond()) { - auto *placeholder = MissingDecl::forUnexpandedMacro(macroRef, decl); + auto *placeholder = MissingDecl::forUnexpandedMacro(macroRef, anchorDecl); name.addToLookupTable(TopLevelAuxiliaryDecls, placeholder); } } @@ -421,7 +483,7 @@ SourceLookupCache::SourceLookupCache(const SourceFile &SF) { FrontendStatsTracer tracer(SF.getASTContext().Stats, "source-file-populate-cache"); - addToUnqualifiedLookupCache(SF.getTopLevelDecls(), false); + addToUnqualifiedLookupCache(SF.getTopLevelItems(), false); addToUnqualifiedLookupCache(SF.getHoistedDecls(), false); } @@ -432,7 +494,7 @@ SourceLookupCache::SourceLookupCache(const ModuleDecl &M) "module-populate-cache"); for (const FileUnit *file : M.getFiles()) { auto *SF = cast(file); - addToUnqualifiedLookupCache(SF->getTopLevelDecls(), false); + addToUnqualifiedLookupCache(SF->getTopLevelItems(), false); addToUnqualifiedLookupCache(SF->getHoistedDecls(), false); if (auto *SFU = file->getSynthesizedFile()) { diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index c1aa67defd3c9..6fc90e7f4c0f5 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -3932,11 +3932,5 @@ ExpandMacroExpansionDeclRequest::evaluate(Evaluator &evaluator, !roles.contains(MacroRole::CodeItem)) return None; - // For now, restrict global freestanding macros in script mode. - if (dc->isModuleScopeContext() && - dc->getParentSourceFile()->isScriptMode()) { - MED->diagnose(diag::global_freestanding_macro_script); - } - return expandFreestandingMacro(MED); } diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 2a0828e308259..970557c1cdcfc 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -471,8 +471,7 @@ ExpandMacroExpansionExprRequest::evaluate(Evaluator &evaluator, else if (macro->getMacroRoles().contains(MacroRole::Declaration) || macro->getMacroRoles().contains(MacroRole::CodeItem)) { if (!mee->getSubstituteDecl()) { - auto *med = mee->createSubstituteDecl(); - TypeChecker::typeCheckDecl(med); + (void)mee->createSubstituteDecl(); } // Return the expanded buffer ID. return evaluateOrDefault( diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index ae0b89651947f..c14585e6f233c 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -2011,6 +2011,23 @@ void TypeChecker::checkIgnoredExpr(Expr *E) { void StmtChecker::typeCheckASTNode(ASTNode &node) { // Type check the expression if (auto *E = node.dyn_cast()) { + auto checkMacroExpansion = [&] { + // If we have a macro expansion expression that's been replaced with a + // declaration, type-check that declaration. + if (auto macroExpr = dyn_cast(E)) { + if (auto decl = macroExpr->getSubstituteDecl()) { + ASTNode declNode(decl); + typeCheckASTNode(declNode); + return true; + } + } + + return false; + }; + + if (checkMacroExpansion()) + return; + auto &ctx = DC->getASTContext(); TypeCheckExprOptions options = TypeCheckExprFlags::IsExprStmt; @@ -2025,6 +2042,11 @@ void StmtChecker::typeCheckASTNode(ASTNode &node) { auto resultTy = TypeChecker::typeCheckExpression(E, DC, /*contextualInfo=*/{}, options); + // Check for a freestanding macro expansion that produced declarations or + // code items. + if (checkMacroExpansion()) + return; + // If a closure expression is unused, the user might have intended to write // "do { ... }". auto *CE = dyn_cast(E); diff --git a/test/Macros/macro_expand.swift b/test/Macros/macro_expand.swift index 82a01f02226d6..6e0eec39055b7 100644 --- a/test/Macros/macro_expand.swift +++ b/test/Macros/macro_expand.swift @@ -335,14 +335,11 @@ let blah = false #endif // Test unqualified lookup from within a macro expansion -// FIXME: Global freestanding macros not yet supported in script mode. -#if false let world = 3 // to be used by the macro expansion below #structWithUnqualifiedLookup() _ = StructWithUnqualifiedLookup().foo() #anonymousTypes { "hello" } -#endif func testFreestandingMacroExpansion() { // Explicit structs to force macros to be parsed as decl.