@@ -26,10 +26,20 @@ public struct FirebaseGenerableMacro: ExtensionMacro {
2626 in _: some SwiftSyntaxMacros
2727 . MacroExpansionContext ) throws
2828 -> [ SwiftSyntax . ExtensionDeclSyntax ] {
29- guard let structDecl = declaration. as ( StructDeclSyntax . self) else {
30- throw MacroExpansionErrorMessage ( " `@FirebaseGenerable` can only be applied to a struct. " )
29+ if let structDecl = declaration. as ( StructDeclSyntax . self) {
30+ return try expansionForStruct ( of: structDecl, type: type)
31+ } else if let enumDecl = declaration. as ( EnumDeclSyntax . self) {
32+ return try expansionForEnum ( of: enumDecl, type: type)
33+ } else {
34+ throw MacroExpansionErrorMessage (
35+ " `@FirebaseGenerable` can only be applied to a struct or enum. "
36+ )
3137 }
38+ }
3239
40+ private static func expansionForStruct( of structDecl: StructDeclSyntax ,
41+ type: some SwiftSyntax . TypeSyntaxProtocol ) throws
42+ -> [ SwiftSyntax . ExtensionDeclSyntax ] {
3343 var propertyInits = [ String] ( )
3444
3545 for member in structDecl. memberBlock. members {
@@ -73,6 +83,57 @@ public struct FirebaseGenerableMacro: ExtensionMacro {
7383
7484 return declarations
7585 }
86+
87+ private static func expansionForEnum( of enumDecl: EnumDeclSyntax ,
88+ type: some SwiftSyntax . TypeSyntaxProtocol ) throws
89+ -> [ SwiftSyntax . ExtensionDeclSyntax ] {
90+ var caseNames = [ String] ( )
91+
92+ for member in enumDecl. memberBlock. members {
93+ guard let caseDecl = member. decl. as ( EnumCaseDeclSyntax . self) else {
94+ continue
95+ }
96+
97+ for element in caseDecl. elements {
98+ if element. parameterClause != nil {
99+ throw MacroExpansionErrorMessage (
100+ " `@FirebaseGenerable` does not currently support enums with associated values. "
101+ )
102+ }
103+ caseNames. append ( element. name. text)
104+ }
105+ }
106+
107+ let caseChecks = caseNames. map { caseName in
108+ " case \" \( caseName) \" : \n self = . \( caseName) "
109+ } . joined ( separator: " \n " )
110+
111+ var declarations = [ ExtensionDeclSyntax] ( )
112+ let declSyntaxString = """
113+ @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
114+ extension \( type. trimmed) : FirebaseAILogic.FirebaseGenerable {
115+ nonisolated init(_ content: FirebaseAILogic.ModelOutput) throws {
116+ let rawValue = try content.value(String.self)
117+ switch rawValue {
118+ \( caseChecks)
119+ default:
120+ throw FirebaseAILogic.GenerativeModel.GenerationError.decodingFailure(
121+ FirebaseAILogic.GenerativeModel.GenerationError.Context(
122+ debugDescription: " Unexpected value \\ " \\ (rawValue) \\ " for \\ (Self.self) "
123+ )
124+ )
125+ }
126+ }
127+ }
128+ """
129+ let declSyntax = DeclSyntax ( stringLiteral: declSyntaxString)
130+ guard let extensionDecl = declSyntax. as ( ExtensionDeclSyntax . self) else {
131+ return [ ]
132+ }
133+ declarations. append ( extensionDecl)
134+
135+ return declarations
136+ }
76137}
77138
78139struct PropertyInfo {
@@ -89,11 +150,20 @@ extension FirebaseGenerableMacro: MemberMacro {
89150 conformingTo _: [ SwiftSyntax . TypeSyntax ] ,
90151 in _: some SwiftSyntaxMacros
91152 . MacroExpansionContext ) throws -> [ SwiftSyntax . DeclSyntax ] {
92- // Ensure the macro is attached to a struct declaration.
93- guard let structDecl = declaration. as ( StructDeclSyntax . self) else {
94- throw MacroExpansionErrorMessage ( " `@Generable` can only be applied to a struct. " )
153+ if let structDecl = declaration. as ( StructDeclSyntax . self) {
154+ return try expansionForStruct ( of: node, structDecl: structDecl)
155+ } else if let enumDecl = declaration. as ( EnumDeclSyntax . self) {
156+ return try expansionForEnum ( of: node, enumDecl: enumDecl)
157+ } else {
158+ throw MacroExpansionErrorMessage (
159+ " `@FirebaseGenerable` can only be applied to a struct or enum. "
160+ )
95161 }
162+ }
96163
164+ private static func expansionForStruct( of node: SwiftSyntax . AttributeSyntax ,
165+ structDecl: StructDeclSyntax ) throws
166+ -> [ SwiftSyntax . DeclSyntax ] {
97167 // Find the description for the struct itself from the @Generable macro.
98168 let structDescription = try getDescriptionFromGenerableMacro ( node)
99169
@@ -149,16 +219,57 @@ extension FirebaseGenerableMacro: MemberMacro {
149219 ) )
150220 }
151221
152- let declarations = generateMembers (
222+ return generateStructMembers (
153223 structDescription: structDescription,
154224 propertyInfos: propertyInfos
155225 )
226+ }
156227
157- return declarations
228+ private static func expansionForEnum( of node: SwiftSyntax . AttributeSyntax ,
229+ enumDecl: EnumDeclSyntax ) throws
230+ -> [ SwiftSyntax . DeclSyntax ] {
231+ var caseNames = [ String] ( )
232+
233+ for member in enumDecl. memberBlock. members {
234+ guard let caseDecl = member. decl. as ( EnumCaseDeclSyntax . self) else {
235+ continue
236+ }
237+
238+ for element in caseDecl. elements {
239+ // Validation already happened in ExtensionMacro
240+ caseNames. append ( element. name. text)
241+ }
242+ }
243+
244+ // Generate `static var jsonSchema: ...` computed property.
245+ let anyOfList = caseNames. map { " \" \( $0) \" " } . joined ( separator: " , " )
246+ let generationSchemaCode = """
247+ nonisolated static var jsonSchema: FirebaseAILogic.JSONSchema {
248+ FirebaseAILogic.JSONSchema(type: Self.self, anyOf: [ \( anyOfList) ])
249+ }
250+ """
251+
252+ // Generate `var modelOutput: ...` computed property.
253+ let switchCases = caseNames. map { caseName in
254+ " case . \( caseName) : \n \" \( caseName) \" .modelOutput "
255+ } . joined ( separator: " \n " )
256+
257+ let modelOutputCode = """
258+ nonisolated var modelOutput: FirebaseAILogic.ModelOutput {
259+ switch self {
260+ \( switchCases)
261+ }
262+ }
263+ """
264+
265+ return [
266+ DeclSyntax ( stringLiteral: generationSchemaCode) ,
267+ DeclSyntax ( stringLiteral: modelOutputCode) ,
268+ ]
158269 }
159270
160- private static func generateMembers ( structDescription: String ? ,
161- propertyInfos: [ PropertyInfo ] ) -> [ DeclSyntax ] {
271+ private static func generateStructMembers ( structDescription: String ? ,
272+ propertyInfos: [ PropertyInfo ] ) -> [ DeclSyntax ] {
162273 var propertyNames = [ String] ( )
163274 var propertySchemas = [ String] ( )
164275 var partiallyGeneratedProperties = [ String] ( )
0 commit comments