Skip to content

Commit

Permalink
fix: only ignore computed properties with getter in decoding/encoding (
Browse files Browse the repository at this point in the history
…#11)

* fix: only ignore computed properties with getter in decoding/encoding
- fixes issue #10

* wip: update method doc
  • Loading branch information
soumyamahunt committed Jul 5, 2023
1 parent 0cc623f commit a6bc3a2
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 64 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let package = Package(
.library(name: "MetaCodable", targets: ["MetaCodable"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-17-a"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.4"),
],
targets: [
Expand Down
24 changes: 14 additions & 10 deletions Sources/CodableMacroPlugin/CodableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,23 +229,27 @@ fileprivate extension VariableDeclSyntax {
}

/// Filters variables in variable declaration that can be initialized
/// first in parent type's Initializer.
/// in parent type's Initializer.
///
/// Filters variables that are not computed properties,
/// Filters variables that are not computed properties with getters,
/// and if immutable not initialized already.
var initializableBindings: [PatternBindingSyntax] {
return self.bindings.filter { binding in
switch binding.accessor {
case .none:
case .some(let block) where block.is(CodeBlockSyntax.self):
false
case .some(let block) where block.is(AccessorBlockSyntax.self):
!block.as(AccessorBlockSyntax.self)!.accessors.contains { decl in
decl.accessorKind.tokenKind == .keyword(.get)
}
// TODO: Re-evaluate when init accessor is introduced
// https://github.com/apple/swift-evolution/blob/main/proposals/0400-init-accessors.md
// || block.as(AccessorBlockSyntax.self)!.accessors.contains { decl in
// decl.accessorKind.tokenKind == .keyword(.`init`)
// }
default:
self.bindingKeyword.tokenKind == .keyword(.var)
|| binding.initializer == nil
// TODO: Reevaluate when init accessor is introduced
// https://github.com/apple/swift-evolution/blob/main/proposals/0400-init-accessors.md
// case .accessors(let block) where block.accessors
// .contains { $0.accessorKind == .keyword(Keyword.`init`)}:
// return true
default:
false
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions Sources/CodableMacroPlugin/Registration/CaseMap.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
import OrderedCollections

extension CodableMacro.Registrar {
/// A map containing all the case names
Expand Down Expand Up @@ -42,9 +42,8 @@ extension CodableMacro.Registrar {
/// The actual case name token syntax.
var token: TokenSyntax {
switch self {
case .field(let token): fallthrough
case .nestedKeyField(let token): fallthrough
case .builtWithKey(let token):
case .field(let token), .nestedKeyField(let token),
.builtWithKey(let token):
return token
}
}
Expand All @@ -53,9 +52,8 @@ extension CodableMacro.Registrar {
/// token syntax as a string.
var name: String {
switch self {
case .field(let token): fallthrough
case .nestedKeyField(let token): fallthrough
case .builtWithKey(let token):
case .field(let token), .nestedKeyField(let token),
.builtWithKey(let token):
return token.text
}
}
Expand All @@ -72,7 +70,7 @@ extension CodableMacro.Registrar {
/// Kept up-to-date by
/// `add(forKeys:field:context:)`
/// method.
private var data: [String: Case] = [:]
private var data: OrderedDictionary<String, Case> = [:]

/// Generates case names for provided keys
/// using the associated `field` and
Expand Down
168 changes: 168 additions & 0 deletions Tests/MetaCodableTests/CodableMacroGroupedVariableTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import XCTest
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
@testable import CodableMacroPlugin

class CodableMacroGroupedVariableTests: XCTestCase {
func testWithoutAnyCustomization() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
let one, two, three: String
}
""",
expandedSource:
"""
struct SomeCodable {
let one, two, three: String
init(one: String, two: String, three: String) {
self.one = one
self.two = two
self.three = three
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.one = try container.decode(String.self, forKey: CodingKeys.one)
self.two = try container.decode(String.self, forKey: CodingKeys.two)
self.three = try container.decode(String.self, forKey: CodingKeys.three)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.one, forKey: CodingKeys.one)
try container.encode(self.two, forKey: CodingKeys.two)
try container.encode(self.three, forKey: CodingKeys.three)
}
enum CodingKeys: String, CodingKey {
case one = "one"
case two = "two"
case three = "three"
}
}
extension SomeCodable: Codable {
}
""",
macros: ["Codable": CodableMacro.self]
)
}

func testWithSomeInitialized() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
let one, two: String, three: String = ""
}
""",
expandedSource:
"""
struct SomeCodable {
let one, two: String, three: String = ""
init(one: String, two: String) {
self.one = one
self.two = two
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.one = try container.decode(String.self, forKey: CodingKeys.one)
self.two = try container.decode(String.self, forKey: CodingKeys.two)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.one, forKey: CodingKeys.one)
try container.encode(self.two, forKey: CodingKeys.two)
}
enum CodingKeys: String, CodingKey {
case one = "one"
case two = "two"
}
}
extension SomeCodable: Codable {
}
""",
macros: ["Codable": CodableMacro.self]
)
}

func testMixedTypes() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
let one, two: String, three: Int
}
""",
expandedSource:
"""
struct SomeCodable {
let one, two: String, three: Int
init(one: String, two: String, three: Int) {
self.one = one
self.two = two
self.three = three
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.one = try container.decode(String.self, forKey: CodingKeys.one)
self.two = try container.decode(String.self, forKey: CodingKeys.two)
self.three = try container.decode(Int.self, forKey: CodingKeys.three)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.one, forKey: CodingKeys.one)
try container.encode(self.two, forKey: CodingKeys.two)
try container.encode(self.three, forKey: CodingKeys.three)
}
enum CodingKeys: String, CodingKey {
case one = "one"
case two = "two"
case three = "three"
}
}
extension SomeCodable: Codable {
}
""",
macros: ["Codable": CodableMacro.self]
)
}

func testMixedTypesWithSomeInitialized() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
let one: String, two: String = "", three: Int
}
""",
expandedSource:
"""
struct SomeCodable {
let one: String, two: String = "", three: Int
init(one: String, three: Int) {
self.one = one
self.three = three
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.one = try container.decode(String.self, forKey: CodingKeys.one)
self.three = try container.decode(Int.self, forKey: CodingKeys.three)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.one, forKey: CodingKeys.one)
try container.encode(self.three, forKey: CodingKeys.three)
}
enum CodingKeys: String, CodingKey {
case one = "one"
case three = "three"
}
}
extension SomeCodable: Codable {
}
""",
macros: ["Codable": CodableMacro.self]
)
}
}
77 changes: 77 additions & 0 deletions Tests/MetaCodableTests/CodableMacroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import XCTest
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
@testable import CodableMacroPlugin

final class CodableMacroTests: XCTestCase {
func testWithoutAnyCustomization() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
let value: String
}
""",
expandedSource:
"""
struct SomeCodable {
let value: String
init(value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.value = try container.decode(String.self, forKey: CodingKeys.value)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.value, forKey: CodingKeys.value)
}
enum CodingKeys: String, CodingKey {
case value = "value"
}
}
extension SomeCodable: Codable {
}
""",
macros: ["Codable": CodableMacro.self]
)
}

func testCustomKey() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
@CodablePath("customKey")
let value: String
}
""",
expandedSource:
"""
struct SomeCodable {
let value: String
init(value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.value = try container.decode(String.self, forKey: CodingKeys.value)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.value, forKey: CodingKeys.value)
}
enum CodingKeys: String, CodingKey {
case value = "customKey"
}
}
extension SomeCodable: Codable {
}
""",
macros: ["Codable": CodableMacro.self]
)
}
}
44 changes: 0 additions & 44 deletions Tests/MetaCodableTests/MetaCodableTests.swift

This file was deleted.

3 changes: 2 additions & 1 deletion Tests/MetaCodableTests/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ public struct CodableData {
var mutableOptional: String? = "some"

@CodablePath("customKey")
var customMutableKeyValue: String
var customMutableKeyValue: String { willSet {} }

var computedInt: Int { 9 }
var computedInt2: Int { get { 9 } set {} }
}

struct PrimitiveCoder: ExternalHelperCoder {
Expand Down

0 comments on commit a6bc3a2

Please sign in to comment.