Skip to content

Commit 1c2cd5f

Browse files
committed
Fix grouped match switch arms so scope drops run before break and guarded arms do not fall through.
Regenerate access_log and showcase C snapshots; note stale-binary pinning in adding-ion-features skill.
1 parent 9eb9dfa commit 1c2cd5f

5 files changed

Lines changed: 69 additions & 14 deletions

File tree

.cursor/skills/adding-ion-features/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ New builtins (channels, Vec, String, spawn) often need C runtime helpers. Keep h
111111

112112
## Debugging a failing stage
113113

114-
**Stale binaries:** Rebuild `target/release/ion-compiler` (and `ion-build` when touching build code) after cgen or tc changes before `test_runner.sh` or regenerating `examples/*.c`. The harness defaults to `../target/release/ion-compiler`. Agent shells may set `CARGO_TARGET_DIR` outside the repo; override with `COMPILER=` or rebuild into `target/release/` and confirm the binary timestamp.
114+
**Stale binaries:** Rebuild `target/release/ion-compiler` (and `ion-build` when touching build code) after cgen or tc changes before `test_runner.sh` or regenerating `examples/*.c`. The harness defaults to `../target/release/ion-compiler`. Agent shells may set `CARGO_TARGET_DIR` outside the repo; pin it to the repo `target/` when verifying locally, or override with `COMPILER=` / `ION_BUILD=` and confirm the binary mtime.
115115

116116
```bash
117117
# See parser/tc errors

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, 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 (functions and fn literals). 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`. `Vec::get`/`Vec::pop`: IR infers `Option<T>` from the vector argument; cgen dereferences `&mut Vec<T>` parameters, unpacks runtime `Option` via `ion_option_from_raw`, and uses a temp for struct-returning `Vec::push`/`Vec::set` call values.
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 (functions and fn literals). 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`. `Vec::get`/`Vec::pop`: IR infers `Option<T>` from the vector argument; cgen dereferences `&mut Vec<T>` parameters, unpacks runtime `Option` via `ion_option_from_raw`, and uses a temp for struct-returning `Vec::push`/`Vec::set` call values. Grouped `match` switch arms: scope drops before the case `break` (GCC `-Wimplicit-fallthrough` under `-Werror`); guarded arms in a shared variant case break inside the guard `if` so fallback arms do not run.
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

examples/access_log.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ int scan_logs(ion_sender_t ION_MAYBE_UNUSED code_tx, int ION_MAYBE_UNUSED expect
172172
case 1: { // ClientError
173173
if ((code == 401)) {
174174
auth_failures = (auth_failures + 1);
175-
break;
175+
break;
176176
}
177177
break;
178178
}

examples/showcase.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ int pattern_matching_example(void) {
346346
case 0: { // Some
347347
int v = match_val_3.data.variant_0.arg0;
348348
if ((v == 42)) {
349-
break;
349+
break;
350350
}
351351
ret_val = 1;
352352
goto epilogue;

src/cgen/mod.rs

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,29 @@ impl Codegen {
14311431
}
14321432
}
14331433

1434+
/// Drop owned bindings in the current scope frame without popping it.
1435+
/// Used when leaving a grouped `switch` arm early (guard match) before case-level cleanup.
1436+
fn scope_emit_top_frame_drops(&mut self) {
1437+
let Some(frame) = self.scope_stack.last().cloned() else {
1438+
return;
1439+
};
1440+
self.emit_frame_cleanup(&frame);
1441+
let to_drop: Vec<String> = frame
1442+
.bindings
1443+
.iter()
1444+
.filter(|b| !b.dropped && self.needs_drop(&b.ty))
1445+
.map(|b| b.name.clone())
1446+
.collect();
1447+
let Some(top) = self.scope_stack.last_mut() else {
1448+
return;
1449+
};
1450+
for name in to_drop {
1451+
if let Some(binding) = top.bindings.iter_mut().find(|b| b.name == name) {
1452+
binding.dropped = true;
1453+
}
1454+
}
1455+
}
1456+
14341457
/// Assign `ret_val` (when needed), run scope unwind, and jump to the function epilogue.
14351458
fn emit_function_return(&mut self, ret: &crate::ir::IRReturn) {
14361459
self.write_indent();
@@ -3788,9 +3811,30 @@ impl Codegen {
37883811
);
37893812
}
37903813
for arm in group_arms {
3791-
self.generate_match_arm_body(arm, match_result);
3814+
if let Some(ref guard) = arm.guard {
3815+
self.write_indent();
3816+
self.write("if (");
3817+
self.generate_expr(guard);
3818+
self.writeln(") {");
3819+
self.indent_level += 1;
3820+
if let Some((result_var, result_type)) = match_result {
3821+
self.emit_match_arm_result_stmts(&arm.body, result_var, result_type);
3822+
} else {
3823+
self.generate_block(&arm.body);
3824+
}
3825+
self.scope_emit_top_frame_drops();
3826+
self.write_indent();
3827+
self.writeln("break;");
3828+
self.indent_level -= 1;
3829+
self.write_indent();
3830+
self.writeln("}");
3831+
} else {
3832+
self.generate_match_arm_body(arm, match_result, false);
3833+
}
37923834
}
37933835
self.scope_emit_exit();
3836+
self.write_indent();
3837+
self.writeln("break;");
37943838
self.indent_level -= 1;
37953839
self.write_indent();
37963840
self.writeln("}");
@@ -3989,7 +4033,7 @@ impl Codegen {
39894033
self.write_indent();
39904034
self.writeln("default:");
39914035
self.indent_level += 1;
3992-
self.generate_match_arm_body(arm, match_result);
4036+
self.generate_match_arm_body(arm, match_result, true);
39934037
self.indent_level -= 1;
39944038
}
39954039
IRPattern::Binding { name } => {
@@ -4005,7 +4049,7 @@ impl Codegen {
40054049
type_params,
40064050
);
40074051
}
4008-
self.generate_match_arm_body(arm, match_result);
4052+
self.generate_match_arm_body(arm, match_result, true);
40094053
self.indent_level -= 1;
40104054
}
40114055
IRPattern::Variant { .. } => {
@@ -4014,7 +4058,12 @@ impl Codegen {
40144058
}
40154059
}
40164060

4017-
fn generate_match_arm_body(&mut self, arm: &IRMatchArm, match_result: Option<(&str, &Type)>) {
4061+
fn generate_match_arm_body(
4062+
&mut self,
4063+
arm: &IRMatchArm,
4064+
match_result: Option<(&str, &Type)>,
4065+
trailing_break: bool,
4066+
) {
40184067
if let Some(ref guard) = arm.guard {
40194068
self.write_indent();
40204069
self.write("if (");
@@ -4028,17 +4077,23 @@ impl Codegen {
40284077
}
40294078
self.indent_level -= 1;
40304079
self.write_indent();
4031-
self.writeln("break;");
4032-
self.write_indent();
40334080
self.writeln("}");
4081+
if trailing_break {
4082+
self.write_indent();
4083+
self.writeln("break;");
4084+
}
40344085
} else if let Some((result_var, result_type)) = match_result {
40354086
self.emit_match_arm_result_stmts(&arm.body, result_var, result_type);
4036-
self.write_indent();
4037-
self.writeln("break;");
4087+
if trailing_break {
4088+
self.write_indent();
4089+
self.writeln("break;");
4090+
}
40384091
} else {
40394092
self.generate_block(&arm.body);
4040-
self.write_indent();
4041-
self.writeln("break;");
4093+
if trailing_break {
4094+
self.write_indent();
4095+
self.writeln("break;");
4096+
}
40424097
}
40434098
}
40444099

0 commit comments

Comments
 (0)