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.