Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion compiler/crates/hy-emit/src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,14 @@ impl Emitter {
if kind != BindingKind::Plain {
self.bindings.insert(names[0].clone(), kind);
}
if let Some(Expr::StructLit { ty: Some(t), .. }) = values.first() {
// `Parenthesised` is semantically transparent, so `x := (T{…})`
// registers exactly the type `x := T{…}` registers. Without the
// peel the struct type never reaches `var_struct`, `stats.mu`
// then has no shape, and the field call emits Go's lowercase
// spelling (`stats.mu.lock()`) — accepted by `hyb check`, refused
// by `go build` (rule BUILDABLE).
if let Some(Expr::StructLit { ty: Some(t), .. }) = values.first().map(Self::peel_parens)
{
self.register_var_ty(&names[0], t);
}
}
Expand Down
52 changes: 52 additions & 0 deletions compiler/crates/hy-emit/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,58 @@ fn assign_slot_loan_copies_and_stores_back() {
);
}

#[test]
fn struct_binding_type_registers_through_redundant_parentheses() {
// #607: `stats := (pool_stats{hits: 1})` is the same binding as
// `stats := pool_stats{hits: 1}`. The struct type is what lets the shape
// table resolve `stats.mu` to `sync.mutex` and emit `stats.mu.Lock()`;
// when the parens stopped `register_var_ty`, `mu` had no shape and the
// field call emitted Go's lowercase spelling (`stats.mu.lock()`) — an
// accepted program that `go build` rejects (rule BUILDABLE).
for init in ["pool_stats{hits: 1}", "(pool_stats{hits: 1})"] {
let go = emit(&format!(
"import \"sync\"\n\nstruct pool_stats {{\n mu sync.mutex\n hits int\n}}\n\nfn main() {{\n stats := {}\n stats.mu.lock()\n stats.hits = 2\n stats.mu.unlock()\n fmt.println(\"hits: {{stats.hits}}\")\n}}\n",
init
));
assert!(
go.contains("stats.mu.Lock()") && go.contains("stats.mu.Unlock()"),
"`stats := {}` must still map the field call to Go's exported name:\n{}",
init,
go
);
assert!(
!go.contains(".lock()") && !go.contains(".unlock()"),
"`stats := {}` must not emit an unexported Go method:\n{}",
init,
go
);
}
}

#[test]
fn foreign_binding_kind_survives_redundant_parentheses() {
// #607 reproducer 1 — already green on main once `binding_kind_of` peeled
// at entry; pinned here so the two legs of the same rule stay covered.
for init in ["http.new_serve_mux()", "(http.new_serve_mux())"] {
let go = emit(&format!(
"import \"net/http\"\n\nfn main() {{\n mux := {}\n mux.handle(\"/ping\", http.handler(fn(w http.response_writer, r *http.request) {{\n fmt.fprintln(w, \"pong\")\n }}))\n}}\n",
init
));
assert!(
go.contains("mux.Handle("),
"`mux := {}` must still map `handle` -> `Handle`:\n{}",
init,
go
);
assert!(
!go.contains(".handle("),
"`mux := {}` must not emit an unexported Go method:\n{}",
init,
go
);
}
}

#[test]
fn assign_slot_loan_into_pointer_var_points_at_copy() {
// A pointer-typed target cannot hold the copied element itself
Expand Down
Loading