Skip to content
Merged
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
28 changes: 28 additions & 0 deletions lib/parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,24 @@ fn_decl:
return_type:
| ARROW ty = type_expr { (Some ty, None) }
| MINUS LBRACE eff = effect_expr RBRACE ARROW ty = type_expr { (Some ty, Some eff) }
/* `-> T / E` — the ADR-008 canonical effect-row return annotation
(issue #135 slice 3). This was the *settled* surface
(SETTLED-DECISIONS ADR-008) yet entirely absent from the grammar; the
whole stdlib effect layer uses it (`extern fn print(s: String) ->
Unit / io;` etc.) and even a normal `fn ... -> T / io` could not be
written. Single `effect_term` covers 100% of stdlib usage; multi-
effect rows remain expressible via the existing `-{ E1 + E2 }->`
form. Grammar-cost note: adding any `ARROW type_expr SLASH _`
production to `return_type` adds exactly one arbitrarily-resolved
reduce/reduce item to an already-r/r state (35 -> 36; the 5 r/r
states are unchanged). This is irreducible without a grammar-wide
restructure of return/effect parsing, is in this grammar's existing
permissive-ambiguity class, and per ADR-009 ("conformance suite is
authoritative; the parser conforms to the spec, validated by tests")
is accepted: behaviour is verified — `-> T / io` parses, division
`a / b`, `-{ E }->`, and braced `effect E {}` are all unaffected,
full suite green. ADR-008 mandates this syntax; it is not sugar. */
| ARROW ty = type_expr SLASH eff = effect_term { (Some ty, Some eff) }

fn_body:
| blk = block { FnBlock blk }
Expand Down Expand Up @@ -489,6 +507,16 @@ effect_decl:
ed_name = name;
ed_type_params = Option.value type_params ~default:[];
ed_ops = ops } }
/* Bare forward-declaration form `effect <name>;` (issue #135 slice 3).
stdlib/effects.affine declares `effect io;` / `effect state;` etc. and
supplies the operations separately as `extern fn ... / io;`. An empty
op list is the right model: the effect is a named row label whose ops
live in externs. */
| vis = visibility? EFFECT name = ident type_params = type_params? SEMICOLON
{ { ed_vis = Option.value vis ~default:Private;
ed_name = name;
ed_type_params = Option.value type_params ~default:[];
ed_ops = [] } }

effect_op_decl:
(* Type parameters on effect operations are allowed: `fn await[T](promise: Promise[T]) -> T;` *)
Expand Down
12 changes: 12 additions & 0 deletions test/test_e2e.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3331,6 +3331,17 @@ let test_slice_index_not_regressed () =
(parse_check_passes
{|fn idx(xs: [Int]) -> Int { return xs[0]; }|})

(* Issue #135 slice 3: bare `effect E;` declaration + the ADR-008 canonical
`-> T / E1, E2` effect-row return annotation (was settled but entirely
absent from the grammar; the whole stdlib effects/io layer uses it). *)
let test_bare_effect_and_effect_row () =
Alcotest.(check bool) "effect E; + extern -> T / E + fn -> T / E" true
(parse_check_passes
{|effect io;
extern fn write(s: String) -> Unit / io;
fn q() -> Int / io { return 0; }
fn plain() -> Int { return 1; }|})

let test_multi_arg_arrow () =
Alcotest.(check bool) "(A, B) -> C parses + typechecks" true
(parse_check_passes
Expand Down Expand Up @@ -3383,6 +3394,7 @@ let type_syntax_sugar_tests = [
Alcotest.test_case "fn(x:Int) -> Int { } (#135 fn-lambda)" `Quick test_fn_lambda_typed_block;
Alcotest.test_case "xs[a:b]/[a:]/[:b]/[:] (#135 slice 2)" `Quick test_slice_full_range;
Alcotest.test_case "xs[0] index non-regressed (#135 sl.2)" `Quick test_slice_index_not_regressed;
Alcotest.test_case "effect E; + -> T / E (#135 slice 3)" `Quick test_bare_effect_and_effect_row;
Alcotest.test_case "(A, B) -> C (multi-arg arrow)" `Quick test_multi_arg_arrow;
Alcotest.test_case "(A, B) without arrow remains tuple" `Quick test_tuple_type_still_works;
]
Expand Down
Loading