Skip to content

Project Status

Typhon is in active development, and has reached its first beta.

The current release is v1.0.0-beta.2 (2026-10-04), the first published beta: v1.0.0-beta.1 plus a Windows build fix (beta.1’s release build failed on Windows, so it was never published).

The v1.0.0-alpha milestone brought the proven production surface together with the type-system frontier earlier releases deferred — higher-kinded type unification, user-generic variance inference, and the general inter-procedural field-init audit. The alpha.2 → alpha.9 point releases since have been a hardening pass across five fronts:

  • Soundness (alpha.2, alpha.4). Flow-narrowing invalidation across calls and alias writes, short-circuit narrowing fixes, a batch of newly-typed positions, three conservative diagnostics (not_a_context_manager, raise_non_exception, frozen_inheritance_conflict), and the H5 scope-blind class-unification fix.
  • Robustness & release engineering (alpha.3). A repository-root MIT LICENSE, the vendored Ruff notice, SECURITY.md / CONTRIBUTING.md / Dependabot, CI-gated release tagging, linear nested-generic assignability, six VM ↔ CPython parity fixes, a 256 MiB LSP stack, atomic tyc fmt, and the TYC_NO_INTROSPECT kill-switch.
  • Performance (alpha.5). VM performance Tier 1 (tyc run) — an allocation-light two-representation integer, method-dispatch caching, slot-resolved locals — plus the [optimise] profile / tyc build -O, seven advice-only perf lints, a free-threading parallelisation wave, and native PEP 810 lazy imports on 3.15 targets.
  • Maintenance (alpha.6, alpha.8, alpha.9). The July dependency wave carried safely across the toml 0.8 → 1.x major, six more secret-name lint keywords, release-pipeline re-pinning, and docs / repo hygiene (alpha.6); then a VM ↔ CPython parity fix — unseeded random now seeds from entropy under tyc run, as CPython does, instead of from a fixed constant, while explicit random.seed(n) still matches CPython byte-for-byte — plus a widening of the warn-level secret-name lint to digit and TitleCase boundaries, two allocation reductions on AST walks, the early-August dependency wave, and docs-site transition polish (alpha.8); then a large widening of that same warn-level secret-name lint — its keyword table grows from 16 entries to 55, consolidating seven overlapping proposals into one longest-first-ordered table — three more compiler allocation reductions, the mid-August dependency wave, and docs-site keyboard-accessibility and reduced-motion polish (alpha.9).
  • Codebase-review remediation (alpha.7). All ten 1.0 blockers from the 2026-07-28 full-codebase review closed, plus the Tier-0 verification gates: instance-attribute type-checking, reverse-MRO constructor field order, keyword-only model constructors, terminating recursive type aliases, parametric sealed-union exhaustiveness, a new warn-level nullable-field-dereference check, let-immutability enforcement in loops and through global/nonlocal, a shared-lexical-mask preprocessor rewrite, five emitter/preprocessor miscompilation fixes, VM ExceptionGroup/except* support (PEP 654) plus five other VM ↔ CPython parity fixes, and two new CI gates (a full-corpus VM ↔ CPython differential baseline and an opt-in-knob codegen matrix).

v1.0.0-beta.1 is the first beta: four review-remediation waves on top of alpha.9 — the 2026-09-01 beta-readiness review and the backlog it deferred, the 2026-09-30 release-readiness review, and the W1–W7 remediation of the six 2026-10-03 full reviews. They close the ways a check-clean program could still crash, miscompile, or behave differently under tyc run than under CPython: field narrowings dropped wherever a call or write can reach them, loops that join their exit paths, match exhaustiveness over Result payloads, T?, bool, literal unions and nested sealed unions, an inline ? that keeps Python’s evaluation order, |> in every expression position, C3 method resolution and CPython-ordered sets on the VM, and an automatic CPython fallback for programs the VM does not model. There is no new syntax, and the new error-level diagnostics (alias_not_a_class, invalid_pattern, impl_forward_reference, and reserved_module_name when the program could not start) fire only on code that already crashed. One documented exception changes a default: [strictness] nullable-use is now "error".

What this means for you:

  • The production path (tyc build → CPython 3.13+) is stable and carries no runtime dependency on the toolchain.
  • The language is additive on correct programs across the whole v0.3.0 → v1.0.0-alpha line — every program that type-checked and ran correctly continues to behave identically. (A few deliberate diagnostics reject only code that already crashed at runtime.)
  • As of v1.0.0-beta.1 the surface listed in the compatibility policy is frozen for the beta line: those forms keep their syntax and meaning in every beta release, and a deprecation or breaking change follows the policy’s warn-first process. The policy also lists beta.1’s deliberate exceptions, chiefly [strictness] nullable-use now defaulting to "error".
  • Still deferred: embedded in-process ty (the Phase 1 subprocess path ships), typeshed-backed checking for pure-extension libraries, and the function-level HKT tail.

The sections below give the recent-release detail; the full release-by-release history lives in the changelog.

The canonical per-feature status lives in the Roadmap. This page is a quick summary.

Landed in v1.0.0-alpha (first feature-complete alpha)

Typhon’s first tagged alpha and first feature-complete milestone — the proven production surface plus the type-system frontier earlier releases deferred. Rolls up milestones M1 (“Tidy & deepen”) and M2 (“Type-system frontier”) of the alpha release plan, the early M3 polish (formatter idempotence, a performance-regression CI gate), and the rescue exception-boundary sugar. Additive on the accepted surface — every previously-accepted program type-checks identically.

Added — type-system frontier

  • ✅ Higher-kinded type unification. A constructor variable (F in class Functor[F[_]]: / interface Functor[F[_]]:) now binds against a concrete head — F[A] against list[int] binds F = list, A = int and substitutes in the return type, in-module and across module boundaries. Wrong arity or a conflicting constructor binding emits the new tyc::kind_mismatch diagnostic. (Function-level def f[F[_]] params, non-class application, and constructor composition remain deferred.)
  • ✅ User-generic variance inference. Each user-declared class type-parameter is classified from its usage — output-only → covariant, input-only → contravariant, both → invariant — and consulted in assignability, so class Producer[T] (T in returns only) accepts Producer[Dog] where Producer[Animal] is expected. A bare @covariant / @contravariant class decorator overrides the inference. Variance propagates across module boundaries.
  • ✅ Sound variance through generic interface bounds (closed a soundness hole, not just a relaxation).
  • ✅ General inter-procedural field-init audit. A per-function summary tracks partial-instance escapes across helper chains, so a partially-initialised instance escaping a non-trivial chain fires tyc::missing_field_init (no corpus false positives).
  • ✅ 2-member non-nullable unions (Union[str, bytes], str | os.PathLike) are modelled at the introspection boundary with sound widening.

Added — boundaries, tooling & polish

  • ✅ rescue exception-boundary sugar (postfix EXPR rescue e: ERR and a block form) — lambda-free, try/except-free bridging into Result, checked against the function’s declared error type.
  • ✅ Annotated[T, …] is type-checked through third-party introspection (catches wrong-typed FastAPI / Typer / Pydantic kwargs).
  • ✅ Cross-file go-to-definition for import aliases and .py siblings; tyc::stdlib_module_shadow now fires on tyc build; tyc migrate no longer emits invalid mut else:.
  • ✅ Idempotent, semantics-preserving tyc fmt and a performance-regression CI gate.

Compatibility & stability

The production path (tyc build → CPython 3.13+) is stable and carries no runtime dependency on the toolchain. As an alpha, the surface syntax is not yet frozen — it may change before 1.0.0 with a documented migration note (no semver stability guarantee on the language surface yet). Deferred to beta: embedded ty Phase 2 (the Phase 1 subprocess path — [checker] external = "ty" / --with-ty — ships), typeshed-backed checking for pure-extension libraries, tyc migrate hardening on the full PyPI set, and the function-level HKT tail.

Landed in v0.15.3 (tooling), v0.15.2, v0.15.0 / v0.15.1

v0.15.0 is a feature release sharpening Typhon at the library boundary, driven by a field report from building a real async app. v0.15.1 is a strict performance + docs-site point release on top of it — no language or API changes.

Added — v0.15.0

  • ✅ as! composes in any expression position. EXPR as! TYPE now lowers structurally (a bracket-, string-, and comment-aware fixpoint rewrite) instead of line by line, so a checked cast works nested in call arguments (save(row[0] as! int, label)), inside comprehensions / collection literals, across a multi-line value expression, and in statement conditions (if raw as! bool:). v0.14.0 only accepted the single-line value-position form. The VM intercepts __typhon_checked_cast__ before argument evaluation, so a cast to a union / parametric type (x as! int | None, d as! dict[str, int]) runs under tyc run.
  • ✅ try_result(thunk[, on_err]) exception→Result combinator. Bridges a library boundary into a Result in one expression instead of a hand-written try: return Ok(x) except E as e: return Err(...). It runs thunk() and returns Ok(result); on any exception it returns Err(on_err(exc)), or Err(exc) when no mapper is given. A prelude name (no import in source, like Ok / Err / Result), typed Result[T, E] (T from the thunk, E from the mapper, Exception when omitted). tyc build auto-injects from typhon_runtime import try_result; the VM registers it as a prelude native.
  • ✅ Compiler-bundled .dty stubs (httpx, requests). tyc ships curated, embedded stubs for popular libraries whose packaging defeats venv introspection, seeded into the project shape map before venv enrichment — so an imported bundled library is shaped out of the box, its construction is type-checked, and its unintrospectable-dependency warning is suppressed, with no .venv or tyc sync required. Gap-fill, not override: an authored project .dty / .ty wins, and the bundle takes precedence over venv introspection. Lives in tyc-db::seed_bundled_stubs.
  • ✅ async_without_await understands async contracts. An awaitless async def is no longer warned when it is async only to honour a contract it can’t opt out of — implementing an async interface method or overriding an async base-class method. Gated on the interface method being async, so an async impl of a sync method still warns.

Fixed — v0.15.0

  • ✅ Qualified cross-module class references unify with their bare form. import httpx; let r: httpx.Response = client.get(...) no longer mismatches the method’s bare Response return. Two class types whose final .-separated segments match unify when at least one side is bare; two different qualified classes stay distinct (httpx.Response is not assignable to requests.Response).

Changed — v0.15.1 (performance + docs-site accessibility)

  • ✅ Source-map generation is now O(N log N) (was O(N²)).
  • ✅ A Result-exhaustiveness hot-path allocation removed.
  • ✅ Docs-site accessibility: a keyboard-focus ring and an anchor-target highlight animation, with a prefers-reduced-motion fallback.

Landed in v0.14.0 (and point releases)

v0.14.0 added the as! checked boundary cast and opt-in traceback remapping; v0.14.1–v0.14.3 layered cross-module shape-propagation completeness, async-gather advice, and live LSP config refresh on top.

Added — v0.14.0

  • ✅ as! checked boundary cast. A sound one-line replacement for the unsafe:-plus-re-assert dance. Lowers to checked_cast in typhon_runtime/cast.py, which performs a recursive structural shape check and raises TypeError on mismatch (int→float widening honoured). (In v0.14.0 the cast was value-position, single-line only; v0.15.0 generalised it to any expression position.)
  • ✅ [emit] traceback-remap (default off). Injects typhon_runtime.traceback.install() into the entry __main__ block so an uncaught exception’s traceback is auto-rewritten to .ty via the .py.map sidecars — the automatic counterpart to the manual tyc trace.

Added — v0.14.1 (cross-module shape-propagation completeness)

  • ✅ Imported newtypes widen to base, transparent type aliases unwrap, enum exhaustiveness carries across module boundaries, and frozen-class write soundness holds cross-module. What already worked within a module now works for imported declarations.

Added — v0.14.2 (async-gather advice)

  • ✅ tyc::gather_opportunity (advice, default on). Flags runs of 2+ adjacent independent awaits and suggests an explicit gather:.
  • ✅ Cross-module auto-gather. Now folds imported @gatherable callees.
  • ✅ Advisory lints surface live in the editor (LSP).

Changed — v0.14.3 (live LSP config refresh)

  • ✅ LSP refreshes diagnostics live on typhon.toml edits via workspace/didChangeWatchedFiles; [strictness] knobs are cached per project root by mtime.

Landed in v0.13.0 (cross-module extend, dict-literal lowering, enum exhaustiveness, Result API)

A fresh adversarial sweep (~35 programs run through both tyc run and tyc build + CPython with output diffing) surfaced two silent-wrong-output defects on documented features, several type-system blind spots, and a batch of VM coverage gaps. All closed here; the workspace suite and the 254-file example corpus stay green. v0.13.1 / v0.13.2 are playground stress-round point releases on top (method-call match, multi-line ?, gather:-in-match import injection, fmt round-trips; async await-propagation, Task await-unwrap, VM project run, pub enum).

Fixed — silent wrong output

  • ✅ Cross-module extend ClassName: silently dropped its methods. The desugar merge only handled same-file targets; an imported target’s methods were discarded, so a clean build crashed with AttributeError at first call. Foreign-target blocks now lower in place to module-level functions plus class-attribute patches, which work on slots=True / frozen dataclasses and third-party classes alike. check, run, and the VM all agree again.
  • ✅ TypedDict-style dict literals against a class / model annotation now lower. let u: User = {"id": 1, "name": "ada"} has type-checked since v0.3.0 but emitted the raw dict — u.name then crashed at runtime. An early desugar pass rewrites the literal to User(id=1, name="ada"), recursing into class-typed fields for nested initialisation.

Added — language / checker

  • ✅ Enum match exhaustiveness. An enum’s member set is a closed set: covering every member satisfies the return-path analysis, and a missing member fires tyc::non_exhaustive_match naming it.
  • ✅ The Result unwrap / query family. unwrap(), expect(msg), unwrap_or(default), unwrap_or_else(f), ok() (→ T?), err() (→ E?), is_ok(), is_err() on Ok / Err / Result. The Result method surface is now closed — an unknown method fires attribute_not_found at check time (previously .unwrap() passed the checker and crashed at runtime).
  • ✅ Exhaustiveness for expression scrutinees. match items[-1]: over a sealed union runs the same analysis as a plain-name subject.
  • ✅ Match-arm scrutinee narrowing. case Action(_, _): narrows the subject variable to Action inside the arm.
  • ✅ Recursive type aliases. type Json = None | bool | int | float | str | list[Json] | dict[str, Json] is now legal — tyc::cyclic_type_alias only fires for a cycle with no type constructor anywhere (no base case). Container literals resolve element expectations through aliases and unions, so nested Json literals check.
  • ✅ Quoted annotations resolve as forward references. next: "Node", -> "Tree[T]", "list[Node]". The literal-singleton union form (type Color = "red" | "green") is unchanged.
  • ✅ Generic interface conformance. MemRepo[int] structurally satisfies interface Repo[T] as Repo[int].
  • ✅ Lambda arity checking. Lambdas infer as Type::Function carrying their arity, so apply(lambda x: x) against Callable[[int, int], int] is a check-time mismatch.
  • ✅ New warn lints: tyc::mutable_default_param, tyc::is_literal_comparison, tyc::incompatible_override, tyc::loop_closure_capture.

Added — VM, CLI & emit

  • ✅ VM cooperative asyncio. tyc run executes async def / await / gather: (incl. return_exceptions=True) / asyncio.run / TaskGroup.create_task / wait_for / spawn (the go lowering) natively through a cooperative-sequential scheduler, with output identical to CPython unless correctness depends on task interleaving.
  • ✅ VM CPython-style traceback frames pointing directly at the .ty file (previously the traceback carried no frames at all).
  • ✅ CPython-exact random — a faithful MT19937 so seeded programs produce byte-identical sequences across tyc run and tyc build + CPython.
  • ✅ tyc migrate enhancements — relocates class-body methods into impl blocks, rewrites class X(Enum): to the enum keyword, simplifies field(default_factory=...) to bare-literal sugar, and prunes the imports those rewrites orphan; migrated output checks with zero errors and zero warnings.
  • ✅ Emitted Python imports the collections.abc names it uses (Iterator, Sequence, Callable, …), so runtime annotation resolution (typing.get_type_hints, FastAPI DI, pydantic) no longer raises NameError.

Shipped (Phases 0 – 3 + Phase 5 complete)

Core compiler

  • ✅ Cargo workspace with the crate-per-stage layout (tyc-syntax, tyc-db, tyc-resolve, tyc-types, tyc-analyse, tyc-desugar, tyc-emit, tyc-format, tyc-diagnostics, tyc-lsp, tyc-vm, tyc).
  • ✅ Salsa-backed incremental queries (preprocessed_text, module_decl_names, resolved_module).
  • ✅ Vendored Ruff fork (ruff_text_size, ruff_source_file, ruff_python_trivia, ruff_python_ast, ruff_python_parser) with let / mut soft keywords and a Mutability field on assignment AST nodes. Migration off rustpython-parser is complete.
  • ✅ Diagnostics infrastructure (miette + thiserror).

Language features

  • ✅ let / mut for binding immutability, with reassignment errors and module-level defaulting.
  • ✅ Nominal types: function signatures, assignment compatibility, primitives, classes, generic containers.
  • ✅ Non-nullable by default with flow narrowing on is None, is not None, isinstance, guard, early-return.
  • ✅ T? sugar for T | None in annotations.
  • ✅ class → @dataclass(slots=True); model → Pydantic BaseModel(extra="forbid"); frozen modifier; class! escape hatch for framework bases; plain class for no-decoration / no-synthesised-__init__ semantics; auto-skip for Enum / Flag / ABC / Protocol / NamedTuple / TypedDict subclasses plus user-configurable [emit] skip-decoration-bases.
  • ✅ impl blocks merged into class bodies at desugar; extend for cross-module method addition; extend BUILTIN: extracts to free functions with static-receiver rewrites.
  • ✅ Sealed unions via type X = A | B, exhaustive match checked at compile time.
  • ✅ Result[T, E] with Ok / Err constructors, the ? operator, and with-chains (with optional else err: block). typhon_runtime module generated when used.
  • ✅ Generics via PEP 695 brackets, bidirectional inference with recursive conflict-widening, bounded type vars.
  • ✅ Interface declarations lowering to class Name(Protocol): with structural conformance check on assignment; isinstance(x, Interface) rejected by default.
  • ✅ unsafe: lexical block, lowered to if True: for scope preservation, with Unsafe[T] boundary marker.
  • ✅ @pure / @memo / @pure(memo=True) decorators with the six-condition purity check.
  • ✅ gather: blocks lowering to asyncio.TaskGroup (default) or asyncio.gather(return_exceptions=True) (strategy="best-effort").
  • ✅ go f(x) lowered through typhon_runtime.tasks.spawn with a strong-ref registry.
  • ✅ lazy import np = numpy with a thread-safe proxy class (__TyphonLazy_np_); lazy from x import … rejected at parse time. Module-level lazy let → lazy_let(lambda: ...); class-body lazy let → @cached_property.
  • ✅ Pipes (a |> f() |> g(arg) → g(f(a), arg)); guards (guard x = expr else: ...).
  • ✅ comptime let bindings with env(name, default?) lookup and the sandboxed evaluator (literals, arithmetic, comparisons, boolean ops, ternaries, int() / str() / float() casts, if/elif/else, return, local bindings). comptime def user-defined functions usable from initialisers.
  • ✅ .dty stub files compiling to PEP 561 .pyi; tyc check --stubs AST diff against runtime modules.

Tooling

  • ✅ tyc init, tyc fmt, tyc check, tyc build (with --check dry-run), tyc run (VM default + --compile fallback), tyc lsp, tyc repl, tyc debug (with --break <ty>:<line> source-mapped breakpoints), tyc trace, tyc profile, tyc migrate, tyc stubtest, tyc add / remove / sync, tyc ty, tyc explain, tyc cheatsheet, tyc install skill.
  • ✅ Source maps (.py.map v2 with per-statement out_line → ty_line tables) for tyc trace, cross-file go-to-definition, and tyc debug --break.
  • ✅ tyc-vm: in-process tree-walking interpreter for .ty source. Default execution mode for tyc run (no .py written, no CPython spawn). --compile falls back to build-then-exec for CPython interop.
  • ✅ tower-lsp-server-backed LSP: diagnostics, hover, go-to-definition, completion (visible bindings + keywords + builtins + venv-driven member access + from-import members), “Remove unused import” code action.
  • ✅ tyc fmt wraps ruff format after the in-process whitespace pass.
  • ✅ Diagnostic deep-links: every tyc:: code carries a miette url(...) clause; 90 catalog pages under docs/diagnostics/ are embedded into the binary for offline tyc explain lookup.
  • ✅ Reference VS Code extension under editors/vscode/.

Project plumbing

  • ✅ CI — nine jobs on every push to main / dev/** / claude/**: test (cargo fmt --check → clippy -D warnings → cargo test --workspace), test-macos (the test suite on macOS), fmt-guard (no out-of-scope cargo fmt reformats), security (cargo-deny advisories / licences / source bans), perf-gate (the build-pipeline performance regression gate), differential (the VM ↔ CPython differential gate over the full example + stress corpus), knob-matrix (the opt-in-knob codegen matrix), valid-corpus (tyc check plus the VM ↔ CPython differential over the corpus/valid/ valid-programs corpus), and fmt-corpus (tyc fmt over a de-formatted copy of the corpus must leave every unit’s emitted Python AST unchanged).
  • ✅ Generated typhon_runtime/ ships as local source — no PyPI dependency.

Landed in v0.9.0 (stress-test cleanup release)

The stress-test cleanup release on top of v0.8.1. Closes 32 of the 36 findings from a v0.8.1 stress sweep spanning the type checker, VM, parser, lowering passes, diagnostics, and CLI. The VM is now usable as the daily-driver runner the docs always advertised; the type checker plugs silent-correctness gaps in covariance, variant flow, narrowing, and error propagation; the diagnostic surface gets a polish pass.

The release is additive on the accepted surface — every previously-accepted program continues to type-check, and the new VM features expand what tyc run accepts rather than narrowing it.

Added — VM coverage (closing the gap between tyc run and tyc build && python build/main.py)

  • ✅ Result combinators (.map / .map_err / .and_then / .or_else) now work on Ok / Err values in the VM via bound NativeFn wrappers that capture the receiver. Previously a typecheck-clean program crashed at run-time with AttributeError: Ok has no attribute 'and_then'.
  • ✅ open() honours write / append / binary modes. open(p, "w") / open(p, "a") / open(p, "wb") / open(p, "r+") and friends now all work. with-blocks honour __enter__ / __exit__ on the resulting file. json.load / json.dump ride on top.
  • ✅ Match against built-in class patterns. match x: case str() as s: / case int() as n: / etc. now matches; the exhaustiveness pass also recognises case None: + case str() as s: as covering str?.
  • ✅ frozenset(...) is hashable as a dict key (new HashKey::FrozenSet variant with insertion-order-independent hashing).
  • ✅ f-string _ thousands separator emits the same way , does.
  • ✅ bytes repr matches CPython. b'hi' (single quotes by default), b"with 'embedded'" fallback, \xNN for non-printable.
  • ✅ Native shims for collections.deque, heapq, contextlib, pydantic. Graph / queue / heap algorithms, @contextmanager identity decorators, and model class declarations all run cleanly. deque rides on Value::List via new popleft / appendleft / extendleft / rotate list methods. pydantic.BaseModel is a placeholder so declaring a model doesn’t ImportError.
  • ✅ @property / @classmethod / @staticmethod / super() builtins are present as identity-ish stubs so decorated methods no longer crash on import.
  • ✅ lazy import np = numpy uses the simpler import M as N rewrite in VM mode (the descriptor-based proxy class the build path emits has nothing to bind against in a tree-walking VM).
  • ✅ Multi-file projects run under both tyc run modes. The VM loads sibling .ty modules from the project source root, honours relative imports (from .repo import x), and caches each module’s bindings as a Value::Module. tyc run --compile now spawns python -m <pkg>.main instead of python build/main.py so relative imports in the entry point resolve correctly.
  • ✅ dataclasses.field(default_factory=list) actually invokes the factory per instance. The mutable-default rewrite no longer shares one list across every instance.
  • ✅ class! synthesised __init__ runs. except HttpError as e: print(e.code) works against class! HttpError(Exception): code: int; message: str — the handler binds the user Instance, and exception-type matching walks the MRO.
  • ✅ freeze let CFG = {...} actually freezes (list → tuple, dict → mappingproxy-tagged dict, recursive). Mutators on a frozen dict raise the same TypeError CPython’s MappingProxy does.
  • ✅ comptime let X = ... inlines in the VM via the substitution pass shared with tyc build. comptime let PORT = int(env(...)) no longer crashes with NameError: env is not defined.
  • ✅ Typed tuple unpack let (a: int, b: str) = pair() parses in the VM (parity with tyc check).

Added — type checker

  • ✅ Read-view covariance for built-in containers. list[Subclass] / tuple[Subclass] / set[Subclass] / frozenset[Subclass] flow into Sequence[Super] / Iterable[Super] / Iterator[Super] / Collection[Super] / Container[Super] / Reversible[Super] when Subclass inherits Super. Mapping / MutableMapping cover dict[K, V] (K invariant, V covariant).
  • ✅ Variant → parametric sealed union assignability. Cons[T] / Cons (where type LL[T] = Cons[T] | Nil) is assignable into LL[T]. Required for recursive ADT walks like mut cur: LL[T] = self.
  • ✅ while True: reachability. A loop whose body always returns / raises on every branch and contains no break is recognised as exiting; the post-loop point is unreachable and missing_return doesn’t fire.
  • ✅ Post-while-loop narrowing. After while y is None: y = load() (no break), the post-loop y is narrowed to non-None. Matches pyright / mypy / pyrefly.
  • ✅ assert x is not None narrows. The standard Python static- checker idiom now works.
  • ✅ *args / **kwargs require annotations (Rule 1). Canonical idiom is *args: object / **kwargs: object.
  • ✅ extend list: dispatches on list[T]-annotated receivers. The synthetic __typhon_builtin_ext_list class shape is consulted before attribute_not_found fires.
  • ✅ Exhaustive match on T? recognises built-in class patterns. case None: ...; case str() as s: ... against str? no longer surfaces missing_return.
  • ✅ with-chain explicit else err: return Err(err) validates the error type against the function’s declared return. Previously the check was gated on the synthetic ?-op temp shape, so a with-chain could silently return the wrong error class.
  • ✅ func[T](args) explicit type instantiation now fires a clear check-time error (was: runtime 'function' object is not subscriptable).
  • ✅ comptime let T: type = int lowers to a PEP 695 type T = int alias statement so T is substitutable wherever a type is expected. tyc check also runs the substitution before parsing the resolved module so check mode sees the same shape as build.
  • ✅ freeze let X = <expr> validates the freezability of the RHS at check time. New tyc::freeze_not_freezable fires when the RHS constructs a non-frozen user class, instead of letting the failure surface as a runtime TypeError at first import.
  • ✅ pub * name collisions surface in tyc check (not just tyc build). The detection logic from tyc build is exposed as detect_pub_star_diagnostics and called from the check command before the per-file loop so CI catches collisions before they reach build.

Added — diagnostics polish

  • ✅ interface_not_conforming arity message now reads “got N non-self parameter(s), expected M” instead of the ambiguous “arity N; expected M”.
  • ✅ invalid_question_op help text mentions both the Result-return cause AND the comprehension carve-out.
  • ✅ Sealed-union impl distribution dedupe. impl Alias: over a sealed union duplicates each method body across every variant; the type-checker dedupes diagnostics by (code, rendered message) so a 10-variant union no longer reports 10 identical errors.
  • ✅ class_attr_shadows_slot no longer false-positives on a class whose only annotated defaults are mutable literals (list[str] = [] etc.). Those become default_factory per-instance fields, not shared constants.
  • ✅ MissingAnnotation text drops the double-backtick wrapping (was rendered as `parameter `x “).

Added — language docs

  • ✅ Cheat sheet documents class X frozen(Base): (the modifier comes BETWEEN the class name and the base list) and the *args: object / **kwargs: object idiom for genuinely variadic functions.

Known limitations carried forward

  • ⏳ Preprocess line-number leakage (B15) — diagnostics still report preprocessed-buffer line numbers for impl Alias: distribution over sealed unions. The dedupe pass above cuts the count of noise diagnostics, but each surviving diagnostic still points at a synthetic line index past EOF of the original source. Tracked for the next release — needs a proper source-map rewrite through the diagnostic constructors.

Landed in v0.8.1 (bugfix point release)

A strict bugfix point release on top of v0.8.0. No language, runtime, or stdlib changes beyond the carve-out.

Fixed — type system

  • ✅ tyc::attribute_not_found no longer fires on venv-introspected third-party classes. The v0.8.0 firing-site widening incorrectly trusted shapes built by runtime introspection (inspect.signature(Cls)) to be method-complete, so obj.method(...) against any third-party Python class with a known __init__ flagged the call as missing the attribute (uvicorn.Server.serve(...), httpx.AsyncClient.aclose(...), fastapi.Request.body(...), …). InterfaceShape now carries a partial flag that class_shape_from_params sets on every venv-derived shape; class_hierarchy_fully_known returns false whenever any class in the inheritance chain is partial, so attribute access stays permissive on third-party APIs whose method surface we can’t see. All fifteen apps under examples/apps/ build clean again.

Landed in v0.8.0 (stress-test sweep)

The stress-test sweep release on top of v0.7.1. Closes 41 findings from a multi-file v0.7.1 stress report spanning the type checker, VM, parser, lowering passes, diagnostics, and CLI.

The release is mostly additive on the accepted surface; the BigInt switch in the VM means programs that relied on silent i64 wrap-around now compute different (correct) results.

Added — type system

  • ✅ tyc::attribute_not_found now fires on class instances and generic classes, not just TypeVar-bounded parameters. The diagnostic was already documented but had no firing site for the most common case. Foreign / venv-introspected classes are tracked with a new partial shape marker and keep the permissive degrade-to-Unknown behaviour so adapters around external libraries don’t get false positives. Skipped in unsafe: regions and for dunder / leading-underscore names.
  • ✅ Interface parameter type conformance. interface_missing_members now compares parameter types position-by-position (contravariant on params) in addition to arity, so a class BadRepo claiming to implement interface Repo: def save(self, item: str) -> bool with a def save(self, item: int) -> bool impl is rejected at conformance time.
  • ✅ Type::LitStr(String) — string-literal singleton types. type Color = "red" | "green" | "blue" and Literal["a", "b"] produce LitStr slots in the resulting Union; assignability rejects paint("orange") against Color. Bidirectional inference widens string literals to LitStr only when the expected type carries one, so unannotated let s = "hi" still infers plain str.
  • ✅ ? propagation inside with-chains. result_error_mismatch fires when the implicit return form of with x = f()?: … routes a mismatching error type through the chain.
  • ✅ tyc::pattern_shadows_outer fires when a match capture binds a name that already exists in the outer scope.
  • ✅ field_default_ordering skips ClassVar fields.
  • ✅ newtype Foo = "literal" is rejected with the new tyc::newtype_invalid_base diagnostic.
  • ✅ Exhaustive-match-with-guards no longer fires missing_return when every variant has at least one (possibly-guarded) case.
  • ✅ Function parameter rebinding requires mut, matching the let/mut rule everywhere else.

Added — parser & lowering

  • ✅ HKT scaffold class Functor[F[_]]: parses.
  • ✅ impl[T] SealedUnionAlias[T]: distributes the methods across every variant of the sealed-union alias.
  • ✅ class X[T] frozen: (generic + frozen) parses cleanly.
  • ✅ async def in interface bodies auto-completes the : ... body (sync def already did).
  • ✅ Outer-annotation tuple unpack let (a, b): tuple[int, str] = … is accepted.

Added — VM (tree-walking interpreter)

  • ✅ Arbitrary-precision integers. Value::Int is now backed by num_bigint::BigInt everywhere. 2 ** 100 and fib(99) no longer overflow. Behaviour change: programs that relied on the VM’s silent i64 wrap-around now produce mathematically-correct results.
  • ✅ Dict insertion order preserved. RcDict is now an indexmap::IndexMap; the same .ty file no longer prints dicts in different orders under tyc run vs tyc build && python build/main.py.
  • ✅ f-string format flags fully wired. Zero-pad, alternate-form, [fill]align, sign, width, comma, precision, and type all match CPython output.
  • ✅ Mapping match patterns (case {"type": "circle"}, case {…, **rest}) and sequence-with-star patterns (case [x, *rest, y]) implemented.
  • ✅ Recursion limit raised to 1000 (was 256) to match CPython.
  • ✅ yield and async def emit a clear NotImplementedError pointing at tyc build && python as the fallback instead of crashing the interpreter.
  • ✅ Extend-builtin rewrites apply in VM mode.
  • ✅ Subclass constructors inherit fields. class Dog(Animal): breed: str accepts Dog(name=…, age=…, breed=…) under tyc run.
  • ✅ freeze let and newtype shims are now native builtins so VM mode no longer crashes with NameError on the lowered call.
  • ✅ Larger native stdlib. Adds re, typing, collections (OrderedDict, defaultdict, Counter, namedtuple), functools (lru_cache, cache, cached_property, reduce, partial), itertools (chain, count, cycle, accumulate, combinations, permutations, product, islice, takewhile, dropwhile, groupby), dataclasses, pathlib.
  • ✅ from typing import Callable no longer crashes — VM-mode lowering strips pure-type imports.

Added — diagnostics & CLI

  • ✅ Synthetic preprocess lines no longer leak into source listings. SanitisedDiagnostic wraps every emitted diagnostic and hides the class __typhon_impl_Foo(object): / from typhon_runtime import … / ?-scaffolding lines, restoring the original text for the user.
  • ✅ Dedicated parse-error hints for multi-line |> chains (wrap in parens) and freeze let at non-module scope.
  • ✅ wrong_arg_count rephrasing for kw-only mismatches — the self-contradictory “expected 2, got 2” message is replaced with a “pass them by name” help block.
  • ✅ Collection variance hint suggests Sequence[Animal] / Mapping[K, V] / frozenset[T] instead of the previously-unhelpful “widen to list[Animal] | list[Dog]”.
  • ✅ Dict-to-model mismatch points users at the constructor form (UserCreate(name=…, age=…, email=…)).
  • ✅ tyc check lib.dty now accepts a single .dty file directly.
  • ✅ tyc run --compile rejects single-file inputs up-front.
  • ✅ tyc migrate strips trivial __init__ methods and emits the resulting class as plain class (not class!), preserving any leading class docstring.
  • ✅ New lint warnings: tyc::empty_collection_no_annotation, tyc::typing_alias_in_annotation, tyc::contains_secret_literal.

Changed

  • ✅ unused_import default severity is now warn (was error). Set [strictness] unused-import = "error" in typhon.toml to restore the old default.

Landed in v0.7.1 (LSP bugfix)

A strict bugfix point release on top of v0.7.0. No language, runtime, or stdlib changes.

  • ✅ Semantic-token positions now line up with the original .ty source instead of the preprocessed Python view. The LSP computed token coordinates against the post-preprocess source (with pub, comptime, freeze, lazy, newtype line-prefix modifiers stripped) but the editor applied those coordinates to the original file. The remap pass now translates token spans into original-source coordinates and validates each one by string match, dropping synthetic identifiers the preprocessor injects.

Landed in v0.7.0 (Round-3 carry-over)

The carry-over minor release that closes the Round-3 apps-feedback campaign. A third stress round built five additional production-shaped apps on top of the original ten (real-time game server, static site generator, vector DB, API gateway, stream processor — under examples/apps/11-… through examples/apps/15-…). Every remaining ergonomics gap from that round is turned into a compiler feature here.

The release is strictly additive on the language surface — every previously-accepted program continues to compile to identical Python.

Added — language & runtime

  • ✅ pub * wildcard re-export aggregation in __init__.ty, including transitive aggregation through sub-packages. A single pub * at the top of a package’s __init__.ty re-exports every direct-sibling module’s pub names. Direct sub-packages contribute their own effective public surface — their pub names plus, recursively, whatever their own pub * aggregates one level deeper, cycle-safe via a visited set. Two new diagnostics: tyc::pub_name_collision (sibling-export name clash) and tyc::pub_star_outside_init (advice: marker outside __init__.ty).
  • ✅ Declare-only let NAME: T with arm-assignment. The let loaded: Cfg; match _load(): case Ok(v): loaded = v; case Err(e): return Err(e) shape no longer fires tyc::missing_initialiser. Sibling match/if/elif/else arms each count as a separate first-assignment path. Companion tyc::use_of_uninitialised definite-assignment analysis covers reads on paths that didn’t assign.
  • ✅ with cm() as r: types r from __enter__ / __aenter__ and from @contextmanager / @asynccontextmanager factories. Three lookup paths in priority order: decorator-aware yield-type inference, concrete-class __enter__ / __aenter__ returns, fall-through to Unknown. The canonical @asynccontextmanager async def session() -> AsyncIterator[Session]: yield Session(...) factory shape now types as-targets correctly.
  • ✅ await on a Callable[..., Awaitable[T]] / Coroutine[Y, S, T] unwraps to T. The canonical async-middleware shape next: Callable[[Req], Awaitable[Resp]] followed by let r: Resp = await next(req) now type-checks without a spurious tyc::type_mismatch. The biggest single Round-3 finding.
  • ✅ Same-newtype arithmetic preserves the newtype. newtype LogIndex = int followed by last_idx + 1 or LogIndex + LogIndex no longer widens to Unknown across + - * // % **. Two distinct newtypes with the same base (LogIndex + Term) still fire tyc::operator_type_mismatch.
  • ✅ Cross-module generic method dispatch propagates class TypeVars. s: Stream[int].map(f) records Callable[[int], U] as the expected parameter and returns Stream[U] bound at the call site. Same fix benefits field access.

Fixed — resolver, syntax, and checker

  • ✅ Nested from X import Y in if/for/while/with/try/ match arms now binds (parser already accepted; resolver silently skipped).
  • ✅ Sibling if/elif branches no longer trip no_block_shadow for same-named let bindings.
  • ✅ Multi-line go expr(...) calls parse. Implicit line continuation inside parens now works for go everywhere.
  • ✅ Ternary body if test else orelse narrows. isinstance(x, T) and x is not None refine x on the truthy side (and the negated form on the falsy side) inside the expression form, matching the statement-level behaviour.
  • ✅ tyc::field_default_ordering catches non-default-after-default class fields at check time — Python would otherwise reject the synthesised __init__ at import time with a misleading TypeError.

Added — examples & tooling

  • ✅ Five new reference apps (11–15). Real-time game server, static site generator, vector DB, API gateway, stream processor. Each tyc checks clean, tyc builds, runs through CPython, and carries a README of the friction surfaced during the build.
  • ✅ All fifteen apps re-organised into grouped subdirectories. No more flat src/ — every app now has 2-5 grouped subdirectories (domain/, storage/, runtime/, transport/, …) with pub * __init__.ty facades so import paths stay short.
  • ✅ VS Code extension 0.1.9 → 0.2.0. Grammar updates: pub def / pub async def highlight pub as a storage modifier; pub * re-export in __init__.ty highlights as a single construct; the modifier slot accepts every combination of pub / freeze / comptime / lazy in front of bindings.

Landed in v0.6.1 (polish)

A polish release on top of v0.6.0. No previously-accepted program changes behaviour.

  • ✅ let name: lowercase_type = … highlights as a type annotation. The standalone binding-declaration rule was a one-shot match that stopped at the binding name; it now owns the : Type slot.
  • ✅ .py.map sidecars now live under <out>/.sourcemaps/. Mirrors the emitted Python tree (build/foo.py → build/.sourcemaps/foo.py.map, build/pkg/bar.py → build/.sourcemaps/pkg/bar.py.map). Map resolvers fall back to the legacy adjacent layout for existing build directories.

Landed in v0.6.0 (apps-feedback minor release)

An apps-feedback minor release on top of v0.5.2. A two-round stress campaign built ten multi-file production-shaped apps under examples/apps/ (event-sourced banking, distributed key-value store, mini-compiler, search engine, GraphQL server, game ECS, trading engine, ML orchestrator, web crawler, task scheduler). Every issue that campaign surfaced is closed, and the apps themselves ship as canonical multi-file reference programs.

The release is strictly additive on the language surface — every previously-accepted program continues to compile to identical Python.

Added — language & runtime

  • ✅ Ok / Err expose the standard Result combinators as methods. map, map_err, and_then, or_else are now bound on the runtime classes (not just the free functions in typhon_runtime/result.py), so heterogeneous-error pipelines can normalise per stage in chain form: let toks = tokenize(src).map_err(_lex_to_pipeline)?. The free-function versions still exist for callers that prefer qualified access.
  • ✅ impl on a sealed-union alias distributes to every variant. impl Event: where type Event = A | B | … used to fire tyc::impl_unknown_class; the desugar pass now replicates the impl body’s methods on every variant class and the type checker mirrors the same fold. Per-variant dispatch via match self: still works because the runtime class on self only matches its own arm. The tyc::duplicate_method check from the concrete-class branch is mirrored into the union branch.
  • ✅ tyc::stdlib_module_shadow warning. A project .ty file whose stem matches a Python 3.13 stdlib top-level module (types, ast, string, io, json, dataclasses, logging, …) emits build/<name>.py, which the default python build/main.py entry point puts on sys.path ahead of the stdlib. Transitive imports then resolve to the project module instead, producing baffling ImportErrors blamed on innocent stdlib packages. The new warning fires per-file in tyc check, is gated on a typhon.toml being present, and points at the rename pattern (lang_types.ty, records.ty, …). Severity is warn (non-fatal).

Fixed — compiler

  • ✅ Cross-module sealed-union variant flow. pub type Event = A | B declared in lib.ty lets A(...) flow into an Event-typed slot within lib.ty, but consumer modules that imported both the variants and the alias used to hit tyc::type_mismatch. Both ModuleShapes and ExternalShapes now carry sealed_unions and interfaces maps; the CLI / LSP re-key them under each local import name (including alias renames).
  • ✅ Cross-module function signatures preserve parameter and return types. Functions imported via from foo import f used to be registered with Type::Unknown placeholders for every parameter and the return type — ArityInfo carried names + counts but no types. A nullable parameter def takes(p: Price?) -> int: consumed from another module would render in tyc::nullable_use as the literal placeholder ?. ArityInfo now records param_types, kwonly_types, and return_type, so an imported function looks identical to a local def at call sites.
  • ✅ Exhaustive match on a sealed union satisfies missing-return for any subject expression. match get_state(): case A(): ...; case B(): ... against a sealed union returned by a function used to fire a false-positive tyc::missing_return because the analyser only inferred subject types for bare names and attribute access. It now falls back to expression inference for any subject shape — function calls, subscripts, arbitrary expressions all flow through the exhaustiveness path.
  • ✅ Partial keyword pattern satisfies match exhaustiveness. case Foo(field=x): (binds only field, ignores the rest) was treated as non-exhaustive even though Python’s match accepts it. Keyword patterns are now folded into the per-variant coverage tally the same way positional patterns are.
  • ✅ Sibling case arms can bind the same let name. case A(): let key = ...; case B(): let key = ... fired tyc::no_block_shadow even though at most one arm runs at runtime. The resolver now drains arm-local bindings into a side buffer between cases, mirroring the per-arm scope-stack pattern the type checker already uses.
  • ✅ For-target no longer rebinds outer let bindings. Python’s for-target is an assignment, not a fresh declaration. The resolver used to trip tyc::immutable_assign against a prior let in the enclosing scope, even when the prior let was inside an unrelated sibling for-loop body. The same silencing now applies to any prior binding when the new binding is a for / with / except / comprehension target. Manual body-level assignment (i = i + 1) is unaffected.
  • ✅ pub freeze let X = … parses. The pub-prefix stripper didn’t recognise freeze let as a multi-word keyword form. pub freeze let DEFAULT: dict[str, int] = {...} now lowers correctly and round-trips through tyc fmt.
  • ✅ pub def is visible to the ? operator validator. The ? propagation pass walked function declarations to check the enclosing return type accepts Result, but pub def f() -> Result[T, E]: looked like a bare def with no return type because the pub-stripper ran after the walk. The stripper now runs upstream.
  • ✅ tyc::nullable_use no longer renders ? as the expected type. Companion fix to the cross-module signature seed: where the bound was still Type::Unknown, the formatter used to print a bare ?. It now substitutes the resolved bound or falls back to a clearer phrasing.
  • ✅ loop.run_until_complete(coro()) no longer fires tyc::missing_await. Added to the coro-acceptor whitelist alongside asyncio.run(...).
  • ✅ @contextmanager factory bodies are exempt from tyc::resource_not_managed. A @contextmanager-decorated function whose body opens a file or socket is the resource manager — the check now skips function bodies carrying @contextmanager or @asynccontextmanager (bare or dotted-module form).

Improved — diagnostics & docs

  • ✅ tyc::type_mismatch help text now suggests widening, not narrowing. Was: “change the value, or update the annotation to <found>”. Now: “change the value so it produces <expected>, or widen the annotation to <expected> | <found> if both are intended”.
  • ✅ New docs/diagnostics/stdlib_module_shadow.md with a rename table (types.ty → lang_types.ty, dataclasses.ty → records.ty, etc.) and an explanation of the ImportError cascade it prevents.
  • ✅ docs/diagnostics/class_attr_shadows_slot.md gains a “nullary sealed-union variants” section pointing at the pub class TyInt frozen: pass idiom.
  • ✅ docs/guides/07-sealed-unions-and-match.md documents keyword patterns as the recommended form for variants with more than two or three fields.
  • ✅ docs/cli.md documents tyc explain --list.

Added — examples & tooling

  • ✅ examples/apps/ — ten production-shaped multi-file apps. Each app tyc checks clean, tyc builds, runs through CPython, and carries a FRICTION.md or README cataloguing the gaps the build surfaced. examples/apps/TYPHON_FEEDBACK.md aggregates the campaign findings.
  • ✅ VS Code extension 0.1.7 → 0.1.8. No new keywords or grammar surface in this batch — the deep audit in v0.5.1 already covers every construct landed since. Version bump only.

Landed in v0.5.2 (correctness + documentation point release)

A correctness + documentation point release on top of v0.5.1. The two compiler fixes broaden the accepted surface — every previously- accepted program continues to compile to identical Python.

Fixed — compiler

  • ✅ tyc-syntax: <ident>? propagation now lowers correctly in value position. let x: T = a? (and return a? / yield a? / raise a?) silently rewrote to x: T = a | None because the ?-pass only recognised f()? (paren-prefixed). The resulting .py crashed at runtime with TypeError: unsupported operand type(s) for |: 'Ok' and 'NoneType'. The pass now disambiguates by RHS position: an = on the line with the identifier-then-? on the RHS, or a return / yield / raise prefix, both trigger the standard __typhon_q_N__ ladder. Pure annotation forms (let x: int?, let x: list[int]?) are unchanged. Unblocks the natural gather: → ? → Ok(...) shape used throughout the tour, first-program, and recipes docs.
  • ✅ tyc-types: set - set and frozenset - frozenset accepted. The operator-compatibility check for - accepted only numeric operands; the sibling & / | / ^ already worked because they fell through to the permissive arm. Added an explicit carve-out matching the existing + carve-out for list / list and tuple / tuple.

Changed — docs site (~40 files)

  • ✅ Side-by-side Typhon / Emitted-Python tabs across the user-facing docs. Every complete, runnable Typhon example in the tour, types, reference, recipes, getting-started, and lowering sections is now presented as a <Tabs> pair, with the Python side produced by running the example through the actual tyc build pipeline rather than written by hand.
  • ✅ Stale claims surfaced by the audit were corrected. lazy let X: T: colon-block form (which doesn’t parse) is replaced with the working lazy let X: T = expr shape, and the generator lazy[T] return-type form is now documented as roadmapped. model X frozen: (also not parsed) is replaced with a .py escape-hatch example. let-shadowing claims in reference/let-mut.mdx, diagnostics/binding-errors.mdx, and tour/five-rules.mdx are corrected (Typhon rejects all let-shadowing because Python is function-scoped). The fall-through analysis claim in tour/control-flow.mdx is replaced with the actual tyc::missing_return behaviour. lowering/runtime.mdx is rewritten to show the exact emitted-runtime source. Multi-line |> pipes in reference/pipes.mdx now wrap in parens (the working form). diagnostics/compile-errors.mdx’s lazy-let example moves into an impl block where the feature belongs.

Added — tooling

  • ✅ docs-site/scripts/verify_examples.py — a re-runnable harness that walks every .mdx, classifies each code block, and tries to compile complete-program blocks via tyc build. --real-only filters partial-snippet noise; exits non-zero when real issues remain so the audit can wire into CI. Parallelised; the default 4-way pool audits ~135 files in under a minute.

Landed in v0.5.1 (correctness + tooling point release)

A correctness + tooling point release on top of v0.5.0. No language-semantics change.

Fixed — compiler (PR #120)

  • ✅ tyc-format: triple-quote tracker desync. The whitespace pass treated """ as three independent toggles of its single-quote tracker. On a line like """, encoding="utf-8") the tracker came out of sync and rewrote "utf-8" as "utf - 8" — an encoding name Python rejects with LookupError. Added a triple-quote state machine that consumes everything up to the matching qqq closer verbatim.
  • ✅ tyc-desugar: case Ok(...) / case Err(...) patterns didn’t trigger auto-import. stmts_use_result_names walked match-case bodies and guards but skipped the patterns themselves, so a file that only ever pattern-matched on Ok / Err (without constructing or returning a Result) didn’t get from typhon_runtime import Ok, Err, Result injected and the emitted .py NameError’d at runtime. Added pattern_uses_result_names that walks every Pattern variant.
  • ✅ tyc-syntax: duplicate __typhon_Err__ alias dedupe. The de-dupe check compared a trimmed line (still carrying its trailing newline) against IMPORT_LINE.trim_end() (no newline), so equality never matched and a second from typhon_runtime import Err as __typhon_Err__ line slipped through whenever ? propagation and with-chain lowering both ran.

Changed — VS Code extension (PRs #119, #121)

  • ✅ Deep TextMate grammar audit against ~19k lines of real Typhon code from examples/ and stress/. Split expression into expression + expression-inner; subscripts [...], dict / set literals {...}, and lambda bodies handle their own : so it stops being eaten as a spurious type annotation. Fixes xs[1:4:2], {k: v for k, v in xs}, lambda x: x + 1. Balanced #parens matcher in expression-inner so s: T = Depends(get_store) consumes its own ).
  • ✅ Tightened type-annotation lookahead. The colon in single-line if isinstance(...): return ... and case Foo(x): bar() no longer fires as a type annotation.
  • ✅ ~18 additional miscoloring fixes across keyword spans (pub / freeze / extend / unsafe), f-string nesting, regex literals, match arms, and decorator argument lists. Extension version 0.1.5 → 0.1.7.

Added — examples

  • ✅ 22 new stdlib-only exercises (47–68): mini-app, newtype IDs, Fibonacci memo, linked list, BST, stack & queue, sorting, graph traversal, word frequency, state machine, iterators / generators, context managers, matrix ops, Caesar cipher, tic-tac-toe, priority queue, event bus, URL router, INI parser, rate limiter, trie, JSON-RPC builder. Each ships .ty source + an emitted .py companion.
  • ✅ Emitted .py companions for examples 01–46 so readers can see the lowering without running tyc build.
  • ✅ examples/testing/ gains a calculator + pytest companion exercising the Result-in-tests pattern.

Landed in v0.5.0 (post-v0.4 roadmap sweep)

The v0.5.0 release lands the seven Phase 4+ items shipped on PR #105 plus four follow-up epics (PRs #110, #111, #112, #113) that close the open frontier work flagged during that sweep.

Incremental compilation

  • ✅ Salsa cache shared across check_file_with_imports. New preprocessed_full tracked query returns the full PreprocessResult so preprocessed_text, resolved_module, module_decl_names, and the new check_source_file_with_imports entry point all share one preprocess pass per revision. The LSP now calls the SourceFile-backed entry directly so a per-keystroke cross-module check hits the cached parse + resolve on every unchanged sibling.
  • ✅ Eliminated double-resolve in check_source_file_with_imports. ArcResolvedModule now carries the resolver diagnostics alongside the resolved module (both behind Arc for pointer-equality salsa::Update), so the second resolve_module_with call that previously ran just to harvest diagnostics is gone.

tyc debug and tyc ty

  • ✅ Typhon-aware pdb wrapper. tyc debug writes a one-shot Python wrapper that subclasses pdb.Pdb and prints [ty] <src>:<line> after every pause; it loads every .py.map sidecar under the build directory at startup.
  • ✅ Full UI translation. The pdb subclass overrides do_list, do_where, format_stack_entry, and the prompt property so the entire debugger surface reads .ty coordinates. Source-snippet rendering (list) reads the .ty file slice when a .py.map resolves the source path.
  • ✅ tyc ty diagnostic remapper handles paths with spaces. parse_py_ref walks left from each .py: occurrence and yields successively longer candidate prefixes, taking the longest match that corresponds to a real .py.map sidecar.

tyc migrate

  • ✅ Three new line-level rewrites. @dataclass(frozen=True[, ...]) (and @dataclasses.dataclass) → class X frozen:; class X(Protocol): / class X(Protocol[T]): → interface X: / interface X[T]:; module-level NAME = NewType("NAME", BASE) → newtype NAME = BASE. Matching Protocol / NewType from typing import … entries are pruned alongside.

Type checker

  • ✅ Cross-function field-init audit. A pre-scan recognises the trivial factory-helper shape def make(): return X.__new__(X) (and the two-statement obj = X.__new__(X); return obj variant). Call sites let c = make() register the LHS as a tracked uninit instance so a downstream escape fires tyc::missing_field_init.
  • ✅ Variance table expansion. generic_param_variance gains AsyncContextManager, KeysView, ValuesView, ItemsView, Type / type, and Counter.
  • ✅ Higher-Kinded Types foundation. New Type::TypeConstructor { name, arity } variant represents type constructors with unbound parameters. type_from_annotation recognises F[_] parameter syntax inside class / function generic parameter lists; walk_typevars traverses the new variant. The full unification surface is staged on this scaffold; the design doc TYPE_SYSTEM_FRONTIER.md records the deferred work.

Analyser

  • ✅ Parallel comprehensions: set-comp and dict-comp support. {f(x) for x in xs} rewrites to set(typhon_runtime.parallel.map_pure(lambda x: f(x), xs)); {k_expr: f(v) for k, v in items} rewrites to a dict-literal unpack form that avoids dict shadowing. Both opt-in via [strictness] auto-parallel.
  • ✅ comptime types-as-values. New ComptimeValue::Type(String) variant lets comptime let T: type = int round-trip through the comptime evaluator. The bare-name resolution covers the eight primitive heads (int, str, bool, float, bytes, None, type, object); Any is intentionally rejected unless imported.

Test infrastructure

  • ✅ Third-party Python corpus round-trip sweep. stress/third-party-py-corpus/ ships six representative Python fixtures and the integration test third_party_corpus_round_trips_cleanly exercises the full tyc migrate → tyc check chain on each.
  • ✅ PyPI sweep harness. stress/pypi-sweep/sweep.py pip-installs typed packages into a tempdir, runs tyc migrate + tyc build, and semantic-diffs smoke-script output. Opt-in nightly; not wired into per-PR CI.
  • ✅ python_semantic_drift audit round 4. stress/round-2026-05-23-drift-round-4/ ships 17 fresh probes covering walrus-in-comprehension, augmented-assignment narrowing, yield from, match-pattern * capture, string multiplication, raise X from Y, and f(*args, **kwargs) unpacking. All probes accept.
  • ✅ Inter-procedural field-init audit design. stress/interprocedural-audit-design.md records the summary-IR sketch for generalising the trivial-factory audit to multi-step factories.

Landed in v0.4.0 (type-checker correctness sweep)

  • ✅ bool ⊆ int subtype widening. let x: int = True, 1 + True, -True now type-check the way CPython evaluates them. One-way only — let b: bool = 1 still rejects.
  • ✅ Subclass constructors see inherited fields. effective_class_shape() walks the inheritance chain (parent fields first, child overrides on collision, cycle guard).
  • ✅ Dotted-attribute annotations resolve to the foreign class shape. let c: foo.ApiClient = ... produces Class("foo.ApiClient") matching call-site convention.
  • ✅ tyc::missing_field_init catches container-literal + alias escapes. Partially-initialised instances escaping via let configs: list[Config] = [c] or let alias: Config = c no longer slip past.
  • ✅ Fixed-arity tuple covariance on every slot. tuple[int, int] now widens slot 0 and slot 1 uniformly. Mutable-container invariance unchanged.
  • ✅ De Morgan narrowing on not (A or B). if not (x is None or y is None): use(x, y) correctly refines both names in the post-if branch.
  • ✅ Triple-quoted strings round-trip as triple-quoted. Multi-line docstrings stop emitting as single-line \n-escaped blobs.
  • ✅ Corpus round-trip CI sweep. Every .ty under examples/ must tyc check clean on every PR.
  • ✅ 266 vendored Ruff insta tests re-enabled across ruff_python_parser / ruff_python_ast / ruff_text_size.

Landed in v0.3.1 (correctness follow-up)

  • ✅ Three CRITICAL silent-wrong-output fixes. not (a or b) no longer round-trips as not a or b (De Morgan violation); not (x if c else y) keeps its parens; the in-process VM’s match arm writes propagate back to the enclosing scope instead of being discarded (every Result walker / sealed-union aggregator / state machine used to read 0 from tyc run and the right value from tyc run --compile).
  • ✅ tyc run gates the VM behind a real static check. Programs with unresolved names now surface tyc::unknown_name at check time instead of crashing with a Python-style NameError at VM time.
  • ✅ tyc migrate rewrites Generic[T] → PEP 695. Pre-3.12 generic idiom (T = TypeVar("T"), class Box(Generic[T]):) is rewritten to class Box[T]: and the dead TypeVar / Generic imports are elided; multi-parameter, mixed-base, and qualified typing.Generic forms covered.
  • ✅ Typed tuple-unpacking let. let (a: int, b: str) = func(x, y) desugars to a hidden temp plus per-element lets carrying user-supplied annotations. Compound annotations and mixed-capture forms covered.
  • ✅ tyc::duplicate_method. Two impl Foo: / extend Foo: blocks defining def get(self) -> … used to merge silently with Python keeping whichever came last. Now anchored at the second def with rename / delete / merge advice.
  • ✅ tyc::newtype_violation covers boundary mismatches. A bare int flowing into a UserId-typed parameter now routes through the newtype diagnostic instead of type_mismatch with wrong-direction advice.
  • ✅ ? inside a comprehension → targeted diagnostic. The previous behaviour silently hoisted past the for-binding; now rejected at desugar.
  • ✅ match self.<field>: exhaustiveness. Subject-type resolver now delegates Expr::Attribute to infer_expr_readonly, so a class with a sealed-union field can match it directly without binding to a local.
  • ✅ from __future__ import annotations not duplicated when the user authored their own.
  • ✅ Comptime str.join(...) added to the sandbox.
  • ✅ LSP polish. Bare-import attribute access (nn.Module, pd.DataFrame) paints as a class via a (receiver, attr) → kind map fed from the venv-introspection cache; introspection failures surface in hover with a recovery hint; import torch.nn as nn prewarms torch.nn (not just torch); per-module timeout 3 s → 10 s; VS Code TextMate grammar gains v0.3.0 keywords (freeze, newtype, pub, frozen, plain, class!) and stacked-modifier (pub freeze let) support.
  • ✅ tyc check groups errors by source file with a per-code summary tally (1 error(s): tyc::arg_count, …) and an tyc explain <code> suggestion footer.
  • ✅ Batched venv signature recovery + Salsa-shared preprocess across the resolver / type-checker / analyser / desugar passes.

Landed in Phase 6 (Python-annoyances surface) — v0.3.0

  • ✅ newtype Name = Base — nominal aliases over base types. Keeps same-shaped primitives (UserId vs PostId, USD vs EUR) from being silently swapped. Asymmetric: a UserId flows into an int slot, but a bare int requires UserId(x) to satisfy a UserId-typed target. Compiles to a zero-cost typing.NewType call. New tyc::newtype_violation diagnostic.
  • ✅ freeze let X = expr — deep-immutable bindings. Wraps the RHS through typhon_runtime.freeze.deep_freeze, recursively replacing list → tuple, dict → MappingProxyType, set → frozenset so the value (not just the name) is locked. Raises TypeError on values without a clean immutable equivalent (file handles, sockets, generators).
  • ✅ pub module-level visibility modifier. When at least one name is pub, desugar synthesises a top-of-file __all__ = [...] so from foo import *, Sphinx autoapi, IDE re-export filters, and the checker’s re-export inference all see the public surface — without anyone hand-maintaining the list.
  • ✅ tyc::blocking_in_async — flags direct calls to known-blocking stdlib functions (time.sleep, requests.get, socket.recv, subprocess.run, input, urllib.request.urlopen, …) inside an async def body. Suggests asyncio.to_thread(...). Suppressed inside unsafe: regions.
  • ✅ tyc::resource_not_managed — flags bare assignments of context-manager-returning calls (open, socket.socket, sqlite3.connect, tempfile.*) that aren’t wrapped in a with statement. Severity defaults to warn; controlled by [strictness] resource-not-managed.
  • ✅ tyc::div_by_zero_literal — constant-fold safety lint for x / 0, x // 0, x % 0 when the divisor is a literal (0, 0.0, -0, -0.0, unary-negated zero). Pure constant-fold with zero false positives.
  • ✅ Cross-platform install. Pre-built tyc binaries for Linux (x86_64 + aarch64) and Windows (x86_64) join the existing macOS (Apple Silicon + Intel) matrix. New install.ps1 PowerShell installer for Windows; the existing install.sh now detects Linux from uname -s and resolves the matching tarball. Release workflow runs a five-job matrix and uploads a combined SHA256SUMS.
  • ✅ Findings sweep — every open finding closed. Across nine stress campaigns (May 17–21 2026, ~600 hand-written .ty programs, ~120 distinct findings), the Open column on docs/findings.md is empty for the first time since tracking began. Notable closures: tyc fmt gains five PEP 8 rules (def f( x:int,y:int)->int: → def f(x: int, y: int) -> int:); TypedDict-style dict literals type-check against class shapes; inline ? works (Ok(add(parse(s)?, parse(t)?))); Sized-style Protocols match every built-in container; tyc migrate rewrites Union[T, None] → T?; the VM’s Result repr matches CPython’s dataclass default; new tyc::unsafe_value_leak, tyc::pattern_shadows_outer, and tyc::extend_builtin diagnostics.

Landed in Phase 5.5 (constructor / method arity safety) — v0.2.0

  • ✅ tyc::arg_count on constructors. The auto-generated __init__ of class and model declarations is now arity-checked at every call site. ApiClient(base_url="…") for a class with a required api_key: str field is rejected at tyc check / tyc build time instead of crashing with TypeError: missing 1 required positional argument at runtime. T? without an explicit = None is still required.
  • ✅ tyc::arg_count on impl methods. Method signatures carry full ArityInfo (param names, defaults, *args / **kwargs) rather than a single arity count. u.greet() is flagged when greet declares a required prefix: str parameter.
  • ✅ Cross-module shape propagation. from foo import ApiClient; ApiClient(…) and import foo as f; f.ApiClient(…) both flow through the new arity checks via a project-wide shape registry built once per invocation. .ty source and .dty stubs participate on equal footing — stubs win on collisions. Works in tyc check, tyc build, and the LSP.
  • ✅ Salsa-cached LSP shape extraction. A tyc_db::module_shapes_query salsa-tracked query caches per-file shape extraction; a keystroke in one file only re-runs extraction for that file.
  • ✅ tyc::missing_field_init post-construction audit. Catches X.__new__(X) / object.__new__(X) bypass patterns where the instance escapes the function (return / call argument) with required fields unassigned. Dropped conservatively on setattr, on obj.method(…) calls, and inside unsafe: regions.

Landed in Phase 5 (interop & DX) — v0.1.6

  • ✅ plain class X: keyword and auto-skip for Enum / Flag / ABC family (configurable via [emit] skip-decoration-bases).
  • ✅ [emit] class-default rejects unknown values at config load (tyc::invalid_config_value).
  • ✅ or / and typed as truthy/falsy union of operands (not bool); generator functions structurally assignable to Iterable[T] / Iterator[T].
  • ✅ tyc explain <code> and tyc cheatsheet for discoverability; richer tyc init scaffold; docs link footer in tyc --help.
  • ✅ .py files in src/ copy verbatim into the build output; tyc::orphan_py_import warns on out-of-src/ relative imports.
  • ✅ Diagnostic deep-links (url(https://github.com/CodeHalwell/Typhon/blob/main/docs/diagnostics/<code>.md) on every tyc:: diagnostic) with 50+ catalog pages.
  • ✅ tyc build --check dry-run; tyc::contains_secret_literal lint.
  • ✅ tyc fmt wraps ruff format; tyc debug --break <ty>:<line> source-mapped breakpoints.

Landed in Phase 4+

  • ✅ Automatic asyncio.gather inference (opt-in via [strictness] auto-gather).
  • ✅ Loop parallelisation for pure list / set / dict comprehensions on free-threaded Python (opt-in via [strictness] auto-parallel, threshold parallel-min-size). Dict-comp coverage landed in v0.5.0.
  • ✅ PGO via tyc profile (opt-in via [strictness] pgo-memoise).
  • ✅ LSP completions (including venv-driven member-access introspection and from-import members from sibling project files) and “Remove unused import” code action.
  • ✅ Cross-file go-to-definition across .ty / .py boundaries.
  • ✅ tyc migrate (typed Python → Typhon).
  • ✅ tyc repl, tyc debug, tyc run (with the tyc-vm interpreter as default).
  • ✅ tyc add / remove / sync package-manager surface over uv.
  • ✅ tyc stubtest runtime probe via mypy.stubtest.
  • ✅ comptime types-as-values via ComptimeValue::Type (v0.5.0). The frontier work — types flowing into annotations through emit — is staged on this variant.
  • ✅ HKT foundation (Type::TypeConstructor, F[_] parameter syntax) shipped in v0.5.0. The unification piece is the remaining work.
  • ✅ Cross-function field-init audit (trivial factory helpers, v0.5.0).
  • ✅ Native debugger UI translation: do_list, do_where, format_stack_entry, prompt all read .ty coordinates (v0.5.0).

Deferred

These items appear on the roadmap but are not actively being worked. They will land when the value justifies the effort.

  • ✅ (landed in v1.0.0-alpha) HKT unification — a constructor variable F in class Functor[F[_]]: now binds against a concrete head, with tyc::kind_mismatch on wrong arity / conflicting binding. Still deferred: function-level def f[F[_]] params, non-class constructor application, and constructor composition. See TYPE_SYSTEM_FRONTIER.md for the design sketch.
  • ✅ (landed in v1.0.0-alpha) Variance inference on user-declared generics — covariant / contravariant type-params are inferred from usage, including across module boundaries, with @covariant / @contravariant overrides.
  • ✅ (landed in v1.0.0-alpha) General inter-procedural field-init audit — partial instances are tracked across helper chains, not just the trivial-factory case.
  • ⏳ ty Phase 2 — embedded library sharing the Salsa db (currently a subprocess call via tyc ty). See docs/ty-integration.md.
  • ⏳ Cached parsed-module AST behind salsa::Update. Today check_source_file_with_imports re-parses on every call; an ArcParsedModule wrapper would close that loop.
  • ⏳ Accumulator-loop parallelisation. for x in xs: out.append(...) still rewrites manually; the comprehension shape is the only auto-parallel form.
  • ✅ (shipped) [emit] model-extra — "forbid" (default) / "ignore" / "allow" control the ConfigDict(extra=…) injected into every model class. See [emit].
  • ⏳ Async public-API stability rules if Phase 4+ ever introduces async inference on exported functions. (Likely answer: inference applies to file-internal functions only; exported functions stay explicit.)

What “Phase X complete” means

A phase is “complete” when every must-have feature from that phase has landed and is covered by tests. It does not mean the implementation is fully polished or that every diagnostic has the perfect wording. It means the feature is shippable and you can use it without hitting “not implemented” errors.

Versioning

Typhon is pre-1.0. The semantics described in these docs are the current semantics, not the locked-in v1 spec. Breaking changes to surface syntax are tracked in the changelog and called out in tyc migrate. Changes to emitted Python (the lowering) follow the same rules.

Once the language hits 1.0, syntax is locked under semver. Until then, expect occasional renames and tightenings — none should break programs that follow the Five Rules.

How to follow along

  • GitHub — issues, PRs, milestones.
  • Roadmap — canonical per-feature status.
  • Risks — known hazards and how we mitigate them.
  • Performance Baseline — measured numbers we will not regress.

If a feature you want is not in the shipped list, file an issue. If a feature you do not want is in the shipped list, file an issue. Active development means the surface is still moveable.