Roadmap
Phases 0–3 are complete. Phase 5 (interop and developer experience) shipped in v0.1.6; Phase 5.5 (constructor / method arity safety) shipped in v0.2.0; Phase 6 (Python-annoyances surface — newtype, freeze let, pub, effect/safety diagnostics, cross-platform install) shipped in v0.3.0, with correctness sweeps and additive features layered in through v0.3.1, v0.4.0, v0.5.0, v0.5.1, v0.5.2, v0.6.0, v0.6.1, v0.7.0, v0.7.1, v0.8.0, v0.8.1, v0.9.0, v0.9.1, v0.9.2, v0.10.0, v0.11.0, v0.12.0, v0.13.0, v0.14.0, and v0.15.0. The current release is v1.0.0-beta.2, the first beta: four review-remediation waves on top of alpha.9 — checker soundness (field narrowings dropped wherever a call or write can reach them, loops joining their exit paths, match exhaustiveness over Result payloads, T?, bool, literal unions and nested sealed unions), lowering fixes (an inline ? keeps Python’s evaluation order, |> works in every expression position, an impl method may read a name bound after its class), VM ↔ CPython parity (C3 method resolution, CPython-ordered sets, an automatic CPython fallback for programs the VM does not model), and symlink-safe tooling. No new syntax; its new error-level diagnostics fire only on code that already crashed, [strictness] nullable-use now defaults to "error" as the documented exception, and the surface listed in the compatibility policy is frozen for the beta line. The prior v1.0.0-alpha.9 was a maintenance release with no language change: the warn-level tyc::contains_secret_literal keyword table grows from 16 entries to 55 (seven overlapping proposals consolidated into one longest-first-ordered table) plus one more name-word boundary — an uppercase keyword directly followed by a lowercase letter — alongside three compiler allocation reductions, the mid-August dependency wave, and docs-site keyboard-accessibility and reduced-motion polish. The alpha.5 → alpha.8 line before it: v1.0.0-alpha.5 was the performance release (VM Tier 1 — 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); v1.0.0-alpha.6 carried the July dependency wave safely across the toml 0.8 → 1.x major (fixing the venv-introspection allow-list regression that bump introduced before it shipped), added six secret-name keywords behind a single shared table, and re-pinned the release pipeline; v1.0.0-alpha.7 was the 2026-07-28 full-codebase-review remediation, closing all ten 1.0 blockers plus the Tier-0 gates that make them verifiable (type-checker and resolver soundness fixes, a shared-lexical-mask preprocessor rewrite, five emitter/preprocessor miscompilation fixes, VM ExceptionGroup / except* support, and a full-corpus VM ↔ CPython differential gate); and v1.0.0-alpha.8 fixed a VM ↔ CPython parity gap — an unseeded random now seeds from entropy under tyc run as CPython does, while explicit random.seed(n) still matches byte-for-byte — and widened the same secret-name lint to digit and TitleCase boundaries. The prior v1.0.0-alpha.4 was a focused hardening pass on top of alpha.3, closing the one HIGH finding the release-readiness review deferred (H5 — a locally declared class no longer unifies with a same-named foreign class of a provably different shape, evidence-gated so the example/stress corpus is byte-identically unchanged), restoring longest-first secret-name matching (APIKEY before KEY), finishing the release-engineering hygiene the alpha.3 review opened (SHA-pinned GitHub Actions, a pre-release-aware installer), and landing a round of dependency / advisory bumps (crossbeam-epoch RUSTSEC-2026-0204, regex, memchr, compact_str) — no new syntax, and, like the alpha.2 diagnostics, the H5 fix is a conservative narrowing that only rejects programs passing a provably-different-shaped class across a module boundary, so no previously-correct program changes behaviour. The prior v1.0.0-alpha.3 was a release-readiness remediation pass closing licensing / packaging gaps (a repository-root MIT LICENSE, the vendored Ruff notice, SECURITY.md / CONTRIBUTING.md / Dependabot, pre-release-tag + CI-gated release hygiene) alongside compiler / VM diagnostic-reporting and complexity fixes (identical errors at distinct locations are all reported, nested-generic assignability is linear again, four more flow-narrowing invalidation holes closed, six VM ↔ CPython parity gaps fixed) and tooling hardening (256 MiB LSP stack, atomic tyc fmt, TYC_NO_INTROSPECT kill-switch, Windows venv discovery, the wired-up [strictness] exhaustive-match knob) — no new syntax, and no previously-correct program changes behaviour. The prior v1.0.0-alpha.2 was the 2026-06-28 adversarial pre-release review remediation: a type-checker soundness sweep (non-local flow-narrowing invalidated across calls/aliases; short-circuit and/or narrowing fixed), more typed positions (slice reads, subscript assignments, tuple-unpack and match captures, walrus, parameter defaults), VM as! enforcement, and three conservative diagnostics (tyc::not_a_context_manager, tyc::raise_non_exception, tyc::frozen_inheritance_conflict) that reject only programs which already crashed at runtime — no new syntax, and no previously-correct program changes behaviour, though those diagnostics do narrow the accepted surface for already-runtime-crashing programs. The prior v1.0.0-alpha was Typhon’s first tagged alpha and first feature-complete milestone: the proven production surface plus the previously-deferred type-system frontier — HKT unification (constructor variables bind against concrete heads, with tyc::kind_mismatch on wrong arity / conflicting binding), user-generic variance inference (covariant / contravariant from usage, cross-module, with @covariant / @contravariant overrides), variance through generic interface bounds, the inter-procedural field-init audit, and 2-member non-nullable union modelling — rolling up the alpha plan’s M1 + M2 plus the rescue boundary sugar. The production path (tyc build → CPython 3.13+) is stable; as an alpha the surface syntax is not yet frozen and may change before 1.0.0 with a documented migration note, and embedded ty Phase 2 + typeshed pure-extension checking are deferred to beta. It follows the v0.15.2 → v0.15.7 robustness line (cross-module extend BUILTIN: propagation, cross-module structural-interface conformance, a ~198-program stress sweep, and third-party method-call arity checking). v0.15.1 was a compiler-performance + docs-site-accessibility point release (source-map generation O(N²)→O(N log N), a Result-exhaustiveness hot-path allocation removed, keyboard-focus ring + anchor-target highlight with a prefers-reduced-motion fallback), with no language or API changes. v0.15.0 sharpened Typhon at the library boundary: as! checked cast composes in any expression position, the try_result exception→Result combinator, compiler-bundled .dty stubs for httpx / requests, async_without_await understanding async contracts, and a qualified↔bare cross-module class-identity fix. v0.14.0 added the as! checked boundary cast (lowering to checked_cast in typhon_runtime/cast.py) and [emit] traceback-remap; v0.14.1–v0.14.3 completed cross-module shape propagation, added tyc::gather_opportunity advice + cross-module auto-gather, and made the LSP refresh diagnostics live on typhon.toml edits. v0.13.0 landed cross-module extend lowering, TypedDict-style dict-literal lowering, enum match exhaustiveness, the closed Result unwrap / query API, VM cooperative asyncio, recursive type aliases, and quoted-annotation forward references. The previous v0.12.0 delivered VM __lt__ parity, dict/str builtins, and deep library introspection. A VM-vs-CPython differential follow-up fixed sorted / min / max ignoring a user __lt__ and added the missing dict.popitem / dict.fromkeys / str.translate / str.maketrans builtins; venv signature introspection now captures parameter and return annotations, so a wrong-typed argument to a fully-typed third-party dependency — function or constructor — is caught at compile time via tyc::type_mismatch (an unintrospectable dependency now warns via [strictness] unintrospectable-dependency instead of silently skipping its checks), and Phase 1 of the typeshed-backed ty integration landed ([checker] external = "ty" / --with-ty on tyc build / tyc check). The previous v0.11.0 was the VM parity sweep + enum keyword. A fresh adversarial stress round against v0.10.0 surfaced 22 findings — almost entirely in the VM. This release closes every finding, lands the enum Name: keyword as a first-class declaration form (sugars over enum.Enum with enum.auto() for bare members), and adds two new VM value kinds: Value::Complex for native complex arithmetic (hashable for set / dict keys) and a dict-view kind backing dict.keys() / .values() / .items() (repr / iterate / in / len match CPython). Bare super() is rewritten by tyc-desugar to the two-arg form so @dataclass(slots=True) no longer crashes; __call__ dispatches on callable instances; __post_init__ fires after auto-generated construction; multi-level inheritance accumulates fields across the full MRO. New / expanded stdlib shims: native enum / datetime (naïve / UTC) / pathlib (/ join, .suffixes, .parts) / collections.defaultdict (factory invoked via subscript __missing__). Real re.Match capture groups, banker’s round, bytes methods, itertools.groupby(key=), str.split(maxsplit=), f-string {x=}, str % runtime formatting. VM value semantics align with CPython — dataclass eq / repr / hash is value-based (class-identity keyed, no cross-module collisions), set equality is order-independent, float repr matches CPython’s shortest round-tripping form. The type checker plugs three more coherence gaps: None flows into object, str % is type-checked, and (5).items() / 5["a"] / for x in 5: fire at check time. The previous v0.10.0 VM completeness release dispatched dunders and rich comparisons (__add__ + reflected forms, __eq__ / __lt__ / …, __str__ / __repr__ / __len__ / __getitem__ / __contains__) on user instances, ran finite generators (yield / yield from, capped at 1M items), modelled type(x) as a real type object, invoked @property getters and bound cls for @classmethod, and shipped the long tail of missing builtins (divmod, pow, format, ascii, int(str, base), full set algebra, the missing string methods, json.dumps(indent=…), time.perf_counter, math.gcd/lcm/factorial/…) plus max / min / list.sort key= / reverse= / default= kwargs and pydantic model_validate / model_dump for flat models. The type checker plugged three exhaustiveness / augmented-assign false positives. The previous v0.9.0 stress-test cleanup release closed 32 findings from a v0.8.1 stress sweep — the VM became the daily-driver runner (Result combinators, open() write/append/binary modes, class patterns on built-ins, deep freeze let, comptime inlining, lazy import, class! exception fields, dataclass factories, collections.deque / heapq / contextlib / pydantic shims, multi-file projects, @property / super() / @contextmanager) and the type checker plugged silent-correctness gaps (Sequence covariance, variant-to-parametric-union flow, while True: reachability + narrowing, assert narrowing, *args annotation policy, extend list[T] dispatch, exhaustive match on T?, with-chain error mismatch, comptime let T: type). v0.9.1 fixed four tyc fmt round-trip corruption modes and a pub * facade hole; v0.9.2 fixed a cross-module class! Sub(Foreign): attribute_not_found false positive. See Project Status for the per-release breakdown. Phase 4+ items continue to land as they prove their value. The minimum-viable Typhon — non-null types + sealed unions + Result + dataclass emit — is shippable today.
Phase 0 — Foundation ✅
- Cargo workspace with crate-per-stage layout.
- Vendored Ruff fork (
ruff_text_size,ruff_source_file,ruff_python_trivia,ruff_python_ast,ruff_python_parser) withlet/mutsoft keywords andMutabilityfield. clap-basedtycshell.miette+thiserrordiagnostics.tyc fmt,tyc check,tyc init.
Phase 1 — Core types ✅
- Salsa DB with
preprocessed_text/module_decl_namesqueries. - Name resolution +
let/mutenforcement. - Nominal types: signatures, assignment compatibility, primitives, classes, generics containers.
- Non-nullable by default with flow narrowing on
is None/is not None/isinstance. T?sugar.tyc checkemits “unknown name”, “type mismatch”, “nullable use” diagnostics.
Phase 2 — Class and value features ✅
class→@dataclass(slots=True);model→ PydanticBaseModel(extra="forbid").- Sealed unions + exhaustive
match. Result[T, E],?operator,with-chains.comptime letwithenv()lookup; required env vars declared in[env].implblocks merged into class bodies.tower-lsp-serverbackend: diagnostics + hover.
Phase 3 — Structural typing and advanced ✅
- Generics syntax: PEP 695 brackets, bidirectional inference, conflict-widening, bounded type vars.
interface→class Name(Protocol):with structural conformance check.unsafe:lexical region withUnsafe[T]boundary marker.@pure/@memo/@pure(memo=True)with six-condition purity check.gather:→asyncio.TaskGroup(orasyncio.gather(return_exceptions=True)for best-effort).go→typhon_runtime.tasks.spawn(strong-ref registry).lazy import np = numpy(proxy class);lazy from … import …rejected.lazy let(module + class-body forms).- Pipe operator (
|>). extend ClassName:andextend BUILTIN:..dtystub files →.pyiemission;tyc check --stubsAST diff.
Phase 5.5 — Constructor / method arity safety ✅ (v0.2.0)
The flagship bug v0.2.0 catches: a class declared with class ApiClient: api_key: str that the user instantiates as ApiClient(base_url="…") — passing tyc check and tyc build in 0.1.6, crashing at runtime with TypeError: missing 1 required positional argument. v0.2.0 surfaces the same bug at check time, before the build ever runs.
- ✅ Constructor arity (
tyc::arg_count). The auto-generated__init__ofclass/modeldeclarations is arity-checked at every call site. Fields without an= defaultare required; fields with a default are optional.T?without an explicit= Noneis still required (Typhon does not auto-inject the default). - ✅ Method arity (
tyc::arg_count).implmethods now carry fullArityInfoon theirMethodSig(param names, defaults,*args/**kwargs).u.greet()is flagged whengreetdeclares a requiredprefix: strparameter; previously method calls fell into the permissive arity shape. - ✅ Cross-module shape propagation.
from foo import ApiClientandimport foo as f; f.ApiClient(…)both flow through the new arity checks..tysource and.dtystubs participate on equal footing through a project-wideExternalShapesregistry built once per invocation; stubs win on name collisions. Works intyc check,tyc build, and the LSP. - ✅ Salsa-cached LSP shape extraction. A new
tyc_db::module_shapes_query(file)salsa-tracked query caches per-file shape extraction by file text. The LSP backend keeps a per-project-rootHashMap<dotted_name, SourceFile>so handles survive across keystrokes; a keystroke in one file only re-runs shape extraction for that file. - ✅
tyc::missing_field_initpost-construction audit. CatchesX.__new__(X)/object.__new__(X)bypass patterns: if the constructed instance escapes the function (return / call argument) with required fields unassigned, the audit fires. Dropped conservatively onsetattr, on method calls, and insideunsafe:regions.
Limitations carried forward:
- ⏳ Dotted-attribute annotations (
let c: f.Cls = …) don’t resolve to the foreign class shape; usefrom foo import Clsor drop the annotation for now. (The constructor call itself still arity-checks correctly.) - ⏳ The post-construction audit doesn’t track container-literal escapes (
return [c]) or outer-scope assignment escapes, and is intra-procedural.
Phase 5 — Interop and developer experience ✅ (v0.1.6)
Real-world adopters were landing on Typhon and hitting the same handful of papercuts in the same order. Phase 5 was the friction list — fix the friction before adding more features.
- ✅
plain class X:keyword — symmetric withfrozen class X:. Emits a bare Python class with no@dataclassdecoration and no synthesised__init__. The canonical Python-interop escape hatch. - ✅ Auto-skip
@dataclassforEnum/IntEnum/StrEnum/Flag/IntFlag/ABC/ABCMetasubclasses (in addition to the always-skippedProtocol/TypedDict/NamedTuple); project-specific bases via[emit] skip-decoration-basesmatched on last identifier segment. - ✅
[emit] class-defaultvalidation. Unknown values ("struct","plain","none", …) now fail config load withtyc::invalid_config_valueinstead of silently falling back to"dataclass". - ✅ Python-semantic alignment.
or/andtyped asUnion[truthy(lhs), rhs](and the falsy dual); generator functions structurally assignable toIterable[T]/Iterator[T]/AsyncIterable[T]/AsyncIterator[T]. Tracked via thetyc::python_semantic_driftaudit warning. - ✅ Discoverability.
tyc explain <code>for the diagnostic catalog (mirrorsrustc --explain);tyc cheatsheetfor the 30-second syntax refresher;tyc initscaffold ships a frozen dataclass +implblock +Result/?/matchexample, plus a fully-commentedtyphon.toml;tyc --helpfooter links docs, language reference, migrate, lsp, cheatsheet, and explain. - ✅
.pyinterop in build output. Stray.pyfiles insrc/copy verbatim into the build dir (honouring__pycache__//tests//.venv// dotfile exclusions);tyc::orphan_py_importwarns when a relative.pyimport resolves outsidesrc/. - ✅ Diagnostic deep-links. Every
tyc::diagnostic carries aurl(https://github.com/CodeHalwell/Typhon/blob/main/docs/diagnostics/<code>.md)clause renderable by miette and surfaced bytyc explain; 50+ catalog pages underdocs/diagnostics/embedded into the binary. - ✅ Build UX.
tyc build --checkdry-runs the pipeline and lists every file that would be written without touching disk;tyc::contains_secret_literalflags inlined env values whose binding name matches a credential suffix (KEY,TOKEN,PASSWORD, …). - ✅
tyc fmtwrapsruff formatafter the in-process whitespace pass, with a guarded check for residual Typhon-only tokens. - ✅
tyc debug --break <ty-file>:<line>translates Typhon source locations via.py.mapand forwards them to the chosen debugger via-c "break …". Repeatable.
Phase 4+ — Beyond v1 (landing as they prove value)
Shipped:
- ✅ Automatic
asyncio.gatherinference ([strictness] auto-gather), with cross-module folding of imported@gatherablecallees (v0.14.2) andtyc::gather_opportunityadvice that suggests an explicitgather:for runs of 2+ adjacent independent awaits (v0.14.2, default on, surfaced live in the LSP). - ✅
enum Name:keyword as a first-class declaration form, sugaring overenum.Enumwithenum.auto()(v0.11.0); enummatchexhaustiveness over the closed member set (v0.13.0, cross-module in v0.14.1). - ✅
as!checked boundary cast — the sound one-line replacement for theunsafe:-plus-re-assert dance, lowering tochecked_castintyphon_runtime/cast.py(v0.14.0); composes in any expression position since v0.15.0. - ✅
try_result(thunk[, on_err])exception→Resultcombinator, a prelude name typedResult[T, E](v0.15.0). - ✅ Compiler-bundled
.dtystubs for the most-imported third-party libraries (httpx, requests) whose packaging defeats venv introspection, seeded before venv enrichment (v0.15.0). - ✅
[emit] traceback-remap— autosys.excepthookrewriting of uncaught tracebacks to.tyvia.py.map, default off (v0.14.0). - ✅ Typeshed-backed
tyintegration Phase 1 —[checker] external = "ty"/--with-tyruns Astral’styover the emitted Python and re-attributes its diagnostics to.tysource (v0.12.0). - ✅ Deep compile-time library introspection — venv signature introspection captures parameter and return annotations, so a wrong-typed argument to a fully-typed third-party function or constructor is caught via
tyc::type_mismatch(v0.12.0); an unintrospectable dependency now warns instead of silently skipping its checks. - ✅ Loop parallelisation for pure list comprehensions on free-threaded Python (
[strictness] auto-parallel). - ✅ PGO via
tyc profile([strictness] pgo-memoise). - ✅ LSP completions (visible bindings + keywords + builtins + venv-driven member-access introspection + from-import members from sibling files) and “Remove unused import” code action.
- ✅ Cross-file go-to-definition across
.ty/.py. - ✅
tyc migrate(typed.py→.ty). - ✅
tyc repl;tyc debug(with--breaksource mapping);tyc run(defaulting to the in-processtyc-vmtree-walking interpreter — no build/, no CPython spawn). - ✅
tyc add/remove/syncoveruv. - ✅ Runtime
stubtestprobe viatyc stubtest(shells out topython -m mypy.stubtest). - ✅ VM performance Tier 1 — small-int fast path, per-class method cache, direct method-call dispatch, and slot-resolved locals compress
tyc run’s slowdown vstyc build+ CPython from ~5–18× to ~3–14× (startup-adjusted). See Performance Baseline anddocs/vm-performance-plan.md. - ✅
[optimise]config profile +tyc build -O— a singleleveldial that flips the default ofauto-memoise/auto-gather/auto-parallel/pgo-memoisetotrue. - ✅ Performance-advice lint family — seven advice-level lints (
perf_membership_in_loop,perf_list_shift_in_loop,perf_str_concat_in_loop,perf_sort_in_loop,perf_sorted_first,perf_keys_membership,lazy_import_opportunity), gated by[strictness] suggest-perf, default on. - ✅ Free-threading parallelisation wave — widened
auto-parallelcomprehension shapes (filters, multi-arg calls, nested pure calls), integer accumulator-loop reductions (auto-parallel-reductions),parallel_opportunity/shared_mut_across_tasksadvice lints, and aparallel-backend = "interpreters"(PEP 734) option. - ✅ Native PEP 810 lazy imports on
[python] target = "3.15"/"3.15t"—lazy importlowers to CPython’s native statement instead of thetyphon_runtimehelper; 3.13/3.14 output unchanged.
Deferred:
- ⏳ Richer comptime: broader stdlib in the sandbox,
forloops incomptime def. (Types as values shipped in v0.5.0; container literals and pure string methods in v0.3.1.) - ⏳ Full structural subtyping with proper variance and bounded higher-kinded forms.
- ⏳
tyintegration Phase 2 as an embedded in-process library sharing the Salsa db (today: Phase 1 ships the subprocess path via[checker] external = "ty"/tyc ty, v0.12.0). Phase 2 was prototyped and proven feasible but is not shipped — it needs a git dependency that the project’scargo denypolicy disallows. - ⏳ A Typhon-native source-mapping debugger UI (today:
pdblauncher with--break TY:LINEtranslation). - ⏳ Broader
tyc::python_semantic_driftaudit beyond theor/andand generator →Iterablecases already fixed. - ⏳ Corpus round-trip sweep across third-party Python projects.
- ⏳ Async public-API stability rules if async inference is ever added.
Scope-cutting rule
The minimum-viable Typhon is non-null types + sealed unions + Result + dataclass emit. That is the floor. Everything else is layered on without changing the meaning of any program written against the minimum core.
Where next
- Risks — known hazards and how we mitigate them.
- Status — quick summary.
- Performance Baseline — measured numbers we will not regress.