# How Monty's Built-in Type Checking Works: A Deep Dive into the Rust Implementation

> Explore Monty's type checking implementation. Learn how it uses Ruff's ty crates and a Salsa database for efficient Python code analysis across interfaces.

- Repository: [Pydantic/monty](https://github.com/pydantic/monty)
- Tags: deep-dive
- Published: 2026-02-16

---

**Monty's built-in type checking leverages Ruff's `ty` crates through an in-memory Salsa database to analyze Python code across Rust, Python, JavaScript, and CLI interfaces without touching the host filesystem.**

Monty is a Python-to-everything compiler developed by Pydantic that ships with a fast, accurate type-checking subsystem. This article explores how Monty's built-in type checking works under the hood, powered by Ruff's semantic analysis engine and exposed through a unified API across multiple language bindings.

## Architecture Overview

The type-checking implementation lives in the `monty-type-checking` crate and relies on Ruff's `ty` crates (`ty_python_semantic`, `ty_module_resolver`, etc.). At its core, the system uses an in-memory Salsa database to manage incremental analysis, ensuring that no real filesystem operations are required during type checking.

## The In-Memory Salsa Database

The foundation of Monty's type checking is the `MemoryDb` struct defined in [`crates/monty-type-checking/src/db.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/src/db.rs). This database implements the Salsa traits required by Ruff's analysis stack:

- `SourceDb` – Provides access to the virtual file system (`system`, `vendored`, `files`)
- `Db` – Stores language-level settings including lint rules, analysis settings, and Python version
- `ModuleResolverDb` – Handles search-path resolution for imports

The database uses a `TestSystem` and the vendored `monty-typeshed` files, creating a completely sandboxed environment isolated from the host filesystem.

## Type Checking Workflow

When `type_check()` is invoked in [`crates/monty-type-checking/src/type_check.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/src/type_check.rs), the system executes a six-step pipeline:

### 1. Register the Virtual Root

```rust
let src_root = SystemPathBuf::from("/");
db.files().try_add_root(&db, &src_root, FileRootKind::Project);

```

This establishes the virtual filesystem root where all source files will reside.

### 2. Write Source Files

```rust
let main_path = src_root.join(python_source.path);
db.write_file(&main_path, python_source.source_code)?;

```

If a stub file is provided, Monty writes it first, then injects a `from <stub> import *` line at the top of the main source file to ensure the type checker sees the type annotations.

### 3. Initialize the Program

```rust
Program::from_settings(
    &db,
    ProgramSettings {
        python_version: PythonVersionWithSource {
            version: db.python_version(),
            source: PythonVersionSource::default(),
        },
        python_platform: PythonPlatform::default(),
        search_paths,
    },
);

```

Creating the `Program` is mandatory—it forces the `ty` database to initialize its internal caches. Without this step, `check_types` would panic.

### 4. Execute Type Analysis

```rust
let main_file = system_path_to_file(&db, &main_path)?;
let mut diagnostics = check_types(&db, main_file);

```

The `check_types` function returns a `Vec<Diagnostic>` from Ruff's diagnostic engine.

### 5. Filter and Adjust Diagnostics

Monty applies a filter to remove spurious "await outside of async function" diagnostics that the current `ty` version emits for Monty-specific code patterns.

If a stub import was injected, `adjust_annotation_span` corrects line numbers by subtracting the offset length, but only for spans belonging to the original main file.

### 6. Package Results

Remaining diagnostics are wrapped in a `TypeCheckingDiagnostics` struct that holds:
- The raw `Vec<Diagnostic>`
- An `Arc<Mutex<MemoryDb>>` for rendering
- Formatting preferences (`DiagnosticFormat`, color flags)

This struct implements `Display` and `Debug` to produce output identical to Ruff's textual format.

## Public API and Integration Points

The primary entry point is the `type_check` function exposed in [`crates/monty-type-checking/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/src/lib.rs):

```rust
pub fn type_check(
    python_source: &SourceFile<'_>,
    stubs_file: Option<&SourceFile<'_>>,
) -> Result<Option<TypeCheckingDiagnostics>, String>

```

Return values indicate:
- `Ok(None)` – Source type-checks cleanly
- `Ok(Some(diagnostics))` – One or more typing errors detected
- `Err(String)` – Unexpected internal error (e.g., virtual filesystem I/O)

### Integration Across Front-Ends

| Crate / File | Integration Method |
|-------------|-------------------|
| `monty-python` – [`src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/src/monty_cls.rs) | Invoked when `type_check=True` is passed; translates Python strings into `SourceFile` and raises `MontyTypingError` |
| `monty-js` – [`src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/src/monty_cls.rs) | Exposes `typeCheck` option on JS `Monty` class; throws `MontyTypingError` exception |
| `monty-cli` – [`src/main.rs`](https://github.com/pydantic/monty/blob/main/src/main.rs) | Direct Rust API call via `--type-check` flag; prints diagnostics to stdout |
| Tests – [`crates/monty-type-checking/tests/main.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/tests/main.rs) | Exercises success paths, error detection, stub handling, and formatting |

## Usage Examples

### Direct Rust Usage

```rust
use monty_type_checking::{SourceFile, type_check};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = SourceFile::new(
        r#"
def greet(name: str) -> str:
    return f"Hello, {name}"
greet(42)   # <-- type error

"#,
        "example.py",
    );

    match type_check(&src, None)? {
        None => println!("✅ No type errors"),
        Some(diag) => println!("❌ Type errors:\n{diag}"),
    }
    Ok(())
}

```

Output format matches Ruff's diagnostic style:

```

TypeCheckingDiagnostics:
example.py:6:8: error: Argument 1 to "greet" has incompatible type "int"; expected "str" [arg-type]

```

### Python API

```python
from pydantic_monty import Monty

code = """
def greet(name: str) -> str:
    return f"Hello, {name}"
greet(42)
"""

m = Monty(code, type_check=True)
try:
    m.run()
except MontyTypingError as exc:
    print(exc)   # Same diagnostics as Rust API

```

### JavaScript API

```javascript
import { Monty, MontyTypingError } from "@pydantic/monty";

const code = `
def greet(name: str) -> str:
    return f"Hello, {name}"
greet(42)
`;

const m = new Monty(code, { typeCheck: true });
try {
    m.run();
} catch (e) {
    if (e instanceof MontyTypingError) {
        console.error(e.message);   // Formatted diagnostics
    }
}

```

### CLI Usage

```bash
$ monty run example.py --type-check
TypeCheckingDiagnostics:
example.py:6:8: error: Argument 1 to "greet" has incompatible type "int"; expected "str" [arg-type]

```

## Summary

- Monty's built-in type checking leverages Ruff's `ty` crates (`ty_python_semantic`, `ty_module_resolver`) through the `monty-type-checking` crate.
- The system uses an in-memory Salsa database (`MemoryDb`) defined in [`crates/monty-type-checking/src/db.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/src/db.rs) to provide a sandboxed virtual filesystem.
- The type-checking pipeline involves registering virtual roots, writing source files, initializing a `Program`, executing `check_types`, and filtering/adjusting diagnostics.
- Results are packaged in `TypeCheckingDiagnostics` which implements `Display` to produce Ruff-compatible error formatting.
- The `type_check` function in [`crates/monty-type-checking/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/src/lib.rs) serves as the unified entry point for Python (`monty-python`), JavaScript (`monty-js`), CLI (`monty-cli`), and direct Rust usage.

## Frequently Asked Questions

### How does Monty's type checker differ from running Ruff directly?

Monty embeds Ruff's `ty` crates internally rather than shelling out to a separate process. This allows Monty to run type checking in a completely sandboxed, in-memory environment using `MemoryDb` without touching the host filesystem. The integration also enables seamless error propagation across Rust, Python, and JavaScript APIs with consistent diagnostic formatting.

### Can I use stub files (.pyi) with Monty's type checker?

Yes. Monty supports stub files through the optional `stubs_file` parameter in the `type_check` function. When provided, Monty writes the stub to the virtual filesystem first, then injects a `from <stub> import *` line at the top of the main source file. The system automatically adjusts diagnostic line numbers to account for this injected import line.

### What happens if the type checker encounters an internal error?

If the type checker encounters an unexpected internal error—such as a failure in the virtual filesystem operations—the `type_check` function returns `Err(String)` with a descriptive message. Successful type checking returns `Ok(None)` for clean code or `Ok(Some(TypeCheckingDiagnostics))` when typing errors are detected. This three-state return pattern allows callers to distinguish between "no errors," "type errors," and "system failures."

### Is Monty's type checking compatible with all Python versions?

Monty uses the `ProgramSettings` struct to configure Python version and platform settings when initializing the type checker. The database stores language-level settings including the target Python version, which is passed to Ruff's `ty` engine. While the specific supported versions depend on the underlying `ty` crates, Monty's architecture allows version configuration through the `Db` trait implementation in [`crates/monty-type-checking/src/db.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-type-checking/src/db.rs).