Typhon
v1.0.0-beta.2 is here

Python, checked
before it runs.

A stricter, statically typed superset of Python.
Catch mistakes early. Compile to readable CPython.

Shell
curl -fsSL https://typhon.sh/install.sh | sh
Scroll sideways if needed. Copy includes the full command.

macOS, Linux and Windows · MIT licensed · In beta

tyc 1.0.0-beta.2
src/main.ty
def find_user(uid: int) -> str?:
    return "ada" if uid == 1 else None

def greet(user: str) -> None:
    print(f"hello, {user}")

def main() -> None:
    let user: str? = find_user(1)
    greet(user)

main()
$ tyc check src/
tyc::nullable_use

This value might be None.

greet expects a str. Typhon catches the mismatch before you run the program.

9  greet(user)
           ^^^^
Guard the value to narrow its type.
A nullable value cannot be passed to a function that requires a string.Verified with tyc 1.0.0-beta.2
.ty in. .py out.One compiler. One binary.CPython 3.13+ in production.

A little stricter. A lot clearer.

Good Python habits,
built into the language.

Types, bindings and control flow that say what you mean. Checked before your code becomes Python.

01

Make the possible explicit.

A plain str cannot hold None. Write str? when it can, then narrow it before use. The compiler keeps the two apart.

Explore the language
Optional values
def find_user(uid: int) -> str?:
    return "ada" if uid == 1 else None

def greet(user: str) -> None:
    print(f"hello, {user}")

def main() -> None:
    let user: str? = find_user(1)
    if user is not None:
        greet(user)

main()
02

Handle every possibility.

Sealed unions make matching exhaustive. Add a new variant, and Typhon points out every match that needs an update.

Explore the language
Exhaustive matching
class Circle:
    radius: float

class Square:
    side: float

type Shape = Circle | Square

def area(shape: Shape) -> float:
    match shape:
        case Circle(radius):
            return 3.14 * radius * radius
        case Square(side):
            return side * side
03

Let failures be part of the type.

Result makes expected errors visible in a function signature. Propagate them with ? and keep the successful path easy to read.

Explore the language
Errors as values
def divide(a: int, b: int) -> Result[int, str]:
    if b == 0:
        return Err("division by zero")
    return Ok(a // b)

def calculate() -> Result[int, str]:
    let value: int = divide(10, 2)?
    return Ok(value * 2)

Familiar on the other side.

What you ship is Python.

Typhon checks your source, then gets out of the way. The output is readable CPython 3.13+, with source maps back to your original code.

main.tyYour sourcetycCheck + compilemain.pyPlain Python
src/main.ty · excerpt
def main() -> None:
    let user: str? = find_user(1)
    if user is not None:
        greet(user)
build/main.py · emitted excerpt
def main() -> None:
    user: str | None = find_user(1)
    if user is not None:
        greet(user)

Use Python libraries. Deploy on CPython. No separate Typhon runtime package to install.

Features such as Result generate small Python helpers inside your project. You can read and own that output too.

See what the compiler produces

From first file to everyday work.

One binary.
The whole workflow.

The compiler, formatter, language server and REPL live in tyc. Start a project, check it and build it with the same tool.

Your first project · shell or PowerShell
tyc init hello
cd hello
tyc check src/
tyc run
tyc build

Before you start.

A few good questions.

All questions
How is this different from Pyright?
Typhon adds language features and enforces its rules as it compiles. It writes the Python you deploy.
Can I use my existing Python libraries?
Yes. Typhon reads typed library signatures from your environment, with .dty stubs for additional boundaries.
Can I migrate existing code?
tyc migrate converts typed Python into Typhon as a starting point.
Is Typhon ready for me?
Typhon is in beta. Read the compatibility policy before adopting it.