github ponylang/ponyc 0.77.0

3 hours ago

Use forward slash as the path separator on all platforms

All paths in the files package now use / as the separator on every platform, including Windows. Paths from Path.cwd() and Path.canonical() are /-separated, Path.sep() returns "/" unconditionally, and Path.clean() converts \ to / on Windows.

Path.is_sep() still accepts \ on Windows for parsing external input. Use Path.to_os() when a backslash path is needed for Windows shell commands.

Path.normcase() now only lowercases the path on case-insensitive platforms. It no longer replaces / with \ on Windows.

Before:

if path == "C:\\Users\\me\\project" then
  // ...
end

After:

if path == "C:/Users/me/project" then
  // ...
end

Code that passes paths to Windows shell commands should use Path.to_os():

let os_path = Path.to_os(path)

Remove Path.from_slash and Path.to_slash

Path.from_slash() and Path.to_slash() have been removed. All paths use / as the separator on every platform, so these functions served no purpose. Remove any calls to them.

Fix iftype specialization on method-level type parameters

When a method had its own type parameter with an iftype specialization guard, the compiler selected the specialized body unconditionally — even when the concrete type argument did not satisfy the guard. Class-level type parameter guards were not affected.

primitive Foo
  fun bar[S: ReadSeq[U8] box](seq: S): U16 ? =>
    (seq(0)?.u16() << 8) or seq(1)?.u16()

  fun bar[S: ReadSeq[U8] box](seq: S): U16
    iftype S <: Array[U8] box
  ? =>
    seq.read_u16(0)?

Calling Foo.bar[WrappedSeq](seq)? would fail to compile with "can't find a compatible 'read_u16' method on type 'WrappedSeq ref'" because the compiler chose the Array[U8]-specialized body instead of the default.

Fix match without else in trait default method always taking the first arm

A match without an explicit else in a trait default method body always took the first capture arm, regardless of the runtime type of the matched value. The first capture arm always ran, which could crash the program or silently produce wrong results when the matched value was a different type.

trait T
  fun get(): (R | None)

  fun check() =>
    match get()
    | let r: R => r.n  // always taken, even when get() returns None
    end

The same match written in a class body, or with an explicit else, worked correctly.

Remove the classic pool allocator

The pool_classic, pool_retain, and pooltrack build options are removed. The arena allocator, the default since 0.69.0, is now the only full allocator. pool_memalign remains for builds that need AddressSanitizer or Valgrind.

Builds that used pool_classic,valgrind should switch to pool_memalign,valgrind:

Before:

cmake --preset debug -DPONY_USES=pool_classic,valgrind

After:

cmake --preset debug -DPONY_USES=pool_memalign,valgrind

Builds that used pool_classic on its own should drop it — the default arena allocator is what runs.

Fix subtype checking for intersections that collectively satisfy an interface

The compiler rejected an intersection type (A & B) as a subtype of an interface when neither A nor B alone provides every method the interface requires, even when together they cover all of them.

interface Sized
  fun rows(): USize
  fun cols(): USize

class HasRows
  fun rows(): USize => 10

class HasCols
  fun cols(): USize => 20

primitive Checker
  fun check(s: Sized) => None

  fun example(x: (HasRows & HasCols)) =>
    check(x) // previously rejected: neither HasRows nor HasCols alone is Sized

The call check(x) failed with a subtype error because the compiler checked each intersection member individually. It now checks the intersection's members collectively against the interface's method list, so (HasRows & HasCols) is accepted as a subtype of Sized.

Detect locals and parameters that shadow inherited methods

The compiler now reports an error when a local variable or parameter reuses the name of a method inherited from a trait or interface. Previously, only methods defined directly on the type were checked — inherited methods were silently shadowed, which could hide the inherited method and cause unexpected behavior.

Locals and parameters that shadow inherited methods are now a compile error

Code that previously compiled may now fail if a local variable or parameter has the same name as a method inherited from a trait or interface. Rename the variable or parameter to fix the error.

Before:

interface Tickable
  fun label(): String => "default"

class MyTimer is Tickable
  fun format(label: String): String =>
    "Timer: " + label

After:

interface Tickable
  fun label(): String => "default"

class MyTimer is Tickable
  fun format(msg: String): String =>
    "Timer: " + msg

[0.77.0] - 2026-10-17

Fixed

  • Fix iftype specialization on method-level type parameters (PR #6292)
  • Fix match without else in trait default method always taking the first arm (PR #6297)
  • Fix subtype checking for intersections that collectively satisfy an interface (PR #6304)
  • Detect locals and parameters that shadow inherited methods (PR #6305)

Changed

  • Use forward slash as the path separator on all platforms (PR #6276)
  • Remove Path.from_slash and Path.to_slash (PR #6276)
  • Use forward slash as the path separator on all platforms (PR #6276)
  • Remove the classic pool allocator (PR #6301)
  • Locals and parameters that shadow inherited methods are now a compile error (PR #6305)

Don't miss a new ponyc release

NewReleases is sending notifications on new releases.