# How Pyrefly Implements Callable Overload Resolution

> Learn how Pyrefly implements callable overload resolution by storing signatures and matching them to PEP 484 rules to prevent type errors.

- Repository: [Meta/pyrefly](https://github.com/facebook/pyrefly)
- Tags: internals
- Published: 2026-05-21

---

**Pyrefly resolves callable overloads by storing multiple signatures in a `TspOverloadedType` and iterating through them at call sites to find the most specific match according to PEP 484 rules, emitting a "No matching overload" error when no candidate succeeds.**

The `facebook/pyrefly` type checker implements Python's `@overload` decorator system through a multi-stage pipeline that converts stub definitions into an efficient intermediate representation. When performing callable overload resolution, Pyrefly unifies argument types against stored signatures and applies subtype-based selection criteria to determine the correct return type. This architecture ensures spec-compliant behavior while maintaining the performance characteristics required for large-scale Python codebases.

## Parsing and Representing Overloaded Types

### From @overload to Overload Objects

When Pyrefly parses a stub file containing `@overload` decorators, each overload block becomes a `pyrefly_types::types::Overload` struct. This object stores a sequence of signatures that may be plain functions or `forall`-quantified polymorphic signatures.

### Conversion to TSP OverloadedType

The transformation from internal representation to solver-facing type occurs in [`pyrefly/lib/tsp/type_conversion.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/tsp/type_conversion.rs) (lines 56-67) via the `convert_overload_to_tsp` function. This routine walks the `signatures` vector, converts each entry to a `TspFunctionType`, and wraps the results in a `TspOverloadedType`:

```rust
// Convert a pyrefly `Overload` to a TSP `OverloadedType`.
fn convert_overload_to_tsp(
    &self,
    overload: &pyrefly_types::types::Overload,
    bound_to_type: Option<Box<TspType>>,
) -> TspType {
    // (shared bound type handling omitted for brevity)
    let overloads: Vec<TspType> = overload
        .signatures
        .iter()
        .map(|sig| {
            let bt = shared.as_ref().map(|arc| Box::new(TspType::clone(arc)));
            match sig {
                pyrefly_types::types::OverloadType::Function(f) => {
                    self.convert_function(&f.signature, &f.metadata.kind, bt)
                }
                pyrefly_types::types::OverloadType::Forall(f) => {
                    self.convert_function(&f.body.signature, &f.body.metadata.kind, bt)
                }
            }
        })
        .collect();
    TspType::Overloaded(TspOverloadedType {
        flags: TypeFlags::CALLABLE,
        id: next_id(),
        implementation: None,
        kind: TypeKind::Overloaded,
        overloads,
        type_alias_info: None,
    })
}

```

## Callable Overload Resolution at Call Sites

### The Resolution Algorithm

When the solver encounters a call expression where the callee has type `TspOverloadedType`, it iterates over the stored `overloads` vector. For each signature, Pyrefly attempts to unify the provided argument types against the parameter types, accounting for default values, `*args`, `**kwargs`, and bound-method receivers.

The core logic resides in [`pyrefly/lib/solver/call.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/solver/call.rs):

```rust
for overload in &overloaded.overloads {
    if args_match_signature(args, overload.parameters) {
        viable.push(overload);
    }
}
// Apply most‑specific selection, emit error if viable.is_empty()

```

If multiple overloads match, Pyrefly applies the **PEP 484 "most specific" rule**: the overload whose parameter types are subtypes of all other matching candidates wins. If no unique best overload exists, or if the `viable` vector remains empty, the solver reports **"No matching overload"**.

### Handling Bound Methods

For method overloads, Pyrefly passes the receiver type as `bound_to_type` to `convert_overload_to_tsp`. This type is cloned into each overload signature, allowing the solver to treat the method as a callable with an implicit first argument (`self` or `cls`) already bound.

### Spec-Compliant Mode

Pyrefly supports strict PEP 484 semantics through the `spec_compliant_overloads` flag defined in [`pyrefly/lib/test/util.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/util.rs) (lines 306-307). When enabled, the type checker rejects ambiguous overload sets and enforces exact compliance with the Python typing specification.

## Practical Example: Overload Resolution in Action

```python

# example.py

from typing import overload

@overload
def greet(name: str) -> str: ...
@overload
def greet(name: int) -> int: ...

def greet(name):
    # implementation omitted

    ...

# Pyrefly type‑checking

reveal_type(greet("Alice"))   # -> str (first overload selected)

reveal_type(greet(42))        # -> int (second overload selected)

# Incorrect call – no overload matches

reveal_type(greet(3.14))      # ❌ error: No matching overload

```

## Summary

- Pyrefly represents `@overload` definitions as `Overload` objects that get converted to `TspOverloadedType` via `convert_overload_to_tsp` in [`pyrefly/lib/tsp/type_conversion.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/tsp/type_conversion.rs)
- Call-site resolution iterates through stored signatures in [`pyrefly/lib/solver/call.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/solver/call.rs), unifying arguments and tracking viable candidates
- Selection follows PEP 484 "most specific" subtype rules when multiple overloads match
- Bound methods handle implicit `self`/`cls` arguments through the `bound_to_type` parameter
- Strict compliance mode ensures adherence to Python typing spec requirements

## Frequently Asked Questions

### How does Pyrefly store multiple overload signatures?

Pyrefly stores them in a `TspOverloadedType` struct containing a vector of `TspType` objects, where each element represents one `@overload` signature. This structure is created by the `convert_overload_to_tsp` function in [`pyrefly/lib/tsp/type_conversion.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/tsp/type_conversion.rs) (lines 56-67), which walks the `signatures` vector of the source `Overload` object and converts each entry to a `TspFunctionType`.

### What happens when no overload matches the call arguments?

When no viable overload is found during callable overload resolution, Pyrefly emits the diagnostic error "No matching overload". This occurs in the solver logic within [`pyrefly/lib/solver/call.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/solver/call.rs) after iterating through all available signatures and finding that none successfully unify with the provided argument types.

### How does Pyrefly handle bound methods with overloads?

For bound methods, Pyrefly passes the receiver type as `bound_to_type` to `convert_overload_to_tsp`, which clones this type into each overload signature. This allows the solver to treat the method as a callable where the first argument (self/cls) is already bound, ensuring correct resolution when the method is accessed on an instance or class.

### Can Pyrefly enforce strict PEP 484 overload semantics?

Yes, Pyrefly provides a `spec_compliant_overloads` configuration flag defined in [`pyrefly/lib/test/util.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/util.rs) (lines 306-307). When enabled, the type checker enforces strict adherence to PEP 484 rules for overload resolution, rejecting ambiguous overload sets and ensuring compatibility with the official Python typing specification.