diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 567f6f8c788a..7e63f0db43ee 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -12,6 +12,8 @@ import re import sys import types +import typing +import typing_extensions import warnings from functools import singledispatch from pathlib import Path @@ -866,8 +868,33 @@ def verify_overloadedfuncdef( def verify_typevarexpr( stub: nodes.TypeVarExpr, runtime: MaybeMissing[Any], object_path: List[str] ) -> Iterator[Error]: - if False: - yield None + if isinstance(runtime, Missing): + # We seem to insert these typevars into NamedTuple stubs, but they + # don't exist at runtime. Just ignore! + if stub.name == "_NT": + return + yield Error(object_path, "is not present at runtime", stub, runtime) + return + if not isinstance(runtime, TypeVar): + yield Error(object_path, "is not a TypeVar", stub, runtime) + return + + +@verify.register(nodes.ParamSpecExpr) +def verify_paramspecexpr( + stub: nodes.ParamSpecExpr, runtime: MaybeMissing[Any], object_path: List[str] +) -> Iterator[Error]: + if isinstance(runtime, Missing): + yield Error(object_path, "is not present at runtime", stub, runtime) + return + paramspec_types = ( + getattr(typing, "ParamSpec", None), + getattr(typing_extensions, "ParamSpec", None) + ) + paramspec_types = tuple(filter(None, paramspec_types)) + if not paramspec_types or not isinstance(runtime, paramspec_types): + yield Error(object_path, "is not a ParamSpec", stub, runtime) + return def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[str]: diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 50b3f90c8fad..f98a6e972228 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -48,6 +48,9 @@ def __getitem__(self, typeargs: Any) -> object: ... class TypeVar: def __init__(self, name, covariant: bool = ..., contravariant: bool = ...) -> None: ... +class ParamSpec: + def __init__(self, name: str) -> None: ... + _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) _K = TypeVar("_K") @@ -1042,6 +1045,33 @@ def foo(self, x: int, y: bytes = ...) -> str: ... error="X.__init__" ) + @collect_cases + def test_type_var(self) -> Iterator[Case]: + yield Case( + stub="from typing import TypeVar", runtime="from typing import TypeVar", error=None + ) + yield Case( + stub="A = TypeVar('A')", + runtime="A = TypeVar('A')", + error=None, + ) + yield Case( + stub="B = TypeVar('B')", + runtime="B = 5", + error="B", + ) + if sys.version_info >= (3, 10): + yield Case( + stub="from typing import ParamSpec", + runtime="from typing import ParamSpec", + error=None + ) + yield Case( + stub="C = ParamSpec('C')", + runtime="C = ParamSpec('C')", + error=None, + ) + def remove_color_code(s: str) -> str: return re.sub("\\x1b.*?m", "", s) # this works!