TYCHO Search

Search documentation

    ↑ ↓ to move · Enter to open · Esc to close

    Changelog

    All notable changes are recorded here against the version that shipped them.

    The version constant lives in compiler/main.ty (VERSION, printed by tychoc --version). Per-release publishing notes stay in RELEASE_NOTES.md; this file is the accumulating record.

    Names used below: surface.lock is the frozen record of keywords, builtins and corelib signatures (checked by make surface-check); tychoc is the reference compiler, tychoc1 the self-hosted compiler. A *lane* is one step of the local gate (make ci); FRICTION n is entry n in the defect log (docs/internals/FRICTION.md); a *probe* is a small program written against the docs alone to test them. See STATUS for the full terms box.

    [Unreleased]

    [0.9.2] — 2026-11-12

    Compiler

    Corelib

    [0.9.1] — 2026-11-10

    Compiler

    Performance

    Runtime

    Tooling

    Corelib

    [0.9.0] — 2026-10-26

    Compiler

    Benchmarks

    Compatibility

    Verification

    [0.8.9] — 2026-10-21

    3D and GPU

    Fixed

    Verification

    [0.8.8] — 2026-10-21

    Breaking

    Core library

    Documentation

    FFI

    Fixed

    Compiler optimizations

    [0.8.7] — 2026-10-18

    Breaking

    Every item here makes a program that 0.8.6 accepted stop compiling. Each one names its rule in the diagnostic. surface.lock moved, by addition only: seven corelib functions joined it (see *Core library*), no frozen signature changed and the keyword list is unchanged at 116.

    Language

    Compiler

    Runtime

    Core library

    Tools

    Documentation

    Verification

    [0.8.6] — 2026-09-25

    Breaking

    Every item here makes a program that 0.8.5 accepted stop compiling. Each one names its rule in the diagnostic.

    Language

    Compiler

    Core library

    The old shape answered instead of refusing: a null the caller had to remember to check (see docs/quiet-results.md §6). Seventeen of the twenty-nine functions in core:io already returned Result; the streaming opener was the odd one out. Failure now says *which* failure: BadPath for an interior NUL (nothing was attempted, so retrying is wrong), otherwise NotFound / IsDir / Failed, classified the way is_dir classifies.

    LineReader is a plain struct with a private C pointer, not a handle. A handle is affine and only an extern fn may return one, so using one would move the opener into C and take the interior-NUL guard with it — and that guard has to stay on the Tycho side, because that is the only side that knows the string's length.

    Two costs, unchanged from the ptr this replaces. A LineReader is an ordinary value, so b := r gives two names for one C reader and closing both is a double free — close exactly one, exactly once. And cleanup is manual: Tycho has no defer, so a forgotten close_lines leaks the C-side reader until the process exits. An affine handle would make the first unrepresentable; the bare ptr did not, and this does not either.

    Migration is mechanical — the null check becomes the Err arm:

      # before -- shown for migration; the `before` half no longer compiles
      r := io.open_lines(p)
      if is_null(r):
          return
      # ... io.read_line(r) ...
      io.close_lines(r)
    
      # after
      match io.open_lines(p):
          Err(e):
              return
          Ok(r):
              # ... io.read_line(r) ...
              io.close_lines(r)
    

    Security

    Fixed

    Release and platforms

    Tools

    Documentation

    Verification

    [0.8.5] — 2026-09-06

    Language

    Compiler

    Core library

    Security

    Fixed

    Verification

    [0.8.0] — 2026-09-02

    Not a breaking release. Every one of the 300 files in the accept corpus that v0.7.0 compiled, 0.8.0 compiles: measured by building the v0.7.0 compiler and running both over tests/*.ty and examples/*.ty — 297 accepted by v0.7.0, 300 by 0.8.0, 0 regressions. The 12 fixtures added under tests/reject/ document rules that already existed; four were checked against the v0.7.0 binary and it refused all four too.

    Compiler — the headline

    Language

    Core library

    Tooling

    Performance

    Verification

    [0.7.0] — 2026-08-15

    Breaking. Several shapes that used to compile no longer do — a copied handle, a copied channel, sink/inout on an affine type, a bounded[N]T generic field that had silently degraded — and two of those were live memory errors, not style. JsonErr gains a variant, which breaks an exhaustive match on it. Read "Language — breaking" and "Core library — breaking" below before upgrading.

    Write new entries here, not under the release heading below — 0.5.0 shipped without this block, so the only heading to write under was a frozen one, and eight entries landed inside a tagged release that does not contain them (see 0.6.0's opening note).

    Language — breaking

    Diagnostics

    Four messages that an outside reader hit in the first ten minutes writing tools/tycho-diff against docs/ alone (tools/tycho-diff/FRICTION-OUTSIDE.md).

    Language — added

    Language — fixed

    Diagnostics

    Tools

    Core library — added

    Core library — security

    Core library — breaking

    [0.6.0] — 2026-08-11

    Seventy commits since v0.5.0. Eight of the entries below were written into the [0.5.0] section after v0.5.0 was tagged and are moved here, where they belong: git merge-base --is-ancestor puts every one of them outside the tag. The cause was structural, and the [Unreleased] block above is the fix. Read compress.decompress, io.read_text, qualified consts, the newtype-collection fix, or_return in main(), the live-copy remedy, the [string]-as-a-value diagnostic and the tycho-ar parser share as 0.6.0 changes; a v0.5.0 tarball does not contain them.

    BREAKING CHANGES

    Six changes refuse or re-shape code that compiled under 0.5.0. All six migrations are mechanical, and each is stated as "what you wrote" → "what you write".

      fn hash(K: int, S: int) -> int:   →   fn hash(k: int, s: int) -> int:
          A := K + S                            a := k + s
    

    const is exempt and unchanged: 264 of this tree's 266 const declarations start uppercase and none starts lowercase. Migrating this tree took 16 sites in 6 files (md5, sha256, dijkstra, weblog, decimal); md5 and sha256 each keep a one-line note mapping the new names back to RFC 1321 and FIPS 180-4, because that mapping is how a reader checks the algorithm. Specified in docs/spec/01-lexical.md §3.5.1.

      iter.filter(xs, fn(x: int) -> int: x % 2)        →   iter.filter(xs, fn(x: int) -> bool: x % 2 != 0)
      iter.count(xs, fn(x: int) -> int: x - lim)       →   iter.count(xs, fn(x: int) -> bool: x != lim)
    

    A named predicate changes the same way: fn even(x: int) -> int returning 1/0 becomes fn even(x: int) -> bool: return x % 2 == 0. map, try_map and reduce are unchanged.

      img := image.decode(b)          →   img := image.decode(b) or_return
      if img.width > 0:                   # or match Ok(img): / Err(e):
    
      out := compress.decompress(b)   →   out := compress.decompress(b) or_return
      if len(out) == 0: ...
    

    The shim always knew which branch it took and discarded it; it now reports a status through an inout int, the FFI shape core:io already used. Callers updated: core:zip (its CRC check already covered the collapse), tools/tycho-ar (reports the cause instead of inferring damage from a length mismatch — the length check stays, since a payload that inflates cleanly to the wrong thing is forgery, not damage), and the worked example.

    Language

      if v is VNull: ...        o is Some     r is Ok     r is Err
    

    core:result now asks with it internally — is_ok is return r is Ok — and is_some was rewritten the same way. Both keep their signatures and their meaning; this is an implementation change, not an API one.

    1. Parameter position only: a [string] return is refused, and inout string stays refused. Documented in docs/spec/14-ffi.md.

    This retires a whole builder protocol. core:os used to reach C through osx_argv_new / osx_argv_push / osx_argv_free and a ptr handle; it now declares extern fn osx_exec(argv: [string]) -> int and passes the array. os.exec and os.exec_out keep byte-identical signatures — no caller outside corelib changes.

    Observable only when a hole carries a call with side effects, which is exactly how it was found: an io.remove in one hole ran before the io.mtime printed to its left. Bare + concatenation and ordinary call arguments remain unspecified — this fixes the construct that *looks* sequential.

    No export keyword and no new syntax: the leading-underscore rule the language already had does the privacy, so pkg._NAME is refused unchanged. One position is left out and marked gap: in the source — a qualified const as a fixed-array length ([pkg.CAP]int), because an array length is resolved while parsing, when the imported package may not be parsed yet; accepting it there would fail or not depending on file order.

    A newtype's purpose is distinctness at assignment, parameter passing and comparison — not hiding what the underlying value can do. Those boundaries are unchanged: [int] still cannot be passed where Ids is wanted, in either direction, and two newtypes over the same underlying type stay incompatible. A slice or a map_set yields the *underlying* type, since it is a fresh value rather than the named one.

    The error type is string and the ok payload is void: an Err reaching the entry point is *printed* and has_str admits no enum, so the error is the message; and a value in the ok slot would make the exit status ambiguous. Both wrong shapes are refused with a diagnostic that names which half is wrong.

    Core library

    Diagnostics

    Four messages stopped pointing at the wrong place or withholding the answer.

    Fixed

    The fix re-derives the index rather than holding the pointer: `int si = (int)(s

    Gates and tooling

    Not user-facing API, but the record matters: eight checks that could not fail now can, and two of them were sitting over live holes.

    [0.5.0] — 2026-08-10

    The first release ever cut. Everything below ships in it. The section was split in two until 2026-08-10 — an [Unreleased] block on top of a [0.5.0] block dated 2026-08-05 that was never tagged — which would have shipped a changelog that did not describe its own artifact. The 08-05 block is a draft, not history, so the two are merged here under the date this shipped.

    Eight entries that were appended here *after* the tag were moved to [0.6.0] on 2026-08-11; a v0.5.0 tarball does not contain them.

    This is the deprecation policy in docs/reference/corelib.md run end to end for the first time. Step (3), the compiler warning, is now a general mechanism rather than a one-off: a # deprecated: <text> comment line directly above a fn marks it, and every call site warns with that text. Locked by tests/warn/deprecated.ty.

    It is contextual, not reserved: significant only when it is the whole statement. pass was already a variable name in corelib/test/testing and tools/prunner, and reserving it would have broken the runner that scores make test-fast, so pass := 0 and pass = pass + 1 still mean what they did. Used where a value is required — a value if/match branch tail — it is refused with a message naming the fix, unless a variable of that name is in scope, in which case it is that variable.

    Shipped with it, because the feature is unusable without it: f() or_return is now a statement when the ok payload is void. It was previously rejected as a bare expression with no effect, which left a void-payload Result no propagation form at all — x := f() or_return has nothing to bind. Over any other payload type it is still an error, now naming the type it would have dropped. docs/internals/FRICTION.md §4 recorded these as one defect with three independent sightings; they are closed together.

    Internally, the generic bind vector's "not yet bound" sentinel moved off T_VOID to its own T_UNBOUND. That collision was latent rather than live — void was not in the type grammar at all — but it is what would have made binding a type parameter to void read as unbound.

    Earlier in the 0.5.0 cycle (recorded 2026-08-05, at the time as 1.0.0)

    The promotion that was later withdrawn. The language surface and the spec are not a stability contract at 0.5 — the README's status banner says so, and ROADMAP.md lists what 1.0 would require. What follows is the chain of work that landed before the version number was corrected.

    [0.1.0] — earlier

    The pre-1.0 research-prototype release. See RELEASE_NOTES.md at the v0.1.0 tag for what it contained; it carried no stability guarantees.