ty

repository·main·Indexed 12 days ago

https://github.com/astral-sh/ty

A high-performance Python type checker and language server written in Rust, designed to be significantly faster than mypy and Pyright. Currently in beta (version 0.0.70), it supports Python 3.10 and later, providing rich diagnostics, an advanced type system with intersection types and reachability analysis, and native editor integrations for VS Code, PyCharm, and Neovim.

Tokens
50.1K
Snippets
226
Records
262
Agent score
97%

What's inside ty

  1. Overview of ty features

    main

    ty is an extremely fast Python type checker and language server written in Rust. It is designed to be significantly faster (10x - 100x) than mypy and Pyright.

    Key features include:

    • High Performance: Optimized for speed and fine-grained incremental analysis for IDEs.
    • Rich Diagnostics: Comprehensive error messages with contextual information.
    • Flexible Configuration: Supports configurable rule levels, per-file overrides, and suppression comments.
    • Advanced Type System: Supports intersection types, type narrowing, reachability analysis, redeclarations, and gradual typing (partially typed code).
    • Language Server (LSP): Provides code navigation, completions, code actions, auto-import, inlay hints, and on-hover help.
    • Editor Integrations: Native support for VS Code, PyCharm, Neovim, and more.
  2. Understand ty diagnostics and error reporting

    main

    ty provides rich diagnostics to help debug type errors. When an error is detected, the diagnostics include:

    • Snippets of your source code showing the error context.
    • Annotations and helpful explanations of the issue.
    • Suggestions for how to fix the reported issue.
    • References to relevant definitions (e.g., pointing to the specific key in a TypedDict definition or the parameter in a function definition).

    If you are using an editor with Language Server Protocol (LSP) support, many of these suggestions can be applied directly as 'quick fixes'.

  3. Use the ty CLI

    main

    The ty command is the entrypoint for the extremely fast Python type checker. It supports several subcommands to manage type checking, language server functionality, and diagnostic explanations.

    Available Commands:

    • ty check: Check a project for type errors.
    • ty server: Start the language server.
    • ty version: Display ty's version.
    • ty explain: Explain rules and other parts of ty.
    • ty help: Print help for ty or a specific subcommand.
    # Basic usage pattern
    ty <COMMAND>
  4. View ty performance benchmarks

    main

    This document provides performance comparisons between ty and other Python type checkers/tools including Pyrefly, mypy, and Pyright. Benchmarks are conducted across various real-world projects such as black, discord.py, homeassistant, isort, jinja, pandas, pandas-stubs, prefect, and pytorch to demonstrate representative usage performance.

    Note: Benchmark results are computed on macOS (Apple M3 Max 16, 128 GB) using specific versions of tools. Performance may vary significantly across different operating systems and project structures.

  5. Navigate Python code with ty

    main

    ty provides several language server features to navigate your Python codebase:

    • Go to Definition: Jump to where a symbol is defined (resolves imports, function calls, class references, etc.).
    • Go to Declaration: Navigate to the declaration site of a symbol (e.g., a stub file).
    • Go to Type Definition: Navigate to the type of a symbol (e.g., jumping to class Person from a variable user: Person).
    • Find all references: Locate every usage of a function, class, or variable across the entire workspace.
    • Document and workspace symbols: View an outline of symbols in the current file or search for symbols across the entire workspace.
  6. Understand the `Divergent` type

    main

    Divergent represents type-level recursion that does not converge. This occurs when ty analyzes a cycle (like a loop) where each iteration produces a new, more complex type, preventing a stable result.

    Divergent is a gradual type, meaning ty allows any operation on the Divergent part of a type. It is an internal type and cannot be used in annotations.

    def some_condition() -> bool:
        ...
    
    x = 1
    while some_condition():
        x = [x]
    
    reveal_type(x)  # Literal[1] | list[Divergent]
  7. Perform reachability analysis based on types

    main

    ty uses type inference to perform reachability analysis. This allows it to detect unreachable code branches based on constant expressions that can be evaluated at type-checking time. This is useful for handling version-specific logic or dependency variations.

    import pydantic
    from pydantic import BaseModel
    
    # This boolean is evaluated at type-checking time
    PYDANTIC_V2 = pydantic.__version__.startswith("2.")
    
    class Person(BaseModel):
        name: str
    
    def to_json(person: Person):
        if PYDANTIC_V2:
            # If checking against pydantic 1.x, this branch is considered unreachable
            return person.model_dump_json()
        else:
            # If checking against pydantic 2.x, this branch is considered unreachable
            return person.json()
  8. Python version support in ty

    main

    Supported Versions

    • Official Support: ty officially supports type checking for code targeting Python 3.10 and later.
    • Legacy Support: You can select earlier versions (Python 3.7 through 3.9), but be aware that this may result in false negatives or false positives because bundled standard library stubs might be missing.

    Target vs. Runtime Version

    The target Python version of the code you are checking is independent of the Python version used to install ty.

    • If you install ty from PyPI using Python 3.8+, you can still type check code targeting Python 3.7.
    • The standalone installer does not require a Python installation to run.
  9. How Python version affects type checking

    main

    The target Python version determines which syntax is allowed and which type definitions are available for the standard library and third-party modules.

    • Syntax: Features like match statements (introduced in 3.10) will trigger an invalid-syntax error if the target version is set to 3.9 or lower.
    • Symbols: Accessing symbols introduced in newer versions (e.g., sys.stdlib_module_names in 3.10) will trigger an unresolved-attribute error if the target version is too low, unless the usage is guarded by a sys.version_info conditional check.
    • Narrowing: ty performs type narrowing based on sys.version_info checks to ensure code is safe for the specified target version.
    import sys
    
    # This triggers `unresolved-attribute` if python-version <= 3.9
    print(sys.stdlib_module_names)
    
    # This is safe because it is guarded by a version check
    if sys.version_info >= (3, 10):
        print(sys.stdlib_module_names)
  10. Understand the `Unknown` type

    main

    Unknown is used by ty to represent types that cannot be fully inferred (e.g., from unresolved imports or untyped third-party code). It behaves similarly to Any but is implicit.

    To prevent false positives in untyped code, ty often uses unions with Unknown. For example, an untyped attribute might be inferred as Unknown | None. This allows assignments to the attribute without errors while still forcing the developer to handle the None case when accessing it.

    from missing_module import MissingClass  # error: unresolved-import
    
    reveal_type(MissingClass)  # Unknown
    
    class Message:
        data = None  # untyped attribute
    
    def receive(msg: Message):
        reveal_type(msg.data)  # Unknown | None
  11. Understand `Top[list[Unknown]]` and generic narrowing

    main

    The type Top[list[Unknown]] represents "all possible lists of any element type". It typically appears when using isinstance(x, list) if the analysis.strict-generic-narrowing option is enabled.

    If x was previously Item | list[Item], ty avoids narrowing it strictly to list[Item] because it respects the possibility of a common subclass that is both an Item and a list (but not necessarily a list[Item]). The resulting type is (Item & Top[list[Unknown]]) | list[Item].

    How to fix:

    • Check for the specific item type instead: if isinstance(x, Item):.
    • Declare the Item type as @typing.final to prevent subclassing.
  12. Understand `float*` and `complex*` notation

    main

    In accordance with the Python typing specification, int is compatible with float and float is compatible with complex. ty uses a starred notation to distinguish between a general compatible type and a value known to be the specific type.

    • float: A type that accepts both int and float values.
    • float*: A value known to be an actual float (not an int).
    • complex: A type that accepts int, float, and complex values.
    • complex*: A value known to be an actual complex.

    Note: These starred spellings are for ty output only and cannot be used in Python annotations.

    def takes_float(value: float) -> None: ...
    
    reveal_type(value)  # float
    reveal_type(1.0)   # float*