Skip to content

🐛 fix: Let judge, expect and refuse run outside meow test - #159

Merged
135yshr merged 1 commit into
mainfrom
claude/next-work-tasks-5b56lt
Sep 26, 2026
Merged

135yshr merged 1 commit into
mainfrom
claude/next-work-tasks-5b56lt

Conversation

@135yshr

@135yshr 135yshr commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Overview

Closes #151

judge, expect and refuse compiled to meow_testing.Judge(...) in a file that never imported the package, so meow run and meow build answered with raw Go — undefined: meow_testing, naming no line of the .nyan file — while meow test worked. The checker accepted them everywhere and the interpreter ran them everywhere, so the CLI was the odd one out.

They work anywhere now. An assertion that holds answers with catnap; one that fails answers with a Furball carrying its message, which ~> can catch:

nya(judge(1 == 1, "one is one"))          # => catnap
nya(judge(1 == 2, "nope") ~> "caught")    # => caught

Option 1 of the two the issue sets out. Option 2 — refusing them outside a test file — would have refused them in the playground as well, which has no test mode. That would undo #140, which implemented them in the interpreter precisely so that the tutorial's section on testing runs when a reader pastes it in.

What to look at

  • The cause was one missing line, in the right place. ensureImport registered the package but never marked it used, and usedImports keeps only what was marked. That is the right rule for a nab import — a program may ask for a package and never call into it, and Go refuses an unused import — and the wrong one for a package the generator itself is about to emit a selector into. Every ensureImport call site is one of those. Only the test wrapper named testing as used, by hand, which is why meow test worked and meow run did not. ensureImport marks it now.
  • seed, from the issue's related note. Codegen answered a seed call with a catnap and spliced its arguments into nothing, so seed(nya("x")) printed nothing compiled and x in the playground. It goes through a runtime meow.Seed now, and the interpreter calls that same function, so the two agree by construction. That also removed the __seed placeholder builtinvalue.go carried so that naming seed as a value had something to wrap.
  • meow.Seed passes an unhandled Furball on, the way nya does, so a seed whose argument failed says so rather than answering catnap as if nothing had happened.
  • meow test -fuzz is unchanged. It still reads seed calls out of a target's body before the body is generated, so a corpus is the same corpus. Run end to end on examples/fuzz_arithmetic.nyan: 5 seeds gathered, then fuzzing for 10s, PASS.

Caught on the way

The first version of the seed fix answered catnap in the interpreter while meow.Seed passed the Furball on in codegen — seed(judge(false, "x")) ~> "caught" printed catnap in the playground and caught compiled. A new divergence, introduced by the fix for an old one. The golden caught it once the case was written into it, and the interpreter now calls meowrt.Seed rather than a catnap of its own.

