@@ -14,7 +14,10 @@ import SwiftSyntax
1414import SwiftSyntaxMacros
1515import 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.
1821public enum TaskLocalMacro { }
1922
2023extension 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
130151extension TypeSyntax {
@@ -141,8 +162,9 @@ extension TypeSyntax {
141162
142163struct 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
0 commit comments