Skip to content

Incremental mode bug for nested ParamSpecs #14322

Description

@ringohoffman

Bug Report

This bug only occurs on the second run when using incremental mode. When using nested ParamSpecs, the outer ParamSpec (M below) is erroneously equal to the inner ParamSpec (P below).

Notably, pyright handles this correctly.

To Reproduce

from __future__ import annotations

from typing import Callable, Concatenate, Generic, ParamSpec, TypeVar


class ABase:
    ...


class ADerived(ABase):
    def __init__(self, a_param_1: float) -> None:
        super().__init__()
        self.a_1 = a_param_1


M = ParamSpec("M")
P = ParamSpec("P")
R = TypeVar("R", bound=ABase)


class B(Generic[P, R]):
    def __init__(
        self,
        a_class: Callable[P, R],
        b_param_1: str,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> None:
        self.a = a_class(*args, **kwargs)
        self.b_1 = b_param_1

    @classmethod
    def build(
        cls: Callable[Concatenate[Callable[P, R], M], B[P, R]], 
        a_class: Callable[P, R]
    ) -> Callable[M, B[P, R]]:

        def new_constructor(
            *args: M.args,
            **kwargs: M.kwargs,
        ) -> B[P, R]:
            return cls(a_class, *args, **kwargs)

        return new_constructor


BADerived = B.build(ADerived)

reveal_type(BADerived)  # note): Revealed type is "def (a_param_1: builtins.float) -> B[[a_param_1: builtins.float], ADerived]"

b_a_derived_1 = B(ADerived, "my_str", 1.0)  # OK
b_a_derived_2 = BADerived(  # [call-arg] error: Too many arguments
    "my_str", 1.0  # [arg-type] error: Argument 1 has incompatible type "str"; expected "float"
)

Expected Behavior

I expect the signature of BADerived to include b_param_1, like this:

mypy_nested_paramspec.py:49: note: Revealed type is "def (b_param_1: builtins.str, a_param_1: builtins.float) -> mypy_nested_paramspec.B[[a_param_1: builtins.float], ADerived]"

I also expect that calls to BADerived with the correct parameter count and types do not raise any errors.

Actual Behavior

mypy_nested_paramspec.py:49: note: Revealed type is "def (a_param_1: builtins.float) -> mypy_nested_paramspec.B[[a_param_1: builtins.float], mypy_nested_paramspec.ADerived]"
mypy_nested_paramspec.py:52: error: Too many arguments  [call-arg]
mypy_nested_paramspec.py:53: error: Argument 1 has incompatible type "str"; expected "float"  [arg-type]

Your Environment

  • Mypy version used: mypy 0.991 (compiled: yes)
  • Python version used: 3.10.6

Activity

  1. ringohoffman commented on Dec 20, 2022

    @ringohoffman
    Author

    BTW if anyone can advise ... I am also at a loss to annotate BADerived and solve my var-annotated error:

    main.py:53: error: Need type annotation for "BADerived"  [var-annotated]
    
  2. ilevkivskyi commented on Feb 2, 2026

    @ilevkivskyi
    Member

    On current master I see the same behaviour with cold and warm cache, so removing the incremental label. The behavior however is still weird: it is different whether I call BADerived = B.build(ADerived) in the same or in a different file. In one case I get

    first.py:47: error: Need type annotation for "BADerived"  [var-annotated]
    first.py:49: note: Revealed type is "def (*Any, **Any) -> first.B[[a_param_1: builtins.float], first.ADerived]"
    

    In other case I get

    second.py:7: note: Revealed type is "def (*Never, **Never) -> first.B[[a_param_1: builtins.float], first.ADerived]"
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-paramspecPEP 612, ParamSpec, Concatenatetopic-self-typesTypes for self

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions