diff --git a/Sources/PugiSwift/Decoding/MacroDefinitions.swift b/Sources/PugiSwift/Decoding/MacroDefinitions.swift index b9820f1..98ef145 100644 --- a/Sources/PugiSwift/Decoding/MacroDefinitions.swift +++ b/Sources/PugiSwift/Decoding/MacroDefinitions.swift @@ -19,3 +19,9 @@ public macro Node(codingKey: String? = nil) = #externalMacro(module: "PugiSwiftMacros", type: "NodeMacro") +@attached(extension, + names: arbitrary) +public macro Restriction(codingKey: String? = nil) = + #externalMacro(module: "PugiSwiftMacros", + type: "RestrictionMacro") + diff --git a/Sources/PugiSwift/Decoding/Restrictions/FloatingResrictions.swift b/Sources/PugiSwift/Decoding/Restrictions/FloatingResrictions.swift index 5ae29c6..ff8c8c3 100644 --- a/Sources/PugiSwift/Decoding/Restrictions/FloatingResrictions.swift +++ b/Sources/PugiSwift/Decoding/Restrictions/FloatingResrictions.swift @@ -7,41 +7,45 @@ import Foundation -public protocol DoubleRestrictions: RestrictionsProtocol { +public protocol FloatingRestrictions: RestrictionsProtocol { + + associatedtype T: FloatingPoint static var fractionDigits: Int { get } - static var maxExclusive: Int { get } + static var maxExclusive: T { get } - static var maxInclusive: Int { get } + static var maxInclusive: T { get } - static var minExclusive: Int { get } + static var minExclusive: T { get } - static var minInclusive: Int { get } + static var minInclusive: T { get } static var totalDigits: Int { get } + var rawValue: T { get } + } -extension DoubleRestrictions { +extension FloatingRestrictions { public static var fractionDigits: Int { 0 } - public static var maxExclusive: Int { + public static var maxExclusive: T { 0 } - public static var maxInclusive: Int { + public static var maxInclusive: T { 0 } - public static var minExclusive: Int { + public static var minExclusive: T { 0 } - public static var minInclusive: Int { + public static var minInclusive: T { 0 } diff --git a/Sources/PugiSwift/Decoding/Restrictions/ListRestrictions.swift b/Sources/PugiSwift/Decoding/Restrictions/ListRestrictions.swift index 6bd89a1..4721742 100644 --- a/Sources/PugiSwift/Decoding/Restrictions/ListRestrictions.swift +++ b/Sources/PugiSwift/Decoding/Restrictions/ListRestrictions.swift @@ -13,6 +13,8 @@ public protocol ListRestrictions: RestrictionsProtocol { static var minLength: Int { get } + static var rawValue: any Collection { get } + } extension ListRestrictions { diff --git a/Sources/PugiSwift/Decoding/Restrictions/NumericRestrictions.swift b/Sources/PugiSwift/Decoding/Restrictions/NumericRestrictions.swift index 57bd938..73f5f3a 100644 --- a/Sources/PugiSwift/Decoding/Restrictions/NumericRestrictions.swift +++ b/Sources/PugiSwift/Decoding/Restrictions/NumericRestrictions.swift @@ -9,33 +9,37 @@ import Foundation public protocol NumericRestrictions: RestrictionsProtocol { - static var maxExclusive: Int { get } + associatedtype T: Numeric - static var maxInclusive: Int { get } + static var maxExclusive: T { get } - static var minExclusive: Int { get } + static var maxInclusive: T { get } - static var minInclusive: Int { get } + static var minExclusive: T { get } + + static var minInclusive: T { get } static var totalDigits: Int { get } + var rawValue: T { get } + } extension NumericRestrictions { - public static var maxExclusive: Int { + public static var maxExclusive: T { 0 } - public static var maxInclusive: Int { + public static var maxInclusive: T { 0 } - public static var minExclusive: Int { + public static var minExclusive: T { 0 } - public static var minInclusive: Int { + public static var minInclusive: T { 0 } diff --git a/Sources/PugiSwift/Decoding/Restrictions/StringRestrictions.swift b/Sources/PugiSwift/Decoding/Restrictions/StringRestrictions.swift index cda615c..485f435 100644 --- a/Sources/PugiSwift/Decoding/Restrictions/StringRestrictions.swift +++ b/Sources/PugiSwift/Decoding/Restrictions/StringRestrictions.swift @@ -16,6 +16,8 @@ public protocol StringRestrictions: RestrictionsProtocol { static var minLength: Int { get } + static var rawValue: String { get } + } extension StringRestrictions { diff --git a/Sources/PugiSwiftDemo/main.swift b/Sources/PugiSwiftDemo/main.swift index db26f24..4098716 100644 --- a/Sources/PugiSwiftDemo/main.swift +++ b/Sources/PugiSwiftDemo/main.swift @@ -13,6 +13,14 @@ import PugiSwift @Element(childrenCodingKey: "record") let records: [Record] } +@Restriction struct ExampleSimpleType: NumericRestrictions { + + var rawValue: Int { + 5 + } + +} + @Node struct Record { let name: String let list: Int diff --git a/Sources/PugiSwiftMacros/AttributeMacro.swift b/Sources/PugiSwiftMacros/AttributeMacro.swift new file mode 100644 index 0000000..6cd12d7 --- /dev/null +++ b/Sources/PugiSwiftMacros/AttributeMacro.swift @@ -0,0 +1,20 @@ +// +// ElementMacro 2.swift +// PugiSwift +// +// Created by Amy on 31/10/2024. +// + + +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct AttributeMacro: PeerMacro { + + public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] { + [] + } + +} diff --git a/Sources/PugiSwiftMacros/ElementMacro.swift b/Sources/PugiSwiftMacros/ElementMacro.swift new file mode 100644 index 0000000..fdf758c --- /dev/null +++ b/Sources/PugiSwiftMacros/ElementMacro.swift @@ -0,0 +1,19 @@ +// +// ElementMacro.swift +// PugiSwift +// +// Created by Amy on 31/10/2024. +// + +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public struct ElementMacro: PeerMacro { + + public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] { + [] + } + +} diff --git a/Sources/PugiSwiftMacros/NodeMacro.swift b/Sources/PugiSwiftMacros/NodeMacro.swift new file mode 100644 index 0000000..025dd17 --- /dev/null +++ b/Sources/PugiSwiftMacros/NodeMacro.swift @@ -0,0 +1,228 @@ +// +// NodeMacro.swift +// PugiSwift +// +// Created by Amy on 31/10/2024. +// + +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros +import MacroToolkit + +public struct NodeMacro: ExtensionMacro { + + private static func createFunction(with access: AccessModifier?, name: String, type: String) -> InitializerDeclSyntax { + let functionParameter = FunctionParameter(label: "from", + name: name, + type: "\(raw: type)") + let parameterList = FunctionParameterListSyntax([functionParameter._syntax]) + let throwsClause = ThrowsClauseSyntax(throwsSpecifier: .keyword(.throws), leftParen: .leftParenToken(), type: .init(IdentifierTypeSyntax(name: "XMLDecoderError")), rightParen: .rightParenToken()) + var function = + InitializerDeclSyntax( + signature: .init(parameterClause: FunctionParameterClauseSyntax(parameters: parameterList), + effectSpecifiers: .init(throwsClause: throwsClause)) + ) + if let access { + let keyword: Keyword = { + switch access { + case .private: + .private + case .fileprivate: + .fileprivate + case .internal: + .internal + case .package: + .package + case .public: + .public + case .open: + .open + } + }() + let declModifier = DeclModifierSyntax(name: .keyword(keyword)) + function.modifiers.append(declModifier) + } + return function + } + + private static func createBlockItem(for property: Property) throws -> [CodeBlockItemSyntax] { + let propertyName = property.identifier + guard let type = property.type else { + throw MacroError("Unknown type for \(propertyName)") + } + + // TODO: Check that every requested type will eventually conform. This is more difficult than it sounds. + + let nodeHelperName = "_\(propertyName)" + + let xmlAttribute = property.attributes.first(where: { $0.attribute?._syntax.attributeName.trimmedDescription == "Attribute" }) + let xmlProperty = property.attributes.first(where: { $0.attribute?._syntax.attributeName.trimmedDescription == "Element" }) + if xmlAttribute != nil && xmlProperty != nil { + throw MacroError("Property cannot both be an attribute and an element") + } + + var codingKey = propertyName + var childrenCodingKey: String? = nil + var attribute = false + + // Get the codingKey + if let xmlProperty { + let expressions = xmlProperty.attribute?.asMacroAttribute?.arguments ?? [] + if let _codingKey = expressions.first(where: { $0.label == "codingKey" }) { + codingKey = _codingKey.expr.asStringLiteral!.value! + } + if let _childCodingKey = expressions.first(where: { $0.label == "childrenCodingKey" }) { + childrenCodingKey = _childCodingKey.expr.asStringLiteral!.value! + } + } + if let xmlAttribute { + attribute = true + let expressions = xmlAttribute.attribute?.asMacroAttribute?.arguments ?? [] + if let _codingKey = expressions.first(where: { $0.label == "codingKey" }) { + codingKey = _codingKey.expr.asStringLiteral!.value! + } + } + + var optional = false + if case .optional = type { + optional = true + } + + if optional { + if attribute { + let code = CodeBlockItemSyntax( + """ + if let \(raw: nodeHelperName) = node.attribute(name: "\(raw: codingKey)") { + self.\(raw: propertyName) = try? .init(from: \(raw: nodeHelperName)) + } else { + self.\(raw: propertyName) = nil + } + """ + ) + return [code] + } else { + if let childrenCodingKey { + guard case let .array(arrayType) = type else { + throw MacroError("\(propertyName) must be an array") + } + let elementType = arrayType._baseSyntax.element.description + let code = CodeBlockItemSyntax( + """ + var _\(raw: nodeHelperName) = \(raw: type.description)() + for child in node.iterateNodes() { + if child.name != "\(raw: childrenCodingKey)" { + continue + } + guard let __\(raw: nodeHelperName) = try? \(raw: elementType.description).init(from: child) else { + continue + } + _\(raw: nodeHelperName).append(__\(raw: nodeHelperName)) + } + self.\(raw: propertyName) = _\(raw: nodeHelperName) + """ + ) + return [code] + } else { + let code = CodeBlockItemSyntax( + """ + if let \(raw: nodeHelperName) = node.child(name: \(raw: codingKey) { + self.\(raw: propertyName) = try? .init(from: \(raw: nodeHelperName)) + } else { + self.\(raw: propertyName) = nil + } + """ + ) + return [code] + } + } + } else { + if !attribute, + let childrenCodingKey { + guard case let .array(arrayType) = type else { + throw MacroError("\(propertyName) must be an array") + } + let elementType = arrayType._baseSyntax.element.description + let code = CodeBlockItemSyntax( + """ + var _\(raw: nodeHelperName) = \(raw: type.description)() + for child in node.iterateNodes() { + if child.name != "\(raw: childrenCodingKey)" { + continue + } + let __\(raw: nodeHelperName) = try \(raw: elementType).init(from: child) + _\(raw: nodeHelperName).append(__\(raw: nodeHelperName)) + } + self.\(raw: propertyName) = _\(raw: nodeHelperName) + """ + ) + return [code] + } + let createHelper: CodeBlockItemSyntax + if attribute { + createHelper = CodeBlockItemSyntax("guard let \(raw: nodeHelperName) = node.attribute(name: \"\(raw: codingKey)\") else { throw .attributeNotFound(codingKey: \"\(raw: codingKey)\") }") + } else { + createHelper = CodeBlockItemSyntax("guard let \(raw: nodeHelperName) = node.child(name: \"\(raw: codingKey)\") else { throw .keyNotFound(codingKey: \"\(raw: codingKey)\") }") + } + let assign = CodeBlockItemSyntax("self.\(raw: propertyName) = try .init(from: \(raw: nodeHelperName))") + + return [createHelper, assign] + } + } + + private static func structExpansion(struct structDecl: Struct) throws -> [SwiftSyntax.ExtensionDeclSyntax] { + // Get the access level of the struct + let modifier = structDecl.accessLevel + // Create the extension syntax + var extensionDecl = + try ExtensionDeclSyntax("extension \(raw: structDecl.identifier): XMLDecodable {}") + var definedFunc = Self.createFunction(with: modifier, name: "node", type: "PugiSwift.XMLNode") + let defineProps = try structDecl.properties.map { try Self.createBlockItem(for: $0) }.joined() + definedFunc.body = CodeBlockSyntax(statements: CodeBlockItemListSyntax(defineProps)) + + let memberBlockItemSyntax = MemberBlockItemSyntax(decl: definedFunc) + extensionDecl.memberBlock.members.append(memberBlockItemSyntax) + + return [extensionDecl] + } + + private static func enumExpansion(enum enumDecl: Enum) throws -> [SwiftSyntax.ExtensionDeclSyntax] { + + var extensionDecl = try ExtensionDeclSyntax("extension \(raw: enumDecl.identifier): XMLDecodable {}") + guard let rawType = enumDecl.inheritedTypes.first?.normalizedDescription else { + throw MacroError("Enum must have a raw type.") + } + + var functionDecl = Self.createFunction(with: enumDecl.accessLevel, + name: "node", + type: "PugiSwift.XMLNode") + let syntax = CodeBlockItemSyntax( + """ + let rawValue = try \(raw: rawType).init(from: node) + guard let enumType = \(raw: enumDecl.identifier).init(rawValue: rawValue) else { + throw .invalidCase + } + self = enumType + """ + ) + functionDecl.body = CodeBlockSyntax(statements: CodeBlockItemListSyntax([syntax])) + let memberBlockItemSyntax = MemberBlockItemSyntax(decl: functionDecl) + extensionDecl.memberBlock.members.append(memberBlockItemSyntax) + return [extensionDecl] + } + + public static func expansion(of node: SwiftSyntax.AttributeSyntax, + attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, + providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, + conformingTo protocols: [SwiftSyntax.TypeSyntax], + in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { + if let structDecl = Struct(declaration) { + return try Self.structExpansion(struct: structDecl) + } else if let enumDecl = Enum(declaration) { + return try Self.enumExpansion(enum: enumDecl) + } + throw MacroError("@Node applied to invalid type") + } + +} diff --git a/Sources/PugiSwiftMacros/PugiSwiftMacros.swift b/Sources/PugiSwiftMacros/PugiSwiftMacros.swift index f772656..3fae1e8 100644 --- a/Sources/PugiSwiftMacros/PugiSwiftMacros.swift +++ b/Sources/PugiSwiftMacros/PugiSwiftMacros.swift @@ -10,249 +10,14 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros -import MacroToolkit - -import Foundation - -public struct AttributeMacro: PeerMacro { - - public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] { - [] - } - -} - -public struct ElementMacro: PeerMacro { - - public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] { - [] - } - -} - -public struct NodeMacro: ExtensionMacro { - - private static func createFunction(with access: AccessModifier?, name: String, type: String) -> InitializerDeclSyntax { - let functionParameter = FunctionParameter(label: "from", - name: name, - type: "\(raw: type)") - let parameterList = FunctionParameterListSyntax([functionParameter._syntax]) - let throwsClause = ThrowsClauseSyntax(throwsSpecifier: .keyword(.throws), leftParen: .leftParenToken(), type: .init(IdentifierTypeSyntax(name: "XMLDecoderError")), rightParen: .rightParenToken()) - var function = - InitializerDeclSyntax( - signature: .init(parameterClause: FunctionParameterClauseSyntax(parameters: parameterList), - effectSpecifiers: .init(throwsClause: throwsClause)) - ) - if let access { - let keyword: Keyword = { - switch access { - case .private: - .private - case .fileprivate: - .fileprivate - case .internal: - .internal - case .package: - .package - case .public: - .public - case .open: - .open - } - }() - let declModifier = DeclModifierSyntax(name: .keyword(keyword)) - function.modifiers.append(declModifier) - } - return function - } - - private static func createBlockItem(for property: Property) throws -> [CodeBlockItemSyntax] { - let propertyName = property.identifier - guard let type = property.type else { - throw MacroError("Unknown type for \(propertyName)") - } - - // TODO: Check that every requested type will eventually conform. This is more difficult than it sounds. - - let nodeHelperName = "_\(propertyName)" - - let xmlAttribute = property.attributes.first(where: { $0.attribute?._syntax.attributeName.trimmedDescription == "Attribute" }) - let xmlProperty = property.attributes.first(where: { $0.attribute?._syntax.attributeName.trimmedDescription == "Element" }) - if xmlAttribute != nil && xmlProperty != nil { - throw MacroError("Property cannot both be an attribute and an element") - } - - var codingKey = propertyName - var childrenCodingKey: String? = nil - var attribute = false - - // Get the codingKey - if let xmlProperty { - let expressions = xmlProperty.attribute?.asMacroAttribute?.arguments ?? [] - if let _codingKey = expressions.first(where: { $0.label == "codingKey" }) { - codingKey = _codingKey.expr.asStringLiteral!.value! - } - if let _childCodingKey = expressions.first(where: { $0.label == "childrenCodingKey" }) { - childrenCodingKey = _childCodingKey.expr.asStringLiteral!.value! - } - } - if let xmlAttribute { - attribute = true - let expressions = xmlAttribute.attribute?.asMacroAttribute?.arguments ?? [] - if let _codingKey = expressions.first(where: { $0.label == "codingKey" }) { - codingKey = _codingKey.expr.asStringLiteral!.value! - } - } - - var optional = false - if case .optional = type { - optional = true - } - - if optional { - if attribute { - let code = CodeBlockItemSyntax( - """ - if let \(raw: nodeHelperName) = node.attribute(name: "\(raw: codingKey)") { - self.\(raw: propertyName) = try? .init(from: \(raw: nodeHelperName)) - } else { - self.\(raw: propertyName) = nil - } - """ - ) - return [code] - } else { - if let childrenCodingKey { - guard case let .array(arrayType) = type else { - throw MacroError("\(propertyName) must be an array") - } - let elementType = arrayType._baseSyntax.element.description - let code = CodeBlockItemSyntax( - """ - var _\(raw: nodeHelperName) = \(raw: type.description)() - for child in node.iterateNodes() { - if child.name != "\(raw: childrenCodingKey)" { - continue - } - guard let __\(raw: nodeHelperName) = try? \(raw: elementType.description).init(from: child) else { - continue - } - _\(raw: nodeHelperName).append(__\(raw: nodeHelperName)) - } - self.\(raw: propertyName) = _\(raw: nodeHelperName) - """ - ) - return [code] - } else { - let code = CodeBlockItemSyntax( - """ - if let \(raw: nodeHelperName) = node.child(name: \(raw: codingKey) { - self.\(raw: propertyName) = try? .init(from: \(raw: nodeHelperName)) - } else { - self.\(raw: propertyName) = nil - } - """ - ) - return [code] - } - } - } else { - if !attribute, - let childrenCodingKey { - guard case let .array(arrayType) = type else { - throw MacroError("\(propertyName) must be an array") - } - let elementType = arrayType._baseSyntax.element.description - let code = CodeBlockItemSyntax( - """ - var _\(raw: nodeHelperName) = \(raw: type.description)() - for child in node.iterateNodes() { - if child.name != "\(raw: childrenCodingKey)" { - continue - } - let __\(raw: nodeHelperName) = try \(raw: elementType).init(from: child) - _\(raw: nodeHelperName).append(__\(raw: nodeHelperName)) - } - self.\(raw: propertyName) = _\(raw: nodeHelperName) - """ - ) - return [code] - } - let createHelper: CodeBlockItemSyntax - if attribute { - createHelper = CodeBlockItemSyntax("guard let \(raw: nodeHelperName) = node.attribute(name: \"\(raw: codingKey)\") else { throw .attributeNotFound(codingKey: \"\(raw: codingKey)\") }") - } else { - createHelper = CodeBlockItemSyntax("guard let \(raw: nodeHelperName) = node.child(name: \"\(raw: codingKey)\") else { throw .keyNotFound(codingKey: \"\(raw: codingKey)\") }") - } - let assign = CodeBlockItemSyntax("self.\(raw: propertyName) = try .init(from: \(raw: nodeHelperName))") - - return [createHelper, assign] - } - } - - private static func structExpansion(struct structDecl: Struct) throws -> [SwiftSyntax.ExtensionDeclSyntax] { - // Get the access level of the struct - let modifier = structDecl.accessLevel - // Create the extension syntax - var extensionDecl = - try ExtensionDeclSyntax("extension \(raw: structDecl.identifier): XMLDecodable {}") - var definedFunc = Self.createFunction(with: modifier, name: "node", type: "PugiSwift.XMLNode") - let defineProps = try structDecl.properties.map { try Self.createBlockItem(for: $0) }.joined() - definedFunc.body = CodeBlockSyntax(statements: CodeBlockItemListSyntax(defineProps)) - - let memberBlockItemSyntax = MemberBlockItemSyntax(decl: definedFunc) - extensionDecl.memberBlock.members.append(memberBlockItemSyntax) - - return [extensionDecl] - } - - private static func enumExpansion(enum enumDecl: Enum) throws -> [SwiftSyntax.ExtensionDeclSyntax] { - - var extensionDecl = try ExtensionDeclSyntax("extension \(raw: enumDecl.identifier): XMLDecodable {}") - guard let rawType = enumDecl.inheritedTypes.first?.normalizedDescription else { - throw MacroError("Enum must have a raw type.") - } - - var functionDecl = Self.createFunction(with: enumDecl.accessLevel, - name: "node", - type: "PugiSwift.XMLNode") - let syntax = CodeBlockItemSyntax( - """ - let rawValue = try \(raw: rawType).init(from: node) - guard let enumType = \(raw: enumDecl.identifier).init(rawValue: rawValue) else { - throw .invalidCase - } - self = enumType - """ - ) - functionDecl.body = CodeBlockSyntax(statements: CodeBlockItemListSyntax([syntax])) - let memberBlockItemSyntax = MemberBlockItemSyntax(decl: functionDecl) - extensionDecl.memberBlock.members.append(memberBlockItemSyntax) - return [extensionDecl] - } - - public static func expansion(of node: SwiftSyntax.AttributeSyntax, - attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, - providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, - conformingTo protocols: [SwiftSyntax.TypeSyntax], - in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { - if let structDecl = Struct(declaration) { - return try Self.structExpansion(struct: structDecl) - } else if let enumDecl = Enum(declaration) { - return try Self.enumExpansion(enum: enumDecl) - } - throw MacroError("@Node applied to invalid type") - } - -} - @main struct PugiSwiftMacrosPlugin: CompilerPlugin { let providingMacros: [Macro.Type] = [ AttributeMacro.self, ElementMacro.self, - NodeMacro.self + NodeMacro.self, + RestrictionMacro.self ] } diff --git a/Sources/PugiSwiftMacros/RestrictionMacro.swift b/Sources/PugiSwiftMacros/RestrictionMacro.swift new file mode 100644 index 0000000..fd38c2c --- /dev/null +++ b/Sources/PugiSwiftMacros/RestrictionMacro.swift @@ -0,0 +1,55 @@ +// +// RestrictionMacro.swift +// PugiSwift +// +// Created by Amy on 31/10/2024. +// + +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros +import MacroToolkit + +public struct RestrictionMacro: ExtensionMacro { + + private static func numericRestrictions(for struct: Struct) -> [SwiftSyntax.ExtensionDeclSyntax] { + [] + } + + private static func floatingRestrictions(for struct: Struct) -> [SwiftSyntax.ExtensionDeclSyntax] { + [] + } + + private static func stringRestrictions(for struct: Struct) -> [SwiftSyntax.ExtensionDeclSyntax] { + [] + } + + private static func listRestrictions(for struct: Struct) -> [SwiftSyntax.ExtensionDeclSyntax] { + [] + } + + public static func expansion(of node: SwiftSyntax.AttributeSyntax, + attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, + providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, + conformingTo protocols: [SwiftSyntax.TypeSyntax], + in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { + guard let structDecl = Struct(declaration) else { + throw MacroError("@Restriction can only be allowed to a struct") + } + let inheritedTypes = structDecl.inheritedTypes + + if inheritedTypes.contains(where: { $0.description == "NumericRestrictions" }) { + return Self.numericRestrictions(for: structDecl) + } else if inheritedTypes.contains(where: { $0.description == "FloatingRestrictions" }) { + return Self.floatingRestrictions(for: structDecl) + } else if inheritedTypes.contains(where: { $0.description == "StringRestrictions" }) { + return Self.stringRestrictions(for: structDecl) + } else if inheritedTypes.contains(where: { $0.description == "ListRestrictions" }) { + return Self.listRestrictions(for: structDecl) + } else { + throw MacroError("\(structDecl.identifier) must conform to one of the following protocols: NumericRestriction, FloatingRestrictions, StringRestrictions, ListRestrictions") + } + } + +}