Skip to content

feat: label with fallthrough default value (#6163)#6174

Draft
ggreif wants to merge 3 commits into
masterfrom
gabor/label-defaults
Draft

feat: label with fallthrough default value (#6163)#6174
ggreif wants to merge 3 commits into
masterfrom
gabor/label-defaults

Conversation

@ggreif

@ggreif ggreif commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Resolves #6163 — a labeled early-exit with a fallthrough default, so

label found : Bool = false
  for (c in values.vals()) { if (cmp(v, #eq, c)) break found true }

replaces the mechanical

label found : Bool do {
  for (c in values.vals()) { if (cmp(v, #eq, c)) break found true };
  false
}

Desugaring (parser only)

label x [: T] = <default> <body>label x [: T] do { <body>; <default> }

The default is the value when no break x fires. Being the block's tail it is evaluated only on demand — skipped once a break exits the label, exactly like the else branch of if.

Default operand level

Parsed at exp_nullary (literals, identifiers, parenthesised expressions). A higher level juxtaposed with the body is LR-ambiguous (unary/binary operator overlap — exp_bin/exp_un both reintroduce menhir conflicts). Complex defaults are written parenthesised: (#err "not found"), (?x).

Typing: which forms work

The label's type still comes from the annotation T (the body — hence the default and any break value — is checked against it). There is no inference of the label type from the body, so:

non-bottom default (false) bottom default (return …/trap, None)
annotated label x : T = D … ✅ (None <: T)
compact label x = D … ❌ M0050 (Bool vs ()) ✅ (None <: ())

Without an annotation the label type is (), so the compact form only typechecks when the default fits () (a bottom default). The compact-with-non-bottom form (type inferred from the default, as floated in the issue) would need a typechecker change and is not part of this parser-only PR.

Tests

  • test/run/label-default.mo — annotated × {non-bottom, bottom} and compact × bottom, plus a check that the default is evaluated lazily. Passes [tc] [run] [run-ir] [run-low] [comp] [valid] [wasm-run].
  • test/fail/label-default-compact.mo — pins compact + non-bottom as a type error (M0050/M0096).

printers.ml handles the menhir-generated optional-default nonterminal in syntax-error messages.

🤖 Generated with Claude Code

Desugar `label x [: T] = <default> <body>` to
`label x [: T] do { <body>; <default> }`.  The default is the value when no
`break x` fires; being the block's tail it is evaluated only on demand, like
the `else` branch of `if`.  Replaces the mechanical `do { <body>; <default> }`
boilerplate.

The default operand is parsed at `exp_nullary` (literals, identifiers,
parenthesised expressions) — a higher level juxtaposed with the body is
LR-ambiguous (unary/binary operator overlap).  Complex defaults such as
`#err "x"` or `?x` are written parenthesised.

The label's type still comes from the annotation T (the body, hence the
default and any `break` value, is checked against it).  So:
  - `label x : T = <default> ...`  works for any default (`None <: T`);
  - `label x = <default> ...`      (compact, no annotation) gives the label
    type (), so it typechecks only when the default fits () — i.e. a bottom
    default like `return`/trap, not a non-bottom one.

Tests:
  - test/run/label-default.mo: annotated x {non-bottom, bottom} and
    compact x bottom, plus a check that the default is evaluated lazily.
  - test/fail/label-default-compact.mo: compact + non-bottom default is a
    type error (M0050/M0096).

printers.ml: handle the menhir-generated optional-default nonterminal in
syntax-error messages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ggreif ggreif requested a review from a team as a code owner June 4, 2026 08:13
@ggreif ggreif self-assigned this Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Cursor AI review

👍 APPROVE — looks safe to merge

Category Assessment Details
Summary Adds parser sugar label x [: T] = <default> <body> desugaring to label x [: T] do { <body>; <default> }, with syntax-error printer support and run/fail tests for annotated, compact-bottom, laziness, and compact+non-bottom rejection.
Code Quality Reuses existing LabelE/BlockE desugaring and preceded(EQ, …) pattern; no redundant helpers.
Consistency Matches existing label/for/while rewriting and BlockE/ExpD construction style in parser.mly.
Correctness Desugaring preserves on-demand default evaluation and existing label typing; default operand level avoids documented menhir conflicts.
Tests test/run/label-default.mo covers annotated/compact-bottom/laziness; test/fail/label-default-compact.mo has matching .tc.ok, .tc-human.ok, and .ret.ok files.
Changelog ⚠️ New user-visible syntax has no top-of-file changelog bullet (repo convention places unreleased items before version headers).

Verdict

Decision: APPROVE
Risk: Low
Reason: Parser-only desugar to existing AST forms with thorough tests and no codegen/typechecker changes; missing changelog is a minor documentation gap, not a correctness defect.


Generated for commit 3d2a9c9

@ggreif ggreif marked this pull request as draft June 4, 2026 08:16
@ggreif ggreif changed the title feat(parser): label with fallthrough default value (#6163) feat: label with fallthrough default value (#6163) Jun 4, 2026
The `label` production gained an optional `('=' <exp_nullary>)?` default; update
the committed grammar.txt so the check-grammar CI step matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Comparing from 56ddce8 to b7f2d4d:
In terms of gas, no changes are observed in 5 tests.
In terms of size, no changes are observed in 5 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow label with fallthrough default value

1 participant