Verification

  • go test ./... passes
  • go vet ./... passes
  • Golden files updated if needed — one new pair, testdata/assertions_anywhere, read line by line. No existing golden moved.
  • Cross-backend: TestTheseGoldensPrintTheSameUnderTheInterpreter (generalised from the one-golden test ✨ feat: Let a binding take a builtin's name where it is called #158 added) holds the interpreter to both binding_shadows_builtin and assertions_anywhere, byte for byte
  • gofmt -l . clean
  • WASM playground builds
  • meow test examples/catwalk_example_test.nyan passes; meow test -fuzz examples/fuzz_arithmetic.nyan passes
  • staticcheck -checks 'all,-ST1005' (the repo's .golangci.yml set) over the changed packages: every finding is on a line this diff does not touch, which is what --new-from-rev reports on

Written test-first: the golden was red for the right reason — eight undefined: meow_testing lines — before ensureImport changed, and red again on the interpreter side before its seed changed.

One thing worth knowing if you reproduce this by hand: a meow binary run from outside the checkout pins the published runtime module rather than the working copy, so a locally built binary run from /tmp answers undefined: meow.Seed until this is released. That is how buildModContent has always worked and it is what #152's meow.BuiltinFunc went through too; from inside the checkout, or with a released binary, the runtime matches the compiler.

Impact

  • Compiler (lexer / parser / checker / codegen)
  • Runtime (meowrt / file / http) — meowrt.Seed
  • CLI
  • Documentation — docs/stdlib.md and its website copy
  • Playground — pkg/interpreter

The stdlib docs still said the three assertions panic on failure, which has not been true since the panic-to-Furball migration, and listed them under nab "testing" as if they needed one. Both are corrected, with a short note that they are builtins usable anywhere.

Checklist

  • I have read the CONTRIBUTING guide
  • My changes follow the project's coding style
  • I have added/updated tests for my changes
  • Commit messages use gitmoji prefix

🤖 Generated with Claude Code

https://claude.ai/code/session_01Scc7p1RecmFoikhcXwnTMu


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • judge, expect, and refuse are available as builtins, including outside tests. Successful assertions return catnap; failures return a message that can be caught with ~>.
    • seed now evaluates its arguments and preserves the first unhandled failure.
  • Bug Fixes

    • Assertion failures now propagate through expressions and functions, and fail the enclosing test when used in meow test.
  • Documentation

    • Updated the standard library guide to describe assertion availability and return behavior.

Closes #151

`judge`, `expect` and `refuse` compiled to `meow_testing.Judge(...)` in a
file that never imported the package, so `meow run` and `meow build`
answered with raw Go — `undefined: meow_testing`, naming no line of the
.nyan file — while `meow test` worked. The checker accepted them
everywhere and the interpreter ran them everywhere, so the CLI was the odd
one out.

They work anywhere now. An assertion that holds answers with catnap; one
that fails answers with a Furball carrying its message, which `~>` can
catch:

    nya(judge(1 == 1, "one is one"))          # => catnap
    nya(judge(1 == 2, "nope") ~> "caught")    # => caught

Option 1 of the two the issue sets out. Option 2, refusing them outside a
test file, would have refused them in the playground too, which has no
test mode — undoing #140, which implemented them there so the tutorial's
section on testing runs when pasted in.

Why. ensureImport registered the package but never marked it used, and
usedImports keeps only what was marked — the right rule for a `nab`
import, which a program may ask for and never call into, and the wrong one
for a package the generator itself is about to emit a selector into. Only
the test wrapper named "testing" as used, by hand, which is why
`meow test` worked. ensureImport marks it now.

seed, from the issue's related note: codegen answered a seed call with a
catnap and spliced its arguments into nothing, so `seed(nya("x"))` printed
nothing compiled and `x` in the playground. It goes through a runtime
`meow.Seed` now, which is also what the interpreter calls, so the two agree
by construction — including on an argument that failed, which Seed passes
on the way nya does. The first version of this fix answered catnap in the
interpreter and passed the Furball on in codegen; the golden caught it.

`meow test -fuzz` still reads seed calls out of a target's body before it
is generated, so a corpus is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Scc7p1RecmFoikhcXwnTMu
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T12:41:26.917122Z ce1b3bb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: e9a16d8c-6bcf-4527-8d26-133066c2f2dc

📥 Commits

Reviewing files that changed from the base of the PR and between b498a0b and ce1b3bb.

📒 Files selected for processing (9)
  • docs/stdlib.md
  • pkg/codegen/builtinvalue.go
  • pkg/codegen/codegen.go
  • pkg/interpreter/interpreter.go
  • pkg/interpreter/interpreter_test.go
  • runtime/meowrt/builtins.go
  • testdata/assertions_anywhere.golden
  • testdata/assertions_anywhere.nyan
  • website/content/doc/stdlib.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change updates generated imports and assertion documentation, adds a runtime Seed builtin, and routes interpreter and generated-code seed calls through runtime implementations. Golden tests cover assertion and seed behavior.

Changes

Assertion builtins

Layer / File(s) Summary
Assertion imports and documented results
pkg/codegen/codegen.go, docs/stdlib.md, website/content/doc/stdlib.md
ensureImport marks registered packages as used. The documentation describes judge, expect, and refuse as globally available builtins that return catnap on success or a message-carrying Furball on failure.

Seed argument handling

Layer / File(s) Summary
Runtime seed behavior
runtime/meowrt/builtins.go
The new Seed builtin returns the first unhandled Furball argument, or NewNil() when none is present.
Backend seed calls and parity coverage
pkg/codegen/builtinvalue.go, pkg/codegen/codegen.go, pkg/interpreter/interpreter.go, pkg/interpreter/interpreter_test.go, testdata/assertions_anywhere.nyan, testdata/assertions_anywhere.golden
The interpreter and generated code route seed calls through runtime functions. Golden coverage includes assertion expressions and seed calls with successful and failing arguments.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to ce1b3

No concrete issue currently blocks merging. The generated seed result path remains unverified, so normal checks should still be completed.

Security Architecture Review

Security architecture risk: 🔵 Low · up to ce1b3

Assertions can now run in ordinary compiled programs, and seed arguments now run consistently across execution modes. The reviewed paths do not show a new privileged capability or a confirmed security finding, but deployment-level controls were not available to verify.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Program-provided seed expressions now execute in compiled programs as they do in the interpreter, but Seed adds no authority beyond the program's existing execution context. Deployment-level exposure remains unverified.

Trust Boundaries and Controls

  • observed — The playground checks submitted source before interpretation and applies an interpreter step limit. The available evidence does not establish its deployment sandbox or identity controls.

Resilience and Maintainability Implications

  • observed — Direct Seed calls preserve an unhandled Furball rather than replacing it with nil; handled Furballs are skipped by Seed. Generic first-class calls perform their own earlier Furball check.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: allowing judge, expect, and refuse to run outside meow test.
Description check ✅ Passed The description is complete and follows the repository template. It explains the cause, selected solution, seed behavior, verification results, affected areas, AI session, and checklist.
Linked Issues check ✅ Passed Issue #151 is directly linked. The code generator now marks testing as used when it emits meow_testing.Judge, Expect, or Refuse, so plain meow run and meow build include the required impor…
Out of Scope Changes check ✅ Passed The changed files stay within issue #151. Codegen and runtime changes implement assertion imports and seed evaluation. The golden fixture and test provide regression coverage. The stdlib documentati…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 5 files. (4 skipped: 4 …
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks assertions in the grass,
catnap greets the day as clouds drift past.
A Furball carries messages along,
While seed calls pass their arguments strong.
The golden lines record the hopping song.

Comment @coderabbitai help to get the list of available commands.

@135yshr 135yshr self-assigned this Sep 26, 2026
@135yshr
135yshr merged commit b1c1407 into main Sep 26, 2026
4 checks passed
@135yshr
135yshr deleted the claude/next-work-tasks-5b56lt branch September 26, 2026 12:42
135yshr added a commit that referenced this pull request Sep 27, 2026
…164)

## Overview

Closes #163

A failure inside a lambda was reported, by the compiled program, at the
line that *called* the lambda. A lambda passed to `lick` was blamed on
the whole call:

```meow
nyan xs = [1, 2]
nya(lick(xs, paw(x) {
  bring to_float("bad")
}))
```

```
prog.nyan:2:1: Hiss! Cannot read "bad" as a Float, nya~   # before, compiled
prog.nyan:3:3: Hiss! Cannot read "bad" as a Float, nya~   # after, and the playground always
```

### Why

The generated body already recorded the failing line. It then handed its
result back through `meow.Returning(__caller, v)`, which always put the
program back at the caller's position. Its doc comment assumed "a call
that fails never reaches it". That holds where a failure panics (a typed
function). A boxed body, though, hands its failure back as a value, an
unhandled Furball, and that goes through `Returning` like any result. So
it was not only lambdas: any function whose result is boxed, such as
`meow f() P` returning a kitty, had its failure moved onto the calling
line.

