Skip to content

Commit d2578fe

Browse files
hyperpolymathclaude
andcommitted
fix(typecheck): auto-deref ref/own/mut for struct field access (Refs #122)
`fn(c: ref Point) = c.x` failed with "Field 'x' not found in type ref {x: Int, ...}": ExprField unified the receiver type directly with the expected record, so a TRef/TMut/TOwn wrapper made projection fail. Strip TRef/TMut/TOwn (via repr) before record projection — Rust-like auto-deref for field access. The borrow checker still governs aliasing separately; this only affects the *type* of the projected field, which is identical through a reference. Trait-method fallback path unchanged. This unblocks receiver-first methods that take `ref/own/mut self|recv` (the idiomatic, allocation-free form) under the Deno-ESM class synth. Regression: tests/codegen-deno/ref_fields.{affine,harness.mjs} — ref/own/mut receivers synthesised as Point methods, field access verified. `dune runtest` unchanged (same 2 pre-existing E2E Node-CJS vscode failures, 214 tests, zero new regressions). Deno-ESM suite green. Refs #122. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3f39c3f commit d2578fe

3 files changed

Lines changed: 43 additions & 1 deletion

File tree

lib/typecheck.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,10 +749,21 @@ let rec synth (ctx : context) (expr : expr) : ty result =
749749
(* Field access — first try record-field projection, then trait method lookup *)
750750
| ExprField (obj, { name = field; _ }) ->
751751
let* obj_ty = synth ctx obj in
752+
(* Auto-deref reference/owned wrappers before record projection
753+
(issue #122 v2): `c.field` where `c : ref/own/mut S` projects the
754+
field of S — the borrow checker still governs aliasing separately.
755+
Without this, `fn(c: ref S) = c.field` failed with
756+
"Field 'field' not found in type ref {...}". *)
757+
let rec strip_ref t =
758+
match repr t with
759+
| TRef u | TMut u | TOwn u -> strip_ref u
760+
| u -> u
761+
in
762+
let obj_ty_deref = strip_ref obj_ty in
752763
let field_ty = fresh_tyvar ctx.level in
753764
let rest_row = fresh_rowvar ctx.level in
754765
let expected_record = TRecord (RExtend (field, field_ty, rest_row)) in
755-
begin match Unify.unify (repr obj_ty) expected_record with
766+
begin match Unify.unify (repr obj_ty_deref) expected_record with
756767
| Ok () -> Ok field_ty
757768
| Error _ ->
758769
(* Record projection failed — try trait method dispatch.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// issue #122 v2.3 regression: struct field access through ref/own/mut
3+
// receivers must typecheck (auto-deref before record projection).
4+
// Previously `fn(c: ref S) = c.field` failed: "Field 'field' not found
5+
// in type ref {...}".
6+
7+
struct Point { x: Int, y: Int }
8+
9+
pub fn mk_point(x: Int, y: Int) -> Point = Point { x: x, y: y };
10+
11+
pub fn sum_ref(p: ref Point) -> Int = p.x + p.y;
12+
13+
pub fn get_x_own(p: own Point) -> Int = p.x;
14+
15+
pub fn get_y_mut(p: mut Point) -> Int = p.y;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// issue #122 v2.3 — field access through ref/own/mut receivers.
3+
// mk_point returns Point (constructor); sum_ref/get_x_own/get_y_mut
4+
// take Point first (ref/own/mut) so they synthesise as Point methods —
5+
// exercising auto-deref field access *and* class synthesis together.
6+
import assert from "node:assert/strict";
7+
import { Point } from "./ref_fields.deno.js";
8+
9+
const p = new Point(3, 4);
10+
assert.equal(p.x, 3, "constructor assigns x");
11+
assert.equal(p.y, 4, "constructor assigns y");
12+
assert.equal(await p.sum_ref(), 7, "field access through ref receiver");
13+
assert.equal(await p.get_x_own(), 3, "field access through own receiver");
14+
assert.equal(await p.get_y_mut(), 4, "field access through mut receiver");
15+
16+
console.log("ref_fields.harness.mjs OK");

0 commit comments

Comments
 (0)