Skip to content

feat: MAT001 — non-exhaustive Result match (?bs 0.9)#79

Open
marcelofarias wants to merge 6 commits into
mainfrom
botkowski/mat001-result-exhaustive
Open

feat: MAT001 — non-exhaustive Result match (?bs 0.9)#79
marcelofarias wants to merge 6 commits into
mainfrom
botkowski/mat001-result-exhaustive

Conversation

@marcelofarias
Copy link
Copy Markdown
Owner

Summary

  • Adds MAT001: from ?bs 0.9, fires when a match expression explicitly handles ok or err but omits the opposing tag without a wildcard _ arm.
  • Closes the second half of the result-contract loop: UNS005 enforces that stdlib calls are wrapped in match; MAT001 enforces that the match is exhaustive.
  • The check is heuristic and scoped to the ok/err tag vocabulary — user-defined tagged unions are unaffected.

Closes #78.

How it works

After passMatch has parsed the source, passMatCheck scans all match expressions:

  • If any arm has a wildcard pattern → skip (exhaustive by wildcard).
  • If arms contain ok or err tag patterns but not both → MAT001 with a rewrite hint.
  • Anchored at the match keyword token, consistent with other pass diagnostics.

Test plan

  • pnpm -r build && pnpm test — 596/596 pass (9 new tests)
  • match with ok arm, no err arm → MAT001
  • match with err arm, no ok arm → MAT001
  • match await ... with only ok → MAT001
  • Both ok and err arms → clean
  • ok arm + wildcard _ arm → clean
  • err arm + wildcard _ arm → clean
  • Match with no ok/err tags (literal or user-defined) → clean
  • Below ?bs 0.9 → no MAT001
  • bs explain MAT001 returns rule/idiom/rewrite

🤖 Generated with Claude Code

Fires when a match explicitly handles ok or err but omits the opposing
tag without a wildcard arm. Closes the second half of the result-contract
loop: UNS005 enforces that stdlib calls are wrapped in match; MAT001
enforces that the match is exhaustive.

Closes #78.

Co-Authored-By: Botkowski <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new compiler diagnostic MAT001 (gated behind ?bs 0.9) to detect non-exhaustive match expressions that handle only one of ok/err without a wildcard arm, and wires the new rule through compiler + MCP “explain” surfaces.

Changes:

  • Introduces passMatCheck to scan match expressions and throw MAT001 when ok/err coverage is incomplete (no _ arm).
  • Registers MAT001 in the compiler error-code registry and MCP explanation registry, plus updates MCP known-codes test coverage.
  • Adds a dedicated compiler test suite for MAT001 behavior across positive/negative cases and version gating.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/compiler/src/passes/mat-check.ts New MAT001 compiler pass that detects non-exhaustive Result-style matches.
packages/compiler/src/transform.ts Adds matCheck into the compiler pass pipeline for ?bs 0.9+.
packages/compiler/src/error-codes.ts Registers MAT001 metadata (rule/idiom/rewrite/example) for diagnostics and bs explain.
packages/compiler/tests/mat-check.test.ts Adds tests validating MAT001 triggering, suppression, and version gating.
packages/mcp/src/explanations.ts Adds MCP “explain” entry for MAT001 (long-form documentation + examples).
packages/mcp/tests/server.test.ts Updates known diagnostic code list to include MAT001.
CHANGELOG.md Documents MAT001 addition under ?bs 0.9 “Added”.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to +67
throw new BotscriptError([{
code: "MAT001",
severity: "error",
file: null,
line,
column,
start: matchStart,
end: tokens[expr.end - 1]?.end ?? matchStart,
message: `match on Result is missing '${missing}' arm — add '${missing} { ... } -> ...' or a wildcard '_ -> ...' arm`,
rule: entry.rule,
idiom: entry.idiom,
rewrite: entry.rewrite,
Comment thread packages/compiler/src/error-codes.ts Outdated
"prefer explicit `ok` and `err` arms over a wildcard when the error type carries useful context — " +
"a wildcard silently discards the payload",
rewrite:
"add 'err { e } -> ...' arm or '_ -> ...' wildcard",
Comment on lines +458 to +465
"**Suppression mechanisms (in order of preference):**\n\n" +
"1. **Explicit err arm** — handle the error case directly:\n" +
" ```\n match http.get(url) {\n ok { value } -> ok(value.body)\n err { e } -> err(e.message)\n }\n ```\n\n" +
"2. **Wildcard arm** — use `_` when you want to coerce or ignore the missing case:\n" +
" ```\n match http.get(url) {\n ok { value } -> ok(value.body)\n _ -> err(\"request failed\")\n }\n ```\n\n" +
"The check is scoped to the `ok`/`err` tag vocabulary — it fires only when at least one " +
"of those tags is explicitly named in an arm. User-defined tagged unions with different " +
"tag names are not affected.",
The rewrite field was hardcoded to suggest adding an err arm, which was
misleading when the ok arm was the missing one. Now constructs the hint
dynamically from the missing tag, and updates the registry entry and MCP
explanation to cover both missing-ok and missing-err cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

*/

import { describe, expect, it } from "vitest";
import { transform, BotscriptError } from "../src/transform.js";
line,
column,
start: matchStart,
end: tokens[expr.end - 1]?.end ?? matchStart,
… BotscriptError import

Copilot review (round 2) flagged two issues:
- end span covered the full match block; now anchors at the match keyword token end,
  consistent with how thr-check and bare-as anchor their diagnostics
- BotscriptError was imported from transform.js (which doesn't export it), causing
  a potential ESM load-time crash; removed since it was unused

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment on lines +78 to +86
it("does not fire when a wildcard arm is present (no ok)", () => {
const src =
"?bs 0.9\n" +
"fn fetchData(url: string) uses { net } -> Result<string, string> {\n" +
" match http.get(url) {\n" +
" ok { value } -> ok(value)\n" +
" _ -> err(\"failed\")\n" +
" }\n" +
"}\n";
Comment on lines +90 to +98
it("does not fire when a wildcard arm is present (no err)", () => {
const src =
"?bs 0.9\n" +
"fn fetchData(url: string) uses { net } -> string {\n" +
" match http.get(url) {\n" +
" err { e } -> e\n" +
" _ -> \"default\"\n" +
" }\n" +
"}\n";
…n cases

Test names said "(no ok)" and "(no err)" but had the logic backwards — each
test includes one of the ok/err arms; the wildcard covers the *other*.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines +445 to +449
MAT001: {
code: "MAT001",
title: "non-exhaustive match on Result — missing ok or err arm",
body:
"From `?bs 0.9`, a `match` expression that explicitly handles the `ok` or `err` tag " +
Copilot review correctly flagged that MAT001 was missing from both the
AGENTS.md diagnostic codes table and the README MCP `explain` tool code
list. Added the row and updated the explain description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

- Make suppression examples in MCP MAT001 explanation symmetric — step 1
  now shows adding the missing arm for both the err-absent and ok-absent cases
- 596/596 tests pass (previously failing due to stale dist — rebuild resolves it)
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.

RFC: MAT001 — non-exhaustive Result match (missing ok or err arm)

2 participants