Make effects a real, enforced, inferred type - #8
Open
dalurness wants to merge 5 commits into
Open
Conversation
Move effect checking from the syntactic collectEffects/perform-only pass into type inference. inferExpr now threads an effect-scope stack: ExprApply unions the effect rows of consumed arrows, ExprPerform records the op's effect, ExprLambda accumulates into the lambda's own row (attached to its last arrow so higher-order callers can propagate it), and ExprHandle discharges handled ops in a child scope. The def-body row is compared against the declared row and undeclared effects are a hard error E308 (was W401, a warning run/check ignored). Subsumption preserved: declaring more than performed is fine; open row-var tails absorb extras. Builtins still carry empty rows, so this is behavior-neutral for the suite (user perform/handle semantics reproduced); Stage 3 turns on builtin effect rows. Retires collectEffects/walkEffects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Builtins now carry their effect rows: io (print, fs writes, env, log,
dt clock, cli, srv), io+exn (fs.read, http.*, proc.run/sh, csv file ops,
streams), exn (raise, json.dec/as), async (spawn, channels, sleep, ...).
map/filter/fold/flat-map are effect-polymorphic — a shared effect var
links the callback's row to the result so the callback's effect
propagates. User function types carry their declared effect row (aliases
resolved via a new up-front pass) so calls propagate effects transitively,
including 0-arg calls. Effect rows now render with <> not {}, and effect
row variables get readable a/b/c names.
Migrates 10 test .clk files whose signatures under-declared effects the
checker now tracks (mostly adding exn to io mains that read files / decode
JSON). Updates the cross-module effect-alias test to the propagation
pattern, since discharging an imported effect's op requires the effect
declaration to be linked (ops are not pub-exported).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add 12 checker tests (builtin io/exn/async enforcement, transitive and higher-order propagation incl. named callbacks, handle discharge, subsumption, pure pipelines stay pure) and 2 eval --type CLI tests (builtin effect rows surfaced). Use code E401 in the effect-error range (E400-499) for an undeclared effect — the error-promotion of the old W401 warning. Update SPEC.md, effect-system.md, README, and the agent skill to state effects are inferred and enforced; correct the stale InferredInfo doc comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deferred: clank doc builtin signatures don't yet show effect rows (the doc registry is a separate approximate type table); clank eval --type is the authoritative way to see a builtin's effects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The macOS runners' dyld refuses to load Mach-O binaries lacking an LC_UUID load command. Go 1.22 (EOL) emits test binaries without it, so every 'go test' binary aborted with 'signal: abort trap' on macOS while ubuntu and windows were unaffected; fail-fast then cancelled windows. Bump both workflows to Go 1.26, matching the local dev toolchain, which emits LC_UUID and restores green CI on all three OSes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Effects are Clank's headline differentiator — but the checker didn't enforce them for builtins. This type-checked clean:
Effect tracking was a syntactic
perform-only pass (aW401warning thatrun/checkignored), with no builtin effects, no cross-call propagation, and no higher-order effect polymorphism. The row-unification machinery existed but was dormant.This moves effect tracking into type inference and makes an under-declared effect row a hard error (E401).
What changed
inferExprthreads an effect-scope stack: applications union consumed arrows' effect rows,performrecords its op, lambdas accumulate into their own row (attached to the last arrow), andhandledischarges handled ops in a child scope. The def-body row is checked against the declared row — subsumption preserved (declaring more than you perform is fine; open row-var tails absorb extras).io(print, fs writes, env, log,dt.now, cli, srv),io+exn(fs.read, http.*, proc.run/sh, csv file ops, streams; json.dec/as → exn),async(spawn, channels, sleep, ...),exn(raise).map/filter/fold/flat-mapshare an effect var between callback and result —map : ([a], (a -> <e> b)) -> <e> [b]— so a callback's effect (inline lambda or named function) propagates to the caller.<>; effect-row variables get readablea/b/cnames.clank eval --type 'fs.read'→(Str -> <io, exn> Str).Migration
10
test/*.clkfiles under-declared effects the checker now tracks (mostly addingexntoiomains that read files / decode JSON). Fixed. The cross-module effect-alias test moved to the propagation pattern, since discharging an imported effect's operation needs the effect declaration linked (ops aren'tpub-exported).Tests
12 checker tests (builtin io/exn/async enforcement, transitive + higher-order propagation incl. named callbacks, handle discharge, subsumption, pure pipelines stay pure) + 2
eval --typeCLI tests. Full suite green.Known follow-ups
clank docbuiltin signatures don't yet show effect rows (the doc registry is a separate approximate type table);clank eval --typeis authoritative.TAny-typed combinators (iter.*,srv.*handlers,spawn's thunk) — they accept effectful callbacks but don't propagate. Inline lambdas and the typed list HOFs do propagate.🤖 Generated with Claude Code