Skip to content

Commit df3aa2a

Browse files
authored
Add naming styles for ParamSpec and TypeVarTuple (#10541)
1 parent f2feabf commit df3aa2a

16 files changed

+167
-22
lines changed

doc/data/messages/i/invalid-name/details.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ name is found in, and not the type of object assigned.
2929
+--------------------+-------------------------------------------------------------------------------------------------------------+
3030
| ``typevar`` | Type variable declared with ``TypeVar``. |
3131
+--------------------+-------------------------------------------------------------------------------------------------------------+
32+
| ``paramspec`` | Parameter specification variable declared with ``ParamSpec``. |
33+
+--------------------+-------------------------------------------------------------------------------------------------------------+
34+
| ``typevartuple`` | Type variable tuple declared with ``TypeVarTuple``. |
35+
+--------------------+-------------------------------------------------------------------------------------------------------------+
3236
| ``typealias`` | Type alias declared with ``TypeAlias`` or assignments of ``Union``. |
3337
+--------------------+-------------------------------------------------------------------------------------------------------------+
3438

@@ -88,6 +92,10 @@ The following types of names are checked with a predefined pattern:
8892
| ``typevar`` | ``T``, ``_CallableT``, ``_T_co``, ``AnyStr``, | ``DICT_T``, ``CALLABLE_T``, ``ENUM_T``, ``DeviceType``, |
8993
| | ``DeviceTypeT``, ``IPAddressT`` | ``_StrType`` |
9094
+--------------------+-------------------------------------------------------+------------------------------------------------------------+
95+
| ``paramspec`` | ``P``, ``_P`` | ``CALLABLE_P`` |
96+
+--------------------+-------------------------------------------------------+------------------------------------------------------------+
97+
| ``typevartuple`` | ``Ts``, ``_Ts`` | ``TUPLE_TS`` |
98+
+--------------------+-------------------------------------------------------+------------------------------------------------------------+
9199
| ``typealias`` | ``GoodName``, ``_GoodName``, ``IPAddressType``, | ``BadNameT``, ``badName``, ``TBadName``, ``TypeBadName``, |
92100
| | ``GoodName2`` and other PascalCase variants that | ``_1BadName`` |
93101
| | don't start with ``T`` or ``Type``. This is to | |
@@ -139,6 +147,10 @@ expression will lead to an instance of ``invalid-name``.
139147

140148
.. option:: --typevar-rgx=<regex>
141149

150+
.. option:: --paramspec-rgx=<regex>
151+
152+
.. option:: --typevartuple-rgx=<regex>
153+
142154
.. option:: --typealias-rgx=<regex>
143155

144156
Multiple naming styles for custom regular expressions

doc/whatsnew/fragments/10541.feature

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add naming styles for ParamSpec and TypeVarTuples which align with the TypeVar style.
2+
3+
Refs #10541

pylint/checkers/base/name_checker/checker.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,30 @@
4040
# Default patterns for name types that do not have styles
4141
DEFAULT_PATTERNS = {
4242
"typevar": re.compile(
43-
r"^_{0,2}(?!T[A-Z])(?:[A-Z]+|(?:[A-Z]+[a-z]+)+T?(?<!Type))(?:_co(?:ntra)?)?$"
43+
r"^_{0,2}(?!T[A-Z])(?:[A-Z]+|(?:[A-Z]+[a-z]+)+(?:T)?(?<!Type))(?:_co(?:ntra)?)?$"
4444
),
45+
"paramspec": re.compile(r"^_{0,2}(?:[A-Z]+|(?:[A-Z]+[a-z]+)+(?:P)?(?<!Type))$"),
46+
"typevartuple": re.compile(r"^_{0,2}(?:[A-Z]+|(?:[A-Z]+[a-z]+)+(?:Ts)?(?<!Type))$"),
4547
"typealias": re.compile(
4648
r"^_{0,2}(?!T[A-Z]|Type)[A-Z]+[a-z0-9]+(?:[A-Z][a-z0-9]+)*$"
4749
),
4850
}
4951

5052
BUILTIN_PROPERTY = "builtins.property"
51-
TYPE_VAR_QNAME = frozenset(
52-
(
53+
TYPE_VAR_QNAMES = {
54+
"typevar": {
5355
"typing.TypeVar",
5456
"typing_extensions.TypeVar",
55-
)
56-
)
57+
},
58+
"paramspec": {
59+
"typing.ParamSpec",
60+
"typing_extensions.ParamSpec",
61+
},
62+
"typevartuple": {
63+
"typing.TypeVarTuple",
64+
"typing_extensions.TypeVarTuple",
65+
},
66+
}
5767

5868

5969
class TypeVarVariance(Enum):
@@ -400,7 +410,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
400410
"typevar-double-variance",
401411
"typevar-name-mismatch",
402412
)
403-
def visit_assignname( # pylint: disable=too-many-branches
413+
def visit_assignname( # pylint: disable=too-many-branches,too-many-statements
404414
self, node: nodes.AssignName
405415
) -> None:
406416
"""Check module level assigned names."""
@@ -414,6 +424,12 @@ def visit_assignname( # pylint: disable=too-many-branches
414424
elif isinstance(assign_type, nodes.TypeVar):
415425
self._check_name("typevar", node.name, node)
416426

427+
elif isinstance(assign_type, nodes.ParamSpec):
428+
self._check_name("paramspec", node.name, node)
429+
430+
elif isinstance(assign_type, nodes.TypeVarTuple):
431+
self._check_name("typevartuple", node.name, node)
432+
417433
elif isinstance(assign_type, nodes.TypeAlias):
418434
self._check_name("typealias", node.name, node)
419435

@@ -433,8 +449,10 @@ def visit_assignname( # pylint: disable=too-many-branches
433449

434450
# Check TypeVar's and TypeAliases assigned alone or in tuple assignment
435451
if isinstance(node.parent, nodes.Assign):
436-
if self._assigns_typevar(assign_type.value):
437-
self._check_name("typevar", assign_type.targets[0].name, node)
452+
if typevar_node_type := self._assigns_typevar(assign_type.value):
453+
self._check_name(
454+
typevar_node_type, assign_type.targets[0].name, node
455+
)
438456
return
439457
if self._assigns_typealias(assign_type.value):
440458
self._check_name("typealias", assign_type.targets[0].name, node)
@@ -447,9 +465,9 @@ def visit_assignname( # pylint: disable=too-many-branches
447465
and node.parent.elts.index(node) < len(assign_type.value.elts)
448466
):
449467
assigner = assign_type.value.elts[node.parent.elts.index(node)]
450-
if self._assigns_typevar(assigner):
468+
if typevar_node_type := self._assigns_typevar(assigner):
451469
self._check_name(
452-
"typevar",
470+
typevar_node_type,
453471
assign_type.targets[0]
454472
.elts[node.parent.elts.index(node)]
455473
.name,
@@ -629,16 +647,16 @@ def _should_exempt_from_invalid_name(node: nodes.NodeNG) -> bool:
629647
self._check_typevar(name, node)
630648

631649
@staticmethod
632-
def _assigns_typevar(node: nodes.NodeNG | None) -> bool:
633-
"""Check if a node is assigning a TypeVar."""
650+
def _assigns_typevar(node: nodes.NodeNG | None) -> str | None:
651+
"""Check if a node is assigning a TypeVar and return TypeVar type."""
634652
if isinstance(node, astroid.Call):
635653
inferred = utils.safe_infer(node.func)
636-
if (
637-
isinstance(inferred, astroid.ClassDef)
638-
and inferred.qname() in TYPE_VAR_QNAME
639-
):
640-
return True
641-
return False
654+
if isinstance(inferred, astroid.ClassDef):
655+
qname = inferred.qname()
656+
for typevar_node_typ, qnames in TYPE_VAR_QNAMES.items():
657+
if qname in qnames:
658+
return typevar_node_typ
659+
return None
642660

643661
@staticmethod
644662
def _assigns_typealias(node: nodes.NodeNG | None) -> bool:

pylint/checkers/base/name_checker/naming_style.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ class AnyStyle(NamingStyle):
137137
KNOWN_NAME_TYPES = {
138138
*KNOWN_NAME_TYPES_WITH_STYLE,
139139
"typevar",
140+
"paramspec",
141+
"typevartuple",
140142
"typealias",
141143
}
142144

pylint/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class WarningScope:
7575
"class_const": "class constant",
7676
"inlinevar": "inline iteration",
7777
"typevar": "type variable",
78+
"paramspec": "parameter specification variable",
79+
"typevartuple": "type variable tuple",
7880
"typealias": "type alias",
7981
}
8082

pylint/utils/linterstats.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class BadNames(TypedDict):
2424
module: int
2525
variable: int
2626
typevar: int
27+
paramspec: int
28+
typevartuple: int
2729
typealias: int
2830

2931

@@ -102,6 +104,8 @@ def __init__(
102104
module=0,
103105
variable=0,
104106
typevar=0,
107+
paramspec=0,
108+
typevartuple=0,
105109
typealias=0,
106110
)
107111
self.by_module: dict[str, ModuleStats] = by_module or {}
@@ -181,6 +185,8 @@ def get_bad_names(
181185
"module",
182186
"variable",
183187
"typevar",
188+
"paramspec",
189+
"typevartuple",
184190
"typealias",
185191
],
186192
) -> int:
@@ -204,6 +210,8 @@ def increase_bad_name(self, node_name: str, increase: int) -> None:
204210
"module",
205211
"variable",
206212
"typevar",
213+
"paramspec",
214+
"typevartuple",
207215
"typealias",
208216
}:
209217
raise ValueError("Node type not part of the bad_names stat")
@@ -222,6 +230,8 @@ def increase_bad_name(self, node_name: str, increase: int) -> None:
222230
"module",
223231
"variable",
224232
"typevar",
233+
"paramspec",
234+
"typevartuple",
225235
"typealias",
226236
],
227237
node_name,
@@ -246,6 +256,8 @@ def reset_bad_names(self) -> None:
246256
module=0,
247257
variable=0,
248258
typevar=0,
259+
paramspec=0,
260+
typevartuple=0,
249261
typealias=0,
250262
)
251263

@@ -341,6 +353,8 @@ def merge_stats(stats: list[LinterStats]) -> LinterStats:
341353
merged.bad_names["module"] += stat.bad_names["module"]
342354
merged.bad_names["variable"] += stat.bad_names["variable"]
343355
merged.bad_names["typevar"] += stat.bad_names["typevar"]
356+
merged.bad_names["paramspec"] += stat.bad_names["paramspec"]
357+
merged.bad_names["typevartuple"] += stat.bad_names["typevartuple"]
344358
merged.bad_names["typealias"] += stat.bad_names["typealias"]
345359

346360
for mod_key, mod_value in stat.by_module.items():
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
typevar-name-incorrect-variance:11:0:11:21::"Type variable name does not reflect variance. ""GoodNameWithoutContra"" is contravariant, use ""GoodNameWithoutContra_contra"" instead":INFERENCE
2+
typevar-double-variance:19:0:19:1::TypeVar cannot be both covariant and contravariant:INFERENCE
3+
typevar-name-incorrect-variance:19:0:19:1::Type variable name does not reflect variance:INFERENCE
4+
typevar-double-variance:23:0:23:4::TypeVar cannot be both covariant and contravariant:INFERENCE
5+
typevar-name-incorrect-variance:23:0:23:4::Type variable name does not reflect variance:INFERENCE
6+
typevar-double-variance:24:0:24:8::TypeVar cannot be both covariant and contravariant:INFERENCE
7+
typevar-name-incorrect-variance:24:0:24:8::Type variable name does not reflect variance:INFERENCE
8+
invalid-name:37:0:37:10::"Type variable name ""CALLABLE_T"" doesn't conform to predefined naming style":HIGH
9+
invalid-name:38:0:38:10::"Type variable name ""DeviceType"" doesn't conform to predefined naming style":HIGH
10+
invalid-name:39:0:39:10::"Type variable name ""IPAddressU"" doesn't conform to predefined naming style":HIGH
11+
invalid-name:42:0:42:7::"Type variable name ""TAnyStr"" doesn't conform to predefined naming style":HIGH
12+
invalid-name:45:0:45:7::"Type variable name ""badName"" doesn't conform to predefined naming style":HIGH
13+
invalid-name:46:0:46:10::"Type variable name ""badName_co"" doesn't conform to predefined naming style":HIGH
14+
invalid-name:47:0:47:14::"Type variable name ""badName_contra"" doesn't conform to predefined naming style":HIGH
15+
invalid-name:51:4:51:13::"Type variable name ""a_BadName"" doesn't conform to predefined naming style":HIGH
16+
invalid-name:52:4:52:26::"Type variable name ""a_BadNameWithoutContra"" doesn't conform to predefined naming style":HIGH
17+
typevar-name-incorrect-variance:52:4:52:26::"Type variable name does not reflect variance. ""a_BadNameWithoutContra"" is contravariant, use ""a_BadNameWithoutContra_contra"" instead":INFERENCE
18+
invalid-name:54:13:54:29::"Type variable name ""a_BadName_contra"" doesn't conform to predefined naming style":HIGH
19+
invalid-name:63:0:63:7::"Type variable name ""badName"" doesn't conform to predefined naming style":HIGH
20+
typevar-double-variance:64:0:64:4::TypeVar cannot be both covariant and contravariant:INFERENCE
21+
typevar-name-incorrect-variance:64:0:64:4::Type variable name does not reflect variance:INFERENCE
22+
invalid-name:71:0:71:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
23+
invalid-name:77:0:77:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
24+
invalid-name:91:0:91:7::"Type variable tuple name ""badName"" doesn't conform to predefined naming style":HIGH

tests/functional/t/type/typevar_naming_style_default.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Test case for typevar-name-incorrect-variance with default settings"""
22
# pylint: disable=too-few-public-methods,line-too-long
3-
from typing import TypeVar
3+
from typing import TypeVar, ParamSpec
44
import typing_extensions as te
55

66
# PascalCase names with prefix
@@ -62,3 +62,30 @@
6262
GoodNameT_co = te.TypeVar("GoodNameT_co", covariant=True)
6363
badName = te.TypeVar("badName") # [invalid-name]
6464
T_co = te.TypeVar("T_co", covariant=True, contravariant=True) # [typevar-double-variance,typevar-name-incorrect-variance]
65+
66+
67+
# -- typing.ParamSpec --
68+
P = ParamSpec("P")
69+
_P = ParamSpec("_P")
70+
GoodNameP = ParamSpec("GoodNameP")
71+
badName = ParamSpec("badName") # [invalid-name]
72+
73+
# -- typing_extensions.ParamSpec --
74+
P = te.ParamSpec("P")
75+
_P = te.ParamSpec("_P")
76+
GoodNameP = te.ParamSpec("GoodNameP")
77+
badName = te.ParamSpec("badName") # [invalid-name]
78+
79+
80+
# # -- typing.TypeVarTuple (TODO 3.11+) --
81+
# Ts = TypeVarTuple("Ts")
82+
# _Ts = TypeVarTuple("_Ts")
83+
# GoodNameTs = TypeVarTuple("GoodNameTs")
84+
# badName = TypeVarTuple("badName") # invalid-name
85+
86+
# -- typing_extensions.TypeVarTuple (3.11+) --
87+
# TODO Can't infer typing_extensions.TypeVarTuple for 3.10 # pylint:disable=fixme
88+
Ts = te.TypeVarTuple("Ts")
89+
_Ts = te.TypeVarTuple("_Ts")
90+
GoodNameTs = te.TypeVarTuple("GoodNameTs")
91+
badName = te.TypeVarTuple("badName") # >=3.11:[invalid-name]

tests/functional/t/type/typevar_naming_style_default.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ invalid-name:54:13:54:29::"Type variable name ""a_BadName_contra"" doesn't confo
1919
invalid-name:63:0:63:7::"Type variable name ""badName"" doesn't conform to predefined naming style":HIGH
2020
typevar-double-variance:64:0:64:4::TypeVar cannot be both covariant and contravariant:INFERENCE
2121
typevar-name-incorrect-variance:64:0:64:4::Type variable name does not reflect variance:INFERENCE
22+
invalid-name:71:0:71:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
23+
invalid-name:77:0:77:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
"""PEP 695 generic typing nodes"""
22

3-
from collections.abc import Sequence
3+
from collections.abc import Callable, Sequence
44

55
type Point[T] = tuple[T, ...]
66
type Point[t] = tuple[t, ...] # [invalid-name]
77

88
# Don't report typevar-name-incorrect-variance for type parameter
99
# The variance is determined by the type checker
1010
type Array[T_co] = Sequence[T_co]
11+
12+
type Call[**_P] = Callable[_P, None]
13+
type Call[**p] = Callable[p, None] # [invalid-name]
14+
15+
type Tpl[*_Ts] = tuple[_Ts]
16+
type Tpl[*ts] = tuple[ts] # [invalid-name]

0 commit comments

Comments
 (0)