Skip to content

Commit eede831

Browse files
hyperpolymathclaude
andcommitted
feat(parser): trait method default bodies (left-factored) (#135 slice 5)
#135 slice 5. `trait_item` had `fn_sig SEMICOLON` (TraitFn) and a whole `fn_decl` (TraitFnDefault) as separate productions; they share the long prefix `visibility? FN name params return_type?`, the LR conflict resolved toward the signature form, so a trait method with a default body — `pub fn ne(ref self, ref other: Self) -> Bool { ... }` in stdlib/traits.affine — failed at the `{`. Fix by left-factoring: parse `fn_sig` once, then branch on SEMICOLON (→ TraitFn) vs `fn_body` (→ TraitFnDefault built from the sig + body). The two trait-method forms now differ purely on the next token — the shared-prefix ambiguity is removed, not papered over. (`ref self`, sig-only methods, and associated types already worked and are verified non-regressed.) Trait defaults don't use `total`/`where` (none in stdlib); those default to false/[]. Effect: traits.affine advances 12 → 124 (cleared 100+ lines; next blocker is `while let` / `Vec::new()`, a distinct slice-4-class construct). Two regression tests; full suite green (228). Advances #135. Refs #128, #135. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a7fdb38 commit eede831

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

lib/parser.mly

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,25 @@ supertraits:
552552

553553
trait_item:
554554
| sig_ = fn_sig SEMICOLON { TraitFn sig_ }
555-
| f = fn_decl { TraitFnDefault f }
555+
/* Trait method *default body* (issue #135 slice 5). Previously the two
556+
forms were `fn_sig SEMICOLON` vs a whole `fn_decl`, which share the
557+
long prefix `visibility? FN name params return_type?`; the LR conflict
558+
resolved toward the signature form, so `pub fn ne(ref self, ...) ->
559+
Bool { ... }` (stdlib/traits.affine) failed at the `{`. Left-factor:
560+
parse `fn_sig` once, then branch on SEMICOLON vs fn_bodyno shared-
561+
prefix ambiguity (the two now differ purely on the next token).
562+
Trait defaults don't use `total`/`where` (none in stdlib); fd_total
563+
= false, fd_where = []. */
564+
| sig_ = fn_sig body = fn_body
565+
{ TraitFnDefault { fd_vis = sig_.fs_vis;
566+
fd_total = false;
567+
fd_name = sig_.fs_name;
568+
fd_type_params = sig_.fs_type_params;
569+
fd_params = sig_.fs_params;
570+
fd_ret_ty = sig_.fs_ret_ty;
571+
fd_eff = sig_.fs_eff;
572+
fd_where = [];
573+
fd_body = body } }
556574
| TYPE name = ident kind = kind_annotation? default = type_default? SEMICOLON
557575
{ TraitType { tt_name = name; tt_kind = kind; tt_default = default } }
558576

test/test_e2e.ml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3342,6 +3342,27 @@ let test_bare_effect_and_effect_row () =
33423342
fn q() -> Int / io { return 0; }
33433343
fn plain() -> Int { return 1; }|})
33443344

3345+
(* Issue #135 slice 5: trait method *default body* (left-factored vs the
3346+
signature form so the shared prefix no longer mis-resolves toward the
3347+
`;` form). `ref self` receiver + sig-only + assoc type unaffected. *)
3348+
let test_trait_default_body () =
3349+
Alcotest.(check bool) "trait fn with default body + ref self" true
3350+
(parse_check_passes
3351+
{|trait Eq {
3352+
pub fn eq(ref self, ref other: Self) -> Bool;
3353+
pub fn ne(ref self, ref other: Self) -> Bool {
3354+
return !self.eq(other);
3355+
}
3356+
}|})
3357+
3358+
let test_trait_sig_and_assoc_not_regressed () =
3359+
Alcotest.(check bool) "sig-only trait fn + associated type still parse" true
3360+
(parse_check_passes
3361+
{|trait Iter {
3362+
type Item;
3363+
pub fn next(mut self) -> Option<Int>;
3364+
}|})
3365+
33453366
let test_multi_arg_arrow () =
33463367
Alcotest.(check bool) "(A, B) -> C parses + typechecks" true
33473368
(parse_check_passes
@@ -3395,6 +3416,8 @@ let type_syntax_sugar_tests = [
33953416
Alcotest.test_case "xs[a:b]/[a:]/[:b]/[:] (#135 slice 2)" `Quick test_slice_full_range;
33963417
Alcotest.test_case "xs[0] index non-regressed (#135 sl.2)" `Quick test_slice_index_not_regressed;
33973418
Alcotest.test_case "effect E; + -> T / E (#135 slice 3)" `Quick test_bare_effect_and_effect_row;
3419+
Alcotest.test_case "trait default body + ref self (#135 sl5)" `Quick test_trait_default_body;
3420+
Alcotest.test_case "trait sig + assoc non-regressed (#135 sl5)" `Quick test_trait_sig_and_assoc_not_regressed;
33983421
Alcotest.test_case "(A, B) -> C (multi-arg arrow)" `Quick test_multi_arg_arrow;
33993422
Alcotest.test_case "(A, B) without arrow remains tuple" `Quick test_tuple_type_still_works;
34003423
]

0 commit comments

Comments
 (0)