Skip to content

Commit 1f965b2

Browse files
committed
Fix tuple Vec codegen and defer struct field nulls after call moves; add todo_demo.
Resolve tuple element types in IR, mangle tuple_Vec_T names with compound-zero returns, flush owned field neutralization at statement boundaries, and add todo_demo plus integration tests.
1 parent 3fa4c1e commit 1f965b2

12 files changed

Lines changed: 326 additions & 134 deletions

File tree

.cursor/skills/finding-ion-bugs/references/bug-hotspots.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ CLI errors use `TypeCheckError` Debug form (`UseAfterMove { ... }`). LSP reforma
1313
## Codegen (`src/cgen/`)
1414

1515
- Drop order and `ion_drop_*` for moved fields
16-
- **Struct field move-out**: owned fields null after partial move (`board.items = NULL`; `struct_field_move_neutralizes_source_field`)
16+
- **Struct field move-out**: owned fields null after partial move on the next statement (`board.items = NULL`; deferred when the move is a call argument)
1717
- **Vec::push lvalues**: struct variables and field paths use `&item`, not compound literal (`vec_push_struct_var_uses_address_of_lvalue`)
1818
- **Enum emission order**: non-generic enums before structs in single-file C output
1919
- **Tuple mangle**: `tuple_type_name` sanitizes `*` and brackets when names include `Vec` types

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- **LSP**: diagnostics, hover, completion, go-to-definition; multi-error reporting; symbol table mirroring; diagnostics cleared on close; hover fixes for `let` bindings and module-qualified calls. Contiguous `//` doc comments attached to declarations (including `pub` items) for hover.
1010
- **Tooling**: GitHub Actions CI (Linux and Windows), pinned toolchain (1.96.0), `test_expectations.tsv` manifest, `--version`, line-numbered errors, Cursor agent skills. Split `tc` and `cgen` into submodules. `ion-build` driver and `ion.toml` manifests (`single`/`multi`, `out_dir`, `cflags`, `ldflags`, `stdlib_paths`, `emit_in_source`); per-example manifests and `build_hello`/`build_bad_main` harness smoke tests. Shared `discover_import_config` and stdlib search paths for `ion-compiler`, `ion-build`, and LSP. Integration harness precompiles `runtime/ion_runtime.c` once per run (`RUNTIME_OBJ`). `writing-ion-code` agent skill; `creating-ion-skills` examples index lists all eight project skills. Documented checked-in `examples/*.c` codegen snapshots in README and integration-test skill; regenerated example C output. Fixed `researching-pl-literature` skill `paper-seeds` reference formatting.
1111
- **Docs**: README, CONTRIBUTING, ION_SPEC, and agent skills aligned on project layout, `ion-build` workflow, `emit_in_source`, stdlib import order, LSP features and limitations, `src/build/` checklists, portable Git Bash paths for `test_runner.sh`, and rebuilding release binaries before harness or example C regen (stale `target/release/ion-compiler` note).
12-
- **Fixes**: match rvalue codegen, `Vec` struct drops, channel codegen, parser handling of `alias::call()`, scope-drop for moved-into-call bindings, HTTP server on Windows, integration harness on Windows. Cgen return-unwind: stop marking bindings dropped after inner-branch `return` (restores outer-path drops), dedupe `_`-prefixed unused silences, mark call arguments moved at emission to avoid double-free on unwind, and unify all function returns via `emit_function_return` (including diverging rvalue `match` arms). Cgen struct field move-out neutralizes owned fields after partial move (e.g. `board.items`); `Vec::push` passes address of struct lvalues; non-generic enums emit before structs; tuple type mangling strips `*` for `Vec`-containing names. Integer indexing and `Vec<i32>` inference in the type checker. Match rvalue arms: reject diverging arms mixed with value arms; structural control-flow analysis for arm bodies (nested `if`/`else`, `loop` without `break`, `unsafe` blocks); reject mixed diverge and value-producing paths within one arm; cgen assigns through `if`/`else` value branches. `fmt::int_to_string` uses `String::push_byte` per digit instead of per-digit `push_str` branches; integration test asserts `0`, negatives, and `int::MIN`. `int::MIN`/`int::MAX` on integer primitives; `Vec::new()`/`with_capacity()` infer `T` from a `let` type annotation. Clippy fix in `portable_source_label`.
12+
- **Fixes**: match rvalue codegen, `Vec` struct drops, channel codegen, parser handling of `alias::call()`, scope-drop for moved-into-call bindings, HTTP server on Windows, integration harness on Windows. Cgen return-unwind: stop marking bindings dropped after inner-branch `return` (restores outer-path drops), dedupe `_`-prefixed unused silences, mark call arguments moved at emission to avoid double-free on unwind, and unify all function returns via `emit_function_return` (including diverging rvalue `match` arms). Cgen struct field move-out neutralizes owned fields after partial move, deferred to statement end when the move is a call argument; `Vec::push` passes address of struct lvalues; non-generic enums emit before structs; tuple IR uses resolved element types and `tuple_Vec_T` mangling with compound-zero `ret_val` for tuple returns. Integer indexing and `Vec<i32>` inference in the type checker. Match rvalue arms: reject diverging arms mixed with value arms; structural control-flow analysis for arm bodies (nested `if`/`else`, `loop` without `break`, `unsafe` blocks); reject mixed diverge and value-producing paths within one arm; cgen assigns through `if`/`else` value branches. `fmt::int_to_string` uses `String::push_byte` per digit instead of per-digit `push_str` branches; integration test asserts `0`, negatives, and `int::MIN`. `int::MIN`/`int::MAX` on integer primitives; `Vec::new()`/`with_capacity()` infer `T` from a `let` type annotation. Clippy fix in `portable_source_label`.
1313
- **Examples**: `text_summary` (fixture file), `data_lib` (multi-module), `channel_worker` (flat single-file). Per-example `*.toml` manifests for `ion-build`. `http_server` accepts one client then exits (verifiable demo).
1414
- **Multi-file fixes**: merge private struct types for cross-module type checking; per-module C symbol prefixes in multi-file codegen (`io_print_int`); walk-up `runtime/` discovery for nested build directories. Integration tests `test_multi_struct`, `test_multi_fmt_io`. Example policy: single-file examples commit `.c` snapshots; `data_lib` multi-file keeps `.ion` only.
1515

