Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var targetDeps: [Target.Dependency] = [
.product(name: "OrderedCollections", package: "swift-collections"),
]
#if os(Linux)
deps.append(.package(url: "https://github.com/reers/ReerKit.git", from: "1.1.9"))
deps.append(.package(url: "https://github.com/reers/ReerKit.git", from: "1.2.2"))
targetDeps.append(.product(name: "ReerKit", package: "ReerKit"))
#endif

Expand Down
2 changes: 1 addition & 1 deletion Package@swift-5.10.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var targetDeps: [Target.Dependency] = [
.product(name: "OrderedCollections", package: "swift-collections"),
]
#if os(Linux)
deps.append(.package(url: "https://github.com/reers/ReerKit.git", from: "1.1.9"))
deps.append(.package(url: "https://github.com/reers/ReerKit.git", from: "1.2.2"))
targetDeps.append(.product(name: "ReerKit", package: "ReerKit"))
#endif

Expand Down
15 changes: 9 additions & 6 deletions Sources/QsSwift/Internal/Decoder+ParseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ extension QsSwift.Decoder {
let obj: Any?

if root == "[]" && options.parseLists {
if options.allowEmptyLists
if Utils.isOverflow(leaf) {
obj = leaf
} else if options.allowEmptyLists
&& ((leaf as? String) == "" || (options.strictNullHandling && leaf == nil))
{
obj = [Any]() // empty list
} else if let arr = leaf as? [Any] {
obj = arr
} else if let arrOpt = leaf as? [Any?] {
obj = arrOpt.map { $0 ?? NSNull() } // normalize to non-optional
} else {
obj = [leaf ?? NSNull()] // wrap scalar
let valueForCombine: Any? = {
if let arr = leaf as? [Any] { return arr }
if let arrOpt = leaf as? [Any?] { return arrOpt }
return leaf ?? NSNull()
}()
obj = Utils.combine([], valueForCombine, listLimit: options.listLimit)
}
} else {
var mutableObj: [AnyHashable: Any] = [:]
Expand Down
10 changes: 8 additions & 2 deletions Sources/QsSwift/Internal/Decoder+ParseQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,14 @@ extension QsSwift.Decoder {
case .combine:
if exists {
let prev: Any? = obj[key] ?? nil
let combined: [Any?] = Utils.combine(prev, value)
obj[key] = combined.map { $0 ?? NSNull() } // normalize optionals
let combined = Utils.combine(prev, value, listLimit: options.listLimit)
if let combinedArray = combined as? [Any?] {
obj[key] = combinedArray.map { $0 ?? NSNull() } // normalize optionals
} else if let combinedArray = combined as? [Any] {
obj[key] = combinedArray
} else {
obj[key] = combined
}
} else {
obj[key] = value ?? NSNull()
}
Expand Down
69 changes: 69 additions & 0 deletions Sources/QsSwift/Internal/Utils+Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,73 @@ extension Utils {

return result
}

/// Combines two objects while honoring list limits; may return an overflow map.
/// - Returns: `[Any?]` when within limit, or `[AnyHashable: Any]` when exceeded.
@usableFromInline
static func combine(_ first: Any?, _ second: Any?, listLimit: Int) -> Any {
if let dict = first as? [AnyHashable: Any], isOverflow(dict) {
return appendOverflow(dict, value: second)
}

var result: [Any?] = []
appendCombineValue(first, into: &result)
appendCombineValue(second, into: &result)

guard listLimit >= 0, result.count > listLimit else {
return result
}

return arrayToOverflowObject(result)
}

private static func appendCombineValue(_ value: Any?, into array: inout [Any?]) {
if let arrOpt = value as? [Any?] {
array.append(contentsOf: arrOpt)
} else if let arr = value as? [Any] {
array.reserveCapacity(array.count + arr.count)
for element in arr {
array.append(element)
}
} else if let value = value {
array.append(value)
}
}

private static func arrayToOverflowObject(_ array: [Any?]) -> [AnyHashable: Any] {
var dict: [AnyHashable: Any] = [:]
dict.reserveCapacity(array.count + 1)
for (index, value) in array.enumerated() {
dict[index] = value ?? NSNull()
}
return markOverflow(dict, maxIndex: array.count - 1)
}

private static func appendOverflow(
_ dict: [AnyHashable: Any],
value: Any?
) -> [AnyHashable: Any] {
var copy = dict
var maxIndex = overflowMaxIndex(copy) ?? -1

func appendElement(_ element: Any?) {
maxIndex += 1
copy[maxIndex] = element ?? NSNull()
}

if let arrOpt = value as? [Any?] {
for element in arrOpt {
appendElement(element)
}
} else if let arr = value as? [Any] {
for element in arr {
appendElement(element)
}
} else {
appendElement(value)
}

setOverflowMaxIndex(&copy, maxIndex)
return copy
}
}
110 changes: 108 additions & 2 deletions Sources/QsSwift/Internal/Utils+Merge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,40 @@ extension QsSwift.Utils {

if let tArr = target as? [Any?], let sDict = source as? [AnyHashable: Any] {
var tDict: [AnyHashable: Any] = [:]
var maxIndex = -1

for (idx, element) in tArr.enumerated() where !(element is Undefined) {
tDict[idx] = element ?? NSNull()
if idx > maxIndex { maxIndex = idx }
}

for (key, value) in sDict where !Utils.isOverflowKey(key) {
tDict[key] = value
if let idx = Utils.intIndex(key), idx > maxIndex {
maxIndex = idx
}
}

if Utils.isOverflow(sDict) {
if let sourceMax = Utils.overflowMaxIndex(sDict), sourceMax > maxIndex {
maxIndex = sourceMax
}
Utils.setOverflowMaxIndex(&tDict, maxIndex)
}
for (key, value) in sDict { tDict[key] = value }
return tDict
}

if let tDict = target as? [AnyHashable: Any], let sArr = source as? [Any?] {
if Utils.isOverflow(tDict) {
var overflow = tDict
var maxIndex = Utils.overflowMaxIndex(overflow) ?? -1
for element in sArr where !(element is Undefined) {
maxIndex += 1
overflow[maxIndex] = element ?? NSNull()
}
Utils.setOverflowMaxIndex(&overflow, maxIndex)
return overflow
}
var sDict: [AnyHashable: Any] = [:]
for (idx, element) in sArr.enumerated() where !(element is Undefined) {
sDict[idx] = element ?? NSNull()
Expand Down Expand Up @@ -135,6 +161,24 @@ extension QsSwift.Utils {
}
}
} else if let targetDict = target as? [AnyHashable: Any] {
if Utils.isOverflow(targetDict) {
var overflow = targetDict
var maxIndex = Utils.overflowMaxIndex(overflow) ?? -1

if let seq = asSequence(source) {
for item in seq where !(item is Undefined) {
maxIndex += 1
overflow[maxIndex] = item
}
} else if !(source is Undefined) {
maxIndex += 1
overflow[maxIndex] = source
}

Utils.setOverflowMaxIndex(&overflow, maxIndex)
return overflow
}

var mutableTarget = targetDict

if let seq = asSequence(source) {
Expand All @@ -159,6 +203,43 @@ extension QsSwift.Utils {
}

if target == nil || !(target is [AnyHashable: Any]) {
if let sourceDict = source as? [AnyHashable: Any], Utils.isOverflow(sourceDict) {
if let targetArray = target as? [Any] {
var mutableTarget: [AnyHashable: Any] = [:]
var maxIndex = -1
for (index, value) in targetArray.enumerated() where !(value is Undefined) {
mutableTarget[index] = value
if index > maxIndex { maxIndex = index }
}
for (key, value) in sourceDict where !Utils.isOverflowKey(key) {
mutableTarget[key] = value
if let idx = Utils.intIndex(key), idx > maxIndex {
maxIndex = idx
}
}
Utils.setOverflowMaxIndex(&mutableTarget, maxIndex)
return mutableTarget
}

var result: [AnyHashable: Any] = [:]
if let target = target {
result[0] = target
} else {
result[0] = NSNull()
}

for (key, value) in sourceDict where !Utils.isOverflowKey(key) {
if let idx = Utils.intIndex(key) {
result[idx + 1] = value
} else {
result[key] = value
}
}

let newMax = (Utils.overflowMaxIndex(sourceDict) ?? -1) + 1
return Utils.markOverflow(result, maxIndex: newMax)
}

if let targetArray = target as? [Any] {
var mutableTarget: [AnyHashable: Any] = [:]
for (index, value) in targetArray.enumerated() where !(value is Undefined) {
Expand Down Expand Up @@ -205,12 +286,37 @@ extension QsSwift.Utils {
}

if let sourceDict = source as? [AnyHashable: Any] {
for (key, value) in sourceDict {
let targetIsOverflow = Utils.isOverflow(mergeTarget)
let sourceIsOverflow = Utils.isOverflow(sourceDict)
var overflowMax: Int?

if targetIsOverflow {
overflowMax = Utils.overflowMaxIndex(mergeTarget) ?? -1
} else if sourceIsOverflow {
overflowMax = -1
}

for (key, value) in sourceDict where !Utils.isOverflowKey(key) {
if let existingValue = mergeTarget[key] {
mergeTarget[key] = merge(target: existingValue, source: value, options: options)
} else {
mergeTarget[key] = value
}

if let idx = Utils.intIndex(key), let current = overflowMax, idx > current {
overflowMax = idx
}
}

if sourceIsOverflow || targetIsOverflow {
if let sourceMax = Utils.overflowMaxIndex(sourceDict),
sourceMax > (overflowMax ?? -1)
{
overflowMax = sourceMax
}
if let maxIndex = overflowMax {
Utils.setOverflowMaxIndex(&mergeTarget, maxIndex)
}
}
}

Expand Down
60 changes: 60 additions & 0 deletions Sources/QsSwift/Internal/Utils+Overflow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation

extension Utils {
internal struct OverflowKey: Hashable, Sendable {}

@usableFromInline
internal static let overflowKey = OverflowKey()

@inline(__always)
@usableFromInline
internal static func intIndex(_ key: AnyHashable) -> Int? {
if let intValue = key.base as? Int { return intValue }
if let number = key.base as? NSNumber { return number.intValue }
return nil
}

@usableFromInline
internal static func isOverflow(_ value: Any?) -> Bool {
guard let dict = value as? [AnyHashable: Any] else { return false }
return dict[overflowKey] is Int
}

@usableFromInline
internal static func overflowMaxIndex(_ dict: [AnyHashable: Any]) -> Int? {
dict[overflowKey] as? Int
}

@usableFromInline
internal static func setOverflowMaxIndex(_ dict: inout [AnyHashable: Any], _ maxIndex: Int) {
dict[overflowKey] = maxIndex
}

@usableFromInline
internal static func markOverflow(
_ dict: [AnyHashable: Any],
maxIndex: Int
) -> [AnyHashable: Any] {
var copy = dict
copy[overflowKey] = maxIndex
return copy
}

@usableFromInline
internal static func isOverflowKey(_ key: AnyHashable) -> Bool {
key.base is OverflowKey
}

@usableFromInline
/// Scans non-overflow keys to compute the maximum integer index and stores it.
/// Sets -1 if no integer keys are present.
internal static func refreshOverflowMaxIndex(_ dict: inout [AnyHashable: Any]) {
var maxIndex = -1
for key in dict.keys where !isOverflowKey(key) {
if let idx = intIndex(key), idx > maxIndex {
maxIndex = idx
}
}
setOverflowMaxIndex(&dict, maxIndex)
}
}
2 changes: 2 additions & 0 deletions Sources/QsSwift/Internal/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ internal enum Utils {
let box = DictBox()
stack.append(.commitDict(box, assign))
for (keyHash, child) in dictAHOpt {
if Utils.isOverflowKey(keyHash) { continue }
let keyString = String(describing: keyHash)
stack.append(.build(node: child, assign: { value in box.dict[keyString] = value }))
}
Expand All @@ -113,6 +114,7 @@ internal enum Utils {
let box = DictBox()
stack.append(.commitDict(box, assign))
for (keyHash, child) in dictAH {
if Utils.isOverflowKey(keyHash) { continue }
let keyString = String(describing: keyHash)
stack.append(.build(node: child, assign: { value in box.dict[keyString] = value }))
}
Expand Down
Loading
Loading