From 36ee76c002abfcd10925a3847ed02fd0afef2ef9 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:32:59 +0100 Subject: [PATCH 01/11] Add coding protocols --- Sources/SWONMacros/Protocols.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Sources/SWONMacros/Protocols.swift diff --git a/Sources/SWONMacros/Protocols.swift b/Sources/SWONMacros/Protocols.swift new file mode 100644 index 0000000..5dbb902 --- /dev/null +++ b/Sources/SWONMacros/Protocols.swift @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2026 Davide De Rosa +// +// SPDX-License-Identifier: MIT + +import SWON + +public protocol SWONEncodable { + func toSWON() throws -> swon_t +} + +public protocol SWONDecodable { + init(fromSWON root: swon_t) throws +} From 0548d521401c2d1456c94452cd95129de9e1d4c5 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:33:17 +0100 Subject: [PATCH 02/11] Add collection extensions --- Sources/SWONMacros/Extensions.swift | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Sources/SWONMacros/Extensions.swift diff --git a/Sources/SWONMacros/Extensions.swift b/Sources/SWONMacros/Extensions.swift new file mode 100644 index 0000000..4b2a0d0 --- /dev/null +++ b/Sources/SWONMacros/Extensions.swift @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2026 Davide De Rosa +// +// SPDX-License-Identifier: MIT + +import SWON + +extension Array: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWONEncodable { + public init(fromSWON root: swon_t) throws { + var list: [Element] = [] + let root = swon_t() + let size = swon_get_array_size(root) + for i in 0.. swon_t { + let list = Array(self) + var root = swon_t() + guard swon_create_array(&root) else { throw SWONError.invalid("Array") } + for el in list { + let item = try el.toSWON() + swon_array_add_item(&root, item) + } + return root + } +} + +extension Set: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWONEncodable { + public init(fromSWON root: swon_t) throws { + self = try Set(Array(fromSWON: root)) + } + + public func toSWON() throws -> swon_t { + try Array(self).toSWON() + } +} From f2b74795a3373e3edd14cd8ed6b8c7bfac3d13e7 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:35:23 +0100 Subject: [PATCH 03/11] Improve bool check --- Sources/SWON_C/swon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SWON_C/swon.c b/Sources/SWON_C/swon.c index bf861a9..335dbca 100644 --- a/Sources/SWON_C/swon.c +++ b/Sources/SWON_C/swon.c @@ -119,7 +119,7 @@ swon_t swon_get_map_next(swon_t src) { bool swon_create_array(swon_t *dst) { dst->impl = cJSON_CreateArray(); - return dst->impl; + return dst->impl != NULL; } bool swon_array_add_item(swon_t *dst, swon_t item) { From 5e1f1fde3c8f1712e7eaf2da027f8f513bc74094 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:36:54 +0100 Subject: [PATCH 04/11] Guard array element --- Sources/SWONMacros/Extensions.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/SWONMacros/Extensions.swift b/Sources/SWONMacros/Extensions.swift index 4b2a0d0..eed3c10 100644 --- a/Sources/SWONMacros/Extensions.swift +++ b/Sources/SWONMacros/Extensions.swift @@ -25,7 +25,9 @@ extension Array: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWO guard swon_create_array(&root) else { throw SWONError.invalid("Array") } for el in list { let item = try el.toSWON() - swon_array_add_item(&root, item) + guard swon_array_add_item(&root, item) else { + throw SWONError.invalid("Array: Element") + } } return root } From ef37b28c434273d62cec0c3cc26a207af0017f30 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:39:08 +0100 Subject: [PATCH 05/11] Move protocols/extensions to impl target --- Sources/{SWONMacros => SWON}/Extensions.swift | 2 -- Sources/{SWONMacros => SWON}/Protocols.swift | 2 -- 2 files changed, 4 deletions(-) rename Sources/{SWONMacros => SWON}/Extensions.swift (99%) rename Sources/{SWONMacros => SWON}/Protocols.swift (94%) diff --git a/Sources/SWONMacros/Extensions.swift b/Sources/SWON/Extensions.swift similarity index 99% rename from Sources/SWONMacros/Extensions.swift rename to Sources/SWON/Extensions.swift index eed3c10..7577fa7 100644 --- a/Sources/SWONMacros/Extensions.swift +++ b/Sources/SWON/Extensions.swift @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: MIT -import SWON - extension Array: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWONEncodable { public init(fromSWON root: swon_t) throws { var list: [Element] = [] diff --git a/Sources/SWONMacros/Protocols.swift b/Sources/SWON/Protocols.swift similarity index 94% rename from Sources/SWONMacros/Protocols.swift rename to Sources/SWON/Protocols.swift index 5dbb902..27189f2 100644 --- a/Sources/SWONMacros/Protocols.swift +++ b/Sources/SWON/Protocols.swift @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: MIT -import SWON - public protocol SWONEncodable { func toSWON() throws -> swon_t } From cea6ca89d67d48068745927fd8f3877d5f0023c9 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:43:34 +0100 Subject: [PATCH 06/11] Add protocol conformances --- Sources/SWONMacros/Macros.swift | 39 ++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Sources/SWONMacros/Macros.swift b/Sources/SWONMacros/Macros.swift index 42c9bb7..08aaa7b 100644 --- a/Sources/SWONMacros/Macros.swift +++ b/Sources/SWONMacros/Macros.swift @@ -13,7 +13,7 @@ struct SWONMacrosPlugin: CompilerPlugin { ] } -public struct SWONCompoundMacro: MemberMacro { +public struct SWONCompoundMacro: MemberMacro, ExtensionMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, @@ -35,4 +35,41 @@ public struct SWONCompoundMacro: MemberMacro { ) return result } + + public static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingExtensionsOf type: some TypeSyntaxProtocol, + conformingTo protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + // Extract the type name (struct/class/enum identifier) + guard let named = declaration.asProtocol(NamedDeclSyntax.self) else { + return [] + } + let typeName = named.name.text + + // Optional: avoid duplicate conformances if already declared + let alreadyConforms: Set = + declaration.inheritanceClause? + .inheritedTypes + .compactMap { $0.type.as(IdentifierTypeSyntax.self)?.name.text } + .reduce(into: Set()) { $0.insert($1) } + ?? [] + let needsEncodable = !alreadyConforms.contains("SWONEncodable") + let needsDecodable = !alreadyConforms.contains("SWONDecodable") + guard needsEncodable || needsDecodable else { + return [] + } + + // Build the conformance list dynamically + var conformances: [String] = [] + if needsEncodable { conformances.append("SWONEncodable") } + if needsDecodable { conformances.append("SWONDecodable") } + let conformanceList = conformances.joined(separator: ", ") + let ext = try ExtensionDeclSyntax( + "extension \(raw: typeName): \(raw: conformanceList) {}" + ) + return [ext] + } } From e49ed39ab6e1f5c5d1e70132e9732bef33b4ba2f Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:43:50 +0100 Subject: [PATCH 07/11] Attach protocols to macro --- Sources/SWON/SWON.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SWON/SWON.swift b/Sources/SWON/SWON.swift index 24f7562..81a9b6a 100644 --- a/Sources/SWON/SWON.swift +++ b/Sources/SWON/SWON.swift @@ -10,6 +10,10 @@ named(toSWON), named(toJSON) ) +@attached( + extension, + conformances: SWONEncodable, SWONDecodable +) public macro SWON() = #externalMacro( module: "SWONMacros", type: "SWONCompoundMacro" From 76663d823a223e0b04d39f0e03364187c48e1abb Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 11:56:12 +0100 Subject: [PATCH 08/11] Fix array decoding --- Sources/SWON/Extensions.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/SWON/Extensions.swift b/Sources/SWON/Extensions.swift index 7577fa7..ce3b263 100644 --- a/Sources/SWON/Extensions.swift +++ b/Sources/SWON/Extensions.swift @@ -5,11 +5,14 @@ extension Array: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWONEncodable { public init(fromSWON root: swon_t) throws { var list: [Element] = [] - let root = swon_t() - let size = swon_get_array_size(root) + var array = swon_t() + guard swon_get_array(&array, root) == SWONResultValid else { + throw SWONError.required("Array") + } + let size = swon_get_array_size(array) for i in 0.. Date: Wed, 25 Feb 2026 11:58:58 +0100 Subject: [PATCH 09/11] Test sets --- Sources/SWONMacros/Decoding.swift | 2 +- Tests/SWONTests/ComplexStruct.swift | 3 +++ Tests/SWONTests/Resources/populated.json | 3 ++- Tests/SWONTests/SWONTests.swift | 11 +++-------- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Sources/SWONMacros/Decoding.swift b/Sources/SWONMacros/Decoding.swift index fcff0f2..1207af5 100644 --- a/Sources/SWONMacros/Decoding.swift +++ b/Sources/SWONMacros/Decoding.swift @@ -151,7 +151,7 @@ struct SWONDecodeMacro: MemberMacro { // context.diagnose( // Diagnostic( // node: Syntax(node), -// message: SWONMessage(message: "Assigning \(field) of type \(type)") +// message: SWONMessage(message: "Assigning \(field) of type \(type) (decoded: \(type.decodedType(context: context)))") // ) // ) assignments.append(""" diff --git a/Tests/SWONTests/ComplexStruct.swift b/Tests/SWONTests/ComplexStruct.swift index 1f8c9ab..6a6b187 100644 --- a/Tests/SWONTests/ComplexStruct.swift +++ b/Tests/SWONTests/ComplexStruct.swift @@ -52,6 +52,9 @@ struct ComplexStruct: Equatable, Codable { let optionalStringToSubStructArray: [String: [SubStruct]]? let uint16: UInt16? + + // Set + let someSet: Set? } @SWON diff --git a/Tests/SWONTests/Resources/populated.json b/Tests/SWONTests/Resources/populated.json index 3459b9b..9185e84 100644 --- a/Tests/SWONTests/Resources/populated.json +++ b/Tests/SWONTests/Resources/populated.json @@ -131,5 +131,6 @@ ] }] }, - "uint16": 60000 + "uint16": 60000, + "someSet": ["blue", "red", "green"] } diff --git a/Tests/SWONTests/SWONTests.swift b/Tests/SWONTests/SWONTests.swift index cf9df80..268a564 100644 --- a/Tests/SWONTests/SWONTests.swift +++ b/Tests/SWONTests/SWONTests.swift @@ -23,13 +23,6 @@ struct SWONTests { @Test func encodingAssociatedEnums() throws { - @SWON - struct LocalContainer { - let enums: [AssociatedEnum] - init(enums: [AssociatedEnum]) { - self.enums = enums - } - } let sub = try SubStruct(fromJSON: """ { "favoriteColor": "red", @@ -81,7 +74,8 @@ struct SWONTests { "nestedColors": [ ["green", "blue"], ["red", "red", "green"] - ] + ], + "colorSet": ["red", "green"] } """) print(try sut.toJSON()) @@ -116,6 +110,7 @@ struct LocalStruct { let another: AnotherStruct? let nestedStrings: [[String]] let nestedColors: [[Color]] + let colorSet: Set } @SWON From 7978350b4b08fd74d43aa882582b276703e619bf Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 12:00:05 +0100 Subject: [PATCH 10/11] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b8ba12..2c3b034 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ SWON uses the amazingly written [cJSON][credits-cjson] parser (C) under the hood ### Things to do - Do not suppress errors inside an optional field (silently made nil). -- Add thorough tests add meaningful examples. +- Add thorough tests and meaningful examples. - Clean up some messy code. ## License From 4da37630c32fa287adb17d87058b6661a8d93579 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 25 Feb 2026 12:05:16 +0100 Subject: [PATCH 11/11] [ci skip] Mention RawRepresentable TODO --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2c3b034..ac019eb 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ SWON uses the amazingly written [cJSON][credits-cjson] parser (C) under the hood ### Things to do +- Handle `RawRepresentable` automatically. - Do not suppress errors inside an optional field (silently made nil). - Add thorough tests and meaningful examples. - Clean up some messy code.