forked from DougGregor/swift-macro-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example macros for member-attribute, member, and accessor macros
Note that this change requires some in-flight fixes that haven't made it to a snapshot release yet.
- Loading branch information
1 parent
9a49431
commit 94de859
Showing
6 changed files
with
248 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import SwiftSyntax | ||
import SwiftSyntaxMacros | ||
|
||
public struct DictionaryStorageMacro { } | ||
|
||
extension DictionaryStorageMacro: AccessorMacro { | ||
public static func expansion< | ||
Context: MacroExpansionContext, | ||
Declaration: DeclSyntaxProtocol | ||
>( | ||
of node: AttributeSyntax, | ||
providingAccessorsOf declaration: Declaration, | ||
in context: Context | ||
) throws -> [AccessorDeclSyntax] { | ||
guard let varDecl = declaration.as(VariableDeclSyntax.self), | ||
let binding = varDecl.bindings.first, | ||
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, | ||
binding.accessor == nil, | ||
let type = binding.typeAnnotation?.type | ||
else { | ||
return [] | ||
} | ||
|
||
// Ignore the "_storage" variable. | ||
if identifier.text == "_storage" { | ||
return [] | ||
} | ||
|
||
guard let defaultValue = binding.initializer?.value else { | ||
throw CustomError.message("stored property must have an initializer") | ||
} | ||
|
||
print(varDecl.recursiveDescription) | ||
return [ | ||
""" | ||
get { | ||
_storage[\(literal: identifier.text), default: \(defaultValue)] as! \(type) | ||
} | ||
""", | ||
""" | ||
set { | ||
_storage[\(literal: identifier.text)] = newValue | ||
} | ||
""", | ||
] | ||
} | ||
} | ||
|
||
extension DictionaryStorageMacro: MemberMacro { | ||
public static func expansion( | ||
of node: AttributeSyntax, | ||
providingMembersOf declaration: some DeclGroupSyntax, | ||
in context: some MacroExpansionContext | ||
) throws -> [DeclSyntax] { | ||
let storage: DeclSyntax = "var _storage: [String: Any] = [:]" | ||
return [ | ||
storage.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) | ||
] | ||
} | ||
} | ||
|
||
extension DictionaryStorageMacro: MemberAttributeMacro { | ||
public static func expansion( | ||
of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, | ||
providingAttributesFor member: DeclSyntax, | ||
in context: some MacroExpansionContext | ||
) throws -> [AttributeSyntax] { | ||
guard let property = member.as(VariableDeclSyntax.self), | ||
property.isStoredProperty | ||
else { | ||
return [] | ||
} | ||
|
||
return [ | ||
AttributeSyntax( | ||
attributeName: SimpleTypeIdentifierSyntax( | ||
name: .identifier("DictionaryStorage") | ||
) | ||
) | ||
.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import SwiftSyntax | ||
import SwiftSyntaxBuilder | ||
import SwiftSyntaxMacros | ||
|
||
/// Implementation of the `wrapStoredProperties` macro, which can be | ||
/// used to apply an attribute to all of the stored properties of a type. | ||
/// | ||
/// This macro demonstrates member-attribute macros, which allow an attribute | ||
/// written on a type or extension to apply attributes to the members | ||
/// declared within that type or extension. | ||
public struct WrapStoredPropertiesMacro: MemberAttributeMacro { | ||
public static func expansion< | ||
Declaration: DeclGroupSyntax, | ||
Context: MacroExpansionContext | ||
>( | ||
of node: AttributeSyntax, | ||
attachedTo decl: Declaration, | ||
providingAttributesFor member: DeclSyntax, | ||
in context: Context | ||
) throws -> [AttributeSyntax] { | ||
guard let property = member.as(VariableDeclSyntax.self), | ||
property.isStoredProperty | ||
else { | ||
return [] | ||
} | ||
|
||
guard case let .argumentList(arguments) = node.argument, | ||
let firstElement = arguments.first, | ||
let stringLiteral = firstElement.expression | ||
.as(StringLiteralExprSyntax.self), | ||
stringLiteral.segments.count == 1, | ||
case let .stringSegment(wrapperName)? = stringLiteral.segments.first else { | ||
throw CustomError.message("macro requires a string literal containing the name of an attribute") | ||
} | ||
|
||
return [ | ||
AttributeSyntax( | ||
attributeName: SimpleTypeIdentifierSyntax( | ||
name: .identifier(wrapperName.content.text) | ||
) | ||
) | ||
.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) | ||
] | ||
} | ||
} | ||
|
||
extension VariableDeclSyntax { | ||
/// Determine whether this variable has the syntax of a stored property. | ||
/// | ||
/// This syntactic check cannot account for semantic adjustments due to, | ||
/// e.g., accessor macros or property wrappers. | ||
var isStoredProperty: Bool { | ||
if bindings.count != 1 { | ||
return false | ||
} | ||
|
||
let binding = bindings.first! | ||
switch binding.accessor { | ||
case .none: | ||
return true | ||
|
||
case .accessors(let node): | ||
for accessor in node.accessors { | ||
switch accessor.accessorKind.tokenKind { | ||
case .keyword(.willSet), .keyword(.didSet): | ||
// Observers can occur on a stored property. | ||
break | ||
|
||
default: | ||
// Other accessors make it a computed property. | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
|
||
case .getter: | ||
return false | ||
|
||
@unknown default: | ||
return false | ||
} | ||
} | ||
} | ||
|
||
extension DeclGroupSyntax { | ||
/// Enumerate the stored properties that syntactically occur in this | ||
/// declaration. | ||
func storedProperties() -> [VariableDeclSyntax] { | ||
return members.members.compactMap { member in | ||
guard let variable = member.decl.as(VariableDeclSyntax.self), | ||
variable.isStoredProperty else { | ||
return nil | ||
} | ||
|
||
return variable | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters