diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ReerCodable.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ReerCodable.xcscheme
index 4f52d6a..91da376 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/ReerCodable.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/ReerCodable.xcscheme
@@ -40,6 +40,16 @@
ReferencedContainer = "container:">
+
+
+
+
[String] {
+ let normalized = type.trimmedDescription
+ .split(whereSeparator: \.isWhitespace)
+ .filter { token in
+ token != "nonisolated" && !token.hasPrefix("@")
+ }
+ .joined(separator: " ")
+
+ return normalized
+ .split(separator: "&")
+ .map { component in
+ component
+ .split(whereSeparator: \.isWhitespace)
+ .joined(separator: " ")
+ .split(separator: ".")
+ .last
+ .map(String.init) ?? ""
+ }
+ .filter { !$0.isEmpty }
+}
+
+private func hasInheritedType(
+ in declaration: some DeclGroupSyntax,
+ matching predicate: (String) -> Bool
+) -> Bool {
+ guard let inheritedTypes = declaration.inheritanceClause?.inheritedTypes else { return false }
+ return inheritedTypes.contains { inheritedType in
+ normalizedInheritedTypeNames(inheritedType.type).contains(where: predicate)
+ }
+}
+
+private func hasNonisolatedModifier(_ declaration: some DeclGroupSyntax) -> Bool {
+ let modifiers: DeclModifierListSyntax?
+ if let structDecl = declaration.as(StructDeclSyntax.self) {
+ modifiers = structDecl.modifiers
+ } else if let classDecl = declaration.as(ClassDeclSyntax.self) {
+ modifiers = classDecl.modifiers
+ } else if let enumDecl = declaration.as(EnumDeclSyntax.self) {
+ modifiers = enumDecl.modifiers
+ } else {
+ modifiers = nil
+ }
+ return modifiers?.contains(where: { $0.name.text == "nonisolated" }) == true
+}
+
+private func extensionIsolationModifier(for declaration: some DeclGroupSyntax) -> String {
+ hasNonisolatedModifier(declaration) ? "nonisolated " : ""
+}
+
+private func makeConformanceExtension(
+ for type: some TypeSyntaxProtocol,
+ declaration: some DeclGroupSyntax,
+ conformances: [String]
+) -> ExtensionDeclSyntax? {
+ guard !conformances.isEmpty else { return nil }
+ let isolationModifier = extensionIsolationModifier(for: declaration)
+ let conformanceList = conformances.joined(separator: ", ")
+ let extensionDecl: DeclSyntax =
+ """
+ \(raw: isolationModifier)extension \(type.trimmed): \(raw: conformanceList) {}
+ """
+ return extensionDecl.cast(ExtensionDeclSyntax.self)
+}
+
extension RECodable: ExtensionMacro {
public static func expansion(
of node: SwiftSyntax.AttributeSyntax,
@@ -56,22 +120,16 @@ extension RECodable: ExtensionMacro {
throw MacroError(text: "@Codable macro is only for `struct`, `class` or `enum`.")
}
- var codableExisted = false
- if let inheritedType = declaration.inheritanceClause?.inheritedTypes,
- inheritedType.contains(where: { $0.type.trimmedDescription == "Codable" }) {
- codableExisted = true
- }
- var delegateExisted = false
- if let inheritedType = declaration.inheritanceClause?.inheritedTypes,
- inheritedType.contains(where: { $0.type.trimmedDescription == "ReerCodableDelegate" }) {
- delegateExisted = true
- }
- if codableExisted && delegateExisted { return [] }
- let extensionDecl: DeclSyntax =
- """
- extension \(type.trimmed):\(raw: codableExisted ? "" : "Codable,") ReerCodableDelegate {}
- """
- return [extensionDecl.cast(ExtensionDeclSyntax.self)]
+ let codableExisted = hasInheritedType(in: declaration) { $0 == "Codable" }
+ let delegateExisted = hasInheritedType(in: declaration) { $0 == "ReerCodableDelegate" }
+ let conformances = [
+ codableExisted ? nil : "Codable",
+ delegateExisted ? nil : "ReerCodableDelegate"
+ ].compactMap(\.self)
+ guard let extensionDecl = makeConformanceExtension(for: type, declaration: declaration, conformances: conformances) else {
+ return []
+ }
+ return [extensionDecl]
}
}
@@ -166,24 +224,18 @@ extension REDecodable: ExtensionMacro {
throw MacroError(text: "@Decodable macro is only for `struct`, `class` or `enum`.")
}
- var decodableExisted = false
- if let inheritedTypeClause = declaration.inheritanceClause {
- decodableExisted = inheritedTypeClause.inheritedTypes.contains { inheritedType in
- let typeName = inheritedType.type.trimmedDescription
- return typeName == "Decodable" || typeName == "Codable"
- }
+ let decodableExisted = hasInheritedType(in: declaration) {
+ $0 == "Decodable" || $0 == "Codable"
+ }
+ let delegateExisted = hasInheritedType(in: declaration) { $0 == "ReerCodableDelegate" }
+ let conformances = [
+ decodableExisted ? nil : "Decodable",
+ delegateExisted ? nil : "ReerCodableDelegate"
+ ].compactMap(\.self)
+ guard let extensionDecl = makeConformanceExtension(for: type, declaration: declaration, conformances: conformances) else {
+ return []
}
- var delegateExisted = false
- if let inheritedType = declaration.inheritanceClause?.inheritedTypes,
- inheritedType.contains(where: { $0.type.trimmedDescription == "ReerCodableDelegate" }) {
- delegateExisted = true
- }
- if decodableExisted && delegateExisted { return [] }
- let extensionDecl: DeclSyntax =
- """
- extension \(type.trimmed): \(raw: decodableExisted ? "" : "Decodable, ")ReerCodableDelegate {}
- """
- return [extensionDecl.cast(ExtensionDeclSyntax.self)]
+ return [extensionDecl]
}
}
@@ -271,24 +323,18 @@ extension REEncodable: ExtensionMacro {
throw MacroError(text: "@Encodable macro is only for `struct`, `class` or `enum`.")
}
- var encodableExisted = false
- if let inheritedTypeClause = declaration.inheritanceClause {
- encodableExisted = inheritedTypeClause.inheritedTypes.contains { inheritedType in
- let typeName = inheritedType.type.trimmedDescription
- return typeName == "Encodable" || typeName == "Codable"
- }
+ let encodableExisted = hasInheritedType(in: declaration) {
+ $0 == "Encodable" || $0 == "Codable"
+ }
+ let delegateExisted = hasInheritedType(in: declaration) { $0 == "ReerCodableDelegate" }
+ let conformances = [
+ encodableExisted ? nil : "Encodable",
+ delegateExisted ? nil : "ReerCodableDelegate"
+ ].compactMap(\.self)
+ guard let extensionDecl = makeConformanceExtension(for: type, declaration: declaration, conformances: conformances) else {
+ return []
}
- var delegateExisted = false
- if let inheritedType = declaration.inheritanceClause?.inheritedTypes,
- inheritedType.contains(where: { $0.type.trimmedDescription == "ReerCodableDelegate" }) {
- delegateExisted = true
- }
- if encodableExisted && delegateExisted { return [] }
- let extensionDecl: DeclSyntax =
- """
- extension \(type.trimmed): \(raw: encodableExisted ? "" : "Encodable, ")ReerCodableDelegate {}
- """
- return [extensionDecl.cast(ExtensionDeclSyntax.self)]
+ return [extensionDecl]
}
}
diff --git a/Tests/ReerCodableMainActorIsolationTests/MainActorIsolationTests.swift b/Tests/ReerCodableMainActorIsolationTests/MainActorIsolationTests.swift
new file mode 100644
index 0000000..57c843b
--- /dev/null
+++ b/Tests/ReerCodableMainActorIsolationTests/MainActorIsolationTests.swift
@@ -0,0 +1,63 @@
+import Foundation
+import ReerCodable
+import Testing
+
+@Codable
+nonisolated struct MainActorDefaultCodableModel: Sendable {
+ let id: Int
+ let name: String
+}
+
+@Decodable
+nonisolated struct MainActorDefaultDecodableModel: Sendable {
+ let id: Int
+}
+
+@Encodable
+nonisolated struct MainActorDefaultEncodableModel: Sendable {
+ let id: Int
+}
+
+private func acceptsCodableSendable(_ type: T.Type) {}
+
+private func acceptsDecodableSendable(_ type: T.Type) {}
+
+private func acceptsEncodableSendable(_ type: T.Type) {}
+
+@Test
+func nonisolatedCodableConformanceSatisfiesNonisolatedGenericRequirement() throws {
+ acceptsCodableSendable(MainActorDefaultCodableModel.self)
+
+ let original = MainActorDefaultCodableModel(id: 1, name: "Reer")
+ let data = try JSONEncoder().encode(original)
+ let decoded = try JSONDecoder().decode(MainActorDefaultCodableModel.self, from: data)
+
+ #expect(decoded.id == original.id)
+ #expect(decoded.name == original.name)
+}
+
+@Test
+func nonisolatedDecodableConformanceSatisfiesNonisolatedGenericRequirement() throws {
+ acceptsDecodableSendable(MainActorDefaultDecodableModel.self)
+
+ let data = #"{"id":2}"#.data(using: .utf8)!
+ let decoded = try JSONDecoder().decode(MainActorDefaultDecodableModel.self, from: data)
+
+ #expect(decoded.id == 2)
+}
+
+@Test
+func nonisolatedEncodableConformanceSatisfiesNonisolatedGenericRequirement() throws {
+ acceptsEncodableSendable(MainActorDefaultEncodableModel.self)
+
+ let data = try JSONEncoder().encode(MainActorDefaultEncodableModel(id: 3))
+ let value = try #require(data.stringAnyDictionary?["id"] as? Int)
+
+ #expect(value == 3)
+}
+
+private extension Data {
+ var stringAnyDictionary: [String: Any]? {
+ try? JSONSerialization.jsonObject(with: self) as? [String: Any]
+ }
+}
diff --git a/Tests/ReerCodableTests/MacroExpandTests.swift b/Tests/ReerCodableTests/MacroExpandTests.swift
index 1a8a797..335c4fb 100644
--- a/Tests/ReerCodableTests/MacroExpandTests.swift
+++ b/Tests/ReerCodableTests/MacroExpandTests.swift
@@ -12,6 +12,8 @@ import ReerCodableMacros
let testMacros: [String: Macro.Type] = [
"Codable": RECodable.self,
+ "Decodable": REDecodable.self,
+ "Encodable": REEncodable.self,
"InheritedCodable": InheritedCodable.self,
"CodingKey": CodingKey.self,
"EncodingKey": EncodingKey.self,
@@ -101,6 +103,489 @@ final class ReerCodableTests: XCTestCase {
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}
+
+ func testNonisolatedTypeGeneratesNonisolatedConformanceExtension() throws {
+ #if canImport(ReerCodableMacros)
+ assertMacroExpansion(
+ """
+ @Codable
+ nonisolated struct User {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated struct User {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension User: Codable, ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Decodable
+ nonisolated struct Incoming {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated struct Incoming {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension Incoming: Decodable, ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Encodable
+ nonisolated struct Outgoing {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated struct Outgoing {
+ let id: Int
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension Outgoing: Encodable, ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ nonisolated final class Session {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated final class Session {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension Session: Codable, ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ nonisolated struct DelegateBacked: ReerCodableDelegate {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated struct DelegateBacked: ReerCodableDelegate {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension DelegateBacked: Codable {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ nonisolated struct NonisolatedCodableBacked: nonisolated Codable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated struct NonisolatedCodableBacked: nonisolated Codable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension NonisolatedCodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ struct CompositionCodableBacked: Codable & Sendable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ struct CompositionCodableBacked: Codable & Sendable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension CompositionCodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ struct CommaSeparatedCodableBacked: Codable, Sendable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ struct CommaSeparatedCodableBacked: Codable, Sendable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension CommaSeparatedCodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ nonisolated struct NonisolatedCompositionCodableBacked: nonisolated Codable & Sendable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ nonisolated struct NonisolatedCompositionCodableBacked: nonisolated Codable & Sendable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ nonisolated extension NonisolatedCompositionCodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Decodable
+ struct CompositionDecodableBacked: Decodable & Sendable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ struct CompositionDecodableBacked: Decodable & Sendable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension CompositionDecodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Decodable
+ struct CommaSeparatedDecodableBacked: Decodable, Sendable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ struct CommaSeparatedDecodableBacked: Decodable, Sendable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension CommaSeparatedDecodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Encodable
+ struct CommaSeparatedEncodableBacked: Encodable, Sendable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ struct CommaSeparatedEncodableBacked: Encodable, Sendable {
+ let id: Int
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension CommaSeparatedEncodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ struct PreconcurrencyCodableBacked: @preconcurrency Codable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ struct PreconcurrencyCodableBacked: @preconcurrency Codable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension PreconcurrencyCodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ assertMacroExpansion(
+ """
+ @Codable
+ @MainActor
+ struct MainActorCodableBacked: @MainActor Codable {
+ let id: Int
+ }
+ """,
+ expandedSource: """
+ @MainActor
+ struct MainActorCodableBacked: @MainActor Codable {
+ let id: Int
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: AnyCodingKey.self)
+ self.id = try container.decode(Int.self, forKey: AnyCodingKey("id", false))
+ try self.didDecode(from: decoder)
+ }
+
+ func encode(to encoder: any Encoder) throws {
+ try self.willEncode(to: encoder)
+ var container = encoder.container(keyedBy: AnyCodingKey.self)
+ try container.encode(value: self.id, key: AnyCodingKey("id", false), treatDotAsNested: true)
+ }
+
+ init(
+ id: Int
+ ) {
+ self.id = id
+ }
+ }
+
+ extension MainActorCodableBacked: ReerCodableDelegate {
+ }
+ """,
+ macros: testMacros,
+ indentationWidth: .spaces(4)
+ )
+ #else
+ throw XCTSkip("macros are only supported when running tests for the host platform")
+ #endif
+ }
func testEnumMacro() throws {
#if canImport(ReerCodableMacros)
diff --git a/Tests/ReerCodableTests/NonisolatedCodableTests.swift b/Tests/ReerCodableTests/NonisolatedCodableTests.swift
new file mode 100644
index 0000000..d12977c
--- /dev/null
+++ b/Tests/ReerCodableTests/NonisolatedCodableTests.swift
@@ -0,0 +1,78 @@
+@testable import ReerCodable
+import Foundation
+import Testing
+
+@Codable
+nonisolated struct NonisolatedCodableStruct: Equatable {
+ let id: Int
+ let name: String
+}
+
+@Codable
+nonisolated final class NonisolatedCodableClass {
+ let id: Int
+ let name: String
+}
+
+@Codable
+nonisolated enum NonisolatedCodableEnum: String {
+ case active
+ case paused
+}
+
+@Decodable
+nonisolated struct NonisolatedDecodableStruct: Equatable {
+ let id: Int
+}
+
+@Encodable
+nonisolated struct NonisolatedEncodableStruct {
+ let id: Int
+}
+
+private func roundTrip(_ value: T, as type: T.Type = T.self) throws -> T {
+ let data = try JSONEncoder().encode(value)
+ return try JSONDecoder().decode(T.self, from: data)
+}
+
+extension TestReerCodable {
+ @Test
+ func nonisolatedCodableStructRoundTrips() throws {
+ let original = NonisolatedCodableStruct(id: 1, name: "Phoenix")
+ let decoded = try roundTrip(original)
+
+ #expect(decoded == original)
+ }
+
+ @Test
+ func nonisolatedCodableClassRoundTrips() throws {
+ let original = NonisolatedCodableClass(id: 2, name: "Reer")
+ let decoded = try roundTrip(original)
+
+ #expect(decoded.id == original.id)
+ #expect(decoded.name == original.name)
+ }
+
+ @Test
+ func nonisolatedCodableEnumRoundTrips() throws {
+ let decoded = try roundTrip(NonisolatedCodableEnum.active)
+
+ #expect(decoded == .active)
+ }
+
+ @Test
+ func nonisolatedDecodableStructDecodes() throws {
+ let data = #"{"id":3}"#.data(using: .utf8)!
+ let decoded = try JSONDecoder().decode(NonisolatedDecodableStruct.self, from: data)
+
+ #expect(decoded.id == 3)
+ }
+
+ @Test
+ func nonisolatedEncodableStructEncodes() throws {
+ let data = try JSONEncoder().encode(NonisolatedEncodableStruct(id: 4))
+ let dict = data.stringAnyDictionary
+
+ #expect(dict.int("id") == 4)
+ }
+}