Acton v0.27.0 Release Notes

Acton v0.27.0 is a large release, with around a thousand commits since v0.26.0.

This is a longer release cycle than we normally want. During this period, most day-to-day feedback has gone through Acton tip releases, which let us keep a tighter development loop while larger compiler and tooling work was still in motion. v0.27.0 closes that cycle. After this release, the goal is to return to a more regular cadence, roughly monthly, so future release notes should be smaller and easier to digest.

The main theme is that Acton is becoming a more complete, more capable, and more polished toolchain. v0.27.0 improves the daily user experience with concurrent builds, content-hash based caching, watch mode, editor diagnostics, completion, hover, signature help, and package management centered around deterministic Build.act metadata. The practical effect is a much snappier edit-build-test loop: Acton computes the build graph incredibly fast, so real work can start almost immediately, and when little or nothing has changed the compiler can reuse the unaffected parts of the build instead of dragging the whole project through the pipeline again.

There is more in the release as well: acton doc, acton sig, optional chaining, test caching, stress testing, the LLDB plugin, the Zig 0.15.2 upgrade, and a long list of language, standard library, runtime, and diagnostics fixes.

Table of Contents

Incremental Compilation

The compiler now has a content-hash based incremental compilation model. Previous builds are reused when their source, public interface, implementation, generated code, and dependency hashes are still valid.

This is not a timestamp-only cache. Acton records source file metadata such as file size, modification time, change time, and device / inode information where the platform provides it. If that metadata still matches, and the source file is strictly older than the cached .ty interface, the compiler can take a fast path without rereading the whole source file.

That metadata is only a fast path; content hashes are the authority. If the metadata is ambiguous, for example because the source and .ty file have the same visible timestamp, Acton reads the source and compares its hash instead. Cached .ty interface files record module source hashes, module public and implementation hashes, per-name source hashes, per-name public type-signature hashes, per-name implementation hashes, and the per-name dependency hashes that were used when a module was compiled.

"Public" here means the public type-signature hash of a top-level name. That is tracked separately from the implementation hash. Type checking downstream modules only depends on the public type signatures of their dependencies, so a downstream module only needs to re-typecheck when one of those public type-signature hashes changes. If only an implementation hash changes, Acton can skip downstream front passes and rerun only the back passes, code generation, and tests that depend on the changed implementation. Since implementation-only edits are the common case, this saves a lot of repeated work.

That distinction gives Acton much sharper rebuild decisions:

For example, suppose one module exports a small pricing helper and another module uses it:

# pricing.act
def tax(amount: int) -> int:
    return amount // 4

# invoice.act
import pricing

def total(subtotal: int) -> int:
    return subtotal + pricing.tax(subtotal)

Changing the body of pricing.tax() to a different tax calculation changes its implementation hash. The public signature (amount: int) -> int does not change, so invoice does not need to re-typecheck just to rediscover the same type fact. Acton still refreshes the downstream back passes or tests that depend on the changed implementation.

If the signature changes instead:

def tax(amount: float) -> float:
    return amount * 0.25

the public type-signature hash changes. Now affected downstream code, such as invoice.total, must re-run front passes because the type facts it depended on are different. The compiler tracks this per top-level name, so a signature change in one exported helper does not automatically poison every module that imports the same file.

Generated C and header files carry the module implementation hash. If those files are missing, or if the embedded hash no longer matches, Acton regenerates them even when the .ty interface is otherwise fresh.

The practical result is sharper rebuild decisions. The compiler can now ask: what changed, who depended on that exact public or implementation fact, and what is the smallest correct amount of work to repeat?

Related work: #2447, #2497, #2581, #2674, #2717, #2747, and #2752.

Concurrent Builds

Acton now schedules module compilation across the build graph. Once a module's dependencies are ready, it can run, and independent branches of the project can make progress in parallel.

Build graph construction is also separated from full parsing. Acton first discovers the project graph and import providers, then schedules parsing as its own stage. Independent modules can be parsed concurrently, and typechecking can start from parsed modules as soon as their dependencies and interfaces are ready.

The later compiler stages are split as well. Once a module has been parsed, kind checked, and type checked, its interface is available to downstream modules. Acton can then begin dependent typechecking while normalization, C generation, and other back-end work for the provider continue in parallel.

Ready modules are prioritized by critical path. This matters when a project has one long dependency branch and several shorter side branches: workers should not sit idle just because the scheduler picked easy leaves first.

The same scheduler is used by normal project builds, watch mode, and the language server. That keeps editor feedback and command-line builds on the same compiler model.

Useful controls added in this release include:

acton build --jobs 8
acton build --timing
acton build --no-progress
acton build --tty never

Related work: #2524, #2527, #2478, #2526, #2628, #2665, and #2731.

Watch Mode and File Builds

acton build --watch now keeps a build running and automatically rebuilds when source files change.

acton build --watch

