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
- Concurrent Builds
- Watch Mode and File Builds
- VS Code and Language Server Support
- Documentation Generation
- Inspecting Signatures
- Projects and Package Management
- Language Changes
- Standard Library and Runtime
- Testing
- Diagnostics and Debugging
- Toolchain and Platform Changes
- Breaking Changes and Migration Notes
- Bug Fixes
- Thank You
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:
- A file whose mtime changed but whose contents did not change only needs a
fast whole-file content hash. If that hash matches the source hash stored in
the
.tyinterface, there is nothing further to compile. Acton also refreshes the cached file metadata in the.tyfile, so the next check can use the metadata fast path again without hashing the.actfile. - A body-only edit changes implementation hashes, so Acton can refresh back passes, generated code, and affected tests without re-typechecking downstream modules. Avoiding redundant typechecking here saves a lot of time, since typechecking is typically the dominant compiler pass.
- A public contract edit, such as changing a parameter type, return type, class field type, or protocol method signature, changes the public type-signature hash and re-triggers typechecking only for downstream code that depends on that name.
- A generated C or header file that is missing or has the wrong implementation
hash can be regenerated while keeping the valid
.tyinterface and avoiding unnecessary front-pass work. - Downstream importers are tracked at the granularity of the names they use, not as a dependency on the whole imported module. Editing one exported function does not force rebuilds of importers that only use other names from the same module.
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:
u1,u8,i8, and builtini64support.repr(None)andtype(None)now work.afteraccepts any expression, including method calls and actor methods.list.count()was added.re.match()accepts an optionalstart_pos.- JSON encode/decode supports list values at the document root.
xml.Node.encode(pretty=True)produces pretty XML output.- URI
quote()andunquote()helpers were added.
Standard Library and Runtime
The standard library and runtime received a broad set of fixes and additions. Highlights include:
- HTTPS server and TLS listener support.
- HTTP request and response validation for malformed input.
- HTTP response callbacks now pass headers and treat defaults case-insensitively.
- Logging accepts optional values in structured data dictionaries.
file.mkdir()andfile.rmdir()are idempotent.- Temporary directories use
mkdtempsemantics. - XML parsing and encoding fixes for namespaces, CDATA, UTF-8, special characters, prefixed attributes, and error types.
- Regular expression Unicode handling fixes.
process.pid()is safe after the process has exited.- The RTS message waiting path was fixed so actors are not missed between waiting states and message delivery.
- One-byte ASCII strings are prebuilt to reduce allocation churn.
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:
- architecture-based default CPU settings for Zig targets
- generated
build.zigandbuild.zig.zondependency wiring moved into the compiler - generated
build.zig.zonfiles use canonical dependency roots - transitive Zig dependency names are isolated to avoid collisions
- container image builds for Acton Debian packages
- Debian package builds moved to Ubuntu 22.04
- macOS CI updated for newer macOS runners, with workarounds for macOS 26.4 command-line tools breaking Zig
- Linux container image builds from Debian packages
- GitHub Actions dependencies refreshed
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:
- parsing fixes for strings, escaped braces, triple-quoted strings, negative integer literals, and unclosed string diagnostics
- type solver fixes for generic tuples, skolemized type variables, optional bounds, scoped constraints, row casts, and coerced index targets
- code generation fixes for C23 keywords, unboxed boolean logic, negative
literals, most-negative
i64, lambda lifting, CPS, closure conversion, and generated name collisions - optional handling fixes for
is not None, optional equality, optional narrowing in comprehension heads, tuple/list printing withNone, and optional logging - dependency and cache invalidation fixes for stale
.tyfiles, compiler version mismatches, missing name hashes, import renames, path overrides, transitive imports, generated output drift, syspath changes, and removed modules - standard library fixes for XML, JSON, HTTP, regex Unicode, UTF-8 string
invariants,
bytes,bytearray,argparse, process handling, and file paths containing spaces - runtime fixes around actor waiting, libuv file requests, and TLS coverage
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.