astral-sh/ruff
Type checker (ty)
End-to-end view of ty check.
What the user sees
ty check src/ty reports type errors, e.g.:
src/foo.py:5:1: invalid-assignment Cannot assign value of type `str` to variable of type `int`
src/foo.py:9:1: unsupported-operator `int` does not support operator `+` with `str`ty also serves as a language server (ty server), powering hover, go-to-definition, completion, and renames.
Pipeline
graph TD
CLI[ty CLI<br/>crates/ty]
Project[ty_project::Project<br/>load ty.toml / pyproject.toml]
Db[ProjectDatabase<br/>Salsa-tracked]
Files[Python files]
Parser[ruff_python_parser]
AST[Mod AST]
Index[ruff_python_index]
Sem[Semantic model]
Resolver[ty_module_resolver<br/>typeshed + site-packages + project]
Vendored[ty_vendored<br/>typeshed stubs]
Site[ty_site_packages<br/>installed packages]
Inference[ty_python_semantic::types<br/>infer_definition_types,<br/>infer_expression_type]
Lints[lint.rs<br/>diagnostic registry]
Diag[Diagnostics]
Output[stdout / LSP]
CLI --> Project
Project --> Db
Db --> Files
Files --> Parser
Parser --> AST
AST --> Index
Index --> Sem
Db --> Resolver
Resolver --> Vendored
Resolver --> Site
Sem --> Inference
Resolver --> Inference
Inference --> Lints
Lints --> Diag
Diag --> OutputSalsa-driven recompute
ty is built on Salsa. Every analysis step is a tracked query: parse a file, build a scope graph, infer the type of a definition, infer the type of an expression, check a statement against its annotation, etc. Salsa caches results keyed by their inputs and recomputes only what's needed when an input changes.
This is what enables the LSP to feel responsive on large projects: editing one file invalidates only the queries that depend on its content.
What ty knows about
The type system supports (in various states of completeness):
- Classes and instances, with PEP 695 generics
- Protocols (structural subtyping) including recursive protocols
- Unions, intersections, literal types, tuple types, callable types
- TypeVar, ParamSpec, TypeVarTuple — variance, defaults, constraints
- Type narrowing (
isinstance,is,==,match/case) - TypeGuard / TypeIs
- Decorators that affect type (
@dataclass,@final,@overload,@functools.partial) - TypedDict, NamedTuple, Enum
- PEP 695
typealiases - Gradual typing (
Any,Never,Unknownfor incomplete inference)
The mdtest corpus (crates/ty_python_semantic/resources/mdtest/) is the de facto specification: each file is a Markdown document mixing examples and expected diagnostics.
Module resolution
ty_module_resolver decides what import x and from x.y import z mean:
- Project source (the workspace files).
ty_site_packages(installed packages discovered through the configured Python interpreter).ty_vendored(bundled typeshed stubs).
Stubs (.pyi) are preferred over runtime sources where present. The resolver mirrors Pyright's algorithm closely (the Ruff acknowledgements credit Pyright for the import-resolution design).
Diagnostics
Each diagnostic has:
- A short stable id (
invalid-assignment,protocol-violation, …) - A message tuned for narrow terminals
- Optional subdiagnostics with extra context (e.g. "argument 1 was expected to be
int, gotstr")
Diagnostics are documented and rendered consistently across CLI and LSP.
Suppressions
# type: ignore and ty-specific # type: ignore[<id>] comments suppress diagnostics. Implementation: crates/ty_python_semantic/src/suppression*.
CLI vs server
The CLI (crates/ty) calls into the same database the LSP (crates/ty_server) uses. The CLI builds a database, walks files, and prints diagnostics. The LSP keeps the database alive across edits and handles incremental requests.
Where to start when extending
- New type-system feature: see
ty_python_semantic. Add an mdtest fixture early — it doubles as a spec. - New diagnostic: extend the
LintMetadataregistry, implement the check, add an mdtest case. - LSP feature: combine a tracked query in
ty_ide(orty_python_semantic) with a handler inty_server. - Module resolution change:
ty_module_resolverplus tests for typeshed + site-packages combinations.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.