Skip to content

Commit dbacc24

Browse files
committed
[Concurrency] Allow @TaskLocal on global variables
1 parent da5b404 commit dbacc24

File tree

4 files changed

+58
-36
lines changed

4 files changed

+58
-36
lines changed

lib/Macros/Sources/SwiftMacros/TaskLocalMacro.swift

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import SwiftSyntax
1414
import SwiftSyntaxMacros
1515
import SwiftDiagnostics
1616

17-
// TODO: docs
17+
/// Macro implementing the TaskLocal functionality.
18+
///
19+
/// It introduces a peer `static let $name: TaskLocal<Type>` as well as a getter
20+
/// that assesses accesses the task local storage.
1821
public enum TaskLocalMacro {}
1922

2023
extension TaskLocalMacro: PeerMacro {
@@ -26,25 +29,29 @@ extension TaskLocalMacro: PeerMacro {
2629
guard let varDecl = try requireVar(declaration, diagnose: false) else {
2730
return []
2831
}
29-
guard try requireModifier(varDecl, .static, diagnose: false) else {
32+
guard try requireStaticContext(varDecl, in: context, diagnose: false) else {
3033
return []
3134
}
3235

3336
guard let firstBinding = varDecl.bindings.first else {
34-
return [] // TODO: make error
37+
throw DiagnosticsError(
38+
syntax: declaration,
39+
message: "'@TaskLocal' property must have declared binding", id: .incompatibleDecl)
3540
}
3641

3742
guard let name = firstBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
38-
return [] // TODO: make error
43+
throw DiagnosticsError(
44+
syntax: declaration,
45+
message: "'@TaskLocal' property must have name", id: .incompatibleDecl)
3946
}
4047

4148
let type = firstBinding.typeAnnotation?.type
42-
let explicitType: String =
43-
if let type {
44-
": TaskLocal<\(type.trimmed)>"
45-
} else {
46-
""
47-
}
49+
let explicitType: String
50+
if let type {
51+
explicitType = ": TaskLocal<\(type.trimmed)>"
52+
} else {
53+
explicitType = ""
54+
}
4855

4956
let initialValue: Any
5057
if let initializerValue = firstBinding.initializer?.value {
@@ -57,9 +64,18 @@ extension TaskLocalMacro: PeerMacro {
5764
message: "'@TaskLocal' property must have default value, or be optional", id: .mustBeVar)
5865
}
5966

67+
// If the property is global, do not prefix the synthesised decl with 'static'
68+
let isGlobal = context.lexicalContext.isEmpty
69+
let staticKeyword: String
70+
if isGlobal {
71+
staticKeyword = ""
72+
} else {
73+
staticKeyword = "static "
74+
}
75+
6076
return [
6177
"""
62-
static let $\(name)\(raw: explicitType) = TaskLocal(wrappedValue: \(raw: initialValue))
78+
\(raw: staticKeyword)let $\(name)\(raw: explicitType) = TaskLocal(wrappedValue: \(raw: initialValue))
6379
"""
6480
]
6581
}
@@ -77,7 +93,7 @@ extension TaskLocalMacro: AccessorMacro {
7793
guard let varDecl = try requireVar(declaration) else {
7894
return []
7995
}
80-
try requireModifier(varDecl, .static)
96+
try requireStaticContext(varDecl, in: context)
8197

8298
guard let firstBinding = varDecl.bindings.first else {
8399
return [] // TODO: make error
@@ -107,24 +123,29 @@ private func requireVar(_ decl: some DeclSyntaxProtocol,
107123
}
108124

109125
@discardableResult
110-
private func requireModifier(_ decl: VariableDeclSyntax,
111-
_ keyword: Keyword,
112-
diagnose: Bool = true) throws -> Bool {
126+
private func requireStaticContext(_ decl: VariableDeclSyntax,
127+
in context: some MacroExpansionContext,
128+
diagnose: Bool = true) throws -> Bool {
113129
let isStatic = decl.modifiers.contains { modifier in
114-
modifier.name.text == "\(keyword)"
130+
modifier.name.text == "\(Keyword.static)"
115131
}
116132

117-
if !isStatic {
118-
if diagnose {
119-
throw DiagnosticsError(
120-
syntax: decl,
121-
message: "'@TaskLocal' can only be applied to 'static' property", id: .mustBeStatic)
122-
} else {
123-
return false
124-
}
133+
if isStatic {
134+
return true
135+
}
136+
137+
let isGlobal = context.lexicalContext.isEmpty
138+
if isGlobal {
139+
return true
125140
}
126141

127-
return true
142+
if diagnose {
143+
throw DiagnosticsError(
144+
syntax: decl,
145+
message: "'@TaskLocal' can only be applied to 'static' property", id: .mustBeStatic)
146+
}
147+
148+
return false
128149
}
129150

130151
extension TypeSyntax {
@@ -141,8 +162,9 @@ extension TypeSyntax {
141162

142163
struct TaskLocalMacroDiagnostic: DiagnosticMessage {
143164
enum ID: String {
144-
case mustBeStatic = "must be static"
145165
case mustBeVar = "must be var"
166+
case mustBeStatic = "must be static"
167+
case incompatibleDecl = "incompatible declaration"
146168
}
147169

148170
var message: String

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4955,15 +4955,6 @@ ActorIsolation ActorIsolationRequest::evaluate(
49554955
if (var->isGlobalStorage() && !isActorType) {
49564956
auto *diagVar = var;
49574957
if (auto *originalVar = var->getOriginalWrappedProperty()) {
4958-
// temporary 5.10 checking bypass for @TaskLocal <rdar://120907014>
4959-
// TODO: @TaskLocal should be a macro <rdar://120914014>
4960-
if (auto *classDecl =
4961-
var->getInterfaceType()->getClassOrBoundGenericClass()) {
4962-
auto &ctx = var->getASTContext();
4963-
if (classDecl == ctx.getTaskLocalDecl()) {
4964-
return isolation;
4965-
}
4966-
}
49674958
diagVar = originalVar;
49684959
}
49694960
if (var->isLet()) {

test/Concurrency/task_local.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct TL {
2121
var notStatic: String?
2222
}
2323

24-
@TaskLocal // expected-error{{'@TaskLocal' can only be applied to 'static' property}}
24+
@TaskLocal
2525
var global: Int = 0
2626

2727
class NotSendable {}
@@ -36,3 +36,12 @@ func test () async {
3636
let _: Int = TL.number
3737
let _: Int = TL.$number.get()
3838
}
39+
40+
@TaskLocal // expected-error{{'accessor' macro cannot be attached to global function ('test')}}
41+
func test() {}
42+
43+
class X {
44+
@TaskLocal // expected-error{{'accessor' macro cannot be attached to static method ('test')}}
45+
static func test() {
46+
}
47+
}

0 commit comments

Comments
 (0)