Cure v0.14.0: Real-World Readiness
by Aleksei Matiushkin
v0.12 built the body. v0.13 gave it teeth. v0.14 makes it usable.
The theme is real-world readiness. A language is real-world ready when programs can have dependencies, the type system follows values through function calls, and the editor tells you what types it inferred before you even ask. v0.14 delivers all three.
What changed
Six phases, each adding a capability the language never had.
1. Package management
Every Cure program was a single file or a manually orchestrated collection.
Not anymore. Cure now has Cure.toml project files:
[project]
name = "my_app"
version = "0.1.0"
[dependencies]
utils = { path = "../shared/utils" }
[compiler]
type_check = true
optimize = true
cure init my_project scaffolds a new project with Cure.toml, lib/,
and test/ directories. cure deps resolves dependencies – path-based
for now, with git support planned. Dependencies are compiled before the
main project, their .beam files added to the code path automatically.
The standard library is treated as an implicit dependency. cure stdlib
compiles it once; projects reference the compiled output.
2. Deep dependent type tracking
v0.13 verified guard constraints at call sites with literal arguments. v0.14 extends this to track value knowledge through the program:
Path-sensitive refinement. In if x > 0 then safe_head(v) else default,
the then-branch knows x > 0 and the else-branch knows not (x > 0).
The type checker creates refinement types per branch via
refine_env_from_condition, extending the guard-based refinement from
multi-clause functions (v0.13) to if/else and match arms.
SMT context accumulation. The checker maintains a stack of known constraints as it descends into expressions. At each call site, all accumulated constraints are asserted before checking the function’s precondition.
3. LSP type holes
The single most impactful developer experience feature. Write _ in any
type annotation position and the LSP tells you what goes there:
fn foo(x: _) -> _ = x + 1
The LSP immediately shows: parameter x is Int, return type is Int.
The code action “Fill type hole with Int” does the rest.
Type holes are parsed as {:type_hole, :infer} in the AST. During type
checking, when a hole is encountered, the checker infers the type that
would make the program well-typed and emits a :type_hole_inferred
pipeline event. The LSP reports these as hint diagnostics (severity 4)
and offers fill-in code actions.
4. LSP incremental compilation
The LSP now caches parsed ASTs per document URI and version number.
When didChange fires, if the version hasn’t changed, the reparse is
skipped entirely. This eliminates redundant work during rapid typing
and reduces Z3 invocations.
5. Cross-module protocol dispatch
v0.13 built the global protocol registry. v0.14 uses it for actual cross-module function calls.
When an unqualified call doesn’t match local functions, imports, or
variables, the codegen now checks the protocol registry. If a matching
protocol method exists, it emits a remote call to the registered module’s
dispatch function. If the registry has no entry at compile time (module
not yet compiled), a runtime lookup via ProtocolRegistry.lookup_impl
plus apply is generated as fallback.
6. Testing infrastructure
Cure programs can now be tested in Cure:
mod Std.Test
fn assert(condition: Bool) -> Atom
fn assert_eq(a: T, b: T) -> Atom
fn assert_ne(a: T, b: T) -> Atom
fn assert_gt(a: T, b: T) -> Atom
fn assert_lt(a: T, b: T) -> Atom
cure test compiles and runs .cure test files, reporting pass/fail
counts. Std.Test is the 18th stdlib module.
Elixir interop
Cure modules compile to BEAM modules with a Cure. prefix – mod Calculator
becomes :"Cure.Calculator". Calling from Elixir is direct:
defmodule CureExample do
def factorial(n), do: :"Cure.Calculator".factorial(n)
def greet(name), do: :"Cure.Greeter".hello(name)
end
The examples/cure_example/ directory is a full Mix project that depends on
Cure, compiles .cure files as a build step, and calls them from Elixir with
doctests proving the interop works.
The website
Cure now has a documentation website built with Phoenix, NimblePublisher,
and makeup_cure for syntax highlighting. Nord color theme (dark/light).
Eight content pages covering the language guide, type system, FSMs,
protocols, standard library, tooling, and roadmap. This blog.
The numbers
606 tests. Zero credo issues. Zero compilation warnings. 49 Elixir source files. 18 stdlib modules. ~210 exported functions. 11 example programs plus the interop project.
995 lines of new code across 13 files in this release cycle.
The Cure rewrite is functionally complete
After v0.14, the only unported legacy Erlang code is:
-
cure_type_optimizer.erl(7,196 lines) – bulk monomorphization and profile-guided optimization, largely superseded by the function inliner -
cure_runtime.erl(970 lines) – bytecode interpreter, not needed since Cure compiles directly to BEAM - Native helpers (697 lines) – replaced by stdlib + FFI
The rewrite is done.
Getting started
git clone https://github.com/Oeditus/cure.git
cd cure
mix deps.get && mix test
mix escript.build
./cure init my_project
./cure version # Cure 0.14.0
The repository is at github.com/Oeditus/cure.