Skip to content

Instantly share code, notes, and snippets.

@aaronpearce
Last active June 7, 2023 08:18
Show Gist options
  • Save aaronpearce/8421f743c28c5349c7d5abfbb7fa1b11 to your computer and use it in GitHub Desktop.
Save aaronpearce/8421f743c28c5349c7d5abfbb7fa1b11 to your computer and use it in GitHub Desktop.
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
public struct Columns: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let memberList = declaration.storedProperties()
let variables = memberList.compactMap({ property -> String? in
// is a property
guard
let propertyName = property.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text else {
return nil
}
// if it has a Transient macro on it, we can skip it
if property.attributes?.first(where: { element in
return element.as(AttributeSyntax.self)?.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "Transient"
}) != nil {
return nil
} else {
return "static let \(propertyName) = Column(CodingKeys.\(propertyName))"
}
})
let columns: DeclSyntax = """
enum Columns {
\(raw: variables.joined(separator: "\n"))
}
"""
return [
columns
]
}
}
public struct Transient: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Does nothing, used only to decorate members with data
return []
}
}
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 memberBlock.members.compactMap { member in
guard let variable = member.decl.as(VariableDeclSyntax.self),
variable.isStoredProperty else {
return nil
}
return variable
}
}
}
@Columns
struct Player: Identifiable, Equatable, Codable {
var id: Int64?
var name: String
var score: Int
@Transient var temp: Int
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment