Roadmap
Implemented: v0.33.0 -- Formalisation
The two branching constructs in the language -- match and pickup
-- graduate from "described in a tutorial" to "specified, normatively,
at version 1.0.0". v0.33.0 ships the contract, not a behaviour change:
the implementation already honours every clause of both specifications.
See the v0.33.0 release post for the full
narrative.
docs/MATCH.md-- ThematchConstruct, Language Specification, Version 1.0.0. EBNF grammar, the full pattern sub-grammar (literals, wildcards, silent bindings, tuples / lists / maps / records / ADT constructors / pin operator / binary segments, repeated variables, refutability, arbitrary nesting), static semantics (T-Match typing rule, Maranget-style exhaustiveness, reachability, scoping, refinement-context strengthening), dynamic semantics, big-step / small-step operational semantics, twenty-five formatter-conformance clauses, tail-position behaviour, lowering, constant folding, macro hygiene, IDE / language-server requirements, the diagnostic catalogue (E004,E021-E025,E031-E034, plus warnings / hints), the soundness proof sketch, an acceptance test corpus, and reserved future syntax (or-patterns, view patterns, range patterns, dependent patterns, as-patterns).docs/PICKUP.md-- ThepickupConstruct, Language Specification, Version 1.0.0. MirrorsMATCH.mdin shape with the typing rules T-Pickup-Else / T-Pickup-Cons, the totality guarantee enforced by the mandatory terminator, source-order short-circuit semantics, refinement narrowing, twenty-five formatter clauses, the migration story for legacyif/elif(cure rewrite if-to-pickup, the newE-IF-REMOVEDdiagnostic), the diagnostic catalogue (E-PICKUP-NO-ELSE,E-PICKUP-ELSE-NOT-LAST,E-PICKUP-MULTIPLE-ELSE,E-PICKUP-GUARD-TYPE,E-PICKUP-BRANCH-MISMATCH, plus warnings / hints), reserved future syntax (pickup as,pickup with,pickup async, trailingwhere), and the soundness proof sketch.- HexDocs integration. Both specifications added to
mix.exsdocs.extras, alongsidedocs/PATTERNS.md,docs/BINARIES.md, and the rest of the language references. - Cross-references.
docs/LANGUAGE_SPEC.md§ "Pattern Matching" now opens with a normative pointer todocs/MATCH.md; a new## Conditional Dispatch (pickup)section opens with a pointer todocs/PICKUP.mdand recapitulates the mental model in one paragraph. - Website.
/matchgrew leading and trailing notes pointing at the new normative documents;/pickupis new and mirrors the/matchshape (grammar, totality, evaluation order, scoping, refinement narrowing, tail position, migration fromif, formatter conventions, diagnostic catalogue, idioms). - No language-surface behaviour changes. Code that compiled and ran under v0.32.0 compiles and runs identically under v0.33.0.
Implemented: v0.32.0 -- Trust, Export, Recall, Narrate
Four independent features completing the sprint. The theme is closing gaps that have been queued since the registry landed in v0.23.0. See the v0.32.0 release post for the full narrative.
- Proof-carrying packages (
Cure.Project.Proof,Cure.Project.Proof.Verifier) -- Whencure publishassembles a tarball the compiler is re-invoked inproof_collectmode. Every proof obligation discharged by the type-checker (equality witnesses, refinement predicates, SMT constraints, totality arguments) is serialised as a certificate and written into<name>.cureproofalongside the source files. Binary format:"CUREPROOF\0" <> <<0x01>> <> gzip(term_to_binary([cert, ...]))).cure verify [path] [--strict]replays each certificate through the offline verifier -- no source, no Z3 for equality / refinement proofs.[publish] include_proofs = falseinCure.tomlopts out. New error codes: E065 Proof File Missing, E066 Proof Verification Failed, E067 Proof Schema Incompatible. New Mix taskmix cure.verify. New docdocs/PROOF_CARRYING.md. - Cross-language ADT export (
Cure.ExportTypes,Cure.ExportTypes.Protobuf) --cure export-types --target protobuftranslates Curerec,type, andenumdeclarations to proto3.protofiles. Complete type mapping:Int->int64,List(T)->repeated T,Option(T)->optional T, pure-enum ADTs to proto3enum, payload-bearing ADTs to wrappermessage+oneof valueblocks + one synthetic payload message per variant. Refinements and dependent types emit abytesfield with a comment and raise E068 Export Type Unmappable (warning, not hard error). Field names converted fromcamelCasetosnake_case; field numbers by declaration order.--out,--dry-run,--verboseflags. New Mix taskmix cure.export_types. New docdocs/EXPORT_TYPES.md. cure snap(Cure.REPL.Snap) -- REPL session snapshots.:snap save [path],:snap load <path>,:snap list [dir]as REPL meta-commands. Binary format:"CURESNAP\0" <> <<0x01>> <> gzip(term_to_binary(snap_map)). Captures: session entries (fn/type/rec/proto/impl/proof), up to 500 history lines, use imports, holes, stdlib mode, theme, editing mode, loaded-file paths. Load merges (last-writer-wins for defs, union for uses, prepend for history); E070 warning for each saved path no longer on disk. New Mix taskmix cure.snap save| load|list. New docdocs/SNAP.md. New error codes: E069 Snap Schema Incompatible, E070 Snap Module Missing.cure story(Cure.Story,Cure.Story.Outline,Cure.Story.Narrator) -- narrativeSTORY.mdgenerator. Parses every.curefile underlib/, classifies AST containers into a five-layer outline (app -> supervisor -> actor -> fsm -> types), and emits template-driven prose for each level.--diagramsembeds MermaidstateDiagram-v2blocks for each FSM.--format htmlwraps the output in a minimal HTML shell.--stdoutprints to stdout without writing a file. Project name read fromCure.toml. New Mix taskmix cure.story. New docdocs/STORY.md.- Cross-cutting: six new error codes E065--E070 added to
Cure.Compiler.Errors; all four commands wired intoCure.CLIandcure help;mix.exsversion 0.32.0; four new doc pages registered in Hex extras.
Implemented: v0.31.0 -- Specialise & Steer
The optimisation release. Cure stops paying for polymorphism it
doesn't use, and starts steering its own optimiser with profiling
data. Two long-deferred items from the ## Future block finally
land: monomorphisation and profile-guided optimisation. ASCII-art
diagrams (cure draw) graduate from stuff/TODO-IDEAS.md as a
static counterpart to the v0.27.0 Mermaid emitter for terminals
that cannot render Mermaid.
- Monomorphisation (
Cure.Optimizer.Monomorphise) -- new MetaAST pass wired in front ofCure.Optimizer.Inline. Walks the module body, identifies polymorphicfunction_defnodes (declared signature mentions{:type_var, _}), unifies formal parameter types against inferred call-site argument types, and synthesises one specialised clone per unique substitution. Mangled nameoriginal__<6-hex-hash>(stable across recompiles). The original generic is always retained alongside the clones; cross-module callers, protocol-registry dispatch, and dynamicapply/3keep working unchanged. Per-function soft cap of 16 specialisations (tunable via[compiler].monomorph_budget). Surfaces under code E064 Monomorphisation Budget Exhausted. Specialised clones getnowarn_unused_functionautomatically so erl_lint stays quiet when the inliner devours their call sites. CLI:cure compiledefaults to--monomorphise; opt out with--no-monomorphise. - Profile-Guided Optimisation (
Cure.PGO,Cure.PGO.Profile,Cure.PGO.Recorder) -- runtime-profile capture + compile-time consumption.Cure.PGO.Recorderis aGenServer-backed ETS counter table withtick/1,tick_with_us/2,tick_branch/2,tick_smt/3, andflush/1operations.Cure.PGO.Profileserialises one record per module to_build/cure/pgo/<mod>.profilevia compressedterm_to_binary.Cure.PGO.load/2partitions entries into hot (top 80% of total cost) and cold sets, with stale-profile detection via:erlang.phash2/1of the livefunction_defmeta. The inliner accepts:pgo+:moduleopts: hot fns getast_size <= 12, cold get<= 2, default stays at<= 5.Cure.SMT.Solver.check_sat/3accepts a:hot | :cold | :defaultbudget hint -- hot doubles the Z3 timeout and emits(set-option :smt.arith.solver 6); cold halves the timeout and conservatively returns:saton:unknown. PGO is strictly opt-in: never auto-discovered, must be requested viacure compile --pgo. cure profilesubcommand --cure profile run,cure profile show [--top N],cure profile clear. Pluscure run --record-profile [--profile-dir DIR]flushes profiles on exit.cure draw <path.cure> [fsm|sup|app]-- new ASCII / Unicode box-drawing renderer (Cure.Doc.Ascii) that reuses the FSM / supervisor / application traversals fromCure.Doc.Mermaid. States render as▢/▣, transitions as──[event]──>with!/?event suffixes, supervisor children as a├──/└──vertical tree, applications as a panel summarising vsn, root, and declaredapplications.- Codegen
nowarn_unused_functionattribute -- per-module attribute synthesised whenever the module's body contains afunction_defwith a:specialised_frommeta. Keeps the monomorphisation + inliner cycle warning-free. - New error code: E064 Monomorphisation Budget Exhausted.
- New docs:
docs/MONOMORPHISATION.md,docs/PGO.md. Both added to the Hex documentation extras list. - New example:
examples/specialise.cure-- small showcase that exercisesid/1andpair_first/2against four distinct substitutions.
Deferred to v0.33.0 and beyond:
- Cure-native notebook (v0.33.0 target) -- first-class
.cnbformat evaluated by a Livebook-style runner. Groundwork in place:Cure.Doc.Mermaid,Cure.Doc.HtmlGenerator,Cure.REPLpipeline. - Typed hot code upgrades (
cure release --upgrade-from,@migration, E057/E058) -- the appup/relup machinery is large and fragile; it continues to get its own cycle. - Automatic PGO instrumentation -- inject
Cure.PGO.Recorder.tick/1at every compiled function's entry. v0.31.0 ships the data layer; users who want PGO today drive ticks manually or viaCure.Profiler. cure export-types --target typescriptand--target rust-- the Protobuf backend (v0.32.0) proved the extractor API; additional emitters are additive.- Cross-module monomorphisation -- whole-program specialisation across compilation unit boundaries.
Implemented: v0.30.0 -- John
Every surface gets a new john button. Named in tribute to John
Carbajal, whose knack for spotting the one line on a dashboard
that actually mattered inspired the release. See the
v0.30.0 release post for the full narrative.
mix cure.john,cure john,:john-- three surfaces, one implementation. Each prints a panoramic Markdown-to-ANSI diagnostic covering Cure application state, BEAM / OTP stats, system info, tooling, the current Cure project (if any), runtime snapshot, doctor severity counts, and tails of the most recent log files.Cure.John--collect/1returns a plain Elixir map (serialisable, assertable);render/2emits CommonMark;run/1collect-renders-writes. All three are testable in isolation.- Marcli fallback --
run/1renders throughMarcli.render/2when its MDEx NIF can load, and falls back to the pure-ElixirCure.REPL.Markdownrenderer inside the escript (where the NIF path is not loadable). - CLI and REPL integration --
cure help,Cure.CLI'sknown_commandslist, and the REPL'sknown_commandslist all advertisejohn/:john, so a typo surfaces a Levenshtein "Did you mean?" suggestion. - Docs -- new
docs/JOHN.mdon-disk reference (added to the Hex docs extras),:johnadded todocs/REPL.mdand the site REPL reference, new v0.30.0 section in the Tooling page, and a release blog post.
Implemented: v0.29.0 -- Make Documentation Great
The documentation release. cure doc, the REPL, the stdlib, the
website, and the editor plugins all pulled up to the same bar. See
the v0.29.0 release post for the full
narrative.
cure doctwo-pane layout -- ExDoc-like HTML site driven by a new[doc]table inCure.toml(main,title,extras,logo,source_url,source_ref, plus[doc.groups_for_modules]). Anchored entries for every public function / type / protocol;/-focusable sidebar filter;prefers-color-schemetheme toggle.--title,--main,--extrasCLI flags.Cure.Doc.Markdown-- NIF-free Markdown-to-HTML pipeline (Md.generate/1+ Makeup highlighting forcure/elixir/erlangfences +0.33.0/v0.33.0placeholder interpolation). Safe inside the escript (where themarcli/MDExNIF path is not loadable).- Stdlib Examples blocks -- every module under
lib/std/now carries a module-level## Examplesblock; four high-trafficStd.Corefunctions (compose,map_ok,and_then,map_option) carry per-function examples. All round-trip throughmix cure.compile_stdlib. /stdlibsite integration --CureSite.Stdlibbundles everyStd.*module at site-compile time;CureSiteWeb.StdlibControllerserves/stdlib(index) and/stdlib/:module(single-module page) with a DaisyUI-styled two-pane layout and a GitHub "View source" link./standard-libraryredirects to/stdlib.- REPL Markdown renderer upgrade --
Cure.REPL.Markdowngains a block-aware parser (fenced code, lists, blockquotes, indented code, inline links, horizontal rules, paragraph breaks) while staying NIF-free. - Parser fix -- consecutive
##doc-comment blocks separated by blank lines are now merged into a single docstring with a paragraph break (collect_all_doc_comments/1,2). - Editor and highlighter alignment --
highlightjs-cure/ships a highlight.js language description plus a minified bundle;vicure/andvscode-cure/are re-aligned with the Cure 0.28.2 grammar (syntax, indent, ftplugin, ftdetect, tests, README / CHANGELOG). - REPL session and signatures --
Cure.REPL.Sessionaccumulates top-level declarations (fn,type,rec,proto,impl,proof);Cure.Types.Checker.infer_expr/2gains an:extra_bindingsoption so session fns show their real type in:tand:effects(promoted from v0.28.1 / v0.28.2). docs/DOC.md-- authoritative on-disk reference for the whole doc pipeline;docs/TUTORIAL.mdChapter 13 "Documenting your modules" walks through docstrings, Examples,[doc]configuration, and runningcure doc.
Implemented: v0.28.0 -- Talk Back
Feedback loops closed at every level. See the v0.28.0 release post for the full narrative.
- Checker bug fix --
infer_and_unify_argssilently swallowed lambda body type errors, returningList(U)instead of a type error for ill-typed polymorphic lambdas. Fixed via error accumulator.Type.numeric?extended to handle named type aliases and type variables without false positives. - Parser error recovery (E063) --
synchronize_to_statement/1after every error-producing expression; one pass, all errors. - "Did you mean?" -- Levenshtein suggestions at every
name-resolution failure: unbound variables, record fields, CLI
subcommands, REPL
:use, REPL meta-commands. cure fmt --dry-run-- colour-annotated unified diff without touching disk; exits 1 when files would change.cure bless-- Socratic type-error assistant with[y/n/s]interactive fix prompting.Cure.Bless,Cure.Bless.Advisor,mix cure.bless, REPL:bless.@record+cure replay--@recorddecorator opts any FSM intoCure.Observe.Journal.cure replayloads.journalfiles and replays event sequences with optional--stepmode.- Playground v2 -- live type-check panel + sandboxed evaluator
(
CureSiteWeb.Eval, 64 MB heap cap, 2 s kill timer). - 1503 tests. E063 added to the error catalog.
Implemented: v0.27.0 -- See Your System Breathe
Observability and verification layered on top of the v0.26.0 OTP application surface. See the v0.27.0 release post for the full narrative.
Cure.OTel-- OpenTelemetry-compatible span bridge. Every pipeline event becomes a span;inject_ctx/extract_ctxcarry trace context across process boundaries;MemoryExporterfor tests.cure top-- snapshot observer for supervisors, actors, and FSMs.cure trace-- typed:dbgwrapper with BEAM signature lookup.Cure.Temporal-- LTL formula DSL + bounded model checker over FSM transition graphs.E059 Temporal Property Violatedwith a minimal counterexample trace.Cure.Protocol-- session types between actor roles, structural verifier, projection to FSM traces.E056 Protocol Violation.cure synth-- typed-hole synthesis via depth-budgeted enumeration + stdlib catalogue.E061 Synthesis Budget Exhausted.- New stdlib --
Std.Time(14 fns),Std.Regex(7 fns,E060),Std.CRDT(5 CRDTs × 4 ops). Cure.Doc.Mermaid-- FSM / sup / app diagrams incure doc.- OSC 8 hyperlinks --
Cure.Term.Hyperlinkwraps error file paths so they are clickable in WezTerm, Kitty, iTerm2, VTE, and Warp. - LiveView Playground v1 -- syntax-highlighted two-pane editor.
examples/cure_atelier/-- showcase project exercising every v0.27.0 surface.- 1451 tests. 5 new error codes (E056--E062).
Implemented: v0.26.0 -- Applications and Releases
v0.26.0 promotes the v0.25.0 supervision surface into a full OTP
application lifecycle. A new app container declares the project's
OTP application directly in Cure source, cross-checked against
Cure.toml's new [application] and [release] tables. The same
compiler cycle emits the <name>.app resource file and a
:"Cure.App.<Name>" Application-behaviour module; a new
cure release subcommand (also available as mix cure.release)
packages the whole thing as a bootable BEAM release. See the
dedicated Applications reference for the full tour.
app container
app Namecontainers declare an OTP application withvsn,description,root,applications,included_applications,registered,envassignments, pluson_start/on_stop/on_phase :namelifecycle clauses. The clause grammar is shared withactorandsup(pattern tuple + optionalwhenguard + body).appis a contextual soft keyword, treated the same way assup: the lexer keeps it as an identifier so programs that useappas a field or variable name keep compiling; the parser dispatchesapp <Name>to the application-container parser only at statement-prefix position.Cure.App.Verifierenforces exactly oneappcontainer per project, matches its name against[application].nameinCure.toml(both sides normalised throughMacro.underscore/1), cross-checks start-phase names, and validates thatroot = ...resolves to a known module atom.Cure.App.Compileremits a loadedApplication-behaviour module viaCode.compile_string/2; the codegen dispatcher returns{:ok, {:app, module()}}. When--output-diris given the bytecode and an OTP<name>.appresource file are persisted alongside every other Cure module.
Release builder
Cure.Releaseassembles a bootable BEAM release under_build/cure/rel/<name>/:lib/<app>-<vsn>/ebin/*.{beam,app},releases/<vsn>/<name>.rel,releases/<vsn>/start.boot,releases/<vsn>/start.script,releases/<vsn>/sys.config,releases/<vsn>/vm.args, and a POSIXbin/<name>runner script.--include-erts(or[release].include_erts = true) bundles the running ERTS into the release directory.Mix.Tasks.Cure.Releaseexposes the subcommand asmix cure.release.
Cure.toml extensions
[application]--name,vsn,description,applications,included_applications,registered,start_phases, plus nested[application.env].nameis authoritative for the emitted<name>.app;applicationsis merged with the container's own list;start_phasesmust agree with the container'son_phaseclauses.[release]--name,vsn,include_erts,applications,vm_args,sys_config.Cure.Releasethreads these into the release assembly.
Standard library additions
Std.App(9 functions) --ensure_all_started/1,start/1,stop/1,get_env/2,get_env/3,put_env/3,which_applications/0,loaded_applications/0,start_phase/3. Thin wrappers over:applicationwith plain-atom return shapes.
Error catalog
Five new codes E051 -- E055:
E051 Duplicate Application-- more than oneappcontainer in a project (or a name mismatch with[application].name).E052 Missing Application--cure releaseinvoked with noappdeclared.E053 Start Phase Mismatch-- TOML and container disagree on phase names.E054 Unresolved Root Supervisor--root = ...does not resolve to a known module reference.E055 Release Build Failed-- wraps:systools.make_script/2or release-write I/O errors.
Example
examples/cure_forge/-- fully-fledged OTP application showcase. Anapp CureForgecontainer withvsn,description,env,applications,on_start,on_stop, and a:warm_cachestart phase; asup Forge.Rootsupervision tree; and four actors (metrics, logger, queue, pool) cooperating through the Melquiades Operator<-|.
Documentation
docs/APP.md-- full on-disk reference for theappcontainer,Cure.toml[application]/[release]sections, thecure releasesubcommand,Std.App, and error codes.docs/TUTORIAL.md-- new Chapter 13 "Applications".docs/LANGUAGE_SPEC.md-- new "Applications (v0.26.0)" section.docs/STDLIB.md-- newStd.Appentry.site/priv/pages/applications.md-- user-facing reference on the Cure website.
Implemented: v0.25.0 -- Typed Supervision Trees
v0.25.0 turns the language into a first-class environment for
writing OTP-style supervision trees on top of the BEAM. Four pieces
land together: the Melquiades Operator <-| for typed sends, an
actor container that compiles to a loaded GenServer module, a
sup container that compiles to a verified Supervisor behaviour
module, and a new stdlib surface exposing the runtime from Cure
source. See the dedicated Actors reference for the full
tour.
Melquiades Operator
pid <-| message(ASCII) andpid ✉ message(unicode U+2709) are interchangeable spellings of a typed send. Both lower to Erlang's!operator in abstract form, return the sent message, and never raise for a dead receiver.- Non-associative; binds one notch below
|>so pipelines feed into sends without extra parentheses. send target, msgremains as a synonymous statement form for backward compatibility withStd.Fsmclients.- Printer preserves the author's spelling through a
:melquiades_formmeta key; round-trips<-|,✉, and the keyword form faithfully.
Typed primitives
Pid(Inbox)-- a typed pid. BarePidelaborates to{:pid, :any}, the top of the covariant family, so existing FFI code remains assignable from narrower typed pids without friction.Ref-- new primitive type for monitor references.Cure.Types.Checker.do_infer/2grows a dedicated clause for{:send, ...}that unifies the message type against the receiver's inbox and emitsE046 Inbox Mismatchon conflict.Cure.Types.Effectsattracts:statefor every send.
Actors (actor container)
actor Name with <init>declares a typed process. Lifecycle hookson_start,on_message,on_stopreuse the FSM callback-clause machinery (pattern tuple, optionalwhenguard, body).Cure.Actor.Compileremits a loadedGenServermodule viaCode.compile_string/2; the codegen dispatcher returns{:ok, {:actor, module()}}.Cure.Actor.Runtimetracks spawned actors in an ETS registry, monitors each pid, and cleans up onDOWN. Supervised byCure.SupervisoralongsideCure.FSM.Runtime.Cure.Actor.Statestruct carriescaller,meta,payload;notify/1inside any clause body resolves the registered caller via the process dictionary.Cure.Actor.BuiltinsbridgesStd.Actorto the runtime.
Supervisors (sup container)
sup Namedeclares a supervisor module with inlinestrategy,intensity,periodsettings plus achildrenblock whose entries areModule as id (restart: ..., shutdown: ...).- Child module resolution: dotted paths are used verbatim; bare names
resolve to
Cure.Actor.<Name>(worker default) orCure.Sup.<Name>(when prefixed withsup <Name> as id). Cure.Sup.Verifierrejects invalid strategies, non-positive periods, duplicate child ids, unknown restart / shutdown values, and trivial self-reference cycles. Emits:sup_verifierpipeline events.Cure.Sup.Compileremits aSupervisor-behaviour module viaCode.compile_string/2; the codegen dispatcher returns{:ok, {:supervisor, module()}}.Cure.Sup.Runtimewraps the compiled module with a lazy ETS registry (start/1,2,stop/1,lookup/1,which_children/1,count_children/1,list/0).supis a contextual keyword -- it stays an identifier at the lexer level so programs usingsupas a field or local variable keep compiling; the parser only dispatchessup <Name>toparse_supervisor/1at statement-prefix position when followed by an identifier.
Stdlib additions
Std.Actor--spawn/1,spawn_with_payload/2,spawn_named/2,stop/1,send/2,get_state/1,is_alive/1,lookup/1.Std.Process--link/1,unlink/1,monitor/1,demonitor/1,trap_exit/1,exit/2,self/0,is_alive/1. Most entries are direct:erlangBIF calls;monitor/1andtrap_exit/1go through thin wrappers inCure.Process.Builtinsso the Cure signatures read idiomatically.Std.Supervisor--start/1,start_with/2,stop/1,which_children/1,count_children/1,lookup/1,list/0.
Error catalog
Six new codes E045 -- E050: Untyped Send, Inbox Mismatch,
Supervisor Unknown Child, Supervisor Cycle, Actor Handler
Non-Exhaustive, Invalid Supervisor Strategy. Run cure explain <code>
for the full text.
Example
examples/cure_colony/-- a worker actor, an echo actor, and asup Colonysupervisor wiring them under:one_for_one. Exercisesactor,sup, and the Melquiades Operator end to end.
Numbers
- 1310 tests pass (up from 1295; 3 doctests + 1307 tests);
mix credo --strict: 0 issues across 200 source files;mix cure.check.stdlib: 30/30 (three new stdlib modules).
Implemented: v0.24.0 -- The REPL You Deserve
v0.24.0 is deliberately single-purpose. The compiler surface is
untouched; every change lands on the interactive read-eval-print
side. The legacy IO.gets loop (shipped in v0.15.0, barely touched
since) is replaced wholesale by a raw-mode line editor with live
syntax highlighting, persistent history, incremental reverse search,
Tab completion, and a minimal vi mode. See the dedicated
REPL reference for the full key-bindings table and
meta-command list.
Raw-mode terminal
Cure.REPL.Terminalputs stdin into cbreak/no-echo mode viastty, restores it on every exit path (normal:quit, EOF on an empty buffer,Ctrl+Con a non-empty buffer, uncaught exception, SIGINT / SIGTERM), and decodes raw byte streams into high-level key events: arrows,Home/End/Delete/PgUp/PgDn, everyCtrl+<letter>andAlt+<char>shortcut,Ctrl+Arrowfor word-wise movement,F1-F12, and bracketed paste.- Non-tty stdin (CI, pipes,
|intocure repl,TERM=dumb) short-circuits to the legacyIO.getsloop so test automation continues to work unchanged.
Pure-function line editor
Cure.REPL.LineEditoris a stateless buffer: every key event produces a new%LineEditor{}struct. Emacs-mode menu covers one- grapheme and one-word cursor movement, begin / end of line, backspace / delete,Ctrl+K/Ctrl+U/Ctrl+W/Alt+Dkill variants, yank (Ctrl+Y) and kill-ring rotation (Alt+Y), transpose chars / words, upcase / downcase / capitalise word, undo / redo (Ctrl+_/Alt+_), and a minimal vi normal mode (h/j/k/l,w/b/e,0/^/$,i/a/I/A,x,D,C,u).
History and reverse search
Cure.REPL.Historypersists entries to~/.cure_historyvia atomic write-and-rename. Consecutive duplicates are deduplicated, the file is capped at 10,000 entries, andUp/Downstep through history while preserving the current draft.Cure.REPL.SearchimplementsCtrl+R/Ctrl+Sincremental reverse and forward search with an inverse-video status line and accept-and-edit semantics (Tab/ arrow keys pull the match into the main buffer for further editing).
Live syntax highlighting
Cure.REPL.Highlightpipes every buffer state throughMakeup.Lexers.CureLexerandMarcli.Formatterfor ANSI syntax highlighting on every keystroke. Cached by buffer hash so repeated redraws do not re-tokenise.Cure.REPL.Themeships:dark(default),:light, and:monopresets.:monois forced automatically whenNO_COLORis set, when stdout is not a tty, or whenTERM=dumb. Toggle at runtime with:theme dark|light|monoor:color on|off.
Tab completion
Cure.REPL.Completerhandles four categories in one pass: meta-commands, file paths (inside:load/:save/:edit), loaded modules (inside:use/:doc), and theme / mode / colour argument literals plus Cure keywords everywhere else.
Meta-commands
All existing meta-commands (:t, :type, :effects, :load,
:reload, :use, :holes, :env, :reset, :fmt, :help,
:quit) are preserved. v0.24.0 adds :history [n], :search term,
:clear, :save path, :edit, :time expr, :bench expr [n],
:ast expr, :theme dark|light|mono, :mode emacs|vi, and
:color on|off. :help output is rendered via Marcli.render/2
as ANSI-styled Markdown.
Dependencies
{:marcli, "~> 0.3"}-- Markdown-to-ANSI renderer and Makeup token-to-ANSI formatter.{:makeup, "~> 1.2"}-- explicit soMakeup.Registryis available at runtime.{:makeup_cure, "~> 0.1"}-- Cure language lexer for Makeup.
Documentation
- New
/repluser-facing reference page on the Cure website alongside the existing docs/REPL.md on-disk contract.
Implemented: v0.23.0 -- Packaging, Proof, and Polish
v0.23.0 ships the remote package-registry story that was
rescheduled through three releases. It lands alongside a broad
ergonomics upgrade: property-based shrinking, two new stdlib
modules, a cure doctor diagnostic, a cure fix project-wide
rewriter, a :telemetry bridge, cure test --cover coverage
reporting, and the cure_brainloop showcase example.
Remote package registry
Cure.Project.Registry-- read-only HTTP client over OTP's:httpc. Content-addressed, disk-cached, hash-verified responses. New:registrypipeline stage (:fetch_start,:fetch_ok,:fetch_failed,:cache_hit,:hash_mismatch).Cure.Project.Lock--Cure.lockreader / writer withresolve_with_lock/2. When every top-level constraint still accepts the locked version,cure depsskips the backtracker and the network entirely.Cure.Project.resolve_deps/1extended: dependencies withoutpathorgitare registry dependencies. Tarballs are fetched, hash-checked, signature-verified, unpacked under_build/deps/<name>-<version>/, and added to the code path.Cure.Project.Signing-- Ed25519 key management on top of OTP:crypto. Keys under~/.cure/keys/with tight file perms. Signsname || NUL || version || NUL || sha256(tarball).Cure.Project.Transparency-- Rekor-style append-only log verifier. Canonicalises entries by key-sorted JSON, validates the Merkle-like hash chain, degrades gracefully to{:ok, :unverified}when/logis unreachable. Promotable to a hard failure withconfig :cure, strict_transparency: true.Cure.Project.Publisher-- assembles the package tarball, signs, uploads.build_hex_tarball/1emits a Hex-compatible tarball (VERSION/CHECKSUM/metadata.config/contents.tar.gz) for cross-publishing viamix hex.publish package --replace.
CLI surface
cure publish,cure publish --dry-run,cure publish --hex.cure search <query>,cure info <name>[:version].cure keys generate <handle>,cure keys list.cure doctor-- environment / project / source health report, suitable as a CI gate.cure fix [--dry-run]-- project-wide safe rewrites (line endings, trailing whitespace, tabs -> two spaces, blank-line runs, trailing newline).cure test --cover-- runs the self-hosted test suite under:cover, emits an HTML index under_build/cure/cover/.
Standard library (25 -> 27)
Std.Json(lib/std/json.cure) -- JSON encoder + decoder backed by theValueADT (Null | Bool | Num | Str | Arr | Obj). Companion to the v0.21.0@derive(JSON)target.Std.Http(lib/std/http.cure) --get/1,get/2(with headers),post/3,head/1returningResult(Response, HttpError). The registry client dogfoods it.Std.Gen.shrink_int/1,shrink_list/1,shrink/1-- shrinking primitives.Std.Test.forall_shrunk/3andforall_shrunk_default/2-- shrinking-aware property runner. Raises{:property_failed_with_shrunk, minimal_value}on the minimised counterexample instead of the first draw.
Infrastructure
Cure.Telemetry-- subscribes to every stage ofCure.Pipeline.Eventsand re-emits events under[:cure, :pipeline, <stage>, <event_type>]. Production deployments can feed compilation and Z3 latency into an existing observability stack.:telemetryis declared optional inmix.exs; missing the library silently turns the bridge into a no-op.Cure.Doctor-- structured diagnostic (Elixir / OTP / Z3 / registry URL / telemetry / project / source health). Non-zero exit on any warning or error finding.Cure.Fix-- project-wide safe rewrites.Cure.Cover--:coverharness with HTML report generation.
Showcase example
examples/cure_brainloop/-- the top-pick showcase from the internalideas_for_cure_apps.md. Toy expression-language interpreter with a REPL FSM. Exercises ADTs, records, refinement types, protocols, effects, FSM callback mode, OTP interop, FFI, and the newStd.Jsonmodule in one self-contained codebase. Ships with an ExUnit suite covering lexer, parser, evaluator, and environment semantics.
Error catalog
Five new codes E038 -- E042: Registry Fetch Failed, Registry
Hash Mismatch, Registry Package Not Found, Registry Signature
Invalid, Transparency Log Unreachable.
Documentation
docs/PACKAGE_REGISTRY.mdrewritten from a v0.17.0-era design document into the authoritative shipped contract.docs/PUBLISHING.md-- end-to-end walkthrough for publishing a Cure package, including Hex cross-publishing and strict transparency-verification mode.
Numbers
- 1144 tests pass (up from 1114; 3 doctests + 1141 tests);
mix credo --strict: 0 issues across 167 source files;mix cure.check.stdlib: 27/27;mix cure.check.examples: 44/44.
Implemented: v0.22.0 -- Loose Ends
v0.22.0 closes the three language-surface gaps that v0.20.0 /
v0.21.0 deliberately left open: multi-statement lambda bodies,
binary comprehension generators, and full byte-size refinement
propagation through rest::binary tails. The v0.21.0 "Unreleased"
first-class FSM overhaul also graduates into a shipped release.
Multi-statement lambda bodies
Cure.Compiler.Parser.parse_lambda_body/2routes the body through a newparse_lambda_block_body/2dispatcher. Indented and single-expression bodies keep their v0.19.0 semantics; two new shapes land for argument positions where the lexer suppresses newlines:
map(xs, fn(x) -> { let y = x + 1; y + 2 })
map(xs, fn(x) -> let y = x + 1; y + 2; end)
- Both shapes emit
{:block, [block_shape: :brace | :end, ...], exprs}, reusing the existing codegen and checker paths. The Printer and algebra formatter round-trip the author's chosen shape. endis now a reserved keyword. Cure's stdlib and example programs do not use it as an identifier; third-party code that does must rename.
Binary comprehension generators
parse_generator_or_filter/1dispatches on:binary_openand delegates toparse_binary_generator/1. Segments inside the generator are parsed at binding power 42 so the trailing<-arrow is not mis-tokenised as a less-than comparison.Cure.Compiler.Codegen.compile_comprehension/3lowers the new:binary_generatorqualifier to Erlang'sb_generateform inside the existing:lccomprehension:
[byte for <<byte <- "abc">>] # [97, 98, 99]
[word for <<word::16 <- buf>>] # 16-bit words
[ch for <<ch::utf8 <- text>>] # UTF-8 code points
Cure.Types.Checker.do_infer/2on:comprehensionnow binds every qualifier's pattern variables into the body's environment viabind_pattern_vars/3.
byte_size arithmetic refinements
Cure.SMT.Translatorspeaksbyte_size/1as an uninterpreted(Int) -> Intfunction. Queries that referencebyte_sizeprepend(declare-fun byte_size (Int) Int)and switch to theQF_UFLIAlogic automatically.Cure.Types.PatternRefinement.narrow/2rewrites its binary- segment branch. Trailingrest::binary/rest::bytes/rest::bitstringtails receive{rest: Bitstring | byte_size(rest) == byte_size(scrutinee) - sum}where the sum is the byte-aligned count of preceding segments. When a segment's size cannot be linearised the tail degrades to plainBitstringand the pipeline emits a:refinement_ignoredevent under codeE037.
Error catalog
Three new codes E035-E037 (Lambda Block Unterminated, Binary
Comprehension Source Not Bitstring, Binary Segment Size Non-Linear).
First-class FSM handling
The v0.21.0 "Unreleased" block that described the %Cure.FSM.State{}
rewrite of callback-mode FSMs is promoted to a shipped surface.
start_link/1 accepts three init shapes, on_transition clauses
receive the struct as their 4th argument, and @notify_transitions
/ @initial / @auto_caller annotations carry first-class payload
semantics. Simple-mode (gen_statem) FSMs are entirely unchanged.
Numbers
- 1114 tests pass (up from 1078; 3 doctests + 1111 tests);
mix credo --strict: 0 issues across 142 source files;mix cure.check.stdlib: 25/25;mix cure.check.examples: 44/44 (up from 40/40). - 3 new example files (
lambda_block.cure,binary_comprehension.cure,byte_size_refinement.cure).
Deferred to v0.23.0
Remote package-registry index service, publication signing, and
Hex.pm cross-publishing -- the Cure.Project.Registry HTTP
client against the read-only index protocol, Ed25519 archive
signing, and the cure publish --hex export path are all
rescheduled for v0.23.0.
Implemented: v0.21.0 -- Through the Segments
The v0.21.0 release turns the v0.20.0 AST scaffolding into
user-visible features and clears three language gaps that surfaced
during the v0.20.0 cycle. The headline is full Erlang-style
destructuring of binaries in match, multi-clause function heads,
and let bindings, with a dedicated exhaustiveness pass that
surfaces under code E031.
Binary destructuring
Cure.Types.Checker.bind_pattern_vars/3grows a:bin_segmentclause. Every<<...>>pattern inmatch, function heads, andletintroduces its inner variables with the type implied by the segment specifier:integer/size(n)->Int,float->Float,utf8/utf16/utf32->Char,binary/bytes/bitstring/bits->Bitstring.Cure.Types.PatternChecker.check_binary_exhaustiveness/2-- dedicated Maranget-lite coverage analysis over a sequence of binary-pattern arms. A top-level wildcard, or the combination of an empty-binary arm and an open-ended tail arm, covers the scrutinee; otherwise a concrete witness ("<<>>"or"<<_, _rest::binary>>") is reported under codeE031.Cure.Types.PatternRefinement.narrow/2gets a binary-literal branch that narrows the scrutinee through the segments.
In-place let destructuring
letbindings reuse the v0.18.0 deep-destructuring engine.let Ok(x) = expr,let %[a, b] = pair,let [h | t] = xs,let Point{x, y} = p, andlet <<tag, _::binary>> = bufall bind the right variables with the right narrowed types.- Non-exhaustive
letpatterns emit codeE034as a warning; the binding still compiles, and Erlang's=raises at runtime on a failed match.
Multi-line type ADT declarations
parse_type_def/1absorbs an optional wrapping:indent/:dedentpair, accepts an optional leading|before the first variant, andparse_more_variants/1skips newlines before peeking for|. Both layouts parse identically to the single-line form:
type Shape =
| Circle(Int)
| Square(Int)
| Triangle(Int, Int, Int)
E033Multi-line Type Layout Invalid added to the error catalog.
ADT function-type payloads
Constructor payloads accept arbitrary type expressions including
function arrows. type Callback = On(Int -> Int) | Off and
type Transform = Morph((Int, Int) -> Int) | Id compile end-to-end;
pattern matching binds the callable to a variable callable from
inside the arm.
Algebra formatter as default
cure fmt now runs the Wadler/Inspect.Algebra-style pretty
printer by default. The legacy byte-level formatter remains
available via cure fmt --safe; cure fmt --check routes through
the algebra formatter so CI agrees with interactive use.
@derive(Functor, Monoid, JSON)
Cure.Types.Derive.derive/3 gains three new targets: :functor
emits fmap(x, f) that applies f to every field; :monoid
emits combine(a, b) that pairwise combines each field with <>;
:json emits to_json(x) that renders the record as a JSON
object whose keys are the field names. can_derive?/1 is extended
accordingly.
Multi-clause parameter destructuring
check_multi_clause/7 routes every clause parameter through
bind_pattern_vars/3 instead of only binding bare variables. ADT
constructors, tuples, cons patterns, record patterns, and binary
patterns in function heads now introduce their inner variables for
the clause body.
Error catalog
Four new codes E031-E034 (Binary Pattern Not Exhaustive,
Function Type Payload Invalid, Multi-line Type Layout Invalid, Let
Pattern Not Exhaustive) with examples and fix guidance.
Numbers
- 1078 tests pass (up from 1050, 3 doctests + 1075 tests);
mix credo --strict: 0 issues across 137 source files;mix cure.check.stdlib: 25/25;mix cure.check.examples: 40/40. - 5 new examples (
binary_destructuring.cure,adt_fn_payload.cure,multi_line_adt.cure,let_destructuring.cure,json_derive.cure); 1 new doc (docs/BINARIES.md); updates todocs/LANGUAGE_SPEC.mdanddocs/TUTORIAL.md(new Chapter 11 "Binary parsing").
Implemented: v0.20.0 -- The Shape of Things
AST polish across four coupled compiler features. Plain #
comments are first-class nodes in the tree, binary literals and
patterns gain the full Elixir-style segment grammar
(<<x::utf8, rest::binary>>), a Wadler/Inspect.Algebra-style
pretty-printer lands behind cure fmt --algebra, and pattern
narrowing can expose disjoint-tag and literal-equality witnesses
via Cure.Types.PatternRefinement. None of the four tracks are
new surface-language features on their own -- they are the
scaffolding the next generation of features (binary destructuring,
width-aware layout, path-sensitive dependent narrowing) will build
on.
Plain comment AST nodes
Cure.Compiler.Lexer.tokenize/2gains apreserve_comments: trueoption. Plain#comments are emitted as:line_commenttokens; doc comments (##,###) continue to be preserved regardless.Cure.Compiler.Parserthreads those tokens into the AST as{:comment, [line:, col:], text}nodes in top-level programs, container bodies, and block bodies. Indented comment-only lines preceding a block's:indentare routed inside the block so the comment ends up a sibling of the definition it documents.Cure.Compiler.CodegenandCure.Types.Checkerskip comment nodes silently so comment preservation has no runtime or type-checking cost.
Full bitstring segments
:colon_colonlexer token for::, distinct from:colon(type annotations) and:atom(symbol literals).Cure.Compiler.Parser.parse_binary_literal/1now wraps every element of<<...>>in a{:bin_segment, meta, [value]}node. Meta keys::type(:integer,:float,:bits,:bitstring,:bytes,:binary,:utf8,:utf16,:utf32),:signedness,:endianness,:size(an AST node),:unit(integer). Specifiers chain with-(::binary-size(n)); a bare integer is shorthand forsize(n). Defaults mirror Erlang:integer-unsigned-big-size(8)-unit(1), withutf8/utf16/utf32providing their own implicit size.Cure.Compiler.CodegenandCure.Compiler.PatternCompilertranslate the segment AST into full Erlang:bin_elementforms in both construction and pattern positions.Cure.Compiler.Printerround-trips segments back into surface syntax.
This is the groundwork for the v0.21.0 headline feature: full
Erlang-style destructuring of binaries in match, function heads,
let, and comprehension generators.
Algebra-based formatter
Cure.Compiler.Algebra-- a newInspect.Algebra-style document module withempty,concat,nest,break,line,group,string,space,fold, and a best-fitformat/2. Agroupfits flat when its content stays within the remaining width; otherwise every soft break inside renders as a newline at the current indent.Cure.Compiler.AlgebraFormatter-- AST-to-document renderer that walks top-level programs, containers, and blocks, and delegates per-node rendering to the existingPrinter. Standalone{:comment, ...}nodes round-trip as# <text>lines; sequences are separated by blank lines.Cure.Compiler.Formatter.format_algebra/2runs the new formatter with comment-aware round-trip verification: the formatted buffer must re-parse to an AST structurally equal to the input (modulo comment placement and position metadata), otherwise the original source is returned unchanged.- CLI:
cure fmt --algebraopts in. The existing byte-level safe formatter stays the default for v0.20.0 and will be promoted to--algebraas the default in v0.21.0.
Structural refinement narrowing
Cure.Types.PatternRefinement.narrow/2returns{bindings, narrowed_scrutinee}for any pattern against any scrutinee type. Literal sub-patterns yield equality refinements ({x: Int | x == 0}for0); constructor patterns (Ok(v),Error(e)) and map patterns with a literal:kindtag yield{:variant, tag, args}witnesses. Integrates with the existingCure.Types.RefinementSMT representation, so every narrowed type is something the SMT translator already understands.bind_pattern_vars/3inCure.Types.Checkerkeeps its existing precise element typing for tuples / lists / records / maps;PatternRefinementis exposed as a separate module for callers that want the narrowed scrutinee. v0.21.0 will routedo_infer({:pattern_match, ...})through it so match-arm bodies take advantage of the narrowing, and the path-sensitive refinement pass can propagate disjoint-tag witnesses across subsequent expressions in the same branch.
Numbers
- 3 new Elixir modules (
Cure.Compiler.Algebra,Cure.Compiler.AlgebraFormatter,Cure.Types.PatternRefinement); major extensions toCure.Compiler.Lexer,Cure.Compiler.Parser,Cure.Compiler.Codegen,Cure.Compiler.PatternCompiler,Cure.Compiler.Printer,Cure.Compiler.Formatter,Cure.Types.Checker, and the CLI. - 1016 tests pass (up from 970);
mix credo --strict: 0 issues across 136 files;mix cure.check.stdlib: 24/24;mix cure.check.examples: 30/30.
Deferred to v0.21.0
Type-checker refinement narrowing through binary patterns
(propagating size(n) into refinements on rest). Exhaustiveness
analysis for binary patterns (Cure.Types.PatternChecker).
Promotion of cure fmt --algebra to the default formatter.
Type-directed @derive extensions (Functor, Monoid, JSON). Full
Erlang-style destructuring of binaries in match expressions,
function heads, let bindings, and comprehension generators --
the segment AST from v0.20.0 is the groundwork; v0.21.0 adds
type-checker narrowing across binary segments and the
exhaustiveness analysis that goes with it.
Three parser/type gaps surfaced during the v0.20.0 cycle also
roll into v0.21.0: ADT constructor payloads must accept arbitrary
type expressions (including function arrows, so
type Callback = On((Int) -> Int) | Off parses and type-checks);
multi-line type ADT declarations with leading | on
continuation lines must parse (the indented layout currently
defeats parse_type_def); and let must support in-place
destructuring with the same depth and exhaustiveness guarantees
as match arms (ADT constructors, tuples, lists, records, and
maps, with E025 on non-exhaustive bindings).
Deferred to v0.22.0
Multi-statement lambda bodies -- brace-delimited
(fn (x) -> { stmt1; stmt2; final }) and end-terminated
(fn (x) ->\n stmt1\n stmt2\nend) block forms for anonymous
functions embedded in argument lists where the existing
indented-block form is not usable.
Deferred to v0.23.0
Remote package-registry index service, publication signing, and
Hex.pm cross-publishing -- a Cure.Project.Registry HTTP client
against a read-only index protocol, Ed25519 archive signing, and
a cure publish --hex export path.
Implemented: v0.19.0 -- Bring the Furniture
Ergonomics, proofs, and the first half of a registry.
Language additions
proofcontainers -- newproof Name.Pathkeyword; every binding inside must returnEq(T, a, b)or a refinement witness. Enforced asE026.assert_type expr : T-- compile-time type assertion that is stripped at codegen. Mismatches surface asE027.- Record field defaults --
rec Person\n name: String = ""-- construction merges declared defaults with user-supplied fields; default/declared mismatches emitE028. @derive(Show, Eq, Ord)wired end-to-end onrecthroughCure.Types.Deriveplus the codegen's expansion pass.- Multi-head cons patterns
[a, b, c | rest]desugar to right-associated cons cells.
Standard library additions
Std.Proof-- reflexivity laws (plus_zero,zero_plus,plus_comm,append_nil,map_id).Std.Gen-- tiny stateless generator API (int_in,bool,one_of,list_int,list_of_int) backed by:rand.Std.Iter-- lazy iterator protocol; constructorsempty,from_list,range; consumersfold,take,to_list.Std.Test.forall/3-- property-based runner.
Totality
Cure.Types.Totality.check_mutual/1-- Tarjan SCC analysis; cycles without a structural-decrease proof raiseE029.
Packaging
Cure.Project.Version-- SemVer + constraint parser (~>,>=,<=,<,>,==, compoundand); MAJOR.MINOR is shorthand for MAJOR.MINOR.0.Cure.Project.Resolver-- deterministic backtracking resolver over a local workspace; conflicts surface asE030. Remote index service deferred to v0.20.0.
Error catalog
Five new codes E026-E030.
Numbers
- 2 new Elixir modules (
Cure.Project.Version,Cure.Project.Resolver); major extensions toTotality,Derive,Codegen,Parser,Checker, andType. - 3 new stdlib modules (
Std.Proof,Std.Gen,Std.Iter); 24 total. - 4 new examples, 1 new doc (
PROOFS.md). - ~970 tests (up from 923);
mix credo --strict: 0 issues;mix cure.check.stdlib: 24/24;mix cure.check.examples: 30/30.
Implemented: v0.18.0 -- Deep Destructuring
match and let grow up. Pattern matching now descends through
arbitrary nesting across tuples, lists (cons and fixed), maps,
records, and ADT constructors. The previous implementation quietly
miscompiled map patterns (wrong Erlang abstract form) and never
recursed into nested shapes; v0.18.0 replaces it wholesale.
Pattern engine
Cure.Compiler.PatternCompiler-- dedicated pattern-to-Erlang translator, separated from expression codegen. Every pattern node is dispatched through a single recursive entry point.- Map patterns emit
map_field_exact(:=), notmap_field_assoc(=>). Matching against a map pattern actually matches now. - Record patterns lower to map patterns with
__struct__ := :tagand per-field exact entries; unspecified fields are open-matched. - Field punning:
Point{x, y}is shorthand forPoint{x: x, y: y}. - Pin operator
^xcompares against a previously-bound value; the compiler lowers it to a syntheticandalsoequality guard. - Repeated variable occurrences in the same pattern emit equality
guards too:
%[x, x]matches only when both slots are equal. - ADT constructors, cons, and tuple patterns recurse correctly through nested sub-patterns.
Type checker
bind_pattern_vars/3rewritten with deep recursion. Tuple patterns narrow element-by-element; map and record patterns resolve per-field through the record schema or the map value type.- Maranget-style nested-exhaustiveness pass in
Cure.Types.PatternChecker.check_nested/2reports concrete missing witnesses (for example"%[Error(_)]") under codeE025. The flat classifier from v0.11.0 remains as a fast path. - New error codes
E021-E025(unknown record field, record-field type mismatch, non-literal map-pattern key, unbound pin, nested non-exhaustive match).
Parser
^lexed as:caret; parser now produces{:pin, meta, [inner]}for the pin operator.- Field-punning in record patterns and map pairs: a bare identifier
followed by
,or}desugars toname: name.
Standard library and examples
- New
Std.Matchmodule -- eight destructuring helpers (unpack_pair/1,fst/1,snd/1,head_tail/2,uncons/1,first_two/2,unwrap_ok/2,unwrap_some/2). Std.List.uncons/1,Std.List.split_first/2added using cons destructuring.examples/destructuring.cure,examples/json_tree.cure-- end-to-end showcases.examples/pattern_guards.cureextended with record patterns, field-punning, and nested ADT destructuring.
Documentation
docs/PATTERNS.md-- authoritative pattern matching reference (Cure surface syntax, Erlang abstract-form mapping, binding semantics, exhaustiveness behaviour).docs/LANGUAGE_SPEC.mdpattern-matching section rewritten from a 12-line stub to a full reference.docs/TUTORIAL.md-- new Chapter 4 "Destructuring inmatch"; later chapters renumbered.
Numbers
- 1 new Elixir module (
Cure.Compiler.PatternCompiler, ~480 LOC); rewrites insideCodegen,Checker, andPatternChecker. - 1 new stdlib module (
Std.Match), 1 extended (Std.List); 21 stdlib modules total. - 2 new examples, 1 extended.
- 923 tests passing (3 doctests, 920 tests) -- 26 new, 0 regressions.
mix credo --strict: 0 issues across 117 source files.mix cure.check.stdlib: 21/21 clean;mix cure.check.examples: 26/26 clean.
Implemented: v0.17.0 -- Proofs & Polish
The dependent-typing core grows up; the everyday UX catches up. Three themes land together: dependent-type maturation, friction-free UX, and ecosystem groundwork.
Dependent-type maturation
- Sigma types
Sigma(name: T1, T2)-- dependent pairs with componentwise subtyping; round-trip to plain runtime tuples. - Pi types -- dependent function types with
:explicit,:implicit, and:erasedparameter modes and AST-based return types. Cure.Types.Reduce-- terminating normaliser for type-level arithmetic, booleans, comparisons, and pair projection. Trivial arithmetic never touches the SMT solver.- Propositional equality
Eq(T, a, b)with constructorrefland eliminatorrewrite; runtime-erased via:cure_refl. Cure.Types.Unify-- first-order unification with occurs check for implicit-argument inference;:unification_tracepipeline event rendered in LSP hover and CLI error output.Cure.Types.Holes--?nameand??placeholders with goal-type and local-context reporting via:hole_goal.Cure.Types.Totality-- coverage + structural-recursion analysis;:total | :partial | :unknownclassification;@total truedecorator.Cure.Types.PathRefinement-- path-sensitive refinement flow alongif/matchguards.Std.Equal--refl,sym,trans,cong.Std.Refine--NonZero,Positive,Negative,NonNegative,Percentage,Probability, and predicate helpers.
Friction-free UX
Cure.REPL-- full rewrite. Multi-line input, meta-commands:t,:doc,:effects,:load,:reload,:use,:holes,:env,:reset,:fmt,:help,:quit. History persisted to~/.cure_history.Cure.Watch--cure watchrecompiles, type-checks, or runs tests on every save with a 200 ms debounce.Cure.LSP.Server-- inlay hints, signature help, formatting,prepareRename/ rename, code lenses, semantic tokens, workspace symbols.- Error catalog codes
E011-E020covering implicit-argument failures, sigma destructuring, totality, unfilled holes, refinement counterexamples, dependent-type mismatches, equality-proof mismatches, and doctests. cure new <name> [--lib | --app | --fsm]with three templates.Cure.locklockfile;cure deps update,cure deps tree; git-based dependency resolution inCure.Project.resolve_git_dep/2.cure bench-- benchmark runner forbench/**/*.cure.cure test --filter PATTERN --doctests;Cure.Doc.Doctestsharvestscure>/=>doctests from##blocks.
Ecosystem groundwork
- MIT
LICENSEat repo root. - Complete
mix.exspackageblock for Hex publication, including docs extras. vscode-cure/-- TypeScript / LSP VS Code extension scaffold with TextMate grammar and language configuration.docs/PACKAGE_REGISTRY.md-- design document for the v0.19.0+ package registry.docs/TUTORIAL.md-- ten progressive chapters.docs/DEPENDENT_TYPES.md-- full guide for the new type-system features.
Numbers
- ~11 new Elixir modules, ~12 new test files, ~155 new tests (830 total, 0 failures). Zero credo issues in strict mode.
- 2 new stdlib
.curemodules (Std.Equal,Std.Refine); 20 total. - 7 new examples, one for each major feature.
mix checkruns 20 stdlib + 24 example regressions in CI.
Implemented: v0.16.0
Finitomata-inspired FSM rewrite. FSM definition and transition logic now
coexist in the same .cure file via callback mode; simple mode stays
backward-compatible.
Dual-mode compilation
- Callback mode -- FSMs with an
on_transitionblock compile toGenServer-based modules with embedded transition tables, user-definedon_transition/4dispatch, and lifecycle callbacks. - Simple mode (backward-compatible) --
gen_statemcodegen now includestransitions/0andallowed/2introspection, hard-event auto-fire vianext_eventactions, and soft-event silent catch-all clauses. Cure.Compilerpipeline handles both modes transparently.
Event suffixes and lifecycle callbacks
- Hard events
event!auto-fire when the FSM enters a state where the event is the sole outgoing transition. The compiler verifies the constraint. - Soft events
event?silently swallow failed transitions without logging or callingon_failure. on_enter,on_exit,on_failure, andon_timerlifecycle callbacks.@timer <ms>annotation for periodicon_timerinvocations.
Introspection and tooling
transitions/0,allowed/2,allowed?/2,responds?/2-- runtime-introspectable FSM API;Cure.FSM.Runtime.allowed?/3andresponds?/3delegation.- LSP: FSM transitions as children in the symbol tree with event-suffix labels; lifecycle callbacks in hover; FSM callback completions inside FSM blocks.
- MCP:
analyze_fsmreports compilation mode, timer, event kinds with suffixes, and callback blocks. - Printer emits event suffixes and
@timerannotations. examples/cure_turnstile/rewritten to use callback mode.
Implemented: v0.15.0
Effect system, documentation generator, developer experience improvements, and full record type support with functional update syntax.
Phase 1: Effect system
Track side effects in the type system. Pure functions can be aggressively optimized; effectful functions are clearly marked.
- Effect-annotated function types --
{:effun, params, ret, effects}type withMapSetof effect kinds::io,:state,:exception,:spawn,:extern. - Syntax:
! Effect-- optional effect annotations after return types:fn read(path: String) -> String ! Io. Inferred by default. - Effect inference --
Cure.Types.Effectsmodule walks function bodies, classifies effects from@externtargets, keywords (send,spawn,throw,receive), and known stdlib functions. Transitive propagation. - Effect subtyping -- pure functions are subtypes of effectful functions with the same signature. Effect sets follow subset ordering.
- Optimizer integration --
Inlinepass uses effect-grounded purity check instead of ad-hoc function name lists. - LSP hover -- shows inferred effects alongside function signatures.
- Pipeline events --
{:type_checker, :effects_inferred, ...}and{:type_checker, :effect_error, ...}events.
Phase 2: Documentation generator
Extract function signatures, type annotations, and doc comments into browsable HTML documentation.
##doc comments -- lexer emits:doc_commenttokens for double-hash lines; parser attaches them as:docmetadata on definitions.Cure.Doc.Extractor-- extracts structured doc maps from module ASTs: module name, module doc, functions with signatures/docs/effects, protocols, types.Cure.Doc.HTMLGenerator-- renders standalone HTML pages with syntax-highlighted signatures, effect badges, dark/light mode CSS.cure doc-- CLI subcommand generates HTML docs for.curefiles.- Stdlib documented -- all 18 stdlib modules annotated with
##doc comments.
Phase 3: Developer experience
cure repl-- minimal interactive session backed bycompile_and_load.cure fmt-- source formatter usingCure.Compiler.Printer.- Unused variable tracking --
Envtracks variable usage; foundation for E007 warnings. - Error catalog E006-E010 -- Effect Violation, Unused Variable, Undocumented Public Function, Unreachable Clause, Missing Effect Annotation.
Phase 4: Records
Full record type support: definitions, typed field access, and functional update syntax.
- Named type representation --
Type.resolve/1now returns{:named, "TypeName"}for user-defined record/ADT names instead of:any. This carries the name through the type checker so field schemas are accessible during inference. - Field schema registration -- the checker's first pass registers each
recdefinition's field types inEnv.types. Field accessp.xon aPoint-typed value infers the declared field type (Int) rather thanAny. - Record construction typing --
Point{x: 1, y: 2}infers type{:named, "Point"}and the codegen emits a BEAM map literal with a__struct__key. - Record update syntax --
TypeName{base | field: val, ...}produces a modified copy. Only listed fields change; all others are preserved. Compiles to the BEAM map-update instruction (Map#{key := val}). The parser detects update vs. construction by probing for|after the first sub-expression, rewinding if not found. - Type checker integration -- override field values are checked against the registered schema. Wrong field types produce a compile-time error.
- 678 tests. Zero Credo issues. Zero compilation warnings. 54 Elixir source files.
Implemented: v0.14.0
Package management, deeper type tracking, LSP type holes, incremental parsing, cross-module protocol dispatch, and extended testing.
Phase 1: Package management
Cure gets its own project file and dependency resolution.
Cure.tomlproject file with[project],[dependencies],[compiler]sections. Parsed atcure compile/cure runtime.cure init <name>scaffolds the project:Cure.toml,lib/,test/, and a hello-worldlib/main.cure.cure depsresolves and compiles path-based dependencies, adding their.beamfiles to the code path.
Phase 2: Deep dependent type tracking
Move beyond call-site literal substitution into full value tracking.
- Type tracking through
letbindings -- bound variables carry their inferred types through the type environment for subsequent call-site checking. - Path-sensitive refinement -- inside
if x > 0 then safe_div(10, x) else 0, the checker applies the branch condition as a type assumption in the then-branch and its negation in the else-branch. - SMT context accumulation -- guard conditions accumulate as type refinements in the environment as the checker descends into branches.
Phase 3: LSP type holes
Interactive type inference via placeholder types.
_in type annotation positions resolves to{:type_hole, :infer}and is compatible with any type (subtype?(_, T)andsubtype?(T, _)are always true).- The type checker infers the hole's type from the function body and emits
a
:type_hole_inferredpipeline event with the inferred type.
Phase 4: LSP incremental compilation
Performance optimization for large projects.
- Version-based AST cache -- on
textDocument/didChange, re-parse and re-diagnostics are skipped when the incoming document version matches the cached entry, avoiding redundant work during rapid edits.
Phase 5: Cross-module protocol dispatch
Complete the protocol story.
- Registry-aware call resolution -- when the codegen encounters an unresolved function call, it queries the global protocol registry; matching methods emit remote calls to the providing module.
- Runtime fallback -- calls not found in the registry fall back to local call resolution, allowing graceful degradation when compilation order does not guarantee visibility.
Phase 6: Testing infrastructure
First-class testing support.
cure testdiscovers.curetest files intest/, compiles each file, runs alltest_*functions, and reports per-test pass/fail counts.Std.Testwithassert,assert_eq,assert_ne,assert_gt,assert_ltassertion primitives.- 670 tests. Zero Credo issues. Zero compilation warnings. 49 Elixir source files. 18 stdlib modules.
Implemented: v0.13.0
Dependent types get teeth. The compiler now proves constraints at call sites.
- Dependent type verification at call sites -- functions with
whenguards register as constrained types. At call sites, the compiler substitutes literals into the guard and sends the predicate to Z3. If unsatisfiable, you get a warning with a counterexample. - Cross-module protocol registry -- global ETS table backing
register_impl,lookup_impl,has_impl?. Everyimplblock registers during compilation, enabling cross-module dispatch resolution. - LSP: go-to-definition -- jump to function and module definitions.
- LSP: document symbols -- hierarchical outline of modules, functions, protocols, FSMs.
- LSP: code actions -- quick-fix for non-exhaustive matches (add wildcard pattern) and did-you-mean suggestions for unbound variables.
- 5 optimizer passes -- added inline (small pure non-recursive functions) and guard simplify (algebraic boolean rules).
- FSM transition guards and actions -- transitions support
whenguards anddoactions:State --event when cond do action--> NextState. - Structured error catalog -- error codes E001-E005 with
cure explain. Errors show source location with caret display. - 4 new stdlib modules -- Map (14 fns), Set (11 fns), Option (10 fns), Functor (1 fn). Total: 17 modules, ~200 functions.
- Std.Test -- assertion primitives (
assert,assert_eq,assert_ne,assert_gt,assert_lt). Total: 18 modules. cure test-- run test files fromtest/, compile and executetest_*functions.cure init-- scaffold a new project withCure.toml.cure explain-- look up error code explanations.- 606 tests. Zero Credo issues. Zero compilation warnings. 48 Elixir source files.
Implemented: v0.12.0
The first complete release. Full compilation pipeline from source to .beam.
- Compilation pipeline -- lexer, parser, type checker, code generator. Each stage emits structured events via PubSub.
- Bidirectional type system -- local type inference with annotation
checking. Refinement types with constraints (
{x: Int | x > 0}). - SMT integration -- Z3 solver verifies refinement type constraints at compile time.
- First-class FSMs --
fsmblocks compile to OTPgen_statemmodules with automatic reachability, deadlock freedom, and terminal state verification. - Protocol system --
proto/implwith guard-based dispatch. Clauses ordered by type specificity (Bool before Atom). - Self-hosted stdlib -- 12 modules written in Cure: Core, List, Math, String, Pair, Io, Show, Eq, Ord, System, Fsm, Result.
- LSP server -- diagnostics, hover, keyword completions over stdio.
- MCP server -- 7 tools for AI integration via JSON-RPC 2.0.
- CLI --
cure compile,cure run,cure check,cure lsp,cure stdlib,cure version. - Optimizer -- 3 passes: constant fold, dead code elimination, pipe inline.
- 569 tests. Zero Credo issues in strict mode. Zero compilation warnings.
Future
These are not scheduled but are on the long-term radar.
Typed hot code upgrades. cure release --upgrade-from to emit
a typed .appup file with SMT-verified state migrations
(@migration, E057/E058). Time-boxed in v0.31.0 and rescheduled
for v0.32.0; the appup/relup machinery is large enough that it
deserves its own release cycle.
Automatic PGO instrumentation. v0.31.0 ships the data layer,
recorder, and consumer; the codegen pass that injects
Cure.PGO.Recorder.tick/1 at every function entry (so users get
profiles by default with cure run --record-profile) is the
natural next step.
Pattern-trigger SMT encoding. v0.31.0 ships the
generate_query/3 pgo_hint parameter and the hot-path
arith.solver=6 switch; richer forall ... :pattern emission for
refinement obligations is the natural follow-up.
Broader IDE support. First-class Helix and Zed configurations.
An upgraded VS Code extension that tracks the current LSP surface
(inlay hints, signature help, code lenses, semantic tokens,
workspace symbols).
REPL-level hot reload. Recompile-and-rebind on every file save
for long-running REPL sessions, closing the loop between editing
.cure files and the running REPL environment.