Ordinary .act edits use the incremental compiler scheduler. Unchanged modules, dependencies, generated output, and test results can be reused instead of rebuilding the whole project. That makes acton test --watch a new way to work: keep the test runner open while editing, let content hashes identify which test results are still valid, and rerun only the tests affected by the code you just changed. The feedback loop stays focused on the code under active development instead of repeatedly paying for the whole project.

acton test --watch

Adding or removing source files, or changing Build.act, triggers project rediscovery so the build graph stays aligned with the project layout.

Single-file watch builds are also supported:

acton build src/main.act --watch
acton src/main.act --watch

This release also adds file-oriented builds through the main CLI:

acton build src/main.act

Related work: #2471, #2571, and #2592.

VS Code and Language Server Support

Acton now has editor support through the Acton language server.

The VS Code integration can show syntax and type errors inline, using the same parser and typechecker as acton build. The language server keeps editor state in memory so diagnostics, completion, and hover can respond without starting from scratch every time.

Completions now cover source-level names such as fields, properties, methods, and keyword arguments. For calls with named parameters, completion suggests the remaining parameter names and filters out names already supplied:

o.netinfra.router.create("r1", id=1, r)

can complete role= when that is the remaining matching keyword argument.

Hover uses the compiler-visible type environment and can show the resolved Acton type for a value, field, or method under the cursor, including docstrings when available.

Related work: #2487 and #2753.

Documentation Generation

acton doc is a new command for generating documentation from Acton source. It supports text, Markdown, and HTML output.

acton doc
acton doc src/foo.act -t
acton doc src/foo.act --md

Plain acton doc opens the HTML module index in a browser when a window environment is available. In a terminal it falls back to colored text output. Piped text output falls back to plain ASCII and honors NO_COLOR.

The generated HTML includes colorized types, links between modules and type definitions, and tooltips for generic types. The feature is backed by docstring support throughout the parser, AST, compiler name information, and interface files. Module docstrings before imports are now accepted, and declaration docstrings are preserved so tooling can show them later.

Related work: #2274, #2282, #2284, #2292, #2293, #2295, #2565, and #2736.

Inspecting Signatures

acton sig prints inferred type signatures using the same project-aware module and dependency resolution as acton build.

acton sig directory.Contact

This is useful when an error points at an attribute selection but the relevant type is defined in another module or dependency. acton sig foo.bar first tries to resolve foo.bar as a module. If that does not exist, it resolves bar as a public name inside module foo.

Because it uses normal build resolution, acton sig reads Build.act, fetches missing dependencies, honors local dependency overrides, and compiles the interfaces it needs without building the final executable.

acton sig --dep directory=../directory directory.Contact

Related work: #2746.

Projects and Package Management

Acton projects now use Build.act as the canonical project file. Root projects must declare both name and fingerprint.

name = "myproject"
fingerprint = 0x1234abcd5678ef00

The fingerprint represents project lineage. Content hashes identify a specific dependency archive; fingerprints identify the project itself so Acton can deduplicate dependencies and detect accidental identity conflicts. If a project is renamed or forked, it should get a new fingerprint.

The CLI can inspect and update Build.act as JSON:

acton spec dump
acton spec update build.json

Package management has also expanded:

acton pkg update
acton pkg search http
acton pkg add foo
acton pkg add foo --repo-url https://github.com/actonlang/foo --repo-ref main
acton pkg upgrade

Dependencies are pinned by archive URL and content hash in Build.act, then fetched and validated at build time. Local dependency overrides are available for development:

acton build --dep foo=../foo

Zig package dependencies can be managed from the Acton CLI as well:

acton zig-pkg add https://github.com/allyourcodebase/zlib/archive/refs/tags/1.3.1.tar.gz zlib --artifact z
acton zig-pkg remove zlib

This release also adds application package install and uninstall commands. Application packages are built from source in release mode and installed under ~/.acton/bin, with manifests under ~/.acton so Acton can track ownership of installed binaries.

acton install PACKAGE
acton uninstall PACKAGE

Related work: #2534, #2554, #2555, #2586, #2596, #2634, #2643, #2644, #2668, #2690, #2696, and #2758.

Language Changes

Optional Chaining and Forced Unwrapping

Optional chaining is a shorter way to keep None flowing through a single expression:

def residence_name(person: ?Person) -> ?str:
    return person?.residence?.name

If the value to the left of ?. is None, the whole expression evaluates to None. Indexing and slicing use ?[...]:

def first_port(config: ?dict[str, list[int]]) -> ?int:
    return config?.get("ports")?[0]

Forced unwrapping uses ! when absence is a broken invariant and should raise ValueError:

def required_residence_name(person: ?Person) -> str:
    return person!.residence!.name!.upper()

Attribute and method access use !.; indexing and slicing use ![...].

Related work: #2672 and #2726.

String Interpolation

String parsing was rewritten. Interpolation now works in ordinary string literals; an f prefix is no longer required.

name = "Acton"
print("hello {name}")
print(f"hello {name}")

Nested interpolation strings, escape sequences, slices, and complex expressions inside interpolations are now handled more consistently.

This is a backwards-incompatible change. A normal string containing literal curly braces must escape them or use a raw string:

print("{{literal braces}}")
print(r"{literal braces}")

Classic % formatting is still supported for now, and curly braces inside that form are not interpolated. The % style is expected to be deprecated in the future.

Related work: #2321.

Hashable and hash()

The old __hash__ special method has been replaced by a composable Hashable protocol and a hash() builtin.

Types implement:

def hash(self, hasher):
    self.x.hash(hasher)
    self.y.hash(hasher)

The builtin computes the final hash value:

h = hash(value)

Built-in types implement the protocol, and the implementation uses wyhash.

Related work: #2255 and #2304.

Other Language and Builtin Changes

Other notable language and builtin changes include:

Standard Library and Runtime

The standard library and runtime received a broad set of fixes and additions. Highlights include:

Related work includes #2576, #2698, #2689, #2693, #2695, and #2705.

Testing

acton test is significantly more capable in v0.27.0.

Test results can now be cached by content hash. A cached result is reusable when the test implementation, implementation dependencies, expected snapshot data, and run context still match.

acton test
acton test --no-cache

Cached failures are still shown by default. Cached successes stay hidden unless requested.

The test command also gained JSON output:

acton test --json
acton test list --json

Snapshot testing now uses snapshots/expected and snapshots/output, and acton test --accept is available as an alias for accepting snapshot or golden output.

Test capability tags can skip tests that require unavailable resources:

testing.require("tls")
acton test --tag tls

Stress testing is a new mode for finding concurrency bugs, especially in FFI and C integration code:

acton test stress
acton test stress --stress-workers 24 --max-time 30000
acton test stress --max-time 0

Stress mode runs multiple workers of the same test in one process, combines synchronized workers with drifted workers, and reports live per-worker progress in an interactive terminal.

Related work: #2598, #2651, #2655, #2683, #2691, #2692, and #2694.

Diagnostics and Debugging

Parser and syntax errors are now rendered through the Diagnose library with structured source locations, notes, and hints.

For example, single uppercase letters are reserved for type variables, and the parser now explains that directly:

[error Parse error]: Invalid name (reserved for type variables)

     +--> test/syntaxerrors/err41.act@1:7-1:8
     |
   1 | class Z(value):
     :       ^
     :       `- invalid name 'Z'
     :
     | Hint: Single upper case character (optionally followed by digits) are reserved for type variables. Use a longer name.
-----+

The compiler also gained many type-system diagnostics and correctness fixes, including better handling of generic tuples, scoped constraints, optional bounds, optional equality, class/protocol witnesses, Self, uninitialized attributes, and optional narrowing.

For native debugging, v0.27.0 adds an Acton LLDB plugin:

acton bt
acton locals
acton demangle
acton break src/main.act:42

The plugin shows Acton-demangled backtraces, argument values, local variables, boxed values, strings, objects, actors, classes, and generic value slots.

Generated C line directives are now behind --dbg-no-lines, so normal builds can preserve source mapping for more useful backtraces.

Related work: #2306, #2307, #2309, #2391, #2518, and #2520.

Toolchain and Platform Changes

The bundled Zig toolchain was upgraded to 0.15.2.

Acton now follows Zig optimization mode names more closely:

acton build --optimize Debug
acton build --optimize ReleaseFast
acton build --release
acton build --release=safe
acton build --release=small
acton build --release=fast

Debug is the default build mode. Bare --release and --optimize=release select ReleaseFast; explicit safe, small, and fast variants are still available.

Other toolchain and CI changes include:

Related work: #2362, #2590, #2594, #2708, #2756, and #2761.

Breaking Changes and Migration Notes

This release contains a few changes worth checking before upgrading.

Normal strings now interpolate {...}. Literal braces in ordinary strings must be escaped, or the string should be raw:

"{{"
"}}"
r"{"

The old __hash__ method is gone. Implement the Hashable protocol and use the hash() builtin.

Projects should have Build.act at the root. Root project files need name and fingerprint. Older Acton.toml and build.act.json based workflows should move to Build.act.

The old Acton-written CLI implementation has been removed. The Haskell compiler package now provides the acton executable directly, and the compiler, package manager, test runner, LSP, and scheduler share the same code paths.

--dev has been removed. Use --optimize Debug for debug builds or --release / --release=fast for optimized release builds.

Numpy support was removed from the compiler, parser, standard library, and tests.

Bug Fixes

The full changelog has the complete list, but several groups are worth calling out:

Thank You

Thank you to everyone who contributed code, tests, documentation, reviews, bug reports, and real-world projects during the v0.27.0 cycle.

The shape of this release is important because it changes what Acton feels like to work with day to day. The language and runtime keep improving, but the bigger step is that the surrounding developer experience is now much more complete: faster feedback, better editor support, stronger project workflows, clearer diagnostics, and more tooling that works from the same compiler view of the program.

That matters for adoption. A wider group of developers can now become productive with Acton without needing to understand as much of the compiler or project internals up front. v0.27.0 is a large release, but the direction after it is simple: keep making Acton easier to try, easier to trust, and faster to use in real projects.