diff --git a/include/swift/AST/ASTMangler.h b/include/swift/AST/ASTMangler.h index 36f4d54409b47..38f8a3f547e0d 100644 --- a/include/swift/AST/ASTMangler.h +++ b/include/swift/AST/ASTMangler.h @@ -408,10 +408,18 @@ class ASTMangler : public Mangler { std::string mangleMacroExpansion(const FreestandingMacroExpansion *expansion); std::string mangleAttachedMacroExpansion( const Decl *decl, CustomAttr *attr, MacroRole role); + std::string mangleAttachedMacroExpansion( + ClosureExpr *attachedTo, CustomAttr *attr, MacroDecl *macro); void appendMacroExpansion(const FreestandingMacroExpansion *expansion); void appendMacroExpansionContext(SourceLoc loc, DeclContext *origDC, - const FreestandingMacroExpansion *expansion); + Identifier macroName, + unsigned discriminator); + + void appendMacroExpansion(ClosureExpr *attachedTo, + CustomAttr *attr, + MacroDecl *macro); + void appendMacroExpansionOperator( StringRef macroName, MacroRole role, unsigned discriminator); diff --git a/include/swift/AST/AnyFunctionRef.h b/include/swift/AST/AnyFunctionRef.h index 014fbf7192307..3f5e0cb309983 100644 --- a/include/swift/AST/AnyFunctionRef.h +++ b/include/swift/AST/AnyFunctionRef.h @@ -243,6 +243,54 @@ class AnyFunctionRef { llvm_unreachable("unexpected AnyFunctionRef representation"); } + DeclAttributes getDeclAttributes() const { + if (auto afd = TheFunction.dyn_cast()) { + return afd->getExpandedAttrs(); + } + + if (auto ace = TheFunction.dyn_cast()) { + if (auto *ce = dyn_cast(ace)) { + return ce->getAttrs(); + } + } + + return DeclAttributes(); + } + + MacroDecl *getResolvedMacro(CustomAttr *attr) const { + if (auto afd = TheFunction.dyn_cast()) { + return afd->getResolvedMacro(attr); + } + + if (auto ace = TheFunction.dyn_cast()) { + if (auto *ce = dyn_cast(ace)) { + return ce->getResolvedMacro(attr); + } + } + + return nullptr; + } + + using MacroCallback = llvm::function_ref; + + void + forEachAttachedMacro(MacroRole role, + MacroCallback macroCallback) const { + auto attrs = getDeclAttributes(); + for (auto customAttrConst : attrs.getAttributes()) { + auto customAttr = const_cast(customAttrConst); + auto *macroDecl = getResolvedMacro(customAttr); + + if (!macroDecl) + continue; + + if (!macroDecl->getMacroRoles().contains(role)) + continue; + + macroCallback(customAttr, macroDecl); + } + } + friend bool operator==(AnyFunctionRef lhs, AnyFunctionRef rhs) { return lhs.TheFunction == rhs.TheFunction; } diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index ab0c92cbd8db0..de083712fe6cc 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7833,6 +7833,9 @@ ERROR(conformance_macro,none, ERROR(experimental_macro,none, "macro %0 is experimental", (DeclName)) +ERROR(experimental_closure_body_macro,none, + "function body macros on closures is experimental", + ()) ERROR(macro_resolve_circular_reference, none, "circular reference resolving %select{freestanding|attached}0 macro %1", diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 37b58726ac8b0..a1f844804b7a6 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -4305,6 +4305,8 @@ class ClosureExpr : public AbstractClosureExpr { BraceStmt *getBody() const { return Body; } void setBody(BraceStmt *S) { Body = S; } + BraceStmt *getExpandedBody(); + DeclAttributes &getAttrs() { return Attributes; } const DeclAttributes &getAttrs() const { return Attributes; } @@ -4422,6 +4424,10 @@ class ClosureExpr : public AbstractClosureExpr { return ExplicitResultTypeAndBodyState.getPointer()->getTypeRepr(); } + /// Returns the resolved macro for the given custom attribute + /// attached to this closure expression. + MacroDecl *getResolvedMacro(CustomAttr *customAttr); + /// Determine whether the closure has a single expression for its /// body. /// diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 177b684d35ce1..9ee065b9fa37a 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -4708,7 +4708,7 @@ class ExpandPreambleMacroRequest class ExpandBodyMacroRequest : public SimpleRequest(AbstractFunctionDecl *), + std::optional(AnyFunctionRef), RequestFlags::Cached> { public: using SimpleRequest::SimpleRequest; @@ -4717,7 +4717,7 @@ class ExpandBodyMacroRequest friend SimpleRequest; std::optional evaluate(Evaluator &evaluator, - AbstractFunctionDecl *fn) const; + AnyFunctionRef fn) const; public: bool isCached() const { return true; } diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 40b0cf16f1f38..94901fa765888 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -531,7 +531,7 @@ SWIFT_REQUEST(TypeChecker, ExpandPreambleMacroRequest, ArrayRef(AbstractFunctionDecl *), Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, ExpandBodyMacroRequest, - std::optional(AbstractFunctionDecl *), + std::optional(AnyFunctionRef), Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, LocalDiscriminatorsRequest, unsigned(DeclContext *), diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 5efe23b967265..b7c849c404c12 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -511,6 +511,9 @@ EXPERIMENTAL_FEATURE(ConcurrencySyntaxSugar, true) /// Allow declaration of compile-time values EXPERIMENTAL_FEATURE(CompileTimeValues, true) +/// Allow function body macros applied to closures. +EXPERIMENTAL_FEATURE(ClosureBodyMacro, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ASTMangler.cpp b/lib/AST/ASTMangler.cpp index 19c75b343f4d1..cea82f8952e82 100644 --- a/lib/AST/ASTMangler.cpp +++ b/lib/AST/ASTMangler.cpp @@ -4759,7 +4759,8 @@ static Identifier encodeLocalPrecheckedDiscriminator( void ASTMangler::appendMacroExpansionContext( SourceLoc loc, DeclContext *origDC, - const FreestandingMacroExpansion *expansion + Identifier macroName, + unsigned macroDiscriminator ) { origDC = MacroDiscriminatorContext::getInnermostMacroContext(origDC); BaseEntitySignature nullBase(nullptr); @@ -4768,9 +4769,9 @@ void ASTMangler::appendMacroExpansionContext( if (auto outermostLocalDC = getOutermostLocalContext(origDC)) { auto innermostNonlocalDC = outermostLocalDC->getParent(); appendContext(innermostNonlocalDC, nullBase, StringRef()); - Identifier name = expansion->getMacroName().getBaseIdentifier(); + Identifier name = macroName; ASTContext &ctx = origDC->getASTContext(); - unsigned discriminator = expansion->getDiscriminator(); + unsigned discriminator = macroDiscriminator; name = encodeLocalPrecheckedDiscriminator(ctx, name, discriminator); appendIdentifier(name.str()); } else { @@ -4875,7 +4876,10 @@ void ASTMangler::appendMacroExpansionContext( return appendMacroExpansionLoc(); // Append our own context and discriminator. - appendMacroExpansionContext(outerExpansionLoc, origDC, expansion); + appendMacroExpansionContext( + outerExpansionLoc, origDC, + macroName, + macroDiscriminator); appendMacroExpansionOperator( baseName.userFacingName(), role, discriminator); } @@ -4902,16 +4906,14 @@ void ASTMangler::appendMacroExpansionOperator( } static StringRef getPrivateDiscriminatorIfNecessary( - const MacroExpansionExpr *expansion) { - auto dc = MacroDiscriminatorContext::getInnermostMacroContext( - expansion->getDeclContext()); - auto decl = dc->getAsDecl(); + const DeclContext *macroDC) { + auto decl = macroDC->getAsDecl(); if (decl && !decl->isOutermostPrivateOrFilePrivateScope()) return StringRef(); // Mangle non-local private declarations with a textual discriminator // based on their enclosing file. - auto topLevelSubcontext = dc->getModuleScopeContext(); + auto topLevelSubcontext = macroDC->getModuleScopeContext(); SourceFile *sf = dyn_cast(topLevelSubcontext); if (!sf) return StringRef(); @@ -4927,6 +4929,13 @@ static StringRef getPrivateDiscriminatorIfNecessary( return discriminator.str(); } +static StringRef getPrivateDiscriminatorIfNecessary( + const MacroExpansionExpr *expansion) { + auto dc = MacroDiscriminatorContext::getInnermostMacroContext( + expansion->getDeclContext()); + return getPrivateDiscriminatorIfNecessary(dc); +} + static StringRef getPrivateDiscriminatorIfNecessary( const FreestandingMacroExpansion *expansion) { switch (expansion->getFreestandingMacroKind()) { @@ -4943,7 +4952,8 @@ void ASTMangler::appendMacroExpansion(const FreestandingMacroExpansion *expansion) { appendMacroExpansionContext(expansion->getPoundLoc(), expansion->getDeclContext(), - expansion); + expansion->getMacroName().getBaseIdentifier(), + expansion->getDiscriminator()); auto privateDiscriminator = getPrivateDiscriminatorIfNecessary(expansion); if (!privateDiscriminator.empty()) { appendIdentifier(privateDiscriminator); @@ -4955,6 +4965,42 @@ ASTMangler::appendMacroExpansion(const FreestandingMacroExpansion *expansion) { expansion->getDiscriminator()); } +void ASTMangler::appendMacroExpansion(ClosureExpr *attachedTo, + CustomAttr *attr, + MacroDecl *macro) { + auto &ctx = attachedTo->getASTContext(); + auto discriminator = + ctx.getNextMacroDiscriminator(attachedTo, + macro->getBaseName()); + + appendMacroExpansionContext( + attr->getLocation(), + attachedTo, + macro->getBaseName().getIdentifier(), + discriminator); + + auto privateDiscriminator = + getPrivateDiscriminatorIfNecessary(attachedTo); + if (!privateDiscriminator.empty()) { + appendIdentifier(privateDiscriminator); + appendOperator("Ll"); + } + + appendMacroExpansionOperator( + macro->getBaseName().userFacingName(), + MacroRole::Body, + discriminator); +} + +std::string +ASTMangler::mangleAttachedMacroExpansion(ClosureExpr *attachedTo, + CustomAttr *attr, + MacroDecl *macro) { + beginMangling(); + appendMacroExpansion(attachedTo, attr, macro); + return finalize(); +} + std::string ASTMangler::mangleMacroExpansion(const FreestandingMacroExpansion *expansion) { beginMangling(); diff --git a/lib/AST/ASTScopeCreation.cpp b/lib/AST/ASTScopeCreation.cpp index 553096e0b42b3..c00c4c95f47f4 100644 --- a/lib/AST/ASTScopeCreation.cpp +++ b/lib/AST/ASTScopeCreation.cpp @@ -309,13 +309,22 @@ ASTSourceFileScope::ASTSourceFileScope(SourceFile *SF, break; } case MacroRole::Body: { - // Use the end location of the function decl itself as the parentLoc - // for the new function body scope. This is different from the end - // location of the original source range, which is after the end of the - // function decl. auto expansion = SF->getMacroExpansion(); - parentLoc = expansion.getEndLoc(); - bodyForDecl = cast(expansion.get()); + if (expansion.is()) { + // Use the end location of the function decl itself as the parentLoc + // for the new function body scope. This is different from the end + // location of the original source range, which is after the end of the + // function decl. + bodyForDecl = cast(expansion.get()); + parentLoc = expansion.getEndLoc(); + break; + } + + // Otherwise, we have a closure body macro. + auto insertionRange = SF->getMacroInsertionRange(); + parentLoc = insertionRange.End; + if (insertionRange.Start != insertionRange.End) + parentLoc = parentLoc.getAdvancedLoc(-1); break; } } diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 95555901bca69..51245dcc30593 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -18,6 +18,7 @@ #include "swift/Basic/Assertions.h" #include "swift/Basic/Statistic.h" #include "swift/Basic/Unicode.h" +#include "swift/Basic/SourceManager.h" #include "swift/AST/ASTContext.h" #include "swift/AST/ASTVisitor.h" #include "swift/AST/Decl.h" // FIXME: Bad dependency @@ -2016,6 +2017,29 @@ BraceStmt * AbstractClosureExpr::getBody() const { llvm_unreachable("Unknown closure expression"); } +BraceStmt *ClosureExpr::getExpandedBody() { + auto &ctx = getASTContext(); + + // Expand a body macro, if there is one. + BraceStmt *macroExpandedBody = nullptr; + if (auto bufferID = evaluateOrDefault( + ctx.evaluator, + ExpandBodyMacroRequest{this}, + std::nullopt)) { + CharSourceRange bufferRange = ctx.SourceMgr.getRangeForBuffer(*bufferID); + auto bufferStart = bufferRange.getStart(); + auto module = getParentModule(); + auto macroSourceFile = module->getSourceFileContainingLocation(bufferStart); + + if (macroSourceFile->getTopLevelItems().size() == 1) { + auto stmt = macroSourceFile->getTopLevelItems()[0].dyn_cast(); + macroExpandedBody = dyn_cast(stmt); + } + } + + return macroExpandedBody; +} + bool AbstractClosureExpr::bodyHasExplicitReturnStmt() const { return AnyFunctionRef(const_cast(this)) .bodyHasExplicitReturnStmt(); @@ -2175,6 +2199,17 @@ void ClosureExpr::setExplicitResultType(Type ty) { ->setType(MetatypeType::get(ty)); } +MacroDecl * +ClosureExpr::getResolvedMacro(CustomAttr *customAttr) { + auto &ctx = getASTContext(); + auto declRef = evaluateOrDefault( + ctx.evaluator, + ResolveMacroRequest{customAttr, this}, + ConcreteDeclRef()); + + return dyn_cast_or_null(declRef.getDecl()); +} + FORWARD_SOURCE_LOCS_TO(AutoClosureExpr, Body) void AutoClosureExpr::setBody(Expr *E) { diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 8371f4e9ca9cf..229d2095cc670 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -359,6 +359,10 @@ static bool usesFeatureCompileTimeValues(Decl *decl) { return decl->getAttrs().hasAttribute(); } +static bool usesFeatureClosureBodyMacro(Decl *decl) { + return false; +} + static bool usesFeatureMemorySafetyAttributes(Decl *decl) { if (decl->getAttrs().hasAttribute() || decl->getAttrs().hasAttribute()) diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 3647d4bfe964e..124329d967f81 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -2681,19 +2681,17 @@ void ExpandPreambleMacroRequest::noteCycleStep(DiagnosticEngine &diags) const { //----------------------------------------------------------------------------// void ExpandBodyMacroRequest::diagnoseCycle(DiagnosticEngine &diags) const { - auto decl = std::get<0>(getStorage()); - diags.diagnose(decl->getLoc(), - diag::macro_expand_circular_reference_entity, - "body", - decl->getName()); + auto fn = std::get<0>(getStorage()); + diags.diagnose(fn.getLoc(), + diag::macro_expand_circular_reference_unnamed, + "body"); } void ExpandBodyMacroRequest::noteCycleStep(DiagnosticEngine &diags) const { - auto decl = std::get<0>(getStorage()); - diags.diagnose(decl->getLoc(), - diag::macro_expand_circular_reference_entity_through, - "body", - decl->getName()); + auto fn = std::get<0>(getStorage()); + diags.diagnose(fn.getLoc(), + diag::macro_expand_circular_reference_unnamed_through, + "body"); } //----------------------------------------------------------------------------// diff --git a/lib/ASTGen/Sources/ASTGen/SourceFile.swift b/lib/ASTGen/Sources/ASTGen/SourceFile.swift index f81189dd1a9f2..d604f3d27d805 100644 --- a/lib/ASTGen/Sources/ASTGen/SourceFile.swift +++ b/lib/ASTGen/Sources/ASTGen/SourceFile.swift @@ -249,13 +249,11 @@ public func emitParserDiagnostics( } } -/// Retrieve a syntax node in the given source file, with the given type. -public func findSyntaxNodeInSourceFile( - sourceFilePtr: UnsafeRawPointer, - sourceLocationPtr: UnsafePointer?, - type: Node.Type, - wantOutermost: Bool = false -) -> Node? { +/// Find a token in the given source file at the given location. +func findToken( + in sourceFilePtr: UnsafeRawPointer, + at sourceLocationPtr: UnsafePointer? +) -> TokenSyntax? { guard let sourceLocationPtr = sourceLocationPtr else { return nil } @@ -277,6 +275,20 @@ public func findSyntaxNodeInSourceFile( return nil } + return token +} + +/// Retrieve a syntax node in the given source file, with the given type. +public func findSyntaxNodeInSourceFile( + sourceFilePtr: UnsafeRawPointer, + sourceLocationPtr: UnsafePointer?, + type: Node.Type, + wantOutermost: Bool = false +) -> Node? { + guard let token = findToken(in: sourceFilePtr, at: sourceLocationPtr) else { + return nil + } + var currentSyntax = Syntax(token) var resultSyntax: Node? = nil while let parentSyntax = currentSyntax.parent { @@ -287,9 +299,8 @@ public func findSyntaxNodeInSourceFile( } } - // If we didn't find anything, complain and fail. + // If we didn't find anything, return nil. guard var resultSyntax else { - print("unable to find node: \(token.debugDescription)") return nil } @@ -310,6 +321,28 @@ public func findSyntaxNodeInSourceFile( return resultSyntax } +/// Retrieve a syntax node in the given source file that satisfies the +/// given predicate. +public func findSyntaxNodeInSourceFile( + sourceFilePtr: UnsafeRawPointer, + sourceLocationPtr: UnsafePointer?, + where predicate: (Syntax) -> Bool +) -> Syntax? { + guard let token = findToken(in: sourceFilePtr, at: sourceLocationPtr) else { + return nil + } + + var currentSyntax = Syntax(token) + while let parentSyntax = currentSyntax.parent { + currentSyntax = parentSyntax + if predicate(currentSyntax) { + return currentSyntax + } + } + + return nil +} + @_cdecl("swift_ASTGen_virtualFiles") @usableFromInline func getVirtualFiles( diff --git a/lib/ASTGen/Sources/MacroEvaluation/Macros.swift b/lib/ASTGen/Sources/MacroEvaluation/Macros.swift index 76e98eceacd04..21c42fbbcbc89 100644 --- a/lib/ASTGen/Sources/MacroEvaluation/Macros.swift +++ b/lib/ASTGen/Sources/MacroEvaluation/Macros.swift @@ -583,15 +583,15 @@ func expandAttachedMacro( return 1 } - // Dig out the node for the declaration to which the custom attribute is - // attached. - guard - let declarationNode = findSyntaxNodeInSourceFile( - sourceFilePtr: declarationSourceFilePtr, - sourceLocationPtr: declarationSourceLocPointer, - type: DeclSyntax.self - ) - else { + // Dig out the node for the closure or declaration to which the custom + // attribute is attached. + let node = findSyntaxNodeInSourceFile( + sourceFilePtr: declarationSourceFilePtr, + sourceLocationPtr: declarationSourceLocPointer, + where: { $0.is(DeclSyntax.self) || $0.is(ClosureExprSyntax.self) } + ) + + guard let node else { return 1 } @@ -622,7 +622,7 @@ func expandAttachedMacro( customAttrSourceFilePtr: customAttrSourceFilePtr, customAttrNode: customAttrNode, declarationSourceFilePtr: declarationSourceFilePtr, - attachedTo: declarationNode, + attachedTo: node, parentDeclSourceFilePtr: parentDeclSourceFilePtr, parentDeclNode: parentDeclNode ) @@ -657,7 +657,7 @@ func expandAttachedMacroImpl( customAttrSourceFilePtr: UnsafePointer, customAttrNode: AttributeSyntax, declarationSourceFilePtr: UnsafePointer, - attachedTo declarationNode: DeclSyntax, + attachedTo declarationNode: Syntax, parentDeclSourceFilePtr: UnsafePointer?, parentDeclNode: DeclSyntax? ) -> String? { @@ -689,7 +689,7 @@ func expandAttachedMacroImpl( )! let declSyntax = PluginMessage.Syntax( - syntax: Syntax(declarationNode), + syntax: declarationNode, in: declarationSourceFilePtr )! diff --git a/lib/Demangling/Demangler.cpp b/lib/Demangling/Demangler.cpp index dbdcf1d0bb6b4..097543eba0f72 100644 --- a/lib/Demangling/Demangler.cpp +++ b/lib/Demangling/Demangler.cpp @@ -4478,7 +4478,7 @@ NodePointer Demangler::demangleMacroExpansion() { context = popContext(); NodePointer discriminator = demangleIndexAsNode(); NodePointer result; - if (isAttached) { + if (isAttached && attachedName) { result = createWithChildren( kind, context, attachedName, macroName, discriminator); } else { diff --git a/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift b/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift index 428030e14f137..a6b9bc489b514 100644 --- a/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift +++ b/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift @@ -50,4 +50,18 @@ public struct StartTaskMacro: BodyMacro { """ ] } + + public static func expansion( + of node: AttributeSyntax, + providingBodyFor closure: ClosureExprSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + return [ + """ + Task { + \(closure.statements) + } + """ + ] + } } diff --git a/lib/Sema/PreCheckTarget.cpp b/lib/Sema/PreCheckTarget.cpp index 70c20492a2297..576d1b1f81e07 100644 --- a/lib/Sema/PreCheckTarget.cpp +++ b/lib/Sema/PreCheckTarget.cpp @@ -1149,7 +1149,7 @@ class PreCheckTarget final : public ASTWalker { ASTContext &getASTContext() const { return Ctx; } - bool walkToClosureExprPre(ClosureExpr *expr); + bool walkToClosureExprPre(ClosureExpr *expr, ParentTy &parent); MacroWalking getMacroWalkingBehavior() const override { return MacroWalking::Arguments; @@ -1251,7 +1251,7 @@ class PreCheckTarget final : public ASTWalker { // but do not walk into the body. That will be type-checked after // we've determine the complete function type. if (auto closure = dyn_cast(expr)) - return finish(walkToClosureExprPre(closure), expr); + return finish(walkToClosureExprPre(closure, Parent), expr); if (auto *unresolved = dyn_cast(expr)) return finish(true, TypeChecker::resolveDeclRefExpr(unresolved, DC)); @@ -1533,7 +1533,27 @@ class PreCheckTarget final : public ASTWalker { /// Perform prechecking of a ClosureExpr before we dive into it. This returns /// true when we want the body to be considered part of this larger expression. -bool PreCheckTarget::walkToClosureExprPre(ClosureExpr *closure) { +bool PreCheckTarget::walkToClosureExprPre(ClosureExpr *closure, + ParentTy &parent) { + if (auto *expandedBody = closure->getExpandedBody()) { + if (Parent.getAsExpr()) { + // We cannot simply replace the body when closure i.e. is passed + // as an argument to a call or is a source of an assignment + // because the source range of the argument list would cross + // buffer boundaries. One way to avoid that is to inject + // elements into a new implicit brace statement with the original + // source locations. Brace statement has to be implicit because its + // elements are in a different buffer. + auto sourceRange = closure->getSourceRange(); + closure->setBody(BraceStmt::create(getASTContext(), sourceRange.Start, + expandedBody->getElements(), + sourceRange.End, + /*implicit=*/true)); + } else { + closure->setBody(expandedBody); + } + } + // Pre-check the closure body. (void)evaluateOrDefault(Ctx.evaluator, PreCheckClosureBodyRequest{closure}, nullptr); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index ea9329f913dbb..7f823f6bf2c1f 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -33,6 +33,7 @@ #include "swift/AST/ExistentialLayout.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/ImportCache.h" +#include "swift/AST/MacroDefinition.h" #include "swift/AST/ModuleNameLookup.h" #include "swift/AST/NameLookup.h" #include "swift/AST/NameLookupRequests.h" @@ -8310,6 +8311,25 @@ class ClosureAttributeChecker return; // it's OK } + auto declRef = evaluateOrDefault( + ctx.evaluator, + ResolveMacroRequest{attr, closure}, + ConcreteDeclRef()); + + auto *decl = declRef.getDecl(); + if (auto *macro = dyn_cast_or_null(decl)) { + if (macro->getMacroRoles().contains(MacroRole::Body)) { + if (!ctx.LangOpts.hasFeature(Feature::ClosureBodyMacro)) { + ctx.Diags.diagnose( + attr->getLocation(), + diag::experimental_closure_body_macro); + } + + // Function body macros are allowed on closures. + return; + } + } + // Otherwise, it's an error. std::string typeName; if (auto typeRepr = attr->getTypeRepr()) { diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index b800948c0f197..eb0c7c15c7f3a 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -971,6 +971,19 @@ static CharSourceRange getExpansionInsertionRange(MacroRole role, } case MacroRole::Body: { + if (auto *expr = target.dyn_cast()) { + ASSERT(isa(expr)); + + auto *closure = cast(expr); + // A closure body macro expansion replaces the full source + // range of the closure body starting from `in` and ending right + // before the closing brace. + return Lexer::getCharSourceRangeFromSourceRange( + sourceMgr, SourceRange(Lexer::getLocForEndOfToken( + sourceMgr, closure->getInLoc()), + closure->getEndLoc())); + } + SourceLoc afterDeclLoc = Lexer::getLocForEndOfToken(sourceMgr, target.getEndLoc()); return CharSourceRange(afterDeclLoc, 0); @@ -1568,6 +1581,142 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, return macroSourceFile; } +static SourceFile *evaluateAttachedMacro(MacroDecl *macro, + ClosureExpr *attachedTo, + CustomAttr *attr, + MacroRole role, + StringRef discriminatorStr = "") { + assert(role == MacroRole::Body); + + DeclContext *dc = attachedTo; + ASTContext &ctx = dc->getASTContext(); + auto moduleDecl = dc->getParentModule(); + + auto attrSourceFile = + moduleDecl->getSourceFileContainingLocation(attr->AtLoc); + if (!attrSourceFile) + return nullptr; + + SourceLoc attachedToLoc = attachedTo->getLoc(); + SourceFile *closureSourceFile = + moduleDecl->getSourceFileContainingLocation(attachedToLoc); + if (!closureSourceFile) + return nullptr; + + if (isFromExpansionOfMacro(attrSourceFile, macro, role) || + isFromExpansionOfMacro(closureSourceFile, macro, role)) { + ctx.Diags.diagnose(attr->getLocation(), + diag::macro_recursive, + macro->getName()); + return nullptr; + } + + // Evaluate the macro. + std::unique_ptr evaluatedSource; + + /// The discriminator used for the macro. + LazyValue discriminator([&]() -> std::string { + if (!discriminatorStr.empty()) + return discriminatorStr.str(); +#if SWIFT_BUILD_SWIFT_SYNTAX + Mangle::ASTMangler mangler(attachedTo->getASTContext()); + return mangler.mangleAttachedMacroExpansion( + attachedTo, attr, macro); +#else + return ""; +#endif + }); + + auto macroDef = macro->getDefinition(); + switch (macroDef.kind) { + case MacroDefinition::Kind::Undefined: + case MacroDefinition::Kind::Invalid: + // Already diagnosed as an error elsewhere. + return nullptr; + + case MacroDefinition::Kind::Builtin: { + switch (macroDef.getBuiltinKind()) { + case BuiltinMacroKind::ExternalMacro: + case BuiltinMacroKind::IsolationMacro: + case BuiltinMacroKind::SwiftSettingsMacro: + // FIXME: Error here. + return nullptr; + } + } + + case MacroDefinition::Kind::Expanded: { + // Expand the definition with the given arguments. + auto result = expandMacroDefinition( + macroDef.getExpanded(), macro, + /*genericArgs=*/{}, // attached macros don't have generic parameters + attr->getArgs()); + evaluatedSource = llvm::MemoryBuffer::getMemBufferCopy( + result, adjustMacroExpansionBufferName(*discriminator)); + break; + } + + case MacroDefinition::Kind::External: { + // Retrieve the external definition of the macro. + auto external = macroDef.getExternalMacro(); + ExternalMacroDefinitionRequest request{ + &ctx, external.moduleName, external.macroTypeName + }; + auto externalDef = + evaluateOrDefault(ctx.evaluator, request, + ExternalMacroDefinition::error("failed request")); + if (externalDef.isError()) { + ctx.Diags.diagnose(attr->getLocation(), + diag::external_macro_not_found, + external.moduleName.str(), + external.macroTypeName.str(), macro->getName(), + externalDef.getErrorMessage()); + macro->diagnose(diag::decl_declared_here, macro); + return nullptr; + } + +#if SWIFT_BUILD_SWIFT_SYNTAX + PrettyStackTraceExpr debugStack( + ctx, "expanding attached macro", attachedTo); + + auto *astGenAttrSourceFile = attrSourceFile->getExportedSourceFile(); + if (!astGenAttrSourceFile) + return nullptr; + + auto *astGenClosureSourceFile = closureSourceFile->getExportedSourceFile(); + if (!astGenClosureSourceFile) + return nullptr; + + auto startLoc = attachedTo->getStartLoc(); + BridgedStringRef evaluatedSourceOut{nullptr, 0}; + assert(!externalDef.isError()); + swift_Macros_expandAttachedMacro( + &ctx.Diags, externalDef.get(), discriminator->c_str(), + "", "", getRawMacroRole(role), + astGenAttrSourceFile, attr->AtLoc.getOpaquePointerValue(), + astGenClosureSourceFile, startLoc.getOpaquePointerValue(), + /*parentDeclSourceFile*/nullptr, /*parentDeclLoc*/nullptr, + &evaluatedSourceOut); + if (!evaluatedSourceOut.unbridged().data()) + return nullptr; + evaluatedSource = llvm::MemoryBuffer::getMemBufferCopy( + evaluatedSourceOut.unbridged(), + adjustMacroExpansionBufferName(*discriminator)); + swift_ASTGen_freeBridgedString(evaluatedSourceOut); + break; +#else + ctx.Diags.diagnose(attachedTo->getLoc(), + diag::macro_unsupported); + return nullptr; +#endif + } + } + + SourceFile *macroSourceFile = createMacroSourceFile( + std::move(evaluatedSource), role, attachedTo, dc, attr); + + return macroSourceFile; +} + bool swift::accessorMacroOnlyIntroducesObservers( MacroDecl *macro, const MacroRoleAttr *attr @@ -1743,9 +1892,12 @@ ArrayRef ExpandPreambleMacroRequest::evaluate( std::optional ExpandBodyMacroRequest::evaluate(Evaluator &evaluator, - AbstractFunctionDecl *fn) const { + AnyFunctionRef fn) const { + auto *dc = fn.getAsDeclContext(); + auto &ctx = dc->getASTContext(); std::optional bufferID; - fn->forEachAttachedMacro(MacroRole::Body, + fn.forEachAttachedMacro( + MacroRole::Body, [&](CustomAttr *customAttr, MacroDecl *macro) { // FIXME: Should we complain if we already expanded a body macro? if (bufferID) @@ -1753,7 +1905,6 @@ ExpandBodyMacroRequest::evaluate(Evaluator &evaluator, // '@StartTask' is gated behind the 'ConcurrencySyntaxSugar' // experimental feature. - auto &ctx = fn->getASTContext(); if (macro->getParentModule()->getName().is("_Concurrency") && macro->getBaseIdentifier().is("StartTask") && !ctx.LangOpts.hasFeature(Feature::ConcurrencySyntaxSugar)) { @@ -1764,8 +1915,16 @@ ExpandBodyMacroRequest::evaluate(Evaluator &evaluator, return; } - auto macroSourceFile = ::evaluateAttachedMacro( - macro, fn, customAttr, false, MacroRole::Body); + SourceFile * macroSourceFile = nullptr; + if (auto *fnDecl = fn.getAbstractFunctionDecl()) { + macroSourceFile = ::evaluateAttachedMacro( + macro, fnDecl, customAttr, false, MacroRole::Body); + } else if (auto *closure = + dyn_cast_or_null(fn.getAbstractClosureExpr())) { + macroSourceFile = ::evaluateAttachedMacro( + macro, closure, customAttr, MacroRole::Body); + } + if (!macroSourceFile) return; @@ -2098,6 +2257,28 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator, dc, expansion->getExpansionInfo(), roles); } } else { + if (isa(dc) && roles.contains(MacroRole::Body)) { + // The closures are type-checked after macros are expanded + // which means that macro cannot reference any declarations + // from inner or outer closures as its arguments. + // + // For example: + // `_: (Int) -> Void = { x in { @Macro(x) in ... }() }` + // + // `x` is not going to be type-checked at macro expansion + // time and cannot be referenced by `@Macro`. + // + // Let's walk up declaration contexts until we find first + // non-closure one. This means that we can support a local + // declaration that is defined inside of a closure because + // they are separately checked after outer ones are already + // processed. + while ((dc = dc->getParent())) { + if (!isa(dc)) + break; + } + } + SourceRange genericArgsRange = macroRef.getGenericArgsRange(); macroExpansion = MacroExpansionExpr::create( dc, macroRef.getSigilLoc(), diff --git a/test/Macros/start_task.swift b/test/Macros/start_task.swift index 2f56849a1ec62..7487e4186927b 100644 --- a/test/Macros/start_task.swift +++ b/test/Macros/start_task.swift @@ -1,6 +1,6 @@ -// REQUIRES: swift_swift_parser, swift_feature_ConcurrencySyntaxSugar +// REQUIRES: swift_swift_parser, swift_feature_ConcurrencySyntaxSugar, swift_feature_ClosureBodyMacro -// RUN: %target-swift-frontend -typecheck -plugin-path %swift-plugin-dir -enable-experimental-feature ConcurrencySyntaxSugar -language-mode 6 %s -dump-macro-expansions 2>&1 | %FileCheck %s +// RUN: %target-swift-frontend -typecheck -plugin-path %swift-plugin-dir -enable-experimental-feature ConcurrencySyntaxSugar -enable-experimental-feature ClosureBodyMacro -language-mode 6 %s -dump-macro-expansions 2>&1 | %FileCheck %s func f() async {} @@ -14,5 +14,108 @@ func sync() { await f() } +func takeClosure( + _ closure: @escaping @Sendable () -> Void, + v: Int = 42 +) { + closure() +} + +func multipleClosures( + a: @escaping @Sendable () -> Void, + b: @escaping @Sendable () -> Void) { +} + +func onClosure() { + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX35_16_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure { @StartTask in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX45_16_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure({ @StartTask in + await f() + }, v: 0) + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX55_21_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + multipleClosures { @StartTask in + await f() + } b: { + } + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX68_9_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + multipleClosures { + _ = 42 + } b: { @StartTask in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX86_4_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: _ = 42 + // CHECK: } + // CHECK: } + + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX88_9_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + multipleClosures { + @StartTask in + _ = 42 + } b: { @StartTask in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX100_12_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + let _ = { + func test() { + _ = { @StartTask in await f() } + } + } + + // CHECK-LABEL: @__swiftmacro_10start_task0021start_taskswift_IfFDefMX114_54_33_EEC79532ED9A2723128F952F754D3F84Ll9StartTaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await test() + // CHECK: } + // CHECK: } + let _ = { + let y = 42 + func test() async { print(y) } + + if case let (_, closure) = (otherValue: 42, fn: { @StartTask in + await test() + }) { + let _: Task<(), Never> = closure() + } + } +}