Skip to content

Commit dc41c99

Browse files
hyperpolymathclaude
andcommitted
fix(parser): resolve state-41 effect-row MINUS/ARROW S/R conflict via precedence
First PR of the dedicated grammar-conflict-elimination workstream (epic #215), family A (effect-row MINUS overload), owner-prioritised first. In `type_expr_arrow`, after a `type_expr_primary`, lookahead `ARROW` (continue an arrow type) or `MINUS` (open the legacy `-{E}->` effect row, parser.mly:410) raced against reducing the base case `type_expr_arrow -> type_expr_primary` (state 41, tokens MINUS ARROW). Shifting is *always* the correct choice (a bare type can never both be followed by `->`/`-{` and want to stop there), and was already Menhir's arbitrary resolution — so making it explicit changes no parse. Fix: a lowest-precedence pseudo-token `LOWEST_TYPE_ARROW` (never lexed) tags the base reduction; `ARROW` is given a precedence just above it. `MINUS` already outranks both, so on either lookahead the parser shifts *by declared precedence* instead of by an arbitrary tie-break, and the conflict disappears. `ARROW` is a conflict lookahead ONLY in state 41 (verified via `menhir --explain`), so giving it precedence is side-effect-free. Rigorous verification (full `menhir --explain` diff, baseline = #214): - S/R arbitrarily resolved: 75 -> 72 - S/R conflict states: 25 -> 23 (state 41 eliminated) - R/R: 10 (untouched); no new conflict states introduced - The 16 pre-existing "precedence/`%prec` never useful" warnings are byte-for-byte identical before and after — NOT introduced here (they are dead precedence declarations; filed as family F on #215). - `dune test --force` green at 257/257 (parse-behaviour-preserving). Refs #215 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4dea918 commit dc41c99

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

lib/parser.mly

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ let rec effect_union_of_list = function
8383
%token EOF
8484

8585
/* Precedence (lowest to highest) */
86+
/* `LOWEST_TYPE_ARROW` is a pseudo-token (never lexed) carrying the
87+
lowest precedence. It tags the base `type_expr_arrow -> type_expr_primary`
88+
reduction so that in state 41 — after a `type_expr_primary`, lookahead
89+
`ARROW` (continue an arrow type) or `MINUS` (open the legacy `-{E}->`
90+
effect row) — the parser SHIFTS rather than reducing the base case.
91+
Shift was already Menhir's (correct) arbitrary resolution there (a
92+
bare type can never be followed by `->`/`-{` *and* want to stop), so
93+
declaring this precedence is parse-behaviour-preserving and only
94+
silences the spurious conflict. `ARROW` has no precedence elsewhere
95+
and is a conflict lookahead ONLY in state 41 (verified via
96+
`menhir --explain`), so giving it precedence here is side-effect-free.
97+
See affinescript#215 (family A). */
98+
%nonassoc LOWEST_TYPE_ARROW
99+
%nonassoc ARROW
86100
%right EQ PLUSEQ MINUSEQ STAREQ SLASHEQ
87101
%left PIPEPIPE
88102
%left AMPAMP
@@ -416,7 +430,7 @@ type_expr_arrow:
416430
past the closing RPAREN. */
417431
| LPAREN ty1 = type_expr COMMA tys = separated_nonempty_list(COMMA, type_expr) RPAREN ARROW ret = type_expr_arrow
418432
{ List.fold_right (fun p acc -> TyArrow (p, None, acc, None)) (ty1 :: tys) ret }
419-
| ty = type_expr_primary { ty }
433+
| ty = type_expr_primary %prec LOWEST_TYPE_ARROW { ty }
420434

421435
type_expr_primary:
422436
| LPAREN RPAREN { TyTuple [] }

0 commit comments

Comments
 (0)