Skip to content

Commit b53ac87

Browse files
io tests: nested + deep-nested user-type IOFMT cases
Extends Io.UserTypes.c from 4 to 9 tests. New ones cover: - 2-level nesting (Bounds embeds two Point2D values): exercises a user writer that recursively expands IOFMT on an inner user type. - 3-level nesting (Region embeds Bounds embeds Point2D, plus i32 and a standalone Point2D): exercises the deepest realistic case where the outer writer mixes a built-in scalar with two distinct user-type arms. - Round-trips through both Bounds and Region, verifying writer and reader stay symmetric across the nesting boundary. - A mixed-call case (f64 + Region + Point2D in one StrAppendFmt) that forces the per-arg _Generic to dispatch four arms in the same call. The single IOFMT_USER_CASES_ hook now lists all three user types so inner expansions resolve every symbol; the existing forward-decl block at the top of the file is the Pattern A layout documented in the extending-io guide.
1 parent ff1a2dc commit b53ac87

1 file changed

Lines changed: 220 additions & 8 deletions

File tree

Tests/Std/Io.UserTypes.c

Lines changed: 220 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,45 @@ typedef struct {
1313
i32 y;
1414
} Point2D;
1515

16+
typedef struct {
17+
Point2D min;
18+
Point2D max;
19+
} Bounds;
20+
21+
typedef struct {
22+
i32 id;
23+
Bounds bbox;
24+
Point2D centroid;
25+
} Region;
26+
1627
// The extension hook. Must be defined BEFORE Misra/Std/Io.h is processed in
1728
// this TU so that IOFMT(x) picks up the extra _Generic arm. The macro body
18-
// references symbols `_write_Point2D` / `_read_Point2D` which only need to
19-
// be declared at the WriteFmt / StrReadFmt call sites further down -- the
20-
// definitions in this file (below the Io.h include) satisfy that.
29+
// references `_write_T` / `_read_T` symbols which only need to be declared
30+
// at the WriteFmt / StrReadFmt call sites further down -- the definitions
31+
// in this file (below the Io.h include) satisfy that.
32+
//
33+
// All three user types live in the same hook because the writer for the
34+
// outer types (Bounds, Region) recursively expands IOFMT on inner-type
35+
// arguments -- every arm must be visible at every call site.
2136
#define IOFMT_USER_CASES_(x, addr) \
2237
Point2D: \
23-
TO_TYPE_SPECIFIC_IO(Point2D, addr),
38+
TO_TYPE_SPECIFIC_IO(Point2D, addr), Bounds : TO_TYPE_SPECIFIC_IO(Bounds, addr), \
39+
Region : TO_TYPE_SPECIFIC_IO(Region, addr),
2440

2541
#include <Misra/Std/Io.h>
2642

2743
#include "../Util/TestRunner.h"
2844

29-
// Forward declarations so the `_Generic` arms inside IOFMT can resolve
30-
// both `_write_Point2D` and `_read_Point2D` from any call site below --
31-
// including from inside `_write_Point2D`'s own body, which calls
32-
// StrAppendFmt and thus expands IOFMT.
45+
// Forward declarations so the `_Generic` arms inside IOFMT resolve every
46+
// user `_write_T` / `_read_T` symbol from every call site -- including
47+
// nested expansions from inside an outer writer's own body. Pattern A in
48+
// the extending-io guide.
3349
bool _write_Point2D(Str *o, FmtInfo *info, Point2D *p);
3450
Zstr _read_Point2D(Zstr i, FmtInfo *info, Point2D *p);
51+
bool _write_Bounds(Str *o, FmtInfo *info, Bounds *b);
52+
Zstr _read_Bounds(Zstr i, FmtInfo *info, Bounds *b);
53+
bool _write_Region(Str *o, FmtInfo *info, Region *r);
54+
Zstr _read_Region(Zstr i, FmtInfo *info, Region *r);
3555

