Skip to content

Commit 3fa4c1e

Browse files
committed
Fix Vec struct push, struct field move-out, and enum/tuple cgen; add todo_demo example.
Neutralize owned struct fields after partial move, pass struct lvalues to Vec::push, emit enums before structs, and sanitize tuple mangling; add integration tests, fix http_server to accept once, refresh example C snapshots, and document stale release-binary rebuilds in skills and CONTRIBUTING.
1 parent c155215 commit 3fa4c1e

19 files changed

Lines changed: 281 additions & 125 deletions

File tree

.cursor/skills/finding-ion-bugs/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ cargo clippy -- -D warnings
6161

6262
**Windows:** Git Bash for `test_runner.sh`. Stop `ion-lsp` / `ion-compiler` if rebuild hits "Access is denied".
6363

64+
**Stale binaries:** Rebuild `target/release/ion-compiler` after cgen changes before `./target/release/ion-compiler` or `test_runner.sh`. If results look wrong, check the binary mtime or set `COMPILER=` to a freshly built path (agent shells may use a sandbox `CARGO_TARGET_DIR`).
65+
6466
## Localize by symptom
6567

6668
| Symptom | First place to inspect | Quick check |

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ 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`)
17+
- **Vec::push lvalues**: struct variables and field paths use `&item`, not compound literal (`vec_push_struct_var_uses_address_of_lvalue`)
18+
- **Enum emission order**: non-generic enums before structs in single-file C output
19+
- **Tuple mangle**: `tuple_type_name` sanitizes `*` and brackets when names include `Vec` types
1620
- **Match scrutinee move-out**: pattern payload bindings null `match_val_N.data.variant_*` fields when ownership transfers (`statement_match_payload_move_neutralizes_scrutinee`); whole-enum binding arms clear active variant payloads via `emit_match_scrutinee_whole_enum_moved_out` (`whole_enum_binding_neutralizes_scrutinee_payloads`). IR infers `enum_type` from the scrutinee when arms use binding/wildcard only (`infer_match_enum_name`).
1721
- **Return unwind**: all function exits use `emit_function_return` (`ret_val`, `scope_emit_return_unwind`, `goto epilogue`), including diverging `return` inside rvalue `match` arms (`rvalue_match_divergent_return_unwinds_owned`). Value-producing rvalue arms still assign and `break` from the `switch`.
1822
- `Box`, `Vec`, `String` layout vs `runtime/ion_runtime.h`

.cursor/skills/ion-integration-tests/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" LDFLAGS="-fsanitiz
6767

6868
**Windows:** Use Git Bash, not WSL. Rebuild release after compiler changes. Stop `ion-lsp` if build fails with "Access is denied".
6969

70+
**Stale binaries:** `test_runner.sh` defaults to `../target/release/ion-compiler`. Rebuild after codegen changes and confirm that path is the binary you just built. Override with `COMPILER=` when your build landed elsewhere (for example a sandbox `CARGO_TARGET_DIR`).
71+
7072
## Manifest (`test_expectations.tsv`)
7173

7274
Tab-separated columns:

.cursor/skills/ion-lang/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Read these when the task matches:
9999
1. **Spec first** - If semantics change, update `ION_SPEC.md` before or alongside code.
100100
2. **Minimal diff** - Match existing patterns in the module you're editing; don't refactor adjacent code.
101101
3. **Test both paths** - Positive compile+run tests and negative compile-error tests where applicable.
102-
4. **Rebuild release** - Integration harness defaults to `target/release/ion-compiler`.
102+
4. **Rebuild release** - Integration harness defaults to `target/release/ion-compiler`. After codegen changes, rebuild before `test_runner.sh` or regenerating `examples/*.c`. Confirm the binary timestamp matches your build; a stale `target/release/ion-compiler` can pass an old harness run or fail on new tests. Cursor agent shells may set `CARGO_TARGET_DIR` outside the repo; point `COMPILER=` at the binary you just built, or unset `CARGO_TARGET_DIR` and rebuild into `target/release/`.
103103

104104
## Common pitfalls
105105

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
- **Compiler**: scope-drop codegen, `pthread` spawn, slice bounds checks, array-to-slice coercion, struct/enum field drops, `String` equality, module function name mangling, lasting-borrow rules (ION_SPEC 5.3), field-path borrow exclusivity, move/copy tracking fixes, generic monomorphization, generated C file banner (repo-relative source labels via `portable_source_label`, GNU C note, merged stdlib note, multi-file provenance, comment-safe path escaping).
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.
11-
- **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, and portable Git Bash paths for `test_runner.sh`.
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). 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`.
13-
- **Examples**: `text_summary` (fixture file), `data_lib` (multi-module), `channel_worker` (flat single-file). Per-example `*.toml` manifests for `ion-build`.
11+
- **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`.
13+
- **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

1616
## 2025-12

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
cargo build --release --bin ion-compiler --bin ion-build --bin ion-lsp
1313
```
1414

15+
After compiler codegen changes, rebuild before `tests/test_runner.sh` or regenerating committed `examples/*.c`. A stale `target/release/ion-compiler` can produce misleading harness results.
16+
1517
Install CLI tools into your Cargo bin directory:
1618

1719
```bash

ION_SPEC.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,7 @@ Built-in methods (`Vec::push`, `String::len`, etc.) show signature hover but hav
12411241

12421242
The CLI `ion-compiler`, `ion-build`, and the LSP use `TypeChecker::check_program_collecting` to gather multiple independent type diagnostics. Import failures are reported per `import` statement via `Compiler::load_imports`.
12431243

1244-
Build with `cargo build --release --bin ion-lsp`. Rebuild after compiler or LSP changes; reload the editor window so `ion.lspPath` picks up the new binary. Set `ion.lspPath` in editor settings to the executable path.
1244+
Build with `cargo build --release --bin ion-lsp`. Rebuild after compiler or LSP changes; reload the editor window so `ion.lspPath` picks up the new binary. A stale `ion-lsp` or workspace `ion-compiler` can disagree with a freshly built CLI. Set `ion.lspPath` in editor settings to the executable path.
12451245

12461246
#### 10.3 Known limitations
12471247

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ On Linux or macOS, use `./target/release/ion-build` and drop `.exe`. Windows cha
193193
| [examples/hello_world.ion](examples/hello_world.ion) | (use `ion-compiler` only; no stdlib) | Minimal FFI `write()` to stdout |
194194
| [examples/hello_world_safe.ion](examples/hello_world_safe.ion) | [hello_world_safe.toml](examples/hello_world_safe.toml) or root [ion.toml](ion.toml) | stdlib `io` module |
195195
| [examples/spawn_channel.ion](examples/spawn_channel.ion) | [spawn_channel.toml](examples/spawn_channel.toml) | `spawn` with cross-thread channels |
196-
| [examples/http_server.ion](examples/http_server.ion) | [http_server.toml](examples/http_server.toml) | Sockets, FFI, concurrent clients via `spawn` |
196+
| [examples/http_server.ion](examples/http_server.ion) | [http_server.toml](examples/http_server.toml) | Sockets, FFI, spawn per client (single accept, then exit) |
197197
| [examples/showcase.ion](examples/showcase.ion) | [showcase.toml](examples/showcase.toml) | Mixed language features |
198198
| [examples/access_log.ion](examples/access_log.ion) | [access_log.toml](examples/access_log.toml) | Log parsing, spawn, channels, fmt/io |
199199
| [examples/minimal.ion](examples/minimal.ion) | (transpile-only with `ion-compiler`) | Smallest valid program |

examples/http_server.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,11 @@ int main(void) {
121121
ret_val = 3;
122122
goto epilogue;
123123
}
124-
while (1) {
125-
uint8_t client_addr[16] = {0};
126-
int addrlen = 16;
127-
int client_fd = accept(server_fd, &client_addr[0], &addrlen);
128-
dispatch_client(client_fd);
129-
}
124+
uint8_t client_addr[16] = {0};
125+
int addrlen = 16;
126+
int client_fd = accept(server_fd, &client_addr[0], &addrlen);
127+
dispatch_client(client_fd);
128+
close(server_fd);
130129
}
131130
ret_val = 0;
132131
goto epilogue;

examples/http_server.ion

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// HTTP server on port 8080. FFI sockets and spawn per client.
1+
// HTTP server on port 8080. FFI sockets, one accept, spawn per client, then exit.
22
//
33
// recv and send are Ion keywords; FFI uses recv_sys and send_sys.
44
// Link with: gcc ... -Drecv_sys=recv -Dsend_sys=send
@@ -94,13 +94,11 @@ fn main() -> int {
9494
return 3;
9595
}
9696

97-
loop {
98-
let mut client_addr: [u8; 16] = [0; 16];
99-
let mut addrlen: int = 16;
100-
101-
let client_fd: int = accept(&server_fd, &mut client_addr[0], &mut addrlen);
102-
dispatch_client(client_fd);
103-
}
97+
let mut client_addr: [u8; 16] = [0; 16];
98+
let mut addrlen: int = 16;
99+
let client_fd: int = accept(&server_fd, &mut client_addr[0], &mut addrlen);
100+
dispatch_client(client_fd);
101+
close(&server_fd);
104102
}
105103

106104
return 0;

0 commit comments

Comments
 (0)