ION_SPEC.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1258,7 +1258,7 @@ stronger contract.
12581258
- LSP go-to-definition for built-in methods (`Vec::push`, `String::len`, etc.) has no target (signature hover only)
12591259
- LSP go-to-definition for type names in type annotations (no source spans on `Type` AST nodes)
12601260
- Function types: capture-free fn literals implemented; no capturing closures, no generic `fn(T) -> R` type parameters, no method values as fn pointers
1261-
- Tuple values: no nested tuples, `==` on tuples, struct fields holding tuples, or generic `(T1, T2)` parameters
1261+
- Tuple values: no nested tuples, `==` on tuples, struct fields holding tuples, or generic `(T1, T2)` parameters. Flat tuples may hold owned heap types (for example `(Vec<T>, int)`).
12621262

12631263
### 11. Future Work (Non-Normative)
12641264

examples/access_log.c

Lines changed: 72 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ int parse_line_at(int ION_MAYBE_UNUSED index);
5353
int scan_logs(ion_sender_t ION_MAYBE_UNUSED code_tx, int ION_MAYBE_UNUSED expected);
5454
int valid_log_count(void);
5555
int main(void);
56-
void fmt_print_int(int ION_MAYBE_UNUSED n);
57-
void fmt_println_int(int ION_MAYBE_UNUSED n);
58-
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n);
5956
void io_print(ion_string_t* ION_MAYBE_UNUSED s);
6057
void io_println(ion_string_t* ION_MAYBE_UNUSED s);
6158
void io_print_str(uint8_t* ION_MAYBE_UNUSED s, int ION_MAYBE_UNUSED len);
6259
void io_print_int(int ION_MAYBE_UNUSED n);
60+
void fmt_print_int(int ION_MAYBE_UNUSED n);
61+
void fmt_println_int(int ION_MAYBE_UNUSED n);
62+
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n);
6363
ion_string_t* line_at(int ION_MAYBE_UNUSED index) {
6464
ion_string_t* ret_val = 0;
6565
if (index == 0) {
@@ -242,73 +242,6 @@ int main(void) {
242242
return ret_val;
243243
}
244244

245-
void fmt_print_int(int ION_MAYBE_UNUSED n) {
246-
io_print_int(n);
247-
goto epilogue;
248-
epilogue:
249-
return;
250-
}
251-
252-
void fmt_println_int(int ION_MAYBE_UNUSED n) {
253-
io_print_int(n);
254-
{
255-
int _newline = write(1, (uint8_t*)"\n", 1);
256-
(void)_newline;
257-
}
258-
goto epilogue;
259-
epilogue:
260-
return;
261-
}
262-
263-
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n) {
264-
ion_string_t* ret_val = 0;
265-
uint8_t buf[12] = {0};
266-
int len = 0;
267-
int negative = 0;
268-
int value = n;
269-
ion_string_t* result = ion_string_new();
270-
if (value == 0) {
271-
ret_val = ion_string_from_literal("0", 1);
272-
(void)result;
273-
(void)negative;
274-
(void)len;
275-
(void)buf;
276-
if (result) { ion_string_free(result); }
277-
goto epilogue;
278-
}
279-
if (value < 0) {
280-
negative = 1;
281-
if (value == (0 - 2147483647 - 1)) {
282-
ret_val = ion_string_from_literal("-2147483648", 11);
283-
(void)result;
284-
(void)negative;
285-
(void)len;
286-
(void)buf;
287-
if (result) { ion_string_free(result); }
288-
goto epilogue;
289-
}
290-
value = (0 - value);
291-
}
292-
while (value > 0) {
293-
int digit = (value % 10);
294-
buf[len] = (uint8_t)(48 + digit);
295-
len = (len + 1);
296-
value = (value / 10);
297-
}
298-
if (negative) {
299-
ion_string_push_byte(result, (unsigned char)((uint8_t)45));
300-
}
301-
int i = (len - 1);
302-
while (i >= 0) {
303-
ion_string_push_byte(result, (unsigned char)(({ int __ion_idx_2 = i; (__ion_idx_2 >= 0 && __ion_idx_2 < 12) ? buf[__ion_idx_2] : (ion_panic("Array index out of bounds"), buf[0]); })));
304-
i = (i - 1);
305-
}
306-
ret_val = result;
307-
goto epilogue;
308-
epilogue:
309-
return ret_val;
310-
}
311-
312245
void io_print(ion_string_t* ION_MAYBE_UNUSED s) {
313246
{
314247
int _result = write(1, s->data, (int)s->len);
@@ -383,8 +316,8 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
383316
}
384317
int i = 0;
385318
while (i < (len / 2)) {
386-
uint8_t tmp = ({ int __ion_idx_3 = i; (__ion_idx_3 >= 0 && __ion_idx_3 < 12) ? buf[__ion_idx_3] : (ion_panic("Array index out of bounds"), buf[0]); });
387-
buf[i] = ({ int __ion_idx_4 = ((len - 1) - i); (__ion_idx_4 >= 0 && __ion_idx_4 < 12) ? buf[__ion_idx_4] : (ion_panic("Array index out of bounds"), buf[0]); });
319+
uint8_t tmp = ({ int __ion_idx_2 = i; (__ion_idx_2 >= 0 && __ion_idx_2 < 12) ? buf[__ion_idx_2] : (ion_panic("Array index out of bounds"), buf[0]); });
320+
buf[i] = ({ int __ion_idx_3 = ((len - 1) - i); (__ion_idx_3 >= 0 && __ion_idx_3 < 12) ? buf[__ion_idx_3] : (ion_panic("Array index out of bounds"), buf[0]); });
388321
buf[((len - 1) - i)] = tmp;
389322
i = (i + 1);
390323
}
@@ -403,6 +336,73 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
403336
return;
404337
}
405338

339+
void fmt_print_int(int ION_MAYBE_UNUSED n) {
340+
io_print_int(n);
341+
goto epilogue;
342+
epilogue:
343+
return;
344+
}
345+
346+
void fmt_println_int(int ION_MAYBE_UNUSED n) {
347+
io_print_int(n);
348+
{
349+
int _newline = write(1, (uint8_t*)"\n", 1);
350+
(void)_newline;
351+
}
352+
goto epilogue;
353+
epilogue:
354+
return;
355+
}
356+
357+
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n) {
358+
ion_string_t* ret_val = 0;
359+
uint8_t buf[12] = {0};
360+
int len = 0;
361+
int negative = 0;
362+
int value = n;
363+
ion_string_t* result = ion_string_new();
364+
if (value == 0) {
365+
ret_val = ion_string_from_literal("0", 1);
366+
(void)result;
367+
(void)negative;
368+
(void)len;
369+
(void)buf;
370+
if (result) { ion_string_free(result); }
371+
goto epilogue;
372+
}
373+
if (value < 0) {
374+
negative = 1;
375+
if (value == (0 - 2147483647 - 1)) {
376+
ret_val = ion_string_from_literal("-2147483648", 11);
377+
(void)result;
378+
(void)negative;
379+
(void)len;
380+
(void)buf;
381+
if (result) { ion_string_free(result); }
382+
goto epilogue;
383+
}
384+
value = (0 - value);
385+
}
386+
while (value > 0) {
387+
int digit = (value % 10);
388+
buf[len] = (uint8_t)(48 + digit);
389+
len = (len + 1);
390+
value = (value / 10);
391+
}
392+
if (negative) {
393+
ion_string_push_byte(result, (unsigned char)((uint8_t)45));
394+
}
395+
int i = (len - 1);
396+
while (i >= 0) {
397+
ion_string_push_byte(result, (unsigned char)(({ int __ion_idx_4 = i; (__ion_idx_4 >= 0 && __ion_idx_4 < 12) ? buf[__ion_idx_4] : (ion_panic("Array index out of bounds"), buf[0]); })));
398+
i = (i - 1);
399+
}
400+
ret_val = result;
401+
goto epilogue;
402+
epilogue:
403+
return ret_val;
404+
}
405+
406406

