Skip to content

Commit ca0e74e

Browse files
feat(parser): fn-type with effect arrow in type position (Refs gitbot-fleet#148) (#371)
## Summary Adds \`fn(A, B) -{E}-> C\` as a type-position production in \`type_expr_primary\`, mirroring the prefix-row arrow that \`type_expr_arrow\` and \`return_type\` already accept. For multi-arg \`fn\`, the row attaches to the **innermost** arrow — that is where the call actually performs the effect, and it matches the single-arg case where \`A -{E}-> R\` puts the row on the lone arrow. Lowering: \`\`\` fn(A, B) -{E}-> R ≡ A -> (B -{E}-> R) \`\`\` ## Why Required by sustainabot hand-port (gitbot-fleet#148) — \`Router.affine\` declares \`fn(Http::Request) -{IO}-> Http::Response\` and similar shapes; the prefix-row form is the natural ergonomic for "a function that performs effect E". ## Conflict-cost Zero. The \`FN LPAREN ... RPAREN\` prefix already disambiguates against every other type-position production, so adding the \`MINUS LBRACE eff RBRACE ARROW\` continuation here introduces no new lookahead conflict beyond the existing \`type_expr_arrow\` row-arrow rule it mirrors. Parser builds with **21 S/R + 1 R/R**, identical to the pre-patch baseline. ## Test plan - [x] \`dune build\` green - [x] Conflict count unchanged: 21 S/R + 1 R/R - [ ] CI green - [ ] Smoke: \`fn handle: fn(Http::Request) -{IO}-> Http::Response;\` parses ## Companion PRs (gitbot-fleet#148 spine) 1. trailing-comma in fn params + expr lists (#370) 2. **this PR** — fn-type with effect arrow in type position 3. builtin/lowercase qualified paths + TOTAL field name 4. lexer \`_\`-prefix idents 5. (hypatia) Levenshtein perf fix 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 44b5341 + 12cd42f commit ca0e74e

1 file changed

Lines changed: 28 additions & 0 deletions

File tree

lib/parser.mly

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,34 @@ type_expr_primary:
544544
{ match params with
545545
| [] -> TyArrow (TyTuple [], None, ret, None)
546546
| _ -> List.fold_right (fun p acc -> TyArrow (p, None, acc, None)) params ret }
547+
/* Effect-row variant: `fn(A, B) -{E}-> C`. Mirrors the prefix-row arrow
548+
already accepted in `type_expr_arrow` and in `return_type`, and is
549+
required by hand-ports such as `fn(Http::Request) -{IO}-> Http::Response`
550+
(gitbot-fleet#148 Router.affine). For multi-arg `fn`, the row attaches
551+
to the *final* (innermost) arrowthat is where the call actually
552+
performs the effect, and it matches the single-arg case where
553+
`A -{E}-> R` puts the row on the lone arrow. Lowering:
554+
`fn(A, B) -{E}-> R` ≡ `A -> (B -{E}-> R)`. Grammar-cost: the
555+
`FN LPAREN ... RPAREN` prefix already disambiguates against every
556+
other type-position production, so adding the `MINUS LBRACE eff
557+
RBRACE ARROW` continuation here introduces no new lookahead conflict
558+
beyond the existing `type_expr_arrow` row-arrow rule it mirrors. */
559+
| FN LPAREN params = separated_list(COMMA, type_expr) RPAREN
560+
MINUS LBRACE eff = effect_expr RBRACE ARROW
561+
ret = type_expr_arrow
562+
{ match params with
563+
| [] -> TyArrow (TyTuple [], None, ret, Some eff)
564+
| _ ->
565+
(* Attach eff to the innermost arrow (the one whose result is ret). *)
566+
let rev_params = List.rev params in
567+
(match rev_params with
568+
| [] -> assert false
569+
| last :: earlier_rev ->
570+
let innermost = TyArrow (last, None, ret, Some eff) in
571+
List.fold_left
572+
(fun acc p -> TyArrow (p, None, acc, None))
573+
innermost
574+
earlier_rev) }
547575
/* Row-polymorphic record type. We use a custom recursive rule rather than
548576
`separated_list` because Menhir's separated_list greedily consumes the
549577
COMMA separator and then cannot backtrack when the next token (ROW_VAR)

0 commit comments

Comments
 (0)