Meow Programming Language v0.21.3: Purity Judged by the Declaration a Name Reaches
Released 2026-08-20. A trill function is pure: it must not call a non-pure
one. Until this release, the guard judged that by name alone, so a trill
body could not call a pure local of its own where an impure top-level function
happened to share the name.
meow helper(x int) int {
nya("side effect")
bring x + 1
}
trill meow adds(n int) int {
nyan helper = paw(x) { bring x + 1 }
bring helper(n)
}
Hiss! pure function adds must not call non-pure function helper, nya~
The local has nothing to do with the top-level helper, but the purity check
looked the name up in the table of top-level functions and never asked whether
it still reached that declaration here.
Ask what the name reaches
v0.21.2 recorded which declaration each occurrence reaches. Both purity sites — a call and a bare reference — now ask that instead of the name:
meow helper(x int) int {
nya("side effect")
bring x + 1
}
trill meow adds(n int) int {
nyan helper = paw(x) { bring x + 1 }
bring helper(n)
}
nya(adds(41)) # 42
nya(helper(1)) # side effect
# 2
The fault predates v0.21.2; v0.21.2 is what made it fixable.
What the guard is for is untouched
An impure function reached by its own name is still refused, called or merely referenced:
bring impure(n) Hiss! pure function bad must not call non-pure function impure
bring lick([n], impure)[0] Hiss! pure function bad must not reference non-pure function impure
A regression caught in review is worth naming, because it made the release
better. Gating on the recorded resolution rested on an assumption: that a
resolution is recorded wherever the purity walk can see a name. It was not —
patterns were never inferred at all, so a name written in a peek arm reached
nothing and the gate let it through, turning a clean refusal into a Go build
error escaping from codegen:
trill meow bad(n int) int {
bring peek(n) {
impure => 1
_ => 0
}
}
The fix was to make the inference go where the walk goes — a pattern is an
expression, and one naming something is a reference like any other — rather
than to make the purity check fall back to the name. A nested meow, a
groom block inside a trill body, a sniff condition, a call inside a
range pattern, and a name bound to the function and then called were all
walked by hand afterwards; all still refuse.
The typed call path was also changed to resolve through the recorded declaration rather than the name table. No program was found that reaches it with a shadowed name — holding a function value pushes the whole body onto the boxed path — so that half is defence in depth rather than a fix for an observed fault.
Upgrading
Nothing to change, and no golden output moved. A trill function that was
refused for a local of its own now compiles.
brew upgrade meow
# or
go install github.com/135yshr/meow/cmd/meow@v0.21.3