3656
bool _write_Point2D(Str *o, FmtInfo *info, Point2D *p) {
3757
(void)info;
@@ -70,10 +90,82 @@ Zstr _read_Point2D(Zstr i, FmtInfo *info, Point2D *p) {
7090
return i;
7191
}
7292

93+
// Bounds delegates to Point2D for its two corners. Verifies the
94+
// `_Generic` arm for `Point2D` resolves correctly from inside another
95+
// user-type writer.
96+
bool _write_Bounds(Str *o, FmtInfo *info, Bounds *b) {
97+
(void)info;
98+
if (!o || !b) {
99+
return false;
100+
}
101+
return StrAppendFmt(o, "[{}..{}]", b->min, b->max);
102+
}
103+
104+
Zstr _read_Bounds(Zstr i, FmtInfo *info, Bounds *b) {
105+
(void)info;
106+
if (!i || !b) {
107+
return i;
108+
}
109+
while (*i == ' ' || *i == '\t') {
110+
i++;
111+
}
112+
if (*i != '[') {
113+
return i;
114+
}
115+
i++;
116+
FmtInfo inner = {0};
117+
i = _read_Point2D(i, &inner, &b->min);
118+
while (*i == '.') {
119+
i++;
120+
}
121+
i = _read_Point2D(i, &inner, &b->max);
122+
while (*i == ' ' || *i == '\t') {
123+
i++;
124+
}
125+
if (*i == ']') {
126+
i++;
127+
}
128+
return i;
129+
}
130+
131+
// Region is 3-deep: it embeds Bounds (which itself embeds Point2D) plus
132+
// a standalone Point2D plus an in-tree i32. Exercises a writer that mixes
133+
// nested user types, repeated user types, and built-ins in one call.
134+
bool _write_Region(Str *o, FmtInfo *info, Region *r) {
135+
(void)info;
136+
if (!o || !r) {
137+
return false;
138+
}
139+
return StrAppendFmt(o, "{}:{}@{}", r->id, r->bbox, r->centroid);
140+
}
141+
142+
Zstr _read_Region(Zstr i, FmtInfo *info, Region *r) {
143+
(void)info;
144+
if (!i || !r) {
145+
return i;
146+
}
147+
FmtInfo inner = {0};
148+
i = _read_i32(i, &inner, &r->id);
149+
while (*i == ' ' || *i == '\t' || *i == ':') {
150+
i++;
151+
}
152+
i = _read_Bounds(i, &inner, &r->bbox);
153+
while (*i == ' ' || *i == '\t' || *i == '@') {
154+
i++;
155+
}
156+
i = _read_Point2D(i, &inner, &r->centroid);
157+
return i;
158+
}
159+
73160
bool test_user_type_write_basic(void);
74161
bool test_user_type_write_mixed_args(void);
75162
bool test_user_type_read_basic(void);
76163
bool test_user_type_round_trip(void);
164+
bool test_nested_user_type_write(void);
165+
bool test_nested_user_type_round_trip(void);
166+
bool test_deep_nested_user_type_write(void);
167+
bool test_deep_nested_user_type_round_trip(void);
168+
bool test_nested_user_type_mixed_with_builtins(void);
77169

78170
bool test_user_type_write_basic(void) {
79171
WriteFmt("Testing user-type write through IOFMT_USER_CASES_\n");
@@ -136,6 +228,121 @@ bool test_user_type_round_trip(void) {
136228
return ok;
137229
}
138230

231+
bool test_nested_user_type_write(void) {
232+
WriteFmt("Testing user-type-in-user-type writer (Bounds embeds Point2D)\n");
233+
234+
DefaultAllocator alloc = DefaultAllocatorInit();
235+
Str out = StrInit(&alloc);
236+
237+
Bounds b = {
238+
.min = { .x = 0, .y = 0},
239+
.max = {.x = 10, .y = 20}
240+
};
241+
bool ok = StrAppendFmt(&out, "{}", b);
242+
ok = ok && (ZstrCompare(StrBegin(&out), "[(0, 0)..(10, 20)]") == 0);
243+
244+
StrDeinit(&out);
245+
DefaultAllocatorDeinit(&alloc);
246+
return ok;
247+
}
248+
249+
bool test_nested_user_type_round_trip(void) {
250+
WriteFmt("Testing nested user-type round-trip (Bounds)\n");
251+
252+
DefaultAllocator alloc = DefaultAllocatorInit();
253+
Str out = StrInit(&alloc);
254+
255+
Bounds src = {
256+
.min = {.x = -5, .y = -7},
257+
.max = { .x = 3, .y = 11}
258+
};
259+
Bounds dst = {0};
260+
261+
bool ok = StrAppendFmt(&out, "{}", src);
262+
Zstr in = StrBegin(&out);
263+
StrReadFmt(in, "{}", dst);
264+
265+
ok = ok && (dst.min.x == src.min.x) && (dst.min.y == src.min.y);
266+
ok = ok && (dst.max.x == src.max.x) && (dst.max.y == src.max.y);
267+
268+
StrDeinit(&out);
269+
DefaultAllocatorDeinit(&alloc);
270+
return ok;
271+
}
272+
273+
bool test_deep_nested_user_type_write(void) {
274+
WriteFmt("Testing 3-level nested user-type writer (Region -> Bounds -> Point2D)\n");
275+
276+
DefaultAllocator alloc = DefaultAllocatorInit();
277+
Str out = StrInit(&alloc);
278+
279+
Region r = {
280+
.id = 42,
281+
.bbox = {.min = {.x = 0, .y = 0}, .max = {.x = 100, .y = 50}},
282+
.centroid = { .x = 50, .y = 25},
283+
};
284+
bool ok = StrAppendFmt(&out, "{}", r);
285+
ok = ok && (ZstrCompare(StrBegin(&out), "42:[(0, 0)..(100, 50)]@(50, 25)") == 0);
286+
287+
StrDeinit(&out);
288+
DefaultAllocatorDeinit(&alloc);
289+
return ok;
290+
}
291+
292+
bool test_deep_nested_user_type_round_trip(void) {
293+
WriteFmt("Testing 3-level nested user-type round-trip (Region)\n");
294+
295+
DefaultAllocator alloc = DefaultAllocatorInit();
296+
Str out = StrInit(&alloc);
297+
298+
Region src = {
299+
.id = -1234,
300+
.bbox = {.min = {.x = -10, .y = -20}, .max = {.x = 30, .y = 40}},
301+
.centroid = { .x = 10, .y = 10},
302+
};
303+
Region dst = {0};
304+
305+
bool ok = StrAppendFmt(&out, "{}", src);
306+
Zstr in = StrBegin(&out);
307+
StrReadFmt(in, "{}", dst);
308+
309+
ok = ok && (dst.id == src.id);
310+
ok = ok && (dst.bbox.min.x == src.bbox.min.x) && (dst.bbox.min.y == src.bbox.min.y);
311+
ok = ok && (dst.bbox.max.x == src.bbox.max.x) && (dst.bbox.max.y == src.bbox.max.y);
312+
ok = ok && (dst.centroid.x == src.centroid.x) && (dst.centroid.y == src.centroid.y);
313+
314+
StrDeinit(&out);
315+
DefaultAllocatorDeinit(&alloc);
316+
return ok;
317+
}
318+
319+
bool test_nested_user_type_mixed_with_builtins(void) {
320+
WriteFmt("Testing nested user types mixed with built-ins in one call\n");
321+
322+
DefaultAllocator alloc = DefaultAllocatorInit();
323+
Str out = StrInit(&alloc);
324+
325+
Region region = {
326+
.id = 7,
327+
.bbox = {.min = {.x = 1, .y = 2}, .max = {.x = 3, .y = 4}},
328+
.centroid = { .x = 2, .y = 3},
329+
};
330+
Point2D origin = {.x = 0, .y = 0};
331+
f64 score = 1.5;
332+
333+
// Single format call mixing: built-in f64 + user Region (which itself
334+
// expands to an i32 plus a nested Bounds plus a Point2D) + a separate
335+
// Point2D arg. Forces the per-arg _Generic to dispatch four distinct
336+
// arms (i32 implicit, Region, Point2D, f64) and proves nested user
337+
// types and built-ins coexist in one IOFMT expansion.
338+
bool ok = StrAppendFmt(&out, "score={.1} region={} origin={}", score, region, origin);
339+
ok = ok && (ZstrCompare(StrBegin(&out), "score=1.5 region=7:[(1, 2)..(3, 4)]@(2, 3) origin=(0, 0)") == 0);
340+
341+
StrDeinit(&out);
342+
DefaultAllocatorDeinit(&alloc);
343+
return ok;
344+
}
345+
139346
int main(void) {
140347
WriteFmt("[INFO] Starting Io.UserTypes tests\n\n");
141348

@@ -144,6 +351,11 @@ int main(void) {
144351
test_user_type_write_mixed_args,
145352
test_user_type_read_basic,
146353
test_user_type_round_trip,
354+
test_nested_user_type_write,
355+
test_nested_user_type_round_trip,
356+
test_deep_nested_user_type_write,
357+
test_deep_nested_user_type_round_trip,
358+
test_nested_user_type_mixed_with_builtins,
147359
};
148360

149361
int total_tests = sizeof(tests) / sizeof(tests[0]);

0 commit comments

Comments
 (0)