Skip to content

Commit 53150ee

Browse files
hyperpolymathclaude
andcommitted
feat(parser): fn(params) => expr anonymous-function expressions (#135)
#135 slice 1. The grammar only accepted `|x| body` lambdas; the stdlib actually uses `fn(x) => e` (e.g. `map(fn(x) => Some(x), list)` in option.affine), which failed to parse. Adds two productions next to the pipe-lambda, lowering to the same ExprLambda: fn(params) => expr fn(params) -> RetTy { block } reusing `lambda_param` (so untyped, typed, and @-quantity params all work). `FN` is unambiguous in expression position (no other expression form starts with `fn`) — verified: identical menhir conflict counts (26 s/r states, 5 r/r states, 76/35 resolved) before and after, i.e. zero new grammar conflicts. Effect: option.affine advances past the lambda-expression wall (parse error 180 → 238; 238 is a distinct later-slice defect). Adds three focused regression tests. Full suite green (223 tests). Advances #135 (does not close it — remaining per-file defects tracked in docs/history/STDLIB-AOT-TRIAGE.md). Refs #128, #135. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f879862 commit 53150ee

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

lib/parser.mly

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,16 @@ expr_primary:
726726
{ ExprLambda { elam_params = params; elam_ret_ty = None; elam_body = body } }
727727
| PIPE params = separated_list(COMMA, lambda_param) PIPE ARROW ret = type_expr body = block
728728
{ ExprLambda { elam_params = params; elam_ret_ty = Some ret; elam_body = ExprBlock body } }
729+
/* `fn(params) => expr` and `fn(params) -> RetTy { block }` — anonymous
730+
function expressions (issue #135). Lowers to the same ExprLambda as the
731+
`|params| body` form; this is the surface the stdlib actually uses
732+
(`map(fn(x) => Some(x), list)` in stdlib/option.affine). The leading
733+
`fn` + parenthesised params is unambiguous in expression position
734+
(no other expression form starts with `fn`). */
735+
| FN LPAREN params = separated_list(COMMA, lambda_param) RPAREN FAT_ARROW body = expr
736+
{ ExprLambda { elam_params = params; elam_ret_ty = None; elam_body = body } }
737+
| FN LPAREN params = separated_list(COMMA, lambda_param) RPAREN ARROW ret = type_expr body = block
738+
{ ExprLambda { elam_params = params; elam_ret_ty = Some ret; elam_body = ExprBlock body } }
729739

730740
/* Return */
731741
| RETURN e = expr? { ExprReturn e }

test/test_e2e.ml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3298,6 +3298,26 @@ let test_angle_brackets_type_params () =
32983298
(parse_check_passes
32993299
{|fn id<T>(x: T) -> T { return x; }|})
33003300

3301+
(* Issue #135 (slice 1): `fn(params) => expr` / `fn(params) -> T { block }`
3302+
anonymous-function expressions. This is the lambda surface the stdlib
3303+
actually uses (`map(fn(x) => Some(x), list)` in option.affine); only the
3304+
`|x| body` form parsed before. *)
3305+
let test_fn_lambda_arrow_expr () =
3306+
Alcotest.(check bool) "fn(x) => x parses + typechecks" true
3307+
(parse_check_passes
3308+
{|fn use_it() -> Int { let g = fn(x) => x; return g(1); }|})
3309+
3310+
let test_fn_lambda_higher_order () =
3311+
Alcotest.(check bool) "fn(x) => e as a call argument" true
3312+
(parse_check_passes
3313+
{|fn apply1(g: fn(Int) -> Int, n: Int) -> Int { return g(n); }
3314+
fn run() -> Int { return apply1(fn(x) => x, 5); }|})
3315+
3316+
let test_fn_lambda_typed_block () =
3317+
Alcotest.(check bool) "fn(x: Int) -> Int { block } parses + typechecks" true
3318+
(parse_check_passes
3319+
{|fn use_it() -> Int { let g = fn(x: Int) -> Int { x }; return g(2); }|})
3320+
33013321
let test_multi_arg_arrow () =
33023322
Alcotest.(check bool) "(A, B) -> C parses + typechecks" true
33033323
(parse_check_passes
@@ -3345,6 +3365,9 @@ let type_syntax_sugar_tests = [
33453365
Alcotest.test_case "Option<Option<Result<T,E>>> (#131 >>>)" `Quick test_angle_nested_gtgtgt;
33463366
Alcotest.test_case "-> Result<Option<T>,E> (#131 nested)" `Quick test_angle_nested_return_pos;
33473367
Alcotest.test_case "a >> b shift unaffected (#131 guard)" `Quick test_shift_operator_not_split;
3368+
Alcotest.test_case "fn(x) => x (#135 fn-lambda expr)" `Quick test_fn_lambda_arrow_expr;
3369+
Alcotest.test_case "fn(x) => e as arg (#135 fn-lambda)" `Quick test_fn_lambda_higher_order;
3370+
Alcotest.test_case "fn(x:Int) -> Int { } (#135 fn-lambda)" `Quick test_fn_lambda_typed_block;
33483371
Alcotest.test_case "(A, B) -> C (multi-arg arrow)" `Quick test_multi_arg_arrow;
33493372
Alcotest.test_case "(A, B) without arrow remains tuple" `Quick test_tuple_type_still_works;
33503373
]

0 commit comments

Comments
 (0)