407407
static void* ion_spawn_entry_0(void* arg) {
408408
ion_spawn_ctx_0* ctx = (ion_spawn_ctx_0*)arg;

examples/text_summary/text_summary.c

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,22 @@ typedef struct TextCounts {
4040
int words;
4141
} TextCounts;
4242

43+
extern int write(int fd, uint8_t* buf, int count);
44+
4345
extern int open(uint8_t* path, int flags);
4446
extern int read(int fd, uint8_t* buf, int count);
4547
extern int close(int fd);
4648

47-
extern int write(int fd, uint8_t* buf, int count);
48-
4949
TextCounts summarize_text(ion_string_t* ION_MAYBE_UNUSED text);
5050
int main(void);
51-
ReadResult fs_read_to_string_result(ion_string_t* ION_MAYBE_UNUSED path);
5251
void fmt_print_int(int ION_MAYBE_UNUSED n);
5352
void fmt_println_int(int ION_MAYBE_UNUSED n);
5453
ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n);
5554
void io_print(ion_string_t* ION_MAYBE_UNUSED s);
5655
void io_println(ion_string_t* ION_MAYBE_UNUSED s);
5756
void io_print_str(uint8_t* ION_MAYBE_UNUSED s, int ION_MAYBE_UNUSED len);
5857
void io_print_int(int ION_MAYBE_UNUSED n);
58+
ReadResult fs_read_to_string_result(ion_string_t* ION_MAYBE_UNUSED path);
5959
TextCounts summarize_text(ion_string_t* ION_MAYBE_UNUSED text) {
6060
TextCounts ret_val = {0};
6161
int lines = 0;
@@ -123,46 +123,6 @@ int main(void) {
123123
return ret_val;
124124
}
125125

126-
ReadResult fs_read_to_string_result(ion_string_t* ION_MAYBE_UNUSED path) {
127-
ReadResult ret_val = {0};
128-
{
129-
int fd = open(path->data, 0);
130-
if (fd < 0) {
131-
ret_val = ReadResult_Err_new((-1));
132-
if (path) { ion_string_free(path); }
133-
goto epilogue;
134-
}
135-
ion_string_t* text = ion_string_new();
136-
uint8_t buf[256] = {0};
137-
int n = read(fd, &buf[0], 256);
138-
while (n > 0) {
139-
int i = 0;
140-
while (i < n) {
141-
ion_string_push_byte(text, (unsigned char)(buf[i]));
142-
i = (i + 1);
143-
}
144-
n = read(fd, &buf[0], 256);
145-
}
146-
if (n < 0) {
147-
int _closed = close(fd);
148-
(void)_closed;
149-
ret_val = ReadResult_Err_new((-2));
150-
if (text) { ion_string_free(text); }
151-
if (path) { ion_string_free(path); }
152-
goto epilogue;
153-
}
154-
int _closed = close(fd);
155-
(void)_closed;
156-
ret_val = ReadResult_Ok_new(text);
157-
if (path) { ion_string_free(path); }
158-
goto epilogue;
159-
}
160-
if (path) { ion_string_free(path); }
161-
goto epilogue;
162-
epilogue:
163-
return ret_val;
164-
}
165-
166126
void fmt_print_int(int ION_MAYBE_UNUSED n) {
167127
io_print_int(n);
168128
goto epilogue;
@@ -324,3 +284,43 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
324284
return;
325285
}
326286

287+
ReadResult fs_read_to_string_result(ion_string_t* ION_MAYBE_UNUSED path) {
288+
ReadResult ret_val = {0};
289+
{
290+
int fd = open(path->data, 0);
291+
if (fd < 0) {
292+
ret_val = ReadResult_Err_new((-1));
293+
if (path) { ion_string_free(path); }
294+
goto epilogue;
295+
}
296+
ion_string_t* text = ion_string_new();
297+
uint8_t buf[256] = {0};
298+
int n = read(fd, &buf[0], 256);
299+
while (n > 0) {
300+
int i = 0;
301+
while (i < n) {
302+
ion_string_push_byte(text, (unsigned char)(buf[i]));
303+
i = (i + 1);
304+
}
305+
n = read(fd, &buf[0], 256);
306+
}
307+
if (n < 0) {
308+
int _closed = close(fd);
309+
(void)_closed;
310+
ret_val = ReadResult_Err_new((-2));
311+
if (text) { ion_string_free(text); }
312+
if (path) { ion_string_free(path); }
313+
goto epilogue;
314+
}
315+
int _closed = close(fd);
316+
(void)_closed;
317+
ret_val = ReadResult_Ok_new(text);
318+
if (path) { ion_string_free(path); }
319+
goto epilogue;
320+
}
321+
if (path) { ion_string_free(path); }
322+
goto epilogue;
323+
epilogue:
324+
return ret_val;
325+
}
326+

0 commit comments

Comments
 (0)