### The change

`Returning` now follows the rule `meow.Call` already keeps: it goes back
to the caller only when the value is not an unhandled Furball. The
change is one runtime function. No generated code changes, so no golden
moves.

A lambda that succeeds, or one that caught its own failure with `~>`,
still leaves the program at the call site. A failure later in the same
statement is still blamed on that statement, and both cases are pinned.

## Verification

- [x] `go test ./...` passes
- [x] `go vet ./...` passes
- [x] Golden files updated if needed: none moved
- [x] New `compiler/lambda_position_test.go` runs 7 programs on **both**
the compiled binary and the interpreter. It requires the same output and
the same failure, position included. Cases: a lambda called by name, a
lambda passed to `lick`, the last line of a longer lambda, a lambda
called inside a typed function, a named function returning a kitty, a
failure after a lambda that succeeded, and a failure after a lambda that
caught its own. The five failure cases fail on the compiled side without
the fix; the two success cases pass either way.
- [x] New `runtime/meowrt/position_test.go` unit tests for `Returning`:
it goes back to the caller for a boxed and for a native result, stays
put for an unhandled Furball, and goes back for a caught one. The
Furball case fails without the fix.
- [x] WASM playground builds. `gofmt -l` is clean, and staticcheck,
gocritic and misspell are clean on the changed files.
- [x] The e2e examples (`hello`, `fibonacci`, `fizzbuzz`, `list_ops`)
run

## Impact

- [ ] Compiler (lexer / parser / checker / codegen)
- [x] Runtime (meowrt / file / http): `Returning` in
`runtime/meowrt/position.go`
- [ ] CLI
- [ ] Documentation
- [ ] Playground: unchanged, it already reported the line that failed

A `meow` binary run outside this checkout builds against the published
runtime module, so it picks this up with the next release that carries
the runtime, as it did for `Seed` in #159.

## AI Session

- Session URL: https://claude.ai/code/session_01Scc7p1RecmFoikhcXwnTMu

## Checklist

- [x] I have read the [CONTRIBUTING guide](../CONTRIBUTING.md)
- [x] My changes follow the project's coding style
- [x] I have added/updated tests for my changes
- [x] Commit messages use [gitmoji](https://gitmoji.dev/) prefix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Scc7p1RecmFoikhcXwnTMu

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Scc7p1RecmFoikhcXwnTMu)_

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Failures inside lambdas are now reported at the line where they occur,
rather than at the caller’s line.
* When a lambda handles its own failure, execution resumes with the
caller’s position correctly restored.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude <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.

judge, expect and refuse cannot be used outside meow test

2 participants