From bac0a10ae2d23e47fe7a33569f9a99d9eb8d7d2b Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Thu, 20 Mar 2025 09:29:21 -0700 Subject: [PATCH] [Macros] Update the name and argument list for the function body macro that wraps a function or closure body in a new top-level task. --- lib/Macros/Sources/SwiftMacros/CMakeLists.txt | 2 +- .../Sources/SwiftMacros/OptionSetMacro.swift | 13 -- .../Sources/SwiftMacros/StartTaskMacro.swift | 67 -------- .../SwiftMacros/SyntaxExtensions.swift | 15 +- .../Sources/SwiftMacros/TaskMacro.swift | 126 ++++++++++++++ lib/Sema/TypeCheckMacros.cpp | 4 +- stdlib/public/Concurrency/Actor.swift | 20 ++- test/Macros/start_task.swift | 121 -------------- test/Macros/task_macro.swift | 157 ++++++++++++++++++ 9 files changed, 318 insertions(+), 207 deletions(-) delete mode 100644 lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift create mode 100644 lib/Macros/Sources/SwiftMacros/TaskMacro.swift delete mode 100644 test/Macros/start_task.swift create mode 100644 test/Macros/task_macro.swift diff --git a/lib/Macros/Sources/SwiftMacros/CMakeLists.txt b/lib/Macros/Sources/SwiftMacros/CMakeLists.txt index 5f719dd7778c6..cde6d3f4f9491 100644 --- a/lib/Macros/Sources/SwiftMacros/CMakeLists.txt +++ b/lib/Macros/Sources/SwiftMacros/CMakeLists.txt @@ -14,7 +14,7 @@ add_swift_macro_library(SwiftMacros OptionSetMacro.swift DebugDescriptionMacro.swift DistributedResolvableMacro.swift - StartTaskMacro.swift + TaskMacro.swift SyntaxExtensions.swift TaskLocalMacro.swift SwiftifyImportMacro.swift diff --git a/lib/Macros/Sources/SwiftMacros/OptionSetMacro.swift b/lib/Macros/Sources/SwiftMacros/OptionSetMacro.swift index 62cc897d336d7..bba078e20bdca 100644 --- a/lib/Macros/Sources/SwiftMacros/OptionSetMacro.swift +++ b/lib/Macros/Sources/SwiftMacros/OptionSetMacro.swift @@ -47,19 +47,6 @@ private let optionsEnumNameArgumentLabel = "optionsName" /// eventually be overridable. private let defaultOptionsEnumName = "Options" -extension LabeledExprListSyntax { - /// Retrieve the first element with the given label. - func first(labeled name: String) -> Element? { - return first { element in - if let label = element.label, label.text == name { - return true - } - - return false - } - } -} - public struct OptionSetMacro { /// Decodes the arguments to the macro expansion. /// diff --git a/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift b/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift deleted file mode 100644 index a6b9bc489b514..0000000000000 --- a/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift +++ /dev/null @@ -1,67 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import SwiftDiagnostics -import SwiftParser -import SwiftSyntax -import SwiftSyntaxBuilder -import SwiftSyntaxMacros - -struct TaskMacroDiagnostic: DiagnosticMessage { - static func diagnose(at node: some SyntaxProtocol) -> Diagnostic { - Diagnostic(node: Syntax(node), message: Self.init()) - } - - var message: String { - "'@StartTask' macro can only be used on functions with an implementation" - } - - var severity: DiagnosticSeverity { .error } - - var diagnosticID: MessageID { - MessageID(domain: "_Concurrency", id: "StartMacro.\(self)") - } -} - - -public struct StartTaskMacro: BodyMacro { - public static func expansion( - of node: AttributeSyntax, - providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, - in context: some MacroExpansionContext - ) throws -> [CodeBlockItemSyntax] { - guard let taskBody = declaration.body else { - context.diagnose(TaskMacroDiagnostic.diagnose(at: node)) - return [] - } - - return [ - """ - Task \(taskBody) - """ - ] - } - - public static func expansion( - of node: AttributeSyntax, - providingBodyFor closure: ClosureExprSyntax, - in context: some MacroExpansionContext - ) throws -> [CodeBlockItemSyntax] { - return [ - """ - Task { - \(closure.statements) - } - """ - ] - } -} diff --git a/lib/Macros/Sources/SwiftMacros/SyntaxExtensions.swift b/lib/Macros/Sources/SwiftMacros/SyntaxExtensions.swift index 0fd0be6c82402..24458b18ac3da 100644 --- a/lib/Macros/Sources/SwiftMacros/SyntaxExtensions.swift +++ b/lib/Macros/Sources/SwiftMacros/SyntaxExtensions.swift @@ -43,4 +43,17 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax { trailingTrivia: self.trailingTrivia ) } -} \ No newline at end of file +} + +extension LabeledExprListSyntax { + /// Retrieve the first element with the given label. + func first(labeled name: String) -> Element? { + return first { element in + if let label = element.label, label.text == name { + return true + } + + return false + } + } +} diff --git a/lib/Macros/Sources/SwiftMacros/TaskMacro.swift b/lib/Macros/Sources/SwiftMacros/TaskMacro.swift new file mode 100644 index 0000000000000..53baf1fd13b11 --- /dev/null +++ b/lib/Macros/Sources/SwiftMacros/TaskMacro.swift @@ -0,0 +1,126 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftDiagnostics +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +extension MacroExpansionContext { + func diagnose( + _ diag: TaskMacroDiagnostic, + at node: some SyntaxProtocol + ) { + diagnose(Diagnostic( + node: Syntax(node), + message: diag + )) + } +} + +enum TaskMacroDiagnostic: String, DiagnosticMessage { + case noImplementation + = "'@Task' macro can only be used on functions with an implementation" + case unsupportedGlobalActor + = "'@Task' global actor must be written 'GlobalActorType'.shared" + + var message: String { rawValue } + + var severity: DiagnosticSeverity { .error } + + var diagnosticID: MessageID { + MessageID(domain: "_Concurrency", id: "TaskMacro.\(self)") + } +} + + +public struct TaskMacro: BodyMacro { + public static func expansion( + of node: AttributeSyntax, + statements: CodeBlockItemListSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + var globalActor: TokenSyntax? = nil + var argumentList: LabeledExprListSyntax = [] + if case .argumentList(let arguments) = node.arguments { + if let actor = arguments.first(labeled: "on") { + guard let member = actor.expression.as(MemberAccessExprSyntax.self), + let declRef = member.base?.as(DeclReferenceExprSyntax.self) else { + context.diagnose(.unsupportedGlobalActor, at: actor) + return [] + } + + argumentList = LabeledExprListSyntax(arguments.dropFirst()) + globalActor = declRef.baseName + } else { + argumentList = arguments + } + } + + let signature: ClosureSignatureSyntax? = + if let globalActor { + .init(attributes: "@\(globalActor) ") + } else { + nil + } + + let parens: (left: TokenSyntax, right: TokenSyntax)? = + if !argumentList.isEmpty { + (.leftParenToken(), .rightParenToken()) + } else { + nil + } + + let taskInit = FunctionCallExprSyntax( + calledExpression: DeclReferenceExprSyntax( + baseName: "Task" + ), + leftParen: parens?.left, + arguments: argumentList, + rightParen: parens?.right, + trailingClosure: ClosureExprSyntax( + signature: signature, + statements: statements + ) + ) + + return ["\(taskInit)"] + } + + public static func expansion( + of node: AttributeSyntax, + providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + guard let taskBody = declaration.body else { + context.diagnose(.noImplementation, at: node) + return [] + } + + return try expansion( + of: node, + statements: taskBody.statements, + in: context) + } + + public static func expansion( + of node: AttributeSyntax, + providingBodyFor closure: ClosureExprSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + try expansion( + of: node, + statements: closure.statements, + in: context) + } +} diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index eb0c7c15c7f3a..5c8817e3fdd74 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1903,10 +1903,10 @@ ExpandBodyMacroRequest::evaluate(Evaluator &evaluator, if (bufferID) return; - // '@StartTask' is gated behind the 'ConcurrencySyntaxSugar' + // '@Task' is gated behind the 'ConcurrencySyntaxSugar' // experimental feature. if (macro->getParentModule()->getName().is("_Concurrency") && - macro->getBaseIdentifier().is("StartTask") && + macro->getBaseIdentifier().is("Task") && !ctx.LangOpts.hasFeature(Feature::ConcurrencySyntaxSugar)) { ctx.Diags.diagnose( customAttr->getLocation(), diff --git a/stdlib/public/Concurrency/Actor.swift b/stdlib/public/Concurrency/Actor.swift index 399acff6ed958..c72fa895a7622 100644 --- a/stdlib/public/Concurrency/Actor.swift +++ b/stdlib/public/Concurrency/Actor.swift @@ -99,10 +99,26 @@ internal func _enqueueOnMain(_ job: UnownedJob) @freestanding(expression) public macro isolation() -> T = Builtin.IsolationMacro +/// Wrap the function body in a new top-level task on behalf of the +/// given actor. @available(SwiftStdlib 5.1, *) @attached(body) -public macro StartTask() = - #externalMacro(module: "SwiftMacros", type: "StartTaskMacro") +public macro Task( + on actor: any GlobalActor, + name: String? = nil, + priority: TaskPriority? = nil +) = + #externalMacro(module: "SwiftMacros", type: "TaskMacro") + +/// Wrap the function body in a new top-level task on behalf of the +/// current actor. +@available(SwiftStdlib 5.1, *) +@attached(body) +public macro Task( + name: String? = nil, + priority: TaskPriority? = nil +) = + #externalMacro(module: "SwiftMacros", type: "TaskMacro") // NOTE: We put SwiftSetting under $Macro since #SwiftSettings() is a macro. @available(SwiftStdlib 9999, *) diff --git a/test/Macros/start_task.swift b/test/Macros/start_task.swift deleted file mode 100644 index 7487e4186927b..0000000000000 --- a/test/Macros/start_task.swift +++ /dev/null @@ -1,121 +0,0 @@ -// REQUIRES: swift_swift_parser, swift_feature_ConcurrencySyntaxSugar, swift_feature_ClosureBodyMacro - -// 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 {} - -// CHECK-LABEL: @__swiftmacro_10start_task4sync9StartTaskfMb_.swift -// CHECK: Task { -// CHECK: await f() -// CHECK: } - -@StartTask -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() - } - } -} diff --git a/test/Macros/task_macro.swift b/test/Macros/task_macro.swift new file mode 100644 index 0000000000000..b13d4091a0229 --- /dev/null +++ b/test/Macros/task_macro.swift @@ -0,0 +1,157 @@ +// REQUIRES: swift_swift_parser, swift_feature_ConcurrencySyntaxSugar, swift_feature_ClosureBodyMacro + +// 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 -disable-availability-checking 2>&1 | %FileCheck %s + +func f() async {} + +// CHECK-LABEL: @__swiftmacro_10task_macro4sync4TaskfMb_.swift +// CHECK: Task { +// CHECK: await f() +// CHECK: } + +@Task +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_10task_macro0021task_macroswift_IfFDefMX35_16_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure { @Task in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX45_16_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure({ @Task in + await f() + }, v: 0) + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX55_21_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + multipleClosures { @Task in + await f() + } b: { + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX68_9_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + multipleClosures { + _ = 42 + } b: { @Task in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX86_4_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: _ = 42 + // CHECK: } + // CHECK: } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX88_9_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + multipleClosures { + @Task in + _ = 42 + } b: { @Task in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX100_12_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { + // CHECK: await f() + // CHECK: } + // CHECK: } + let _ = { + func test() { + _ = { @Task in await f() } + } + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX114_54_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.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: { @Task in + await test() + }) { + let _: Task<(), Never> = closure() + } + } +} + +func onClosureWithArguments() { + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX129_16_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task { @MainActor in + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure { @Task(on: MainActor.shared) in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX139_16_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task(name: "MyTask") { @MainActor in + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure { @Task(on: MainActor.shared, name: "MyTask") in + await f() + } + + // CHECK-LABEL: @__swiftmacro_10task_macro0021task_macroswift_IfFDefMX149_16_33_39E2B7F186C0B70273105736DD2F3721Ll4TaskfMb_.swift + // CHECK: { + // CHECK: Task(name: "MyTask", priority: .high) { + // CHECK: await f() + // CHECK: } + // CHECK: } + takeClosure { @Task(name: "MyTask", priority: .high) in + await f() + } +} + +// FIXME: Remove `-disable-availability-checking` from the run line after +// SE-0469 review wraps up. This comment is at the end of the file because +// closure body macro mangling includes source locations.