diff --git a/Sources/DiscriminatedUnion/DiscriminatedUnion.swift b/Sources/DiscriminatedUnion/DiscriminatedUnion.swift index 2ae4d4c..9596974 100644 --- a/Sources/DiscriminatedUnion/DiscriminatedUnion.swift +++ b/Sources/DiscriminatedUnion/DiscriminatedUnion.swift @@ -1,9 +1,16 @@ +@attached( + extension, + conformances: DiscriminatedUnion +) @attached( member, - names: named(Discriminant), named(discriminant) + names: arbitrary +// prefixed(tupleFrom), +// named(Discriminant), +// named(discriminant), +// named(ExtractorError) ) -@attached(extension, conformances: DiscriminatedUnion) public macro discriminatedUnion() = #externalMacro( module: "DiscriminatedUnionMacros", type: "DiscriminatedUnionMacro") diff --git a/Sources/DiscriminatedUnionClient/main.swift b/Sources/DiscriminatedUnionClient/main.swift index 5b8acc4..d7758ba 100644 --- a/Sources/DiscriminatedUnionClient/main.swift +++ b/Sources/DiscriminatedUnionClient/main.swift @@ -8,6 +8,8 @@ enum Pet { case parrot(loud: Bool) case snake case turtle(snapper: Bool) +// case bird(name: String, Int) + } Swift.print("usiyan::: Pet.Discriminant.dog == Pet.dog.discriminant: \(String(describing: Pet.Discriminant.dog == Pet.dog.discriminant))") diff --git a/Sources/DiscriminatedUnionMacros/DiscriminatedUnionMacro.swift b/Sources/DiscriminatedUnionMacros/DiscriminatedUnionMacro.swift index 81f8896..1c0f556 100644 --- a/Sources/DiscriminatedUnionMacros/DiscriminatedUnionMacro.swift +++ b/Sources/DiscriminatedUnionMacros/DiscriminatedUnionMacro.swift @@ -38,6 +38,7 @@ extension DiscriminatedUnionMacro: MemberMacro { public static func expansion( of node: SwiftSyntax.AttributeSyntax, providingMembersOf declaration: Declaration, + conformingTo protocols: [TypeSyntax], in context: Context ) throws -> [SwiftSyntax.DeclSyntax] where @@ -53,19 +54,34 @@ extension DiscriminatedUnionMacro: MemberMacro { let unvalidatedPropertyDecl = try instance.declareDiscriminantProperty() let validatedPropertyDecl = try DeclSyntax(validating: unvalidatedPropertyDecl) + let validatedExtractors = try instance.doSomethingSpecial().map { + try DeclSyntax(validating: $0) + } + + let validatedExtractorError = try DeclSyntax(validating: extractorErrorDecl()) return try [ DeclSyntax(validating: "\(raw: discriminantDecl)"), - validatedPropertyDecl - ] + validatedPropertyDecl, + validatedExtractorError + ] + validatedExtractors } enum Error: Swift.Error { case attemptPrint(String) } + static func extractorErrorDecl() -> DeclSyntax { + """ + public enum ExtractorError: Swift.Error { + case invalidExtraction(expected: Discriminant, actual: Discriminant) + } + """ + } + func declareDiscriminantType() throws -> EnumDeclSyntax { - try EnumDeclSyntax("public enum Discriminant: DiscriminantType") { + return try EnumDeclSyntax("public enum Discriminant: DiscriminantType") { + for singleCase in childCases { EnumCaseDeclSyntax( leadingTrivia: .newline) { @@ -75,25 +91,24 @@ extension DiscriminatedUnionMacro: MemberMacro { } } - "\n" "\n" - try declareAssociatedTypeFunction() + try declareHasAssociatedTypeFunction() } } - func declareAssociatedTypeFunction() throws -> DeclSyntax { + func declareHasAssociatedTypeFunction() throws -> DeclSyntax { let theCases = childCases.map { singleCase in let myTrivia = singleCase.parameterClause?.parameters.description return "case .\(singleCase.name): \(singleCase.parameterClause != nil) // \(String(describing: myTrivia))" } let theSwitch = """ - switch self { + return switch self { \(theCases.joined(separator: "\n")) } """ return DeclSyntax(stringLiteral:""" - + public var hasAssociatedType: Bool { \(theSwitch) @@ -102,6 +117,54 @@ extension DiscriminatedUnionMacro: MemberMacro { ) } + func doSomethingSpecial() throws -> [DeclSyntax] { + let theCases = childCases.compactMap { singleCase in + if let parameterClause = singleCase.parameterClause { + let bindings = parameterClause.parameters.enumerated().map({ (index, parameter) in + "let \(parameter.firstName ?? "index\(raw: index)")" + }) + + let rawOut = parameterClause.parameters.enumerated().map({ (index, parameter) in + "\(parameter.firstName ?? "index\(raw: index)")" + }) + + let output: String + if parameterClause.parameters.count == 1 { + output = rawOut.first! + } else { + output = "(\(rawOut.joined(separator: ", ")))" + } + + return ( + String(describing: singleCase.name), + parameterClause.parameters.count == 1 ? String(describing: parameterClause.parameters.first!.type) : "(\(parameterClause.parameters.description))", + bindings.joined(separator: ", "), + output + ) + } else { + return nil + } + } + + let theSomethings: [DeclSyntax] = theCases.map { caseName, tupleType, pBindings, returnValue in + let titleCasedName = "\(caseName.first!.uppercased())\(caseName.dropFirst())" + return """ + + public func tupleFrom\(raw: titleCasedName)() throws -> \(raw: tupleType) { + if case .\(raw: caseName)(\(raw: pBindings)) = self { + return \(raw: returnValue) + } else { + throw ExtractorError.invalidExtraction(expected: .\(raw: caseName), actual: self.discriminant) + } + } + + """ + } + + return theSomethings + } + + func declareDiscriminantProperty() throws -> DeclSyntax { let casesWrittenOut = childCases.map { """ @@ -110,17 +173,17 @@ extension DiscriminatedUnionMacro: MemberMacro { """ }.joined(separator: "\n") - let switchWrittenOut: DeclSyntax = + let switchWrittenOut: String = """ switch self { - \(raw: casesWrittenOut) + \(casesWrittenOut) } """ return """ public var discriminant: Discriminant { - \(switchWrittenOut) + \(raw: switchWrittenOut) } """ diff --git a/Tests/DiscriminatedUnionTests/DiscriminatedUnionTests.swift b/Tests/DiscriminatedUnionTests/DiscriminatedUnionTests.swift index e9d9b25..1b0cc22 100644 --- a/Tests/DiscriminatedUnionTests/DiscriminatedUnionTests.swift +++ b/Tests/DiscriminatedUnionTests/DiscriminatedUnionTests.swift @@ -23,6 +23,7 @@ final class DiscriminatedUnionTests: XCTestCase { case cat(curious: Bool) case parrot case snake + case bird(name: String, Int) } """, @@ -33,15 +34,17 @@ enum Pet { case cat(curious: Bool) case parrot case snake + case bird(name: String, Int) public enum Discriminant: DiscriminantType { case dog case cat case parrot case snake + case bird public var hasAssociatedType: Bool { - switch self { + return switch self { case .dog: false // nil case .cat: @@ -50,6 +53,8 @@ enum Pet { false // nil case .snake: false // nil + case .bird: + true // Optional("name: String, Int") } } } @@ -64,6 +69,28 @@ enum Pet { return .parrot case .snake: return .snake + case .bird: + return .bird + } + } + + public enum ExtractorError: Swift.Error { + case invalidExtraction(expected: Discriminant, actual: Discriminant) + } + + public func catTupleValue() throws -> Bool { + if case .cat(let curious) = self { + return curious + } else { + throw ExtractorError.invalidExtraction(expected: .cat, actual: self.discriminant) + } + } + + public func birdTupleValue() throws -> (name: String, Int) { + if case .bird(let name, let index1) = self { + return (name, index1) + } else { + throw ExtractorError.invalidExtraction(expected: .bird, actual: self.discriminant) } } }