Skip to content

Commit b55dcf5

Browse files
authored
stubtest: check typevar and paramspec (#12851)
Came up in #12825
1 parent 4024748 commit b55dcf5

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

mypy/stubtest.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import re
1313
import sys
1414
import types
15+
import typing
16+
import typing_extensions
1517
import warnings
1618
from functools import singledispatch
1719
from pathlib import Path
@@ -867,8 +869,32 @@ def verify_overloadedfuncdef(
867869
def verify_typevarexpr(
868870
stub: nodes.TypeVarExpr, runtime: MaybeMissing[Any], object_path: List[str]
869871
) -> Iterator[Error]:
870-
if False:
871-
yield None
872+
if isinstance(runtime, Missing):
873+
# We seem to insert these typevars into NamedTuple stubs, but they
874+
# don't exist at runtime. Just ignore!
875+
if stub.name == "_NT":
876+
return
877+
yield Error(object_path, "is not present at runtime", stub, runtime)
878+
return
879+
if not isinstance(runtime, TypeVar):
880+
yield Error(object_path, "is not a TypeVar", stub, runtime)
881+
return
882+
883+
884+
@verify.register(nodes.ParamSpecExpr)
885+
def verify_paramspecexpr(
886+
stub: nodes.ParamSpecExpr, runtime: MaybeMissing[Any], object_path: List[str]
887+
) -> Iterator[Error]:
888+
if isinstance(runtime, Missing):
889+
yield Error(object_path, "is not present at runtime", stub, runtime)
890+
return
891+
maybe_paramspec_types = (
892+
getattr(typing, "ParamSpec", None), getattr(typing_extensions, "ParamSpec", None)
893+
)
894+
paramspec_types = tuple([t for t in maybe_paramspec_types if t is not None])
895+
if not paramspec_types or not isinstance(runtime, paramspec_types):
896+
yield Error(object_path, "is not a ParamSpec", stub, runtime)
897+
return
872898

873899

874900
def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[str]:

mypy/test/teststubtest.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ def __getitem__(self, typeargs: Any) -> object: ...
4848
class TypeVar:
4949
def __init__(self, name, covariant: bool = ..., contravariant: bool = ...) -> None: ...
5050
51+
class ParamSpec:
52+
def __init__(self, name: str) -> None: ...
53+
5154
_T = TypeVar("_T")
5255
_T_co = TypeVar("_T_co", covariant=True)
5356
_K = TypeVar("_K")
@@ -329,8 +332,8 @@ def test_default_value(self) -> Iterator[Case]:
329332
yield Case(
330333
stub="""
331334
from typing import TypeVar
332-
T = TypeVar("T", bound=str)
333-
def f6(text: T = ...) -> None: ...
335+
_T = TypeVar("_T", bound=str)
336+
def f6(text: _T = ...) -> None: ...
334337
""",
335338
runtime="def f6(text = None): pass",
336339
error="f6",
@@ -1042,6 +1045,33 @@ def foo(self, x: int, y: bytes = ...) -> str: ...
10421045
error="X.__init__"
10431046
)
10441047

1048+
@collect_cases
1049+
def test_type_var(self) -> Iterator[Case]:
1050+
yield Case(
1051+
stub="from typing import TypeVar", runtime="from typing import TypeVar", error=None
1052+
)
1053+
yield Case(
1054+
stub="A = TypeVar('A')",
1055+
runtime="A = TypeVar('A')",
1056+
error=None,
1057+
)
1058+
yield Case(
1059+
stub="B = TypeVar('B')",
1060+
runtime="B = 5",
1061+
error="B",
1062+
)
1063+
if sys.version_info >= (3, 10):
1064+
yield Case(
1065+
stub="from typing import ParamSpec",
1066+
runtime="from typing import ParamSpec",
1067+
error=None
1068+
)
1069+
yield Case(
1070+
stub="C = ParamSpec('C')",
1071+
runtime="C = ParamSpec('C')",
1072+
error=None,
1073+
)
1074+
10451075

10461076
def remove_color_code(s: str) -> str:
10471077
return re.sub("\\x1b.*?m", "", s) # this works!

0 commit comments

Comments
 (0)