diff --git a/Sources/PluginCore/Variables/Enum/Switcher/AdjacentlyTaggableSwitcher.swift b/Sources/PluginCore/Variables/Enum/Switcher/AdjacentlyTaggableSwitcher.swift index bc6e73478..1ad68da0b 100644 --- a/Sources/PluginCore/Variables/Enum/Switcher/AdjacentlyTaggableSwitcher.swift +++ b/Sources/PluginCore/Variables/Enum/Switcher/AdjacentlyTaggableSwitcher.swift @@ -244,6 +244,9 @@ extension InternallyTaggedEnumSwitcher: AdjacentlyTaggableSwitcher { ).combined() if containerType.isOptionalTypeSyntax { + let needsContainer = location.cases.contains { variable, _ in + variable.variables.contains { $0.label != nil } + } let topContainerOptional = decodingNode.children .flatMap(\.value.linkedVariables) .allSatisfy { variable in @@ -256,7 +259,7 @@ extension InternallyTaggedEnumSwitcher: AdjacentlyTaggableSwitcher { } let header: SyntaxNodeString = - topContainerOptional && !rawRepresentable + needsContainer && topContainerOptional && !rawRepresentable ? "if let \(container) = \(container), let \(location.container) = \(location.container)" : "if let \(container) = \(container)" try! IfExprSyntax(header) { diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift index bb7eddd2f..6e9a8b19b 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift @@ -1,3 +1,4 @@ +import Foundation import HelperCoders import MetaCodable import Testing @@ -673,4 +674,132 @@ struct CodedAtEnumTests { ) } } + + struct WithOnlyAssociatedVariablesAtTopLevel { + @Codable + @CodedAt("type") + enum TypeObject { + case type1(Type1) + + @Codable + struct Type1 { + let int: Int + } + } + + @Test + func expansion() throws { + assertMacroExpansion( + """ + @Codable + @CodedAt("type") + enum TypeObject { + case type1(Int) + } + """, + expandedSource: + """ + enum TypeObject { + case type1(Int) + } + + extension TypeObject: Decodable { + init(from decoder: any Decoder) throws { + var typeContainer: KeyedDecodingContainer? + let container = try? decoder.container(keyedBy: CodingKeys.self) + if let container = container { + typeContainer = container + } else { + typeContainer = nil + } + if let typeContainer = typeContainer { + let typeString: String? + do { + typeString = try typeContainer.decodeIfPresent(String.self, forKey: CodingKeys.type) ?? nil + } catch { + typeString = nil + } + if let typeString = typeString { + switch typeString { + case "type1": + let _0: Int + _0 = try Int(from: decoder) + self = .type1(_0) + return + default: + break + } + } + } + let context = DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Couldn't match any cases." + ) + throw DecodingError.typeMismatch(Self.self, context) + } + } + + extension TypeObject: Encodable { + func encode(to encoder: any Encoder) throws { + let container = encoder.container(keyedBy: CodingKeys.self) + var typeContainer = container + switch self { + case .type1(let _0): + try typeContainer.encode("type1", forKey: CodingKeys.type) + try _0.encode(to: encoder) + } + } + } + + extension TypeObject { + enum CodingKeys: String, CodingKey { + case type = "type" + } + } + """ + ) + } + + @Test + func decodingAndEncoding() throws { + let original = TypeObject.type1(TypeObject.Type1(int: 42)) + let encoded = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode( + TypeObject.self, from: encoded) + if case .type1(let data) = decoded { + #expect(data.int == 42) + } else { + Issue.record("Expected type1 case") + } + } + + @Test + func decodingFromJSON() throws { + let jsonStr = """ + { + "type": "type1", + "int": 42 + } + """ + let jsonData = try #require(jsonStr.data(using: .utf8)) + let decoded = try JSONDecoder().decode( + TypeObject.self, from: jsonData) + if case .type1(let data) = decoded { + #expect(data.int == 42) + } else { + Issue.record("Expected type1 case") + } + } + + @Test + func encodingToJSON() throws { + let original = TypeObject.type1(TypeObject.Type1(int: 42)) + let encoded = try JSONEncoder().encode(original) + let json = + try JSONSerialization.jsonObject(with: encoded) + as! [String: Any] + #expect(json["type"] as? String == "type1") + #expect(json["int"] as? Int == 42) + } + } }