Skip to content

Commit 280fa84

Browse files
committed
chore: dce and coverage
1 parent 64ef45f commit 280fa84

10 files changed

Lines changed: 108 additions & 74 deletions

File tree

assembly/__tests__/json-runtime.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,3 +597,18 @@ describe("SWAR: JSON.Obj[] empty array as @json class field", () => {
597597
const o = JSON.parse<ObjArr>('{"items":[]}');
598598
expect(o.items.length).toBe(0);
599599
});
600+
601+
// naive/object.ts parseObjectBody:329 — inner object consumes the outer } so parseObjectBody
602+
// exhausts srcEnd and returns srcEnd (success path, no throw needed)
603+
describe("JSON.Obj: parseObjectBody returns srcEnd when inner object uses the closing brace (naive/object.ts:329)", () => {
604+
const obj = JSON.parse<JSON.Obj>('{"k":{"a":1}');
605+
expect(obj.has("k")).toBe(true);
606+
});
607+
608+
// swar/array/object.ts:51 — shrink path: reusing a JSON.Obj[] with more elements than the new parse
609+
describe("SWAR: JSON.Obj[] field shrink path on reuse (swar/array/object.ts:51)", () => {
610+
const o1 = JSON.parse<ObjArr>('{"items":[{"x":1},{"x":2}]}');
611+
const o2 = JSON.parse<ObjArr>('{"items":[{"x":3}]}', o1);
612+
expect(o2.items.length).toBe(1);
613+
expect(o2.items[0].getAs<f64>("x")).toBe(3.0);
614+
});

assembly/__tests__/typedarray.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ class BinaryContainer {
252252
right: BinaryEnvelope = new BinaryEnvelope();
253253
}
254254

255+
256+
@json
257+
class TAFieldHolder {
258+
items: Int16Array = makeInt16Array();
259+
raw: ArrayBuffer = makeArrayBuffer();
260+
}
261+
255262
describe("Should serialize and deserialize typed arrays by default", () => {
256263
const int8 = makeInt8Array();
257264
expect(JSON.stringify(int8)).toBe("[-1,0,127]");
@@ -610,3 +617,48 @@ describe("SWAR: JSON.parse<Int64Array> parses 64-bit integer array", () => {
610617
expect(v[0]).toBe(1000000000000);
611618
expect(v[1]).toBe(-2);
612619
});
620+
621+
// simple TAFieldHolder sanity check
622+
describe("TAFieldHolder basic round-trip", () => {
623+
const h = JSON.parse<TAFieldHolder>('{"items":[1,2,3],"raw":[10,20]}');
624+
const serialized = JSON.stringify(h);
625+
expect(serialized).toBe('{"items":[1,2,3],"raw":[10,20]}');
626+
expect(h.items.length).toBe(3);
627+
expect(h.items[0]).toBe(1);
628+
expect(h.items[2]).toBe(3);
629+
expect(h.raw.byteLength).toBe(2);
630+
});
631+
632+
// swar/typedarray.ts:78 IfBranch — reuse non-empty Int32Array field for empty array
633+
describe("SWAR TypedArray field: reuse non-empty field for [] triggers empty path (swar/typedarray.ts:78)", () => {
634+
const h1 = JSON.parse<TAFieldHolder>('{"items":[1,2,3],"raw":[]}');
635+
const h2 = JSON.parse<TAFieldHolder>('{"items":[],"raw":[]}', h1);
636+
expect(h2.items.length).toBe(0);
637+
});
638+
639+
// swar/typedarray.ts:87 Block — reuse Int32Array field with wrong length for non-empty array
640+
describe("SWAR TypedArray field: reuse wrong-size field for new array triggers resize (swar/typedarray.ts:87)", () => {
641+
const h1 = JSON.parse<TAFieldHolder>('{"items":[1,2],"raw":[]}');
642+
const h2 = JSON.parse<TAFieldHolder>(
643+
'{"items":[10,20,30,40,50],"raw":[]}',
644+
h1,
645+
);
646+
expect(h2.items.length).toBe(5);
647+
});
648+
649+
// swar/typedarray.ts:178 IfBranch+Assignment — reuse non-empty ArrayBuffer field for empty array
650+
describe("SWAR ArrayBuffer field: reuse non-empty field for [] triggers empty path (swar/typedarray.ts:178)", () => {
651+
const h1 = JSON.parse<TAFieldHolder>('{"items":[],"raw":[10,20,30]}');
652+
const h2 = JSON.parse<TAFieldHolder>('{"items":[],"raw":[]}', h1);
653+
expect(h2.raw.byteLength).toBe(0);
654+
});
655+
656+
// swar/typedarray.ts:185+186 Block+Assignment — reuse wrong-size ArrayBuffer field for non-empty array
657+
describe("SWAR ArrayBuffer field: reuse wrong-size field for new array triggers resize (swar/typedarray.ts:185)", () => {
658+
const h1 = JSON.parse<TAFieldHolder>('{"items":[],"raw":[1,2]}');
659+
const h2 = JSON.parse<TAFieldHolder>(
660+
'{"items":[],"raw":[10,20,30,40,50,60,70]}',
661+
h1,
662+
);
663+
expect(h2.raw.byteLength).toBe(7);
664+
});

assembly/deserialize/naive/map.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export function deserializeMapBody<T extends Map<any, any>>(
7070
if (load<u16>(srcStart) == BRACE_RIGHT) return srcStart + 2;
7171

7272
while (srcStart < srcEnd) {
73-
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
7473
if (load<u16>(srcStart) != QUOTE) break;
7574

7675
const keyStart = srcStart + 2;

assembly/deserialize/naive/set.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ function deserializeSetDirect<T extends Set<any>>(
6363
if (load<u16>(srcStart) == BRACKET_RIGHT) return srcStart + 2;
6464

6565
while (srcStart < srcEnd) {
66-
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
6766
const code = load<u16>(srcStart);
6867

6968
// @ts-ignore: type

assembly/deserialize/swar/array/integer.ts

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function storeUnsignedIntegerE<E extends number>(
4040
}
4141
}
4242

43-
// The four parse helpers below take a `slot` pointer (`writePtr`) and store
43+
// The parse helpers below take a `slot` pointer (`writePtr`) and store
4444
// the value directly via `store<valueof<T>>(slot, ...)`. The outer dispatcher
4545
// owns the array's `out.length = maxElements` pre-allocation and the
4646
// `writePtr` advance, so the per-element `Array.push` capacity check and
@@ -49,57 +49,6 @@ function storeUnsignedIntegerE<E extends number>(
4949
// Parsers are also E-parameterised so they're shareable with
5050
// `swar/typedarray.ts`. The body is byte-identical to the prior version
5151
// modulo s/valueof<T>/E/.
52-
export function parseSignedIntegerScalar<E extends number>(
53-
srcStart: usize,
54-
srcEnd: usize,
55-
slot: usize,
56-
): usize {
57-
let negative = false;
58-
let code = load<u16>(srcStart);
59-
if (code == 45) {
60-
negative = true;
61-
srcStart += 2;
62-
if (srcStart >= srcEnd) return 0;
63-
code = load<u16>(srcStart);
64-
}
65-
66-
let digit = <u32>code - 48;
67-
if (digit > 9) return 0;
68-
69-
let value: u64 = digit;
70-
srcStart += 2;
71-
while (srcStart < srcEnd) {
72-
digit = <u32>load<u16>(srcStart) - 48;
73-
if (digit > 9) break;
74-
value = value * 10 + digit;
75-
srcStart += 2;
76-
}
77-
78-
storeSignedIntegerE<E>(slot, negative ? -(<i64>value) : <i64>value);
79-
return srcStart;
80-
}
81-
82-
export function parseUnsignedIntegerScalar<E extends number>(
83-
srcStart: usize,
84-
srcEnd: usize,
85-
slot: usize,
86-
): usize {
87-
let digit = <u32>load<u16>(srcStart) - 48;
88-
if (digit > 9) return 0;
89-
90-
let value: u64 = digit;
91-
srcStart += 2;
92-
while (srcStart < srcEnd) {
93-
digit = <u32>load<u16>(srcStart) - 48;
94-
if (digit > 9) break;
95-
value = value * 10 + digit;
96-
srcStart += 2;
97-
}
98-
99-
storeUnsignedIntegerE<E>(slot, value);
100-
return srcStart;
101-
}
102-
10352
export function parseSignedIntegerSWAR<E extends number>(
10453
srcStart: usize,
10554
srcEnd: usize,
@@ -448,9 +397,11 @@ function deserializeIntegerArrayImpl<T extends number[]>(
448397

449398
if (isSigned<valueof<T>>()) {
450399
while (srcStart < srcEnd) {
451-
const next = useSWAR
452-
? parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr)
453-
: parseSignedIntegerScalar<valueof<T>>(srcStart, srcEnd, writePtr);
400+
const next = parseSignedIntegerSWAR<valueof<T>>(
401+
srcStart,
402+
srcEnd,
403+
writePtr,
404+
);
454405
if (!next) break;
455406
writePtr += elementSize;
456407
srcStart = next;
@@ -469,9 +420,11 @@ function deserializeIntegerArrayImpl<T extends number[]>(
469420
}
470421
} else {
471422
while (srcStart < srcEnd) {
472-
const next = useSWAR
473-
? parseUnsignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr)
474-
: parseUnsignedIntegerScalar<valueof<T>>(srcStart, srcEnd, writePtr);
423+
const next = parseUnsignedIntegerSWAR<valueof<T>>(
424+
srcStart,
425+
srcEnd,
426+
writePtr,
427+
);
475428
if (!next) break;
476429
writePtr += elementSize;
477430
srcStart = next;

assembly/serialize/naive/staticarray.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ function maxIntegerBytes<T extends number>(): u32 {
1313
return isSigned<T>() ? 42 : 40;
1414
}
1515
function reservePrimitiveStaticArray<T>(len: i32): void {
16-
if (len <= 0) return;
1716
if (isBoolean<T>()) {
1817
bs.proposeSize(4 + <u32>len * 12);
1918
} else if (isInteger<T>()) {

assembly/serialize/naive/string.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,3 @@ export function serializeStringRange(srcPtr: usize, srcSize: usize): void {
8585
store<u16>(bs.offset, QUOTE);
8686
bs.offset += 2;
8787
}
88-
function write_u_escape(code: u16): void {
89-
bs.growSize(10);
90-
store<u32>(bs.offset, U_MARKER); // "\u"
91-
store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
92-
bs.offset += 12;
93-
}

assembly/serialize/naive/typedarray.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ function maxIntegerBytes<T extends number>(): u32 {
1111
}
1212

1313
function reserveTypedArray<T extends ArrayLike<number>>(len: i32): void {
14-
if (len <= 0) return;
1514
if (isFloat<valueof<T>>()) {
1615
bs.proposeSize(4 + <u32>len * (sizeof<valueof<T>>() == 4 ? 34 : 66));
1716
} else {

transform/lib/index.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,10 @@ export class JSONTransform extends Visitor {
11311131
type.startsWith("JSON.Box<") ||
11321132
isEnum(type, this.sources.get(this.schema.node.range.source), this.parser))
11331133
sortedMembers.number.push(member);
1134-
else if (isArray(type) || type == "JSON.Arr" || type == "Arr")
1134+
else if (isArray(type) ||
1135+
type == "JSON.Arr" ||
1136+
type == "Arr" ||
1137+
needsReferenceLoad(type))
11351138
sortedMembers.array.push(member);
11361139
else
11371140
sortedMembers.object.push(member);
@@ -1953,8 +1956,13 @@ export class JSONTransform extends Visitor {
19531956
const getSlowValueStore = (member, valueStart, valueEnd, prefix) => {
19541957
if (member.flags.has(PropertyFlags.Lazy))
19551958
return getLazyRangeStore(member, valueStart, valueEnd, prefix);
1959+
const offset = JSON.stringify(member.name);
1960+
if (needsReferenceLoad(member.type)) {
1961+
return (prefix +
1962+
`store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}, changetype<usize>(load<${member.type}>(changetype<usize>(out), offsetof<this>(${offset})))), offsetof<this>(${offset}));\n`);
1963+
}
19561964
return (prefix +
1957-
`store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}), offsetof<this>(${JSON.stringify(member.name)}));\n`);
1965+
`store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}), offsetof<this>(${offset}));\n`);
19581966
};
19591967
const getSlowBooleanStore = (member, value, valueStart, valueEnd, prefix) => {
19601968
if (member.flags.has(PropertyFlags.Lazy))

transform/src/index.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,9 +1714,15 @@ export class JSONTransform extends Visitor {
17141714
)
17151715
)
17161716
sortedMembers.number.push(member);
1717-
// JSON.Arr serializes as `[...]`, so the deserializer routes it through
1718-
// the array (`[`) branch - it must live in the array bucket, not object.
1719-
else if (isArray(type) || type == "JSON.Arr" || type == "Arr")
1717+
// JSON.Arr and TypedArrays/ArrayBuffer all serialize as `[...]`, so the
1718+
// deserializer routes them through the array (`[`) branch — they must
1719+
// live in the array bucket, not the object bucket.
1720+
else if (
1721+
isArray(type) ||
1722+
type == "JSON.Arr" ||
1723+
type == "Arr" ||
1724+
needsReferenceLoad(type)
1725+
)
17201726
sortedMembers.array.push(member);
17211727
else sortedMembers.object.push(member);
17221728
// else console.warn("Could not determine type " + type + " for member " + member.name + " in class " + this.schema.name);
@@ -2881,9 +2887,19 @@ export class JSONTransform extends Visitor {
28812887
): string => {
28822888
if (member.flags.has(PropertyFlags.Lazy))
28832889
return getLazyRangeStore(member, valueStart, valueEnd, prefix);
2890+
const offset = JSON.stringify(member.name);
2891+
// TypedArrays and ArrayBuffer are reference types whose JSON form is
2892+
// `[...]`. Pass the existing field pointer as `dst` so the deserializer
2893+
// can reuse the allocation when size matches instead of always allocating.
2894+
if (needsReferenceLoad(member.type)) {
2895+
return (
2896+
prefix +
2897+
`store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}, changetype<usize>(load<${member.type}>(changetype<usize>(out), offsetof<this>(${offset})))), offsetof<this>(${offset}));\n`
2898+
);
2899+
}
28842900
return (
28852901
prefix +
2886-
`store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}), offsetof<this>(${JSON.stringify(member.name)}));\n`
2902+
`store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}), offsetof<this>(${offset}));\n`
28872903
);
28882904
};
28892905

0 commit comments

Comments
 (0)