Repository navigation
Conversation
…elector (limitlessprojects#618) `(hits).add(1)` was accepted by `hyb check` with 0 diagnostics and then emitted verbatim, so `go build` failed: (hits).add undefined (type "sync/atomic".Int64 has no field or method add, but does have method Add) The type WAS known — `type_of_expr`, `member_name` and the four `emit_call` dispatch branches each pattern-matched the receiver directly, so an `Expr::Paren` wrapper matched none of them, resolution returned `None`, and every path fell through to "emit the name verbatim". `peel_parens` / `unwrap_parens` already existed for the O8 work; they were simply not applied at these entry points. - `type_of_expr` peels at entry, and the user-struct field route peels the base before the `var_struct` lookup - `member_name` peels its base before the package-root / foreign decision - the `emit_call` package-qualified, shape/foreign and associated-fn branches peel the base - a new `selector_base` peels a redundant receiver paren in the `Expr::Field` emission arm, so `(x).f` emits exactly `x.f` — byte-identical to the bare form, which is what makes the fix verifiable rather than "it builds now". The parens are kept when the inner expression is not a primary expression, because `(*p).f` and `(a + b).f` need them; this is the same whitelist the `.pop()` lowering already uses. Test: `compiler/crates/hy-emit/tests/shapes_conversions_and_atomics.rs` pins all three shapes from the report — atomic local, atomic struct field, and a struct receiver — byte-for-byte, and asserts no redundant parens survive. No Rust/Go toolchain exists on the machine this was written on, so CI is the oracle for the build (and for the byte-exact assertions above).
|
I wrote #618 (the report this fixes) and also had my own take on it — closing mine (#619) in favour of Built
One suggestion, since your own test may not cover it. The body names struct inner { replicas int }
fn main() {
e := inner{replicas: 3}
v := (&e).replicas
fmt.println("sum: {v + 1}") // a naive peel: invalid operation: v + 1 (mismatched types *int and untyped int)
}So a pin on Credit note: #618's three reproductions are the report; this PR is the fix, and the whitelist is your |
The defect (from #618)
(hits).add(1)is accepted byhyb checkwith 0 diagnostics, thenhyb buildemits it verbatim andgo buildfails:The type is known to the emitter — Go's own diagnostic names
sync/atomic.Int64— so the shape table had the answer and never got asked: every spelling decision pattern-matched the receiver directly, anExpr::Parenwrapper matched none of them, resolution returnedNone, and each path fell through to "emit the name verbatim".The fix
peel_parens/unwrap_parensalready existed (added for the O8 work) and were simply not applied at these entry points:hy-emit/src/expr.rs::type_of_exprvar_structlookuphy-emit/src/stmt.rs::member_namehy-emit/src/expr.rs::emit_callhy-emit/src/expr.rsExpr::Fieldarmselector_basepeels a redundant receiver paren, so(x).femits exactlyx.f— byte-identical to the bare formselector_basedrops the parens only when the inner expression is itself a primary expression (Ident/Field/Index), because(*p).fand(a + b).fneed them. That is the same whitelist the.pop()lowering already uses, so there is one rule for "is this receiver paren redundant".Result for the report's three shapes:
(hits).add(1)(hits).add(1)→ Go errorhits.Add(1)(stats).mu.lock()(stats).mu.lock()→ Go errorstats.mu.Lock()(s.hits).add(2)(s.hits).add(2)→ Go errors.hits.Add(2)Verification
compiler/crates/hy-emit/tests/shapes_conversions_and_atomics.rsgains one test that pins all three shapes byte-for-byte and asserts no redundant paren survives:That is the byte-for-byte check the report asked for instead of "it builds now".
Honest limits. This machine has no Rust or Go toolchain, so I could not compile or run anything locally: CI is the oracle for the build, the new test, and the existing suite. If anything is red, that is real signal and I will fix it rather than argue. Also: no corpus fixture is added here, so the
go buildleg of the differential loop does not exercise a parenthesized receiver. Happy to add an ACCEPT fixture (plus itsVERDICTS.mdrow and the count updates that go with it) if you want that end-to-end leg rather than the emission assertion.Credit for the report and its root-cause analysis is #618's.
Duplicate disclosure: #619 claims the same issue
There is a competing open PR for the same issue, #619 (
fix(hy-emit): resolve and emit a parenthesized receiver in its own type, opened 2026-10-18T10:26Z, CI green butCONFLICTINGagainst currentmain). I did not see it before opening this — that is my miss, and per BOUNTY.md the first reporter takes the payout, so the maintainer should treat that as the tie-break, not this PR.Posting it anyway because the two fixes are not identical and the difference matters for which one is cheaper to land:
type_of_exprentry + struct-field route,member_name, threeemit_callbranches(x).femits exactlyx.fvia aselector_basehelper that only peels when the inner expression is a primary expression (Ident/Field/Index), so(*p).fand(a + b).fkeep the parens they need — the same whitelist.pop()already usesCONFLICTINGMERGEABLE, rebased on currentmain(aa4a9d9)shapes_conversions_and_atomics.rspinning all three shapes byte-for-byte plus a no-redundant-paren assertionIf you prefer #619, close this one; if you prefer this, it needs no rebase. Either way credit is yours to assign.