Skip to content

Commit 498d7cc

Browse files
authored
Merge pull request #80187 from hborla/task-macro-improvements
[Macros] Update the name and argument list for the `@Task` function body macro.
2 parents d97a8ce + bac0a10 commit 498d7cc

File tree

9 files changed

+318
-207
lines changed

9 files changed

+318
-207
lines changed

lib/Macros/Sources/SwiftMacros/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ add_swift_macro_library(SwiftMacros
1414
OptionSetMacro.swift
1515
DebugDescriptionMacro.swift
1616
DistributedResolvableMacro.swift
17-
StartTaskMacro.swift
17+
TaskMacro.swift
1818
SyntaxExtensions.swift
1919
TaskLocalMacro.swift
2020
SwiftifyImportMacro.swift

lib/Macros/Sources/SwiftMacros/OptionSetMacro.swift

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,6 @@ private let optionsEnumNameArgumentLabel = "optionsName"
4747
/// eventually be overridable.
4848
private let defaultOptionsEnumName = "Options"
4949

50-
extension LabeledExprListSyntax {
51-
/// Retrieve the first element with the given label.
52-
func first(labeled name: String) -> Element? {
53-
return first { element in
54-
if let label = element.label, label.text == name {
55-
return true
56-
}
57-
58-
return false
59-
}
60-
}
61-
}
62-
6350
public struct OptionSetMacro {
6451
/// Decodes the arguments to the macro expansion.
6552
///

lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift

Lines changed: 0 additions & 67 deletions
This file was deleted.

lib/Macros/Sources/SwiftMacros/SyntaxExtensions.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,17 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax {
4343
trailingTrivia: self.trailingTrivia
4444
)
4545
}
46-
}
46+
}
47+
48+
extension LabeledExprListSyntax {
49+
/// Retrieve the first element with the given label.
50+
func first(labeled name: String) -> Element? {
51+
return first { element in
52+
if let label = element.label, label.text == name {
53+
return true
54+
}
55+
56+
return false
57+
}
58+
}
59+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftDiagnostics
14+
import SwiftParser
15+
import SwiftSyntax
16+
import SwiftSyntaxBuilder
17+
import SwiftSyntaxMacros
18+
19+
extension MacroExpansionContext {
20+
func diagnose(
21+
_ diag: TaskMacroDiagnostic,
22+
at node: some SyntaxProtocol
23+
) {
24+
diagnose(Diagnostic(
25+
node: Syntax(node),
26+
message: diag
27+
))
28+
}
29+
}
30+
31+
enum TaskMacroDiagnostic: String, DiagnosticMessage {
32+
case noImplementation
33+
= "'@Task' macro can only be used on functions with an implementation"
34+
case unsupportedGlobalActor
35+
= "'@Task' global actor must be written 'GlobalActorType'.shared"
36+
37+
var message: String { rawValue }
38+
39+
var severity: DiagnosticSeverity { .error }
40+
41+
var diagnosticID: MessageID {
42+
MessageID(domain: "_Concurrency", id: "TaskMacro.\(self)")
43+
}
44+
}
45+
46+
47+
public struct TaskMacro: BodyMacro {
48+
public static func expansion(
49+
of node: AttributeSyntax,
50+
statements: CodeBlockItemListSyntax,
51+
in context: some MacroExpansionContext
52+
) throws -> [CodeBlockItemSyntax] {
53+
var globalActor: TokenSyntax? = nil
54+
var argumentList: LabeledExprListSyntax = []
55+
if case .argumentList(let arguments) = node.arguments {
56+
if let actor = arguments.first(labeled: "on") {
57+
guard let member = actor.expression.as(MemberAccessExprSyntax.self),
58+
let declRef = member.base?.as(DeclReferenceExprSyntax.self) else {
59+
context.diagnose(.unsupportedGlobalActor, at: actor)
60+
return []
61+
}
62+
63+
argumentList = LabeledExprListSyntax(arguments.dropFirst())
64+
globalActor = declRef.baseName
65+
} else {
66+
argumentList = arguments
67+
}
68+
}
69+
70+
let signature: ClosureSignatureSyntax? =
71+
if let globalActor {
72+
.init(attributes: "@\(globalActor) ")
73+
} else {
74+
nil
75+
}
76+
77+
let parens: (left: TokenSyntax, right: TokenSyntax)? =
78+
if !argumentList.isEmpty {
79+
(.leftParenToken(), .rightParenToken())
80+
} else {
81+
nil
82+
}
83+
84+
let taskInit = FunctionCallExprSyntax(
85+
calledExpression: DeclReferenceExprSyntax(
86+
baseName: "Task"
87+
),
88+
leftParen: parens?.left,
89+
arguments: argumentList,
90+
rightParen: parens?.right,
91+
trailingClosure: ClosureExprSyntax(
92+
signature: signature,
93+
statements: statements
94+
)
95+
)
96+
97+
return ["\(taskInit)"]
98+
}
99+
100+
public static func expansion(
101+
of node: AttributeSyntax,
102+
providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
103+
in context: some MacroExpansionContext
104+
) throws -> [CodeBlockItemSyntax] {
105+
guard let taskBody = declaration.body else {
106+
context.diagnose(.noImplementation, at: node)
107+
return []
108+
}
109+
110+
return try expansion(
111+
of: node,
112+
statements: taskBody.statements,
113+
in: context)
114+
}
115+
116+
public static func expansion(
117+
of node: AttributeSyntax,
118+
providingBodyFor closure: ClosureExprSyntax,
119+
in context: some MacroExpansionContext
120+
) throws -> [CodeBlockItemSyntax] {
121+
try expansion(
122+
of: node,
123+
statements: closure.statements,
124+
in: context)
125+
}
126+
}

lib/Sema/TypeCheckMacros.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,10 +1903,10 @@ ExpandBodyMacroRequest::evaluate(Evaluator &evaluator,
19031903
if (bufferID)
19041904
return;
19051905

1906-
// '@StartTask' is gated behind the 'ConcurrencySyntaxSugar'
1906+
// '@Task' is gated behind the 'ConcurrencySyntaxSugar'
19071907
// experimental feature.
19081908
if (macro->getParentModule()->getName().is("_Concurrency") &&
1909-
macro->getBaseIdentifier().is("StartTask") &&
1909+
macro->getBaseIdentifier().is("Task") &&
19101910
!ctx.LangOpts.hasFeature(Feature::ConcurrencySyntaxSugar)) {
19111911
ctx.Diags.diagnose(
19121912
customAttr->getLocation(),

stdlib/public/Concurrency/Actor.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,26 @@ internal func _enqueueOnMain(_ job: UnownedJob)
9999
@freestanding(expression)
100100
public macro isolation<T>() -> T = Builtin.IsolationMacro
101101

102+
/// Wrap the function body in a new top-level task on behalf of the
103+
/// given actor.
102104
@available(SwiftStdlib 5.1, *)
103105
@attached(body)
104-
public macro StartTask() =
105-
#externalMacro(module: "SwiftMacros", type: "StartTaskMacro")
106+
public macro Task(
107+
on actor: any GlobalActor,
108+
name: String? = nil,
109+
priority: TaskPriority? = nil
110+
) =
111+
#externalMacro(module: "SwiftMacros", type: "TaskMacro")
112+
113+
/// Wrap the function body in a new top-level task on behalf of the
114+
/// current actor.
115+
@available(SwiftStdlib 5.1, *)
116+
@attached(body)
117+
public macro Task(
118+
name: String? = nil,
119+
priority: TaskPriority? = nil
120+
) =
121+
#externalMacro(module: "SwiftMacros", type: "TaskMacro")
106122
107123
// NOTE: We put SwiftSetting under $Macro since #SwiftSettings() is a macro.
108124
@available(SwiftStdlib 9999, *)

0 commit comments

Comments
 (0)