Skip to content

Releases: ponylang/ponyc

0.63.1

11 Apr 15:07

Choose a tag to compare

Add style/docstring-leading-blank lint rule

pony-lint now flags docstrings where a blank line immediately follows the opening """. The first line of content should begin on the line right after the opening delimiter.

// Flagged — blank line after opening """
class Foo
  """

  Foo docstring.
  """

// Clean — content starts on the next line
class Foo
  """
  Foo docstring.
  """

Types and methods annotated with \nodoc\ are exempt, consistent with style/docstring-format.

Fix rare silent connection hangs on macOS and BSD

On macOS and BSD, if the OS failed to fully register an I/O event (due to resource exhaustion or an FD race), the failure was silently ignored. Actors waiting for network events that were never registered would hang indefinitely with no error. This could appear as connections that never complete, listeners that stop accepting, or timers that stop firing — with no indication of what went wrong.

The runtime now detects these registration failures and notifies the affected actor, which tears down cleanly — the same as any other I/O failure. Stdlib consumers like TCPConnection and TCPListener handle this automatically.

If you implement AsioEventNotify outside the stdlib, you can now detect registration failures with the new AsioEvent.errored predicate. Without handling it, a failure is silently ignored (the same behavior as before, but now you have the option to detect it):

be _event_notify(event: AsioEventID, flags: U32, arg: U32) =>
  if AsioEvent.errored(flags) then
    // Registration failed — tear down
    _close()
    return
  end
  // ... normal event handling

Fix rare silent connection hangs on Linux

On Linux, if the OS failed to register an I/O event (due to resource exhaustion or an FD race), the failure was silently ignored. Actors waiting for network events that were never registered would hang indefinitely with no error. This could appear as connections that never complete, listeners that stop accepting, or timers that stop firing — with no indication of what went wrong.

The runtime now detects these registration failures and notifies the affected actor, which tears down cleanly — the same as any other I/O failure. Stdlib consumers like TCPConnection and TCPListener handle this automatically.

Also fixes the ASIO backend init to correctly detect epoll_create1 and eventfd failures (previously checked for 0 instead of -1), and to clean up all resources on partial init failure.

LSP: Fix goto_definition range end

The Pony language server textDocument/definition response now returns a correct range.end position. Previously, it had an off-by-one error in the column value.

Add hierarchical configuration for pony-lint

pony-lint now supports .pony-lint.json files in subdirectories, not just at the project root. A subdirectory config overrides the root config for all files in that subtree, using the same JSON format.

For example, to turn off the style/package-docstring rule for everything under your examples/ directory, add an examples/.pony-lint.json:

{"rules": {"style/package-docstring": "off"}}

Precedence follows proximity — the nearest directory with a setting wins. Category entries (e.g., "style": "off") override parent rule-specific entries in that category. Omitting a rule from a subdirectory config defers to the parent, not the default.

Malformed subdirectory configs produce a lint/config-error diagnostic and fall through to the parent config — the subtree is still linted, just with the parent's rules.

Protect pony-lint against oversize configuration files

pony-lint now rejects .pony-lint.json files larger than 64 KB. With hierarchical configuration, each directory in a project can have its own config file — an unexpectedly large file could cause excessive memory consumption. Config files that exceed the limit produce a lint/config-error diagnostic with the file size and path.

Protect pony-lint against oversize ignore files

pony-lint now rejects .gitignore and .ignore files larger than 64 KB. With hierarchical ignore loading, each directory in a project can have its own ignore files — an unexpectedly large file could cause excessive memory consumption. Ignore files that exceed the limit or that cannot be opened produce a lint/ignore-error diagnostic with exit code 2.

Add LSP textDocument/documentHighlight support

The Pony language server now handles textDocument/documentHighlight requests. Placing the cursor on any symbol highlights all occurrences in the file, covering fields, locals, parameters, constructors, functions, behaviours, and type names.

Fix pony-lsp failures with some code constructs

Fixed go-to-definition failing for type arguments inside generic type aliases. For example, go-to-definition on String or U32 in Map[String, U32] now correctly navigates to their definitions. Previously, these positions returned no result.

Fix silent timer hangs on Linux

On Linux, if a timer system call failed (due to resource exhaustion or other system error), the failure was silently ignored. Actors waiting for timer notifications would hang indefinitely with no error — timers that should fire simply never did.

The runtime now detects timer setup and arming failures and notifies the affected actor, which tears down cleanly — the same as any other I/O failure. Stdlib consumers like Timers handle this automatically.

Add LSP textDocument/inlayHint support

pony-lsp now supports inlay hints. Editors that request textDocument/inlayHint will receive inline type annotations after the variable name for let and var declarations whose type is inferred rather than explicitly written.

Add LSP textDocument/references support

The Pony language server now handles textDocument/references requests. References searches across all packages in the workspace, and supports the includeDeclaration option to optionally include the definition site in the results.

Fix pony-lsp hanging after shutdown and exit

pony-lsp would hang indefinitely after receiving the LSP shutdown request followed by the exit notification. The process had to be killed manually. The exit handler now properly disposes all actors, allowing the runtime to shut down cleanly.

Fix pony-lsp hanging on startup on Windows

pony-lsp was unresponsive on Windows when launched by an editor. The LSP base protocol uses explicit \r\n sequences in message headers, but Windows opens stdout in text mode by default, which translates every \n to \r\n. This turned the header separator \r\n\r\n into \r\r\n\r\r\n on the wire — a sequence that LSP clients don't recognize, causing them to wait forever for the end of the headers.

pony-lsp now sets stdout to binary mode on Windows at startup, so \r\n is written to the pipe unchanged.

Fix type checking failure for interfaces with interdependent type parameters

Previously, interfaces with multiple type parameters where one parameter appeared as a type argument to the same interface would fail to type check:

interface State[S, I, O]
  fun val apply(state: S, input: I): (S, O)
  fun val bind[O2](next: State[S, O, O2]): State[S, I, O2]
Error:
type argument is outside its constraint
  argument: O #any
  constraint: O2 #any

The compiler replaced type variables one at a time during reification, so replacing S with its value could inadvertently transform a different parameter's constraint before that parameter was processed. This has been fixed by replacing all type variables in a single pass.

Fix incorrect code generation for this-> in lambda type parameters

When a lambda type used this-> for viewpoint adaptation (e.g., {(this->A)}), the compiler desugared it into an anonymous interface where this incorrectly referred to the interface's own receiver rather than the enclosing class's receiver. This caused wrong vtable dispatch, incorrect results, or segfaults when the lambda was forwarded to another function.

class Container[A: Any #read]
  fun box apply(f: {(this->A)}) =>
    f(_value)

The desugaring now correctly preserves the polymorphic behavior of this-> across different receiver capabilities.

Add LSP go-to-definition for type aliases

The Pony language server now supports go-to-definition on type alias names. For example, placing the cursor on Map in Map[String, U32] and invoking go-to-definition navigates to the type Map declaration in the standard library. Previously, go-to-definition only worked on the type arguments (String, U32) but not on the alias name itself.

This also works for local type aliases defined in the same package.

Fix soundness hole in match capture bindings

Match let bindings with viewpoint-adapted or generic types could bypass the compiler's capability checks, allowing creation of multiple iso references to the same object. A direct let x: Foo iso capture was correctly rejected, but let x: this->B iso and let x: this->T (where T could be iso) slipped through because viewpoint adaptation through box erases the ephemeral marker that the existing check relies on to detect unsoundness.

The compiler now checks whether a capture type has a capability that would change under aliasing (iso, trn, or a generic cap that includes them) and rejects the capture when the match expression isn't ephemeral. Previously-accepted code that hits this check was unsound and could segfault at runtime.

How to fix code broken by this change

Consume the match expression so the discriminee is ephemeral:

Before (unsound, now rejected):

match u
| let ut: T =>
  do_something(consume ut)
else
  (consume u, default())
end

After:

match consume u
| let ut: T =>
  do_something(consume ut)
| let uu: U =>
  (consume uu, default())
end

The else branch becomes | let uu: U => because u is consumed and no longer...

Read more

0.63.0

04 Apr 17:53

Choose a tag to compare

Fix use-after-free in IOCP ASIO system

We fixed a pair of use-after-free races in the Windows IOCP event system. A previous fix introduced a token mechanism to prevent IOCP callbacks from accessing freed events, but missed two windows where raw pointers could outlive the event they pointed to. One was between the callback and event destruction, the other between a queued message and event destruction.

This is the hard part that Pony protects you from. Concurrent access to mutable data across threads is genuinely difficult to get right, even when you have a mechanism designed specifically to handle it.

Remove support for Alpine 3.20

Alpine 3.20 has reached end-of-life. We no longer test against it or build ponyc releases for it.

Fix with tuple only processing first binding in build_with_dispose

When using a with block with a tuple pattern, only the first binding was processed for dispose-call generation and _ validation. Later bindings were silently skipped, which meant dispose was never called on them and _ in a later position was not rejected.

For example, the following code compiled without error even though _ is not allowed in a with block:

class D
  new create() => None
  fun dispose() => None

actor Main
  new create(env: Env) =>
    with (a, _) = (D.create(), D.create()) do
      None
    end

This now correctly produces an error: _ isn't allowed for a variable in a with block.

Additionally, valid tuple patterns like with (a, b) = (D.create(), D.create()) do ... end now correctly generate dispose calls for all bindings, not just the first.

Fix memory leak in Windows networking subsystem

Fixed a memory leak on Windows where an IOCP token's reference count was not decremented when a network send operation encountered backpressure. Over time, this could cause memory to grow unboundedly in programs with sustained network traffic.

Remove docgen pass

We've removed ponyc's built-in documentation generation pass. The --docs, -g, and --docs-public command-line flags no longer exist, and --pass docs is no longer a valid compilation limit.

Use pony-doc instead. It shipped in 0.61.0 as the replacement and has been the recommended tool since then. If you were using --docs-public, pony-doc generates public-only documentation by default. If you were using --docs to include private types, use pony-doc --include-private.

Fix spurious error when assigning to a field on an as cast in a try block

Assigning to a field on the result of an as expression inside a try block incorrectly produced an error about consumed identifiers:

class Wumpus
  var hunger: USize = 0

actor Main
  new create(env: Env) =>
    let a: (Wumpus | None) = Wumpus
    try
      (a as Wumpus).hunger = 1
    end
can't reassign to a consumed identifier in a try expression if there is a
partial call involved

The workaround was to use a match expression instead. This has been fixed. The as form now compiles correctly, including when chaining method calls before the field assignment (e.g., (a as Wumpus).some_method().hunger = 1).

Fix segfault when using Generator.map with PonyCheck shrinking

Using Generator.map to transform values from one type to another would segfault during shrinking when a property test failed. For example, this program would crash:

let gen = recover val
  Generators.u32().map[String]({(n: U32): String^ => n.string()})
end
PonyCheck.for_all[String](gen, h)(
  {(sample: String, ph: PropertyHelper) =>
    ph.assert_true(sample.size() > 0)
  })?

The underlying compiler bug affected any code where a lambda appeared inside an object literal inside a generic method and was then passed to another generic method. The lambda's apply method was silently omitted from the vtable, causing a segfault when called at runtime.

Add --shuffle option to PonyTest

PonyTest now has a --shuffle option that randomizes the order tests are dispatched. This catches a class of bug that's invisible under fixed ordering: test B passes, but only because test A ran first and left behind some state. You won't find out until someone removes test A and something breaks in a way that's hard to trace.

Use --shuffle for a random seed or --shuffle=SEED with a specific U64 seed for reproducibility. When shuffle is active, the seed is printed before any test output:

Test seed: 8675309

Grab that seed from your CI log and pass it back to reproduce the exact ordering:

./my-tests --shuffle=8675309

Shuffle applies to all scheduling modes. For CI environments that run tests sequentially to avoid resource contention, --sequential --shuffle is the recommended combination: stable runs without flakiness, and each run uses a different seed so test coupling surfaces over time instead of hiding forever.

--list --shuffle=SEED shows the test names in the order that seed would produce, so you can preview orderings without running anything.

Fix pony-lint blank-lines rule false positives on multi-line docstrings

The style/blank-lines rule incorrectly counted blank lines inside multi-line docstrings as blank lines between members. A method or field whose docstring contained blank lines (e.g., between paragraphs) would be flagged for having too many blank lines before the next member. The rule now correctly identifies where a docstring ends rather than using only its start line.

Fix FloatingPoint.frexp returning unsigned exponent

FloatingPoint.frexp (and its implementations on F32 and F64) returned the exponent as U32 when C's frexp writes a signed int. Negative exponents were silently reinterpreted as large positive values.

The return type is now (A, I32) instead of (A, U32). If you destructure the result and type the exponent, update it:

// Before
(let mantissa, let exp: U32) = my_float.frexp()

// After
(let mantissa, let exp: I32) = my_float.frexp()

Fix asymmetric NaN handling in F32/F64 min and max

F32.min and F64.min (and max) gave different results depending on which argument was NaN. F32.nan().min(5.0) returned 5.0, but F32(5.0).min(F32.nan()) returned NaN. The result of a min/max operation shouldn't depend on argument order.

The root cause was the conditional implementation if this < y then this else y end. IEEE 754 comparisons involving NaN always return false, so the else branch fires whenever this is NaN but not when only y is NaN.

Use LLVM intrinsics for NaN-propagating float min and max

Float min and max now use LLVM's llvm.minimum and llvm.maximum intrinsics instead of conditional comparisons. These implement IEEE 754-2019 semantics: if either operand is NaN, the result is NaN.

This is a breaking change. Code that relied on min/max to silently discard a NaN operand will now get NaN back. That said, the old behavior was order-dependent and unreliable, so anyone depending on it was already getting inconsistent results.

Before:

// Old behavior: result depended on argument order
F32.nan().min(F32(5.0)) // => 5.0
F32(5.0).min(F32.nan()) // => NaN

After:

// New behavior: NaN propagates regardless of position
F32.nan().min(F32(5.0)) // => NaN
F32(5.0).min(F32.nan()) // => NaN

[0.63.0] - 2026-04-04

Fixed

  • Fix use-after-free in IOCP ASIO system (PR #5091)
  • Fix with tuple only processing first binding in build_with_dispose (PR #5095)
  • Fix memory leak in Windows networking subsystem (PR #5096)
  • Fix spurious error when assigning to a field on an as cast in a try block (PR #5070)
  • Fix segfault when using Generator.map with PonyCheck shrinking (PR #5006)
  • Fix pony-lint blank-lines rule false positives on multi-line docstrings (PR #5109)
  • Fix FloatingPoint.frexp returning unsigned exponent (PR #5113)
  • Fix asymmetric NaN handling in F32/F64 min and max (PR #5114)

Added

  • Add --shuffle option to PonyTest (PR #5076)

Changed

  • Remove support for Alpine 3.20 (PR #5094)
  • Remove docgen pass (PR #5097)
  • Change FloatingPoint.frexp exponent return type from U32 to I32 (PR #5113)
  • Use LLVM intrinsics for NaN-propagating float min and max (PR #5114)

0.62.1

28 Mar 19:11

Choose a tag to compare

Fix IOCP use-after-free crash

The fix for this issue in 0.62.0 was incomplete. That fix checked for specific Windows error codes (ERROR_OPERATION_ABORTED and ERROR_NETNAME_DELETED) in the IOCP completion callback to detect orphaned I/O operations. However, Windows can deliver completions with other error codes after the socket is closed, and ERROR_NETNAME_DELETED can also arrive from legitimate remote peer disconnects — making error-code matching the wrong approach entirely.

The new fix addresses the root cause: IOCP completion callbacks can fire on Windows thread pool threads after the owning actor has destroyed the ASIO event via pony_asio_event_destroy, leaving the callback with a dangling pointer to freed memory.

Each ASIO event now allocates a small shared liveness token (iocp_token_t) containing an atomic dead flag and a reference count. Every in-flight IOCP operation holds a pointer to the token and increments the reference count. When pony_asio_event_destroy runs, it sets the dead flag (release store) before freeing the event. Completion callbacks check the dead flag (acquire load) before touching the event — if dead, they clean up the IOCP operation struct without accessing the freed event. The last callback to decrement the reference count to zero frees the token.

This correctly handles all error codes and all IOCP operation types (connect, accept, send, recv) without swallowing events the actor needs to see.

Fix pony-lint ignore matching on Windows

pony-lint's .gitignore and .ignore pattern matching failed on Windows because path separator handling was hardcoded to /. On Windows, where paths use \, ignore rules were silently ineffective — files that should have been skipped were linted, and anchored patterns like src/build/ never matched. Windows CI for tool tests has been added to prevent regressions.

Fix pony-lsp on Windows

pony-lsp's JSON-RPC initialization failed on Windows because filesystem paths containing backslashes were embedded directly into JSON strings, producing invalid escape sequences. The LSP file URI conversion also didn't handle Windows drive-letter paths correctly. Additionally, several directory-walking loops in the workspace manager and router used Path.dir to walk up to the filesystem root, terminating when the result was "." — which works on Unix but not on Windows, where Path.dir("C:") returns "C:" rather than ".", causing an infinite loop. Windows CI for tool tests has been added to prevent regressions.

Enforce documented maximum for --ponysuspendthreshold

The help text for --ponysuspendthreshold has always said the maximum value is 1000 ms, but the runtime never actually enforced it. You could pass any value and it would be accepted. Values above ~4294 would silently overflow during an internal conversion to CPU cycles, producing nonsensical thresholds.

The documented maximum of 1000 ms is now enforced. Passing a value above 1000 on the command line will produce an error. Values set via RuntimeOptions are clamped to 1000.

Fix compiler crash when calling methods on invalid shift expressions

The compiler would crash with a segmentation fault when a method call was chained onto a bit-shift expression with an oversized shift amount. For example, y.shr(33).string() where y is a U32 would crash instead of reporting the "shift amount greater than type width" error. The shift amount error was detected internally but the crash occurred before it could be reported. Standalone shift expressions like y.shr(33) were not affected and correctly produced an error message.

Enforce documented bounds for --ponycdinterval

The help text for --ponycdinterval has always said the minimum is 10 ms and the maximum is 1000 ms, but the runtime never actually enforced either bound on the command line. You could pass any non-negative value and it would be silently clamped deep in the cycle detector initialization. Values above ~2147 would also overflow during an internal conversion to CPU cycles, producing nonsensical detection intervals.

The documented bounds are now enforced. Passing a value outside [10, 1000] on the command line will produce an error. Values set via RuntimeOptions continue to be clamped to the valid range.

Add missing NULL checks for gen_expr results in gencall.c

Two additional code paths in the compiler could crash instead of reporting errors when a receiver sub-expression encountered a codegen error. These are the same class of bug as the recently fixed crash when calling methods on invalid shift expressions, but in the gen_funptr and gen_pattern_eq code paths. These are harder to trigger in practice but could cause segfaults in the LLVM optimizer if encountered.

Fix type system soundness hole

The compiler incorrectly accepted aliased type parameters (X!) as subtypes of their unaliased form when used inside arrow types. This allowed code that could duplicate iso references, breaking reference capability guarantees. For example, a function could take an aliased (tag) reference and return it as its original capability (potentially iso), giving you two references to something that should be unique.

Code that relied on this — likely by accident — will now get a type error. The most common pattern affected is reading a field into a local variable and returning it from a method with a this->A return type:

// Before: compiled but was unsound
class Container[A]
  var inner: A
  fun get(): this->A =>
    let tmp = inner
    consume tmp

// After: return the field directly instead of going through a local
class Container[A]
  var inner: A
  fun get(): this->A => inner

The intermediate let binding auto-aliases the type to this->A!, and consuming doesn't undo the alias. Returning the field directly avoids the aliasing entirely.

The persistent list in the collections/persistent package had four signatures using val->A! that relied on this bug. These have been changed to val->A. If you had code implementing the same interfaces with explicit val->A! types, change them to val->A.

Fix cap_isect_constraint returning incorrect capability for empty intersections

When a type parameter was constrained by an intersection of types with incompatible capabilities (e.g., ref and val), the compiler incorrectly computed the effective capability as #any (the universal set) instead of recognizing that no capability satisfies both constraints. This could cause the compiler to silently accept type parameter constraints that have no valid capability, rather than reporting an error.

The compiler now correctly detects empty capability intersections and reports "type parameter constraint has no valid capability" when the intersection of capabilities in a type parameter's constraint is empty. This also fixes incorrect results for iso intersected with #share and #share intersected with concrete capabilities outside its set, which were caused by missing break statements and an incorrect case in the capability intersection logic.

Fix incorrect pool free when tidying the reachability painter

The compiler’s reachability “painter” frees internal colour_record_t nodes when cleaning up. Those allocations must be returned to the same memory pool they came from. A bug passed a size expression as the first argument to POOL_FREE instead of the type name, so the wrong pool index was used when freeing those records.

That could corrupt the allocator’s bookkeeping during compilation in scenarios that exercise that cleanup path. The free now uses the correct type, matching how other POOL_FREE calls work in the codebase.

[0.62.1] - 2026-03-28

Fixed

  • Fix IOCP use-after-free crash (PR #5055)
  • Fix pony-lint FileNaming false positives on Windows (PR #5059)
  • Enforce documented maximum for --ponysuspendthreshold (PR #5061)
  • Fix compiler crash when calling methods on invalid shift expressions (PR #5063)
  • Enforce documented bounds for --ponycdinterval (PR #5065)
  • Fix code generation failure for iftype with union return type (PR #5066)
  • Add missing NULL checks for gen_expr results in gencall.c (PR #5067)
  • Fix type system soundness hole (PR #4963)
  • Fix cap_isect_constraint returning incorrect capability for empty intersections (PR #4999)
  • Fix POOL_FREE first argument in painter_tidy (PR #5082)

0.62.0

21 Mar 21:08

Choose a tag to compare

Fix incorrect CLOCK_MONOTONIC value on OpenBSD

On OpenBSD, CLOCK_MONOTONIC is defined as 3, not 4 like on FreeBSD and DragonFly BSD. Pony's time package was using 4 for all BSDs, which meant that on OpenBSD, every call to Time.nanos(), Time.millis(), and Time.micros() was actually reading CLOCK_THREAD_CPUTIME_ID — the CPU time consumed by the calling thread — instead of monotonic wall-clock time.

The practical result is that the Timers system, which relies on Time.nanos() for scheduling, would misfire on OpenBSD. Timers set for wall-clock durations would instead fire based on how much CPU time the scheduler thread had burned. For a mostly-idle program, a 60-second timer could take far longer than 60 seconds to fire. Any other code that depends on monotonic time would see similarly wrong values. Hilarity ensues.

Don't allow --path to override standard library

Previously, directories passed via --path on the ponyc command line were searched before the standard library when resolving package names. This meant a --path directory could contain a subdirectory that shadows a stdlib package. For example, --path /usr/lib would cause /usr/lib/debug/ to shadow the stdlib debug package, breaking any code that depends on it.

The standard library is now always searched first, before any --path entries. This is the same fix that was applied to PONYPATH in #3779.

If you were relying on --path to override a standard library package, that will no longer work.

Fix illegal instruction crashes in pony-lint, pony-lsp, and pony-doc

The tool build commands didn't pass --cpu to ponyc, so ponyc targeted the build machine's specific CPU via LLVMGetHostCPUName(). Release builds happen on CI machines that often have newer CPUs than user machines. When a user ran pony-lint, pony-lsp, or pony-doc on an older CPU, the binary could contain instructions their CPU doesn't support, resulting in a SIGILL crash.

The C/C++ side of the build already respected PONY_ARCH (e.g., arch=x86-64 targets the baseline x86-64 instruction set). The Pony code generation for the tools just wasn't wired up to use it. Now it is.

Fix failure to build pony tools with Homebrew

When building ponyc from source with Homebrew, the self-hosted tools (pony-lint, pony-lsp, pony-doc) failed to link because the embedded LLD linker couldn't find zlib. Homebrew installs zlib outside the default system linker search paths, and while CMake resolved the correct location for linking ponyc itself, that path wasn't forwarded to the ponyc invocations that compile the tools.

Fix memory leak

When a behavior was called through a trait reference and the concrete actor's parameter had a different trace-significant capability (e.g., the trait declared iso but the actor declared val), the ORCA garbage collector's reference counting was broken. The sender traced the parameter with one trace kind and the receiver traced with another, causing field reference counts to never reach zero. Objects reachable from the parameter were leaked.

trait tag Receiver
  be receive(b: SomeClass iso)

actor MyActor is Receiver
  be receive(b: SomeClass val) =>
    // b's fields were leaked when called through a Receiver reference
    None

The leak is not currently active because make_might_reference_actor — an optimization that was disabled as a safety net — masks it by forcing full tracing of all immutable objects. Without this fix, the leak would have become active as we started re-enabling that optimization.

This applies to simple nominal parameters as well as compound types — tuples, unions, and intersections — where the elements have different capabilities between the trait and concrete method.

The sender and receiver now use consistent tracing for each parameter, regardless of capability differences between the trait and concrete method.

Fix intermittent hang during runtime shutdown

There was a race condition in the scheduler shutdown sequence that could cause the runtime to hang indefinitely instead of exiting. When the runtime initiated shutdown, it woke all suspended scheduler threads and sent them a terminate message. But a thread could re-suspend before processing the terminate, and once other threads exited, the suspend loop's condition (active_scheduler_count <= thread_index) became permanently true — trapping the thread in an endless sleep cycle with nobody left to wake it.

This was more likely to trigger on slower systems (VMs, heavily loaded machines) where the timing window was wider, and only affected programs using multiple scheduler threads. The fix adds a shutdown flag that the suspend loop checks, preventing threads from re-entering sleep once shutdown has begun.

Fix tuple literals not matching correctly in union-of-tuples types

When a tuple literal was assigned to a union-of-tuples type, inner tuple elements that should have been boxed as union values were stored inline instead. This caused match to produce incorrect results — the match would fail to recognize valid data, or extract wrong values.

type DependencyOp is ((Link, ID) | (Unlink, ID) | Spawn)

type Change is (
  (TimeStamp, Principal, NOP, None)
  | (TimeStamp, Principal, Dependency, DependencyOp)
)

// This produced incorrect match results:
let cc: Change = ((0, 0), "bob", Dependency, (Link, "123"))

// Workaround was to construct the inner tuple separately:
let op: DependencyOp = (Link, "123")
let cc: Change = ((0, 0), "bob", Dependency, op)

Both forms now produce correct results. The fix also applies when tuple literals are passed directly as function arguments.

Fix false positive in _final send checking for generic classes

The compiler incorrectly rejected _final methods that called methods on generic classes instantiated with concrete type arguments. For example, calling a method on a Generic[Prim] where Generic has a trait-constrained type parameter would produce a spurious "_final cannot create actors or send messages" error, even though the concrete type is known and does not send messages.

The finaliser pass now resolves generic type parameters to their concrete types before analyzing method bodies for potential message sends. This expands the range of generic code accepted in _final methods, though some cases involving methods inherited through a provides chain (like Range) still produce false positives.

Fix #share capability constraint intersection

When intersecting a #share generic constraint with capabilities outside the #share set (like ref or box), the compiler incorrectly reported a non-empty intersection instead of recognizing that the capabilities are disjoint. This did not affect type safety — full subtyping checks always ran afterward — but the internal function returned incorrect intermediate results.

Fix use-after-free crash in IOCP runtime on Windows

When a Pony actor closed a TCP connection on Windows, pending I/O completions could fire after the connection's ASIO event and owning actor were already freed, causing an intermittent access violation crash. The runtime now detects these orphaned completions and cleans them up safely without touching freed memory.

Fix pony_os_ip_string returning NULL for valid IP addresses

The runtime function pony_os_ip_string had an inverted check on the result of inet_ntop. Since inet_ntop returns non-NULL on success, the inverted condition meant the function returned NULL for every valid IP address and only attempted to use the result buffer on failure.

Any code that called pony_os_ip_string with a valid IPv4 or IPv6 address got NULL back. The most visible downstream effect was in the ssl library, where X509.all_names() calls this function to convert IP SANs from certificates into strings. The NULL result produced empty strings, corrupting the names array and breaking hostname verification for certificates that use IP SANs.

[0.62.0] - 2026-03-21

Fixed

  • Fix incorrect CLOCK_MONOTONIC value on OpenBSD (PR #5035)
  • Fix illegal instruction crashes in pony-lint, pony-lsp, and pony-doc (PR #5041)
  • Fix failure to build pony tools with Homebrew (PR #5042)
  • Fix memory leak and re-enable immutable-send GC optimization (PR #4944)
  • Fix intermittent hang during runtime shutdown (PR #5037)
  • Fix tuple literals not matching correctly in union-of-tuples types (PR #4970)
  • Fix false positive in _final send checking for generic classes (PR #4982)
  • Fix #share capability constraint intersection (PR #4998)
  • Fix use-after-free crash in IOCP runtime on Windows (PR #5046)
  • Fix pony_os_ip_string returning NULL for valid IP addresses (PR #5049)

Changed

  • Don't allow --path to override standard library (PR #5040)

0.61.1

14 Mar 21:24

Choose a tag to compare

Fix pool_memalign crash due to insufficient alignment for AVX instructions

pool_memalign used malloc() for allocations smaller than 1024 bytes. On x86-64 Linux, malloc() only guarantees 16-byte alignment, but the Pony runtime uses SIMD instructions that require stronger alignment (32-byte for AVX, 64-byte for AVX-512). This caused a SIGSEGV on startup for any program built with -DUSE_POOL_MEMALIGN. Small allocations now use posix_memalign() with 64-byte alignment; large allocations continue to use the full 1024-byte POOL_ALIGN.

Fix assignment-indent to require RHS indented relative to assignment

The style/assignment-indent lint rule previously only checked that multiline assignment RHS started on the line after the =. It did not verify that the RHS was actually indented beyond the assignment line. Code like this was incorrectly accepted:

    let chunk =
    recover val
      Array[U8]
    end

The rule now flags RHS that starts on the next line but is not indented relative to the assignment. The correct form is:

    let chunk =
      recover val
        Array[U8]
      end

Fix tool install with use flags

When building ponyc with use flags (e.g., use=pool_memalign), make install failed because the tools (pony-lsp, pony-lint, pony-doc) were placed in a different output directory than ponyc. Tools now use the same suffixed output directory, and the install target handles missing tools gracefully.

Fix stack overflow in AST tree checker

The compiler's --checktree AST validation pass used deep mutual recursion that could overflow the default 1MB Windows stack when validating programs with deeply nested expression trees. The tree checker has been converted to use an iterative worklist instead.

This is unlikely to affect end users. The --checktree flag is a compiler development tool used primarily when building ponyc itself and running the test suite.

Return memory to the OS when freeing large pool allocations

The pool allocator now returns physical memory to the OS when freeing allocations larger than 1 MB. Previously, freed blocks were kept on the free list with their physical pages committed, meaning the RSS never decreased even after the memory was no longer needed. Now, the page-aligned interior of freed blocks is decommitted via madvise(MADV_DONTNEED) on POSIX or VirtualAlloc(MEM_RESET) on Windows. The virtual address space is preserved, so pages fault back in transparently on reuse.

This primarily benefits programs that make large temporary allocations (the compiler during compilation, programs with large arrays or strings). Programs whose memory usage is dominated by small allocations (< 1 MB) will see little change — per-size-class caching is a separate mechanism.

To preserve the old behavior (never decommit), build with make configure use=pool_retain.

Fix scheduler stats output for memory usage

Applications compiled against the runtime which prints periodic scheduler statistics will now display the correct memory usage values per scheduler and for messages "in-flight".

Fix compiler crash when object literal implements enclosing trait

The compiler crashed with an assertion failure when an object literal inside a trait method implemented the same trait. This code would crash the compiler regardless of whether the trait was used:

trait Printer
  fun double(): Printer =>
    object ref is Printer
      fun apply() => None
    end
  fun apply() => None

This is now accepted. The object literal correctly creates an anonymous class that implements the enclosing trait, and inherited methods that reference the object literal work as expected.

Fix TCPListener accept loop spin on persistent errors

The pony_os_accept FFI function returns a signed int, but TCPListener declared the return type as U32. When accept returned -1 to signal a persistent error (e.g., EMFILE — out of file descriptors), the accept loop treated it as "try again" and spun indefinitely, starving other actors of CPU time.

The FFI declaration now correctly uses I32, and the accept loop bails out on -1 instead of retrying. The ASIO event will re-notify the listener when the socket becomes readable, so no connections are lost.

Update to LLVM 21.1.8

We've updated the LLVM version used to build Pony from 18.1.8 to 21.1.8.

Compile-time string literal concatenation

The compiler now folds adjacent string literal concatenation at compile time, avoiding runtime allocation and copying. This works across chains that mix literals and variables — adjacent literals are merged while non-literal operands are left as runtime .add() calls. For example, "a" + "b" + x + "c" + "d" is folded to the equivalent of "ab".add(x).add("cd"), reducing four runtime concatenations down to two.

Exempt unsplittable string literals from line length rule

The style/line-length lint rule no longer flags lines where the only reason for exceeding 80 columns is a string literal that contains no spaces. Strings without spaces — URLs, file paths, qualified identifiers — cannot be meaningfully split across lines, so flagging them produced noise with no actionable fix.

Strings that contain spaces are still flagged because they can be split at space boundaries using compile-time string concatenation at zero runtime cost:

// Before: flagged, and splitting is awkward
let url = "https://github.com/ponylang/ponyc/blob/main/packages/builtin/string.pony"

// After: no longer flagged — the string has no spaces and can't be split

// Strings with spaces can still be split, so they remain flagged:
let msg = "This is a very long error message that should be split across multiple lines"

// Fix by splitting at spaces:
let msg =
  "This is a very long error message that should be split "
  + "across multiple lines"

Lines inside triple-quoted strings (docstrings) and lines containing """ delimiters are not eligible for this exemption — docstring content should be wrapped regardless of whether it contains spaces.

Fix compiler crash on return error

Previously, writing return error in a function body would crash the compiler with an assertion failure instead of producing a diagnostic error. The compiler now correctly reports that a return value cannot be a control statement.

Add --sysroot option

A new --sysroot option specifies the target system root. The compiler uses the sysroot to locate libc CRT objects and system libraries for the target platform. For native builds, the host root filesystem is used by default; for cross-compilation, common cross-toolchain locations are searched automatically.

Use embedded LLD for Linux targets

All Linux builds — both native and cross-compilation — now use the embedded LLD linker directly instead of invoking an external compiler driver via system(). For cross-compilation, this eliminates the requirement to have a target-specific GCC cross-compiler installed solely for linking.

The embedded LLD path activates automatically for any Linux target without --linker set. To use an external linker instead, pass --linker=<command> as an escape hatch to the legacy linking path. The --link-ldcmd flag is ignored when using embedded LLD; use --linker instead to get legacy behavior.

Fix compilation not correctly triggered upon startup with pony-lsp

It often happens that, during pony-lsp startup, compilation would be triggered before the necessary settings are being provided (e.g. ponypath), so the initial parsing and type-checking of pony-lsp failed. It needed to be retriggered by opening another file or save a currently open file.

This has been fixed by enqueue compilations until settings are provided and only run them if either settings have been provided or we can be sure that no settings can be provided (e.g. if the lsp-client does not implement the necessary protocol bits).

Native macOS builds now use embedded LLD

macOS linking now uses the embedded LLD linker (Mach-O driver) instead of invoking the system ld command. This is part of the ongoing work to eliminate external linker dependencies across all platforms.

If the embedded linker causes issues, use --linker=ld to fall back to the system linker.

Native Windows builds now use embedded LLD

Windows linking now uses the embedded LLD linker (COFF driver) instead of invoking the MSVC link.exe command. This is part of the ongoing work to eliminate external linker dependencies across all platforms.

If the embedded linker causes issues, use --linker=<path-to-link.exe> to fall back to the system linker.

Add OpenBSD 7.8 as a tier 3 CI target

OpenBSD is now a tier 3 (best-effort) platform for ponyc. A weekly CI job builds and tests the compiler, standard library, and tools (pony-doc, pony-lint, pony-lsp) on OpenBSD 7.8.

The tools previously couldn't find the standard library on OpenBSD because they hardcoded their binary name when looking up the executable directory. This works on Linux, macOS, and FreeBSD, which have platform APIs that ignore argv0, but fails on OpenBSD where argv0 is the only mechanism for resolving the executable path. All three tools now pass the real argv[0] from the runtime.

libponyc-standalone is now built on OpenBSD, so programs can link against the compiler as a library on this platform.

Add safety/exhaustive-match lint rule to pony-lint

pony-lint now flags exhaustive match expressions that don't use the \exhaustive\ annotation. Without \exhaustive\, adding a new variant to a union type compiles silently — the compiler injects else None for missing cases instead of raising an error. The new safety/exhaustive-match rule catches these matches so you can add the annotation and get compile-time protection against incomplete case handling.

The rule is enabled by default. To suppress it for a specific match, use // pony-lint: allow safety/exhaustive-match on...

Read more

0.61.0

28 Feb 20:52

Choose a tag to compare

Fix pony-lsp inability to find the standard library

Previously, pony-lsp was unable to locate the Pony standard library on its own. It relied entirely on the PONYPATH environment variable to find packages like builtin. This meant that while the VS Code extension could work around the issue by configuring the path explicitly, other editors using pony-lsp would fail with errors like "couldn't locate this path in file builtin".

pony-lsp now automatically discovers the standard library by finding its own executable directory and searching for packages relative to it — the same approach that ponyc uses. Since pony-lsp is installed alongside ponyc, the standard library is found without any manual configuration, making pony-lsp work out of the box with any editor.

Fix persistent HashMap returning incorrect results for None values

The persistent HashMap used None as an internal sentinel to signal "key not found" in its lookup methods. This collided with user value types that include None (e.g., Map[String, (String | None)]). Using HashMap with a None value could lead to errors in user code as "it was none" and "it wasn't present" were impossible to distinguish.

The internal lookup methods now use error instead of None to signal a missing key, so all value types work correctly.

This is a breaking change for any code that was depending on the previous (incorrect) behavior. For example, code that expected apply to raise for keys mapped to None, or that relied on contains returning false for None-valued entries, will now see correct results instead.

Add pony-lint to the ponyc distribution

pony-lint is a text-based linter for Pony source files that checks for style guide violations. It was previously a standalone project and is now distributed with ponyc. This means the linter will track changes in ponyc and will always be up-to-date with the compiler's version of the standard library. pony-lint currently checks for line length, trailing whitespace, hard tabs, and comment spacing violations.

Fix stack overflow in reachability pass on deeply nested ASTs

The reachability pass traversed AST trees using unbounded recursion, adding a stack frame for each child node. On Windows x86-64, which has a default stack size of 1MB, this could overflow when compiling programs with large type graphs that produce deeply nested ASTs (200+ frames deep).

The recursive child traversal is now iterative, using an explicit worklist. This removes the dependency on call stack depth for AST traversal in the reachability pass.

Add pony-doc tool

We've added an experimental new tool, pony-doc, that is intended to replace the --docs pass in ponyc. The docs pass will remain in ponyc for now but will eventually be removed.

The most notable difference from the existing --docs flag is that pony-doc generates documentation for public items only by default. To include private types and methods in the output, use the --include-private flag. This replaces the old --docs-public flag which worked in reverse — generating everything by default and requiring a flag to restrict to public items only.

Usage:

pony-doc [options] <package-directory>

Options:

  • -o, --output: Output directory (default: current directory)
  • --include-private: Include private types and methods
  • -V, --version: Print version and exit

Add Support for Settings to pony-lsp

pony-lsp can now be provided with settings from the lsp-client (most of the time this is the editor).
It supports two settings, which are both optional:

  • defines: A list of strings that will be defined during compilation and can be checked for with ifdef. Those defines are usually provided to ponyc with the -D flag.
  • ponypath: A string, containing a path or list of paths (like used e.g. in the $PATH environment variable) that will be used to tell pony-lsp about additional entries to its package search path.

Example settings as JSON:

{
  "defines": ["FOO", "BAR"],
  "ponypath": "/path/to/pony-package:/another/path"
}

Improve pony-lsp diagnostics

pony-lsp only relayed the main message from errors provided by ponyc. Now, it also passes the additional information to help explain the error.

Make pony-lsp version dynamic to match ponyc version

Previously, the version of pony-lsp was hardcoded in the source code. Now it is dynamically generated from the ponyc version.

Add \exhaustive\ annotation for match expressions

A new \exhaustive\ annotation can be applied to match expressions to assert that all cases are explicitly handled. When present, the compiler will emit an error if the match is not exhaustive and no else clause is provided, instead of silently injecting an else None.

Without the annotation, a non-exhaustive match silently compiles with an implicit else None, which changes the result type to (T | None) and produces an indirect type error like "function body isn't the result type." With the annotation, the error message directly identifies the problem.

type Choices is (T1 | T2 | T3)

primitive Foo
  fun apply(p: Choices): String =>
    match \exhaustive\ p
    | T1 => "t1"
    | T2 => "t2"
    end

The above produces: match marked \exhaustive\ is not exhaustive

Adding the missing case or an explicit else clause resolves the error. The annotation is also useful on matches that are already exhaustive as a future-proofing measure. Without it, if a new member is later added to the union type and the match isn't updated, the compiler silently injects else None. You'll only get an error if the match result is assigned to a variable whose type doesn't include None -- otherwise the bug is completely silent. With \exhaustive\, the compiler catches the missing case immediately.

Update Docker image base to Alpine 3.23

The ponylang/ponyc:nightly and ponylang/ponyc:release Docker images now use Alpine 3.23 as their base image, updated from Alpine 3.21.

[0.61.0] - 2026-02-28

Fixed

  • Fix pony-lsp inability to find the standard library (PR #4829)
  • Make pony-lsp version dynamic to match ponyc version (PR #4830)
  • Fix persistent HashMap returning incorrect results for None values (PR #4839)
  • Fix stack overflow in reachability pass on deeply nested ASTs (PR #4858)

Added

  • Add pony-lint to the ponyc distribution (PR #4842)
  • Add configuration from lsp-client and improve diagnostics handling (PR #4837)
  • Add \exhaustive\ annotation for match expressions (PR #4863)

Changed

  • Changed persistent hash map apply signature (PR #4839)
  • Update Docker image base to Alpine 3.23 (PR #4887)

0.60.6

06 Feb 22:10

Choose a tag to compare

Add Alpine 3.23 support

We've added support for Alpine 3.23. Builds will be available for both arm64 and amd64.

Fix crash when ephemeral type used in parameter with default argument

We've fixed an error where attempting to assign an ephemeral type to a variable caused an assertion failure in the compiler.

The following code will now cause the pony compiler to emit a helpful error message:

class Foo

actor Main
  fun apply(x: Foo iso^ = Foo) => None

  new create(env: Env) => None
Error:
main.pony:4:16: invalid parameter type for a parameter with a default argument: Foo iso^
  fun apply(x: Foo iso^ = Foo) => None
               ^

Update Docker Image Base to Alpine 3.23

We've updated the base image for our ponyc images from Alpine 3.21 to Alpine 3.23.

Fix incorrect array element type inference for union types

Due to a small bug in the type system implementation, the following correct code would fail to compile. We have fixed this bug so it will now compiler.

type Foo is (Bar box | Baz box | Bool)

class Bar
  embed _items: Array[Foo] = _items.create()

  new create(items: ReadSeq[Foo]) =>
    for item in items.values() do
      _items.push(item)
    end

class Baz

actor Main
  new create(env: Env) =>
    let bar = Bar([
      true
      Bar([ false ])
    ])
    env.out.print("done")

Fix segfault when lambda captures uninitialized field

Previously, the following illegal code would successfully compile, but produce a segfault on execution.

class Foo
  let n: USize

  new create(n': USize) =>
    n = n'

class Bar
  let _foo: Foo
  let lazy: {(): USize} = {() => _foo.n }

  new create() =>
    _foo = Foo(123)

actor Main
  let _bar: Bar = Bar

  new create(env: Env) =>
    env.out.print(_bar.lazy().string())

Now it correctly refuses to compile with an appropriate error message.

Error:
main.pony:9:34: can't use an undefined variable in an expression
  let lazy: {(): USize} = {() => _foo.n }
                                 ^

Fix compiler crash when assigning ephemeral capability types

Assigning ephemeral capability to a variable was causing the compiler to crash. We've updated to provide a descriptive error message.

Where previously code like:

actor Main
  new create(env: Env) => 
    let c: String iso^ = String

caused a segfault, you will now get a helpful error message instead:

Error:
/tmp/main.pony:3:24: Invalid type for field of assignment: String iso^
    let c: String iso^ = String

[0.60.6] - 2026-02-06

Fixed

  • Fix crash when ephemeral type used in parameter with default argument (PR #4796)
  • Fix incorrect array element type inference for union types (PR #4794)
  • Fix segfault when lambda captures uninitialized field (PR #4791)
  • Fix compiler crash when assigning to ephemeral capability type (PR #4790)

Added

Changed

  • Update Docker Image Base to Alpine 3.23 (PR #4804)

0.60.5

31 Jan 13:18

Choose a tag to compare

Add Pony Language Server to the Ponyc distribution

We've moved the Pony Language Server that previously was distributed as it's own project to the standard ponyc distribution. This means that the language server will track changes in ponyc. Going forward, the language server that ships with a given ponyc will be fully up-to-date with the compiler and most importantly, it's version of the standard library.

Handle Bool with exhaustive match

Previously, this function would fail to compile, as true and false were not seen as exhaustive, resulting in the match block exiting and the function attempting to return None.

  fun fourty_two(err: Bool): USize =>
    match err
    | true => return 50
    | false => return 42
    end

We have modified the compiler to recognize that if we have clauses for both true and false, that the match is exhaustive.

Consequently, you can expect the following to now fail to compile with unreachable code:

  fun forty_three(err: Bool): USize =>
    match err
    | true => return 50
    | false => return 43
    else
      return 44 // Unreachable
    end

Fix ponyc crash when partially applying constructors

We've fixed a compilation assertion error that would result in a compiler crash when utilizing partial constructors.

Previously, this would fail to compile:

class A
  let n: USize

  new create(n': USize) =>
    n = n'

actor Main
  new create(env: Env) =>
    let ctor = A~create(2)
    let a: A = ctor()

Add support for complex type formatting in LSP hover

The Pony Language Server now properly formats complex types in hover information. Previously, hovering over variables with union types, tuple types, intersection types, or arrow types would display internal token names like TK_UNIONTYPE instead of the actual type signature.

Now, the language server correctly displays formatted types such as:

  • Union types: (String | U32 | None)
  • Tuple types: (String, U32, Bool)
  • Intersection types: (ReadSeq[U8] & Hashable)
  • Arrow types: function signatures

Add hover support for generic types in LSP

The LSP server now provides hover information for generic types, including type parameters, type arguments, and capabilities.

Hover displays:

  • Type parameters on classes and traits (e.g., class Container[T: Any val])
  • Type parameters on methods (e.g., fun map[U: Any val](f: {(T): U} val): U)
  • Type arguments in instantiated types (e.g., let items: Array[String val] ref)
  • Capabilities on all types (val, ref, box, iso, trn, tag)
  • Nested generic types (e.g., Array[Array[String val] ref] ref)
  • Viewpoint adaptation/arrow types (e.g., fun compare(that: box->T): I32)
  • Constructor calls with type arguments (e.g., GenericPair[String, U32](...))

When hovering on variable declarations, the full inferred type is now shown including all generic type arguments and capabilities, making it easier to understand the exact types in your code.

Add extra hover support to LSP

The Pony Language Server Protocol (LSP) implementation now provides improved hover capabilities with the following fixes.

Hover on field usages

When hovering over a field reference like field_name in the expression field_name.size(), the LSP now displays:

let field_name: String val
class Example
  let field_name: String

  fun get_length(): USize =>
    field_name.size()  // Hovering over 'field_name' here shows: let field_name: String val

Previously, hovering over field usages did not show any information.

Hover on local variable usages

When hovering over a local variable reference like count in the expression count + 1, the LSP now displays:

var count: U32 val
fun increment(): U32 =>
  var count: U32 = 0
  count = count + 1  // Hovering over 'count' here shows: var count: U32 val
  count

Previously, hovering over variable usages did not show type information.

Hover on parameter usages

When hovering over a parameter reference like name in the expression name.size(), the LSP now displays:

param name: String val
fun get_name_length(name: String): USize =>
  name.size()  // Hovering over 'name' here shows: param name: String val

Previously, hovering over parameter usages did not show any information.

Hover on parameter declarations

When hovering over a parameter declaration like x: U32 in a function signature, the LSP now displays:

param x: U32 val
fun calculate(x: U32, y: U32): U32 =>
  // Hovering over 'x' in the signature shows: param x: U32 val
  x + y

Previously, hovering over parameter declarations provided no information.

Hover on method calls

When hovering over a method name in a call expression like my_object.get_value(42), the LSP now correctly highlights only the method name and displays:

fun get_value(x: U32): String val
class MyObject
  fun get_value(x: U32): String val =>
    x.string()

actor Main
  new create(env: Env) =>
    let my_object = MyObject
    let result = my_object.get_value(42)  // Hovering over 'get_value' highlights only the method name

Previously, both the method name and receiver name were highlighted.

Fix crash when using bare integer literals in array match patterns

Array does not implement the Equatable interface, which queries for structural equality. Previous to this fix, an error in the type inference logic resulted in the code below resulting in a compiler crash instead of the expected helpful error message:

actor Main
  new create(env: Env) =>
    let arr: Array[U8 val] = [4; 5]
    match arr
    | [2; 3] => None
    else
      None
    end

Now, the correct helpful error message is provided:

Error:
main.pony:5:7: couldn't find 'eq' in 'Array'
    | [2; 3] => None
      ^
Error:
main.pony:5:7: this pattern element doesn't support structural equality
    | [2; 3] => None
      ^

Add support for go to definition for arrow types in LSP

The LSP server now correctly handles "go to definition" for method calls on arrow types (viewpoint-adapted types like this->Type).

Previously, go-to-definition would fail on method calls where the receiver had an arrow type, such as calling methods on this (which has type this->MyClass rather than just MyClass).

Add hover support for receiver capability to LSP

The Language Server Protocol (LSP) hover feature now displays receiver capabilities in method signatures.

When hovering over a method reference, the hover information will now show the receiver capability (e.g., box, val, ref, iso, trn, tag) in the method signature. This provides more complete type information at a glance.

Examples

Previously, hovering over a method would show:

fun boxed_method(): String

Now, it correctly displays:

fun box boxed_method(): String

This applies to all receiver capabilities:

  • fun box method() - read-only access
  • fun ref method() - mutable reference access
  • fun val method() - immutable value access
  • fun iso method() - isolated reference access
  • fun trn method() - transition reference access
  • fun tag method() - opaque reference access

[0.60.5] - 2026-01-31

Fixed

  • Fix crash when partially applying constructors (PR #4783)
  • Fix crash when using bare integer literals in array match patterns (PR #4797)

Added

  • Add Pony Language Server to the ponyc distribution (PR #4777)
  • Handle Bool with exhaustive match (PR #4782)
  • Add support for complex type formatting in LSP hover (PR #4785)
  • Add hover support for generic types in LSP (PR #4793)
  • Add extra hover support to LSP (PR #4795)
  • Add hover support for receiver capability to LSP (PR #4798)
  • Add support for go to definition for arrow types in LSP (PR #4792)

0.60.4

31 Oct 14:26

Choose a tag to compare

Add Alpine 3.22 as a supported platform

We've added arm64 and amd64 builds for Alpine Linux 3.22. We'll be building ponyc releases for it until it stops receiving security updates in 2027. At that point, we'll stop building releases for it.

Stop creating Fedora 41 builds

We are no longer creating Fedora 41 builds of Pony. Fedora 41 is about to reach its end of life. If you need Pony support on Fedora 41, you will need to build from source.

[0.60.4] - 2025-10-31

Added

  • Add Alpine 3.22 as a supported platform (PR #4760)

Changed

  • Stop creating Fedora 41 builds (PR #4763)

0.60.3

20 Oct 20:08

Choose a tag to compare

Stop building versioned "-alpine" images

We used to release images for ponyc like ponyc:0.38.0-alpine that were based on Alpine Linux.However, we now just have those as ponyc:0.38.0 as we don't have any glibc based images anymore.

Stop Creating Generic musl Builds

We previously created releases for a generic "musl" build of ponyc. This was done to provide a build that would run on any Linux distribution that used musl as its C standard library. However, these could easily break if the OS they were built on changed.

We now provide specific Alpine version builds instead.

Stop Creating "alpine" Docker Image

We previously were creating Docker images with a musl C standard library Ponyc under the tag alpine. We are no longer creating those images. You should switch to our new equivalent images tagged nightly.

Add Multiplatform Versioned Release Docker Images

We've added multiplatform versioned release Docker images for Ponyc. These images are built for both amd64 and arm64 architectures and are tagged with the current Ponyc version. For example, ponyc:0.60.3.

Previously, the versioned release images were only available for the amd64 architecture.

nightly and release tags were already avaialble as multiplatform images. This update extends that support to versioned images as well. With this change, we now provide consistent multiplatform support across all our Docker image tags.

[0.60.3] - 2025-10-20

Added

  • Add Multiplatform Versioned Release Docker Images (PR #4754)

Changed

  • Stop building -alpine versioned container images (PR #4751)
  • Stop Creating Generic musl Builds (PR #4752)
  • Stop Creating "Alpine" Docker Images (PR #4753)