PyO3 / PyO3/pyo3

`experimental-inspect`: `PyList` / `PyDict` / `PyTuple` emit bare `list` / `dict` / `tuple`, which fail `mypy --strict`

Open
#6,236 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
16.2k
Forks
1k
Avg merge
2d 6h
Merged PRs (30d)
66

Description

Bug Description
Summary

pyo3-introspection annotates the concrete container types Bound<'_, PyList>, Bound<'_, PyDict>, Bound<'_, PyTuple> (and their Py<...> equivalents) as the bare generics list, dict, tuple:

def make_list() -> list: ...
def take_dict(_x: dict) -> None: ...

Bare generics are rejected by mypy --strict, because --strict turns on disallow_any_generics:

error: Missing type arguments for generic type "list"  [type-arg]

So any stub that mentions one of these container types is not --strict-clean out of the box. PyO3 already knows the container kind; emitting the element-agnostic parametrization (list[Any], dict[Any, Any], tuple[Any, ...]) means exactly the same thing and passes --strict.

Steps to Reproduce
Minimal reproduction

Cargo.toml:

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
pyo3 = { version = "0.29", features = ["abi3-py312", "extension-module", "experimental-inspect"] }

src/lib.rs:

use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyTuple};

#[pyfunction] fn take_list(_x: Bound<'_, PyList>) {}
#[pyfunction] fn make_list(py: Python<'_>) -> Bound<'_, PyList> { PyList::empty(py) }
#[pyfunction] fn take_dict(_x: Bound<'_, PyDict>) {}
#[pyfunction] fn make_dict(py: Python<'_>) -> Bound<'_, PyDict> { PyDict::new(py) }
#[pyfunction] fn take_tuple(_x: Bound<'_, PyTuple>) {}
#[pyfunction] fn make_tuple(py: Python<'_>) -> PyResult<Bound<'_, PyTuple>> { PyTuple::new(py, [1, 2, 3]) }

#[pymodule]
mod baregen_repro {
    #[pymodule_export]
    use super::{make_dict, make_list, make_tuple, take_dict, take_list, take_tuple};
}
Generated stub (module_stub_files)
def make_dict() -> dict: ...
def make_list() -> list: ...
def make_tuple() -> tuple: ...
def take_dict(_x: dict) -> None: ...
def take_list(_x: list) -> None: ...
def take_tuple(_x: tuple) -> None: ...
mypy --strict output

Running mypy --strict on the generated stub (no other config):

baregen_repro.pyi:1: error: Missing type arguments for generic type "dict"  [type-arg]
baregen_repro.pyi:2: error: Missing type arguments for generic type "list"  [type-arg]
baregen_repro.pyi:3: error: Missing type arguments for generic type "tuple"  [type-arg]
baregen_repro.pyi:4: error: Missing type arguments for generic type "dict"  [type-arg]
baregen_repro.pyi:5: error: Missing type arguments for generic type "list"  [type-arg]
baregen_repro.pyi:6: error: Missing type arguments for generic type "tuple"  [type-arg]
Found 6 errors in 1 file
Backtrace

Your operating system and version

Linux

Your Python version (python --version)

3.14.6

Your Rust version (rustc --version)

1.96.0

Your PyO3 version

0.29.0

How did you install python? Did you use a virtualenv?

uv

Additional Info
Suggested fix

Emit the element-agnostic parametrization for these container types:

Rust type current proposed
PyList list list[Any]
PyDict dict dict[Any, Any]
PyTuple tuple tuple[Any, ...]

This is semantically identical (element types are unknown to PyO3) but passes mypy --strict. Verified that the parametrized form is --strict-clean:

from typing import Any
def make_list() -> list[Any]: ...
def take_dict(_x: dict[Any, Any]) -> None: ...
def make_tuple() -> tuple[Any, ...]: ...
# -> Success: no issues found
Notes
  • Applies to both Bound<'_, PyX> and Py<PyX> — they share the same INPUT_TYPE / OUTPUT_TYPE. The repro above uses Bound; the Py<...> forms produce the identical bare generics.
  • stubtest is indifferent to the parametrization (the runtime objects are just list / dict / tuple), so the change is safe there — this is purely about --strict cleanliness.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the src/lib.rs reproduction and the pyo3-introspection path used by module_stub_files for PyList, PyDict, and PyTuple. Run mypy --strict on the generated stub, then verify that both Bound and Py forms emit list[Any], dict[Any, Any], and tuple[Any, ...] and produce no strict-mode errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.