Skip to content

Implement vector/tuple pattern binding (issue #17)#80

Merged
ecto merged 1 commit into
mainfrom
claude/nifty-galileo-5c2179
Jul 7, 2026
Merged

Implement vector/tuple pattern binding (issue #17)#80
ecto merged 1 commit into
mainfrom
claude/nifty-galileo-5c2179

Conversation

@ecto

@ecto ecto commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

Fixes issue #17 (P0): vector/tuple patterns silently didn't bind on the default EIR VM backend. [match v #[a b] ...] never matched and [let [x y] v] bound nothing — both prints in the repro returned (). The checker also rejected the pattern variables as unbound (E0201), so it didn't understand vector patterns either.

Now the repro prints 3 and 15 on both loon run and --legacy, and loon check passes it cleanly.

Semantics (DESIGN.md §6, new ruling)

No prior ruling existed, so I added one:

  • Match #[a b]: matches a vector or tuple of exactly that length (Rust slice-pattern prior); element subpatterns match recursively; non-match falls through.
  • Destructuring let [let [x y] v]: requires a vector/tuple with at least as many elements as binders (extra allowed, Clojure prior). Too short or a non-sequence value is a loud runtime error on both backends (destructuring expected at least N elements, got M / destructuring requires a vector or tuple) — never a silent () bind, per the agent-first rule that silent-wrong is the worst failure mode.

Changes

  • Checker (check/mod.rs): bind_pattern_vars handles Vec/Tuple patterns and binds variables with inferred element types (Vec elem type, positional tuple types); infer_let destructures via new bind_destructure_vars; map-destructure names are bound so they no longer E0201.
  • EIR (lower.rs, vm.rs, mod.rs): new internal builtins SeqLen and DestructureCheck (new VmErrorKind::DestructureMismatch); Op::Field with Selector::Index now works on vectors; pattern test/bind and the lower_let TODO replaced with recursive destructuring.
  • Legacy interp (interp/mod.rs): pattern_matches gets the Vec/Tuple arm (previously fell through to no-match, as suspected); bind_param errors on short destructures; #[x y] accepted as a let binding. The interp/machine VM shares these helpers.

Tests

  • 11 new tests in interp_tests.rs (both backends via shared helpers)
  • 9 new backend-parity corpus entries + destructure_mismatch_raises_on_both_backends
  • 2 prior-alignment corpus entries (Clojure destructure, Rust slice pattern)
  • cargo test --workspace passes; cargo fmt run

Reviewer notes

  1. Match arms now genuinely typecheck (they bind real element types), so a match with heterogeneous arm bodies gets a legitimate E0200 from loon check — this is correct, not a regression, and doesn't block loon run.
  2. Map destructuring ([let {a b} m]) still silently binds nothing on the EIR VM — same failure class, out of scope here, flagged as a follow-up.

🤖 Generated with Claude Code

Vector/tuple patterns silently produced () on the default EIR VM backend:
`[match v #[a b] ...]` never matched and `[let [x y] v]` bound nothing. The
checker also rejected the pattern variables as unbound (E0201). Fixed across
all three layers.

- Checker: bind_pattern_vars now handles Vec/Tuple patterns and binds
  variables with inferred element types (Vec elem type, positional tuple
  types); infer_let destructures via new bind_destructure_vars, and map
  destructuring names are bound so they no longer E0201.
- EIR: new internal builtins SeqLen (length-or-minus-one test) and
  DestructureCheck (loud guard, new VmErrorKind::DestructureMismatch);
  Op::Field with Selector::Index now works on vectors; match pattern
  test/bind and the lower_let TODO replaced with recursive destructuring.
- Legacy interp: pattern_matches gets the Vec/Tuple arm (previously fell
  through to no-match); bind_param errors on short destructures instead of
  silently binding (); #[x y] accepted as a let binding.

Semantics (DESIGN.md §6): match `#[a b]` requires exact length (Rust slice
prior); destructuring `let` requires at-least length, extra elements allowed
(Clojure prior). Too short or non-sequence is a loud runtime error on every
backend — never a silent () bind, per the agent-first rule.

Tests: 11 interp_tests (both backends), 9 backend-parity corpus entries plus
destructure_mismatch_raises_on_both_backends, 2 prior-alignment corpus
entries. cargo test --workspace passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
loon Ready Ready Preview, Comment Jul 7, 2026 5:18am

Request Review

@ecto ecto merged commit 7b417dd into main Jul 7, 2026
6 checks passed
@chojiai

chojiai Bot commented Jul 7, 2026

Copy link
Copy Markdown

What shipped

Vector and tuple patterns now work correctly in both match expressions and destructuring assignments.

Previously, writing [match v #[a b] ...] or [let [x y] v] silently did nothing on the default backend — patterns never matched and variables were never bound, with no error to indicate anything had gone wrong. The type checker also incorrectly flagged the bound variables as undefined.

  • Match patterns like #[a b] now match a vector or tuple of exactly that length and bind the elements, with non-matching lengths falling through to the next arm as expected.
  • Destructuring assignments like [let [x y] v] now correctly bind each element; extra elements in the value are allowed.
  • Passing a value that is too short or not a sequence at all now raises a clear error ("destructuring expected at least N elements, got M") instead of silently producing wrong results.

Plain-English summary generated by Choji from this pull request.

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.

1 participant