Skip to content

fix(hy-emit): register a struct binding's type through redundant parens (fixes #607) - #608

Open
Leimiu-G wants to merge 1 commit into
limitlessprojects:mainfrom
Leimiu-G:fix/emit-binding-kind-parens
Open

Leimiu-G wants to merge 1 commit into
limitlessprojects:mainfrom
Leimiu-G:fix/emit-binding-kind-parens

Conversation

@Leimiu-G

@Leimiu-G Leimiu-G commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #607 — the remaining leg.

What

stats := (pool_stats{hits: 1}) binds exactly what stats := pool_stats{hits: 1} binds, but
register_var_ty matched values.first() directly, so the struct type never reached var_struct.
With no type the shape table cannot resolve stats.mu to sync.mutex, and the field call emits Go's
lowercase spelling:

import "sync"

struct pool_stats {
    mu   sync.mutex
    hits int
}

fn main() {
    stats := (pool_stats{hits: 1})       // one paren pair vs pool_stats{hits: 1}
    stats.mu.lock()
    stats.hits = 2
    stats.mu.unlock()
    fmt.println("hits: {stats.hits}")
}
$ hyb check p.hy
p.hy: OK (0 diagnostics)

$ hyb build p.hy -o out --emit-go
emitted out.go

$ go build
out.go:15:11: stats.mu.lock undefined (type "sync".Mutex has no field or method lock,
              but does have method Lock)
out.go:17:11: stats.mu.unlock undefined (type "sync".Mutex has no field or method unlock,
              but does have method Unlock)

An accepted program that does not build — rule BUILDABLE / corpus contract.

The fix is one peel_parens on the value that decides the binding's type:

 if let Some(Expr::StructLit { ty: Some(t), .. }) = values.first().map(Self::peel_parens) {
     self.register_var_ty(&names[0], t);
 }

Scope note — why this revision is smaller than the first one

#607 lists two reproducers. Reproducer 1 is already fixed on main: binding_kind_of now peels
at entry, so mux := (http.new_serve_mux()) emits mux.Handle(...) today:

$ hyb build on aa4a9d9 --emit-go
mux.Handle("/ping", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

So the binding_kind_of hunk from my first revision is dropped instead of re-proposed, and its
test is kept only as a pin (it is green with and without this change — stated in the test's comment so
it does not read as coverage it does not provide).

I also re-checked the Try-operand leg (f := (os.open(x))? then f.close()) on aa4a9d9: both
spellings emit f.Close(), so no peel is needed there either. One line is the whole fix.

Verification (on aa4a9d9)

cargo test -p hy-emit          186 passed / 0 failed   (184 before, +2)

Falsifiable: with the stmt.rs hunk reverted and both tests kept, exactly one fails and the other
185 pass:

without the peel:  FAILED. 185 passed; 1 failed
                   tests::struct_binding_type_registers_through_redundant_parentheses
with the peel:     ok.     186 passed; 0 failed

End-to-end, past the Rust tests to the Go toolchain and the running binary:

source emitted go build runs
stats := pool_stats{hits: 1} stats.mu.Lock() ok hits: 2
stats := (pool_stats{hits: 1}) stats.mu.Lock() ok hits: 2

Both spellings now produce byte-identical Go.

Known local-only failure, not from this change: cargo test --workspace shows
hyb --bin hyb tests::generate_one_ignores_stale_generated_hy_go_siblings_on_package_rename failing.
It fails identically on an untouched tree in this environment (hyb generate + non-ASCII %TEMP%),
which I confirmed against a clean checkout before reporting this PR.

Related

Same invariant as the merged #611 (hy-syntax) and #631 (hy-emit), and the report #618: Paren is
documented as span-only, so every site that reads the shape of an expression has to look through it.

…ns (limitlessprojects#607)

`stats := (pool_stats{hits: 1})` binds exactly what `stats :=
pool_stats{hits: 1}` binds, but `register_var_ty` matched `values.first()`
directly, so the struct type never reached `var_struct`. With no type the
shape table cannot resolve `stats.mu` to `sync.mutex`, and the field call
emits Go's lowercase spelling:

    $ hyb check p.hy   ->  OK (0 diagnostics)
    $ hyb build p.hy   ->  emitted
    stats.mu.lock()
    $ go build
    stats.mu.lock undefined (type "sync".Mutex has no field or method
                             lock, but does have method Lock)

An accepted program that does not build (rule BUILDABLE).

This is the remaining leg of limitlessprojects#607. The other leg - `mux :=
(http.new_serve_mux())` losing its foreign binding kind - is already
fixed on main: `binding_kind_of` now peels at entry, and that reproducer
emits `mux.Handle(...)` today. So this revision carries only the
`register_var_ty` peel; the earlier `binding_kind_of` hunk is dropped
rather than re-proposed, and the corresponding test stays as a pin.

Verified on aa4a9d9: `cargo test -p hy-emit` 186 passed / 0 failed (184
before, +2). With the `stmt.rs` hunk reverted and only the tests kept,
exactly `struct_binding_type_registers_through_redundant_parentheses`
fails and the other 185 pass; `foreign_binding_kind_survives_redundant_
parentheses` is green either way, which is why it is labelled a pin.

End-to-end on the Go toolchain: the reproducer emits `stats.mu.Lock()` /
`stats.mu.Unlock()`, builds, and prints `hits: 2`.

Known local-only failure, not from this change: `hyb --bin hyb
tests::generate_one_ignores_stale_generated_hy_go_siblings_on_package_
rename` fails identically on an untouched tree in this environment
(non-ASCII %TEMP% / path handling in `hyb generate`).
@Leimiu-G
Leimiu-G force-pushed the fix/emit-binding-kind-parens branch from 971ea25 to 104f3ac Compare October 19, 2026 00:39
@Leimiu-G Leimiu-G changed the title fix(hy-emit): keep a binding's type through redundant parentheses (fixes #607) fix(hy-emit): register a struct binding's type through redundant parens (fixes #607) Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[soundness] P0 (hy-emit): a parenthesized initializer loses the binding's type — accepted programs emit mux.handle / stats.mu.lock and fail go build

1 participant