Repository navigation
Conversation
…pe (limitlessprojects#618) A postfix receiver never needs parentheses, but the emitter both KEPT them in the Go text and dropped them for every resolution decision — so the member was never normalized: fn probe(hits *atomic.int64) { (hits).add(1) fmt.println("hits: {hits.load()}") } $ hyb check r2.hy -> OK (0 diagnostics) $ hyb build r2.hy -> emitted, then: out.go:10:9: (hits).add undefined (type "sync/atomic".Int64 has no field or method add, but does have method Add) Go names the right type in the error: the type was resolvable all along. The dispatch that decides how a member is spelled keys on the receiver's SHAPE, and `Expr::Paren` matched none of the sites: * `type_of_expr` (expr.rs) at entry, and at its user-struct root lookup * `member_name` (stmt.rs) for the foreign / package-root decision * `emit_call`'s four `if let Expr::Field { base, .. } = callee` branches Each fell through to "emit the name verbatim", so `add` / `lock` / `handle` survived as lowercase Go methods that do not exist (`has no field or method add` / `lock` / `handle`). The fix has two halves: 1. resolve through the parens at those entry points (`peel_parens`, which already lived in stmt.rs for the O8 work, becomes pub(crate)); 2. emit the receiver peeled — a postfix receiver never needs parens, so `(x).f` now emits exactly `x.f`, which makes the equivalence checkable byte-for-byte instead of "it builds now". No lowering changes. Tests: `paren_receiver_keeps_atomic_method_mapping`, `paren_receiver_keeps_struct_field_method_mapping`, `paren_receiver_drops_the_parens_on_a_foreign_handle` — all three fail with expr.rs + stmt.rs reverted to main (170 passed / 3 failed) and pass here (173 passed / 0 failed). They use a PARAMETER receiver, so they exercise this defect without depending on limitlessprojects#611. Verified on 0706813: hy-check 160 / hy-syntax 107 passed, unchanged; all 78 corpus fixtures still agree with their EXPECTED-VERDICT headers (23 ACCEPT / 55 REJECT, zero mismatches). This is the fourth independent locus of one class (limitlessprojects#575 P0-04, limitlessprojects#607, limitlessprojects#469, this); see limitlessprojects#585 for the argument that it should be one normalization point rather than six.
`emit_expr_exp` is the only site that gives a type-elided composite
literal its head from the surrounding type, and it matched the
expression directly. A redundant parenthesis therefore put the literal
out of reach of the `ty: None` / `elem: None` arms, the expected type
was discarded, and the emit fell through to the "spell the type"
refusal - on a program `hyb check` accepts with 0 diagnostics:
struct svc { replicas int
tag string }
fn main() {
m := map[string]svc{"a": ({replicas: 2, tag: "x"})}
fmt.println("{m}")
}
$ hyb check elided.hy -> OK (0 diagnostics)
$ hyb build elided.hy -> error [emit]: not yet supported:
type-elided struct literal - spell the type
Without the parens the same program builds, runs, and prints
`map[a:{2 x}]`; with the peel it prints the same thing.
`Paren` is documented as span-only and semantically transparent
(`hy-syntax/src/ast.rs`: "kept for faithful spans"), so the elision has
to apply identically through it. Dropping the parens from output is safe
at every call site: `emit_expr_exp` is only reached from the element
slots of composite literals, where Go gives parentheses no meaning.
The peel stays local to `emit_expr_exp` on purpose - `Self::peel_parens`
is private to `stmt.rs` today and #619 promotes it separately, so this
change is one hunk in one file and does not touch that helper.
Tests: `diff_elided_map_value_struct_literal_under_parens` and
`diff_elided_slice_elem_struct_literal_under_parens` (the latter also
covers double parens) fail without the peel; `diff_elided_map_value_
struct_literal` pins the unparenthesised spelling unchanged.
Verified on 28754ab: `cargo test -p hy-emit --lib` 173 passed / 0 failed
(171 before, +2); with expr.rs reverted and the tests kept, exactly the
two new ones fail and the other 171 pass; `cargo test --workspace` all
suites ok; `tools/differential_loop.py` accept-side 21/23, reject-side
56/56 - the two misses are the local non-ASCII %TEMP% cgo fixtures
(`a7-borrow-by-default`, `a7-own-transfer-at-return`), which fail the
same way on unpatched main.
|
Closing — my own fix is unsound and #741 is the correct take. While re-auditing this branch against a newer struct inner { replicas int }
fn main() {
e := inner{replicas: 3}
v := (&e).replicas // the parens are NOT redundant: & binds looser than .
fmt.println("sum: {v + 1}")
}Without this branch the same program emits
Disclosure for the record: this branch was written before that whitelist existed, and CI was green on #618 itself stays valid as the report; the finding and the three reproductions stand, and #741 is the |
Fixes #618.
What
Parentheses around a postfix receiver were honoured by the emitter (kept in the Go text) but not by
any of the resolution paths, so the member was never normalized:
hyb check→OK (0 diagnostics);hyb build→Same failure through all three normalization routes:
(hits).add(1)(hits).add(1)has no field or method add, but does have method Add(stats).mu.lock()(stats).mu.lock()"sync".Mutex has no field or method lock, but does have method Lock(mux).handle("/p", …)(mux).handle(…)*http.ServeMux has no field or method handleGo's error names the correct type in every case — the type was resolvable, the emitter just never
asked, because the dispatch keys on the receiver's shape and
Parenmatched nothing.Fix
Two halves, both needed:
expr.rs+stmt.rs):type_of_exprat entry and at itsuser-struct root lookup,
member_name, and the fourif let Expr::Field { base, .. }dispatchbranches in
emit_call.peel_parens— already instmt.rsfrom the O8 work — becomespub(crate)soexpr.rscan use it.(x).femitsexactly
x.f. That is what makes this checkable byte-for-byte rather than "it builds now" — theparenthesized spelling and the plain one now produce identical Go.
No lowering changes.
Tests
Three tests, all failing without the patch:
paren_receiver_keeps_atomic_method_mappinghits.Add(1),hits.Load(), no(hits)paren_receiver_keeps_struct_field_method_mappingstats.mu.Lock()/stats.mu.Unlock(), no(stats)paren_receiver_drops_the_parens_on_a_foreign_handlemux.Handle(, no.handle(, no(mux)Reverting
expr.rs+stmt.rsto0706813fwith onlytests.rsapplied:With the patch:
test result: ok. 173 passed; 0 failed.Verification (main @
0706813f)cargo test -p hy-emit --libcargo test -p hy-check -p hy-syntax --libhits.Add(1)/mux.Handle(...)— identical to the plain spellingFourth locus of the same class
#575 P0-04 (
as_slot_loan_operand), #607 (binding_kind_of/hint_from_values), #469 (?hoisting) and this one are independent sites of one defect: parens are semantically transparent, so
every shape-matching site must remember to peel, and each one that forgets fails silently — the
checker accepts and the wrong name reaches Go. None of the four fixes subsumes another. #585 argues
for eliminating the class once instead of paying per site; this PR is the per-site fix.
The three tests use a parameter receiver, so they exercise this defect without depending on
#611 (a line-leading
(used to be folded into the previous expression, which made the shapeunwritable outside a block). Independent fixes, either order.