Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/data/messages/i/invalid-name/details.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ name is found in, and not the type of object assigned.
+--------------------+-------------------------------------------------------------------------------------------------------------+
| ``typevar`` | Type variable declared with ``TypeVar``. |
+--------------------+-------------------------------------------------------------------------------------------------------------+
| ``paramspec`` | Parameter specification variable declared with ``ParamSpec``. |
+--------------------+-------------------------------------------------------------------------------------------------------------+
| ``typevartuple`` | Type variable tuple declared with ``TypeVarTuple``. |
+--------------------+-------------------------------------------------------------------------------------------------------------+
| ``typealias`` | Type alias declared with ``TypeAlias`` or assignments of ``Union``. |
+--------------------+-------------------------------------------------------------------------------------------------------------+

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

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

.. option:: --paramspec-rgx=<regex>

.. option:: --typevartuple-rgx=<regex>

.. option:: --typealias-rgx=<regex>

Multiple naming styles for custom regular expressions
Expand Down
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/10541.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add naming styles for ParamSpec and TypeVarTuples which align with the TypeVar style.

Refs #10541
54 changes: 36 additions & 18 deletions pylint/checkers/base/name_checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,30 @@
# Default patterns for name types that do not have styles
DEFAULT_PATTERNS = {
"typevar": re.compile(
r"^_{0,2}(?!T[A-Z])(?:[A-Z]+|(?:[A-Z]+[a-z]+)+T?(?<!Type))(?:_co(?:ntra)?)?$"
r"^_{0,2}(?!T[A-Z])(?:[A-Z]+|(?:[A-Z]+[a-z]+)+(?:T)?(?<!Type))(?:_co(?:ntra)?)?$"
),
"paramspec": re.compile(r"^_{0,2}(?:[A-Z]+|(?:[A-Z]+[a-z]+)+(?:P)?(?<!Type))$"),
"typevartuple": re.compile(r"^_{0,2}(?:[A-Z]+|(?:[A-Z]+[a-z]+)+(?:Ts)?(?<!Type))$"),
"typealias": re.compile(
r"^_{0,2}(?!T[A-Z]|Type)[A-Z]+[a-z0-9]+(?:[A-Z][a-z0-9]+)*$"
),
}

BUILTIN_PROPERTY = "builtins.property"
TYPE_VAR_QNAME = frozenset(
(
TYPE_VAR_QNAMES = {
"typevar": {
"typing.TypeVar",
"typing_extensions.TypeVar",
)
)
},
"paramspec": {
"typing.ParamSpec",
"typing_extensions.ParamSpec",
},
"typevartuple": {
"typing.TypeVarTuple",
"typing_extensions.TypeVarTuple",
},
}


class TypeVarVariance(Enum):
Expand Down Expand Up @@ -400,7 +410,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
"typevar-double-variance",
"typevar-name-mismatch",
)
def visit_assignname( # pylint: disable=too-many-branches
def visit_assignname( # pylint: disable=too-many-branches,too-many-statements
self, node: nodes.AssignName
) -> None:
"""Check module level assigned names."""
Expand All @@ -414,6 +424,12 @@ def visit_assignname( # pylint: disable=too-many-branches
elif isinstance(assign_type, nodes.TypeVar):
self._check_name("typevar", node.name, node)

elif isinstance(assign_type, nodes.ParamSpec):
self._check_name("paramspec", node.name, node)

elif isinstance(assign_type, nodes.TypeVarTuple):
self._check_name("typevartuple", node.name, node)

elif isinstance(assign_type, nodes.TypeAlias):
self._check_name("typealias", node.name, node)

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

# Check TypeVar's and TypeAliases assigned alone or in tuple assignment
if isinstance(node.parent, nodes.Assign):
if self._assigns_typevar(assign_type.value):
self._check_name("typevar", assign_type.targets[0].name, node)
if typevar_node_type := self._assigns_typevar(assign_type.value):
self._check_name(
typevar_node_type, assign_type.targets[0].name, node
)
return
if self._assigns_typealias(assign_type.value):
self._check_name("typealias", assign_type.targets[0].name, node)
Expand All @@ -447,9 +465,9 @@ def visit_assignname( # pylint: disable=too-many-branches
and node.parent.elts.index(node) < len(assign_type.value.elts)
):
assigner = assign_type.value.elts[node.parent.elts.index(node)]
if self._assigns_typevar(assigner):
if typevar_node_type := self._assigns_typevar(assigner):
self._check_name(
"typevar",
typevar_node_type,
assign_type.targets[0]
.elts[node.parent.elts.index(node)]
.name,
Expand Down Expand Up @@ -629,16 +647,16 @@ def _should_exempt_from_invalid_name(node: nodes.NodeNG) -> bool:
self._check_typevar(name, node)

@staticmethod
def _assigns_typevar(node: nodes.NodeNG | None) -> bool:
"""Check if a node is assigning a TypeVar."""
def _assigns_typevar(node: nodes.NodeNG | None) -> str | None:
"""Check if a node is assigning a TypeVar and return TypeVar type."""
if isinstance(node, astroid.Call):
inferred = utils.safe_infer(node.func)
if (
isinstance(inferred, astroid.ClassDef)
and inferred.qname() in TYPE_VAR_QNAME
):
return True
return False
if isinstance(inferred, astroid.ClassDef):
qname = inferred.qname()
for typevar_node_typ, qnames in TYPE_VAR_QNAMES.items():
if qname in qnames:
return typevar_node_typ
return None

@staticmethod
def _assigns_typealias(node: nodes.NodeNG | None) -> bool:
Expand Down
2 changes: 2 additions & 0 deletions pylint/checkers/base/name_checker/naming_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ class AnyStyle(NamingStyle):
KNOWN_NAME_TYPES = {
*KNOWN_NAME_TYPES_WITH_STYLE,
"typevar",
"paramspec",
"typevartuple",
"typealias",
}

Expand Down
2 changes: 2 additions & 0 deletions pylint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class WarningScope:
"class_const": "class constant",
"inlinevar": "inline iteration",
"typevar": "type variable",
"paramspec": "parameter specification variable",
"typevartuple": "type variable tuple",
"typealias": "type alias",
}

Expand Down
14 changes: 14 additions & 0 deletions pylint/utils/linterstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class BadNames(TypedDict):
module: int
variable: int
typevar: int
paramspec: int
typevartuple: int
typealias: int


Expand Down Expand Up @@ -102,6 +104,8 @@ def __init__(
module=0,
variable=0,
typevar=0,
paramspec=0,
typevartuple=0,
typealias=0,
)
self.by_module: dict[str, ModuleStats] = by_module or {}
Expand Down Expand Up @@ -181,6 +185,8 @@ def get_bad_names(
"module",
"variable",
"typevar",
"paramspec",
"typevartuple",
"typealias",
],
) -> int:
Expand All @@ -204,6 +210,8 @@ def increase_bad_name(self, node_name: str, increase: int) -> None:
"module",
"variable",
"typevar",
"paramspec",
"typevartuple",
"typealias",
}:
raise ValueError("Node type not part of the bad_names stat")
Expand All @@ -222,6 +230,8 @@ def increase_bad_name(self, node_name: str, increase: int) -> None:
"module",
"variable",
"typevar",
"paramspec",
"typevartuple",
"typealias",
],
node_name,
Expand All @@ -246,6 +256,8 @@ def reset_bad_names(self) -> None:
module=0,
variable=0,
typevar=0,
paramspec=0,
typevartuple=0,
typealias=0,
)

Expand Down Expand Up @@ -341,6 +353,8 @@ def merge_stats(stats: list[LinterStats]) -> LinterStats:
merged.bad_names["module"] += stat.bad_names["module"]
merged.bad_names["variable"] += stat.bad_names["variable"]
merged.bad_names["typevar"] += stat.bad_names["typevar"]
merged.bad_names["paramspec"] += stat.bad_names["paramspec"]
merged.bad_names["typevartuple"] += stat.bad_names["typevartuple"]
merged.bad_names["typealias"] += stat.bad_names["typealias"]

for mod_key, mod_value in stat.by_module.items():
Expand Down
24 changes: 24 additions & 0 deletions tests/functional/t/type/typevar_naming_style_default.311.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
typevar-name-incorrect-variance:11:0:11:21::"Type variable name does not reflect variance. ""GoodNameWithoutContra"" is contravariant, use ""GoodNameWithoutContra_contra"" instead":INFERENCE
typevar-double-variance:19:0:19:1::TypeVar cannot be both covariant and contravariant:INFERENCE
typevar-name-incorrect-variance:19:0:19:1::Type variable name does not reflect variance:INFERENCE
typevar-double-variance:23:0:23:4::TypeVar cannot be both covariant and contravariant:INFERENCE
typevar-name-incorrect-variance:23:0:23:4::Type variable name does not reflect variance:INFERENCE
typevar-double-variance:24:0:24:8::TypeVar cannot be both covariant and contravariant:INFERENCE
typevar-name-incorrect-variance:24:0:24:8::Type variable name does not reflect variance:INFERENCE
invalid-name:37:0:37:10::"Type variable name ""CALLABLE_T"" doesn't conform to predefined naming style":HIGH
invalid-name:38:0:38:10::"Type variable name ""DeviceType"" doesn't conform to predefined naming style":HIGH
invalid-name:39:0:39:10::"Type variable name ""IPAddressU"" doesn't conform to predefined naming style":HIGH
invalid-name:42:0:42:7::"Type variable name ""TAnyStr"" doesn't conform to predefined naming style":HIGH
invalid-name:45:0:45:7::"Type variable name ""badName"" doesn't conform to predefined naming style":HIGH
invalid-name:46:0:46:10::"Type variable name ""badName_co"" doesn't conform to predefined naming style":HIGH
invalid-name:47:0:47:14::"Type variable name ""badName_contra"" doesn't conform to predefined naming style":HIGH
invalid-name:51:4:51:13::"Type variable name ""a_BadName"" doesn't conform to predefined naming style":HIGH
invalid-name:52:4:52:26::"Type variable name ""a_BadNameWithoutContra"" doesn't conform to predefined naming style":HIGH
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
invalid-name:54:13:54:29::"Type variable name ""a_BadName_contra"" doesn't conform to predefined naming style":HIGH
invalid-name:63:0:63:7::"Type variable name ""badName"" doesn't conform to predefined naming style":HIGH
typevar-double-variance:64:0:64:4::TypeVar cannot be both covariant and contravariant:INFERENCE
typevar-name-incorrect-variance:64:0:64:4::Type variable name does not reflect variance:INFERENCE
invalid-name:71:0:71:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
invalid-name:77:0:77:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
invalid-name:91:0:91:7::"Type variable tuple name ""badName"" doesn't conform to predefined naming style":HIGH
29 changes: 28 additions & 1 deletion tests/functional/t/type/typevar_naming_style_default.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Test case for typevar-name-incorrect-variance with default settings"""
# pylint: disable=too-few-public-methods,line-too-long
from typing import TypeVar
from typing import TypeVar, ParamSpec
import typing_extensions as te

# PascalCase names with prefix
Expand Down Expand Up @@ -62,3 +62,30 @@
GoodNameT_co = te.TypeVar("GoodNameT_co", covariant=True)
badName = te.TypeVar("badName") # [invalid-name]
T_co = te.TypeVar("T_co", covariant=True, contravariant=True) # [typevar-double-variance,typevar-name-incorrect-variance]


# -- typing.ParamSpec --
P = ParamSpec("P")
_P = ParamSpec("_P")
GoodNameP = ParamSpec("GoodNameP")
badName = ParamSpec("badName") # [invalid-name]

# -- typing_extensions.ParamSpec --
P = te.ParamSpec("P")
_P = te.ParamSpec("_P")
GoodNameP = te.ParamSpec("GoodNameP")
badName = te.ParamSpec("badName") # [invalid-name]


# # -- typing.TypeVarTuple (TODO 3.11+) --
# Ts = TypeVarTuple("Ts")
# _Ts = TypeVarTuple("_Ts")
# GoodNameTs = TypeVarTuple("GoodNameTs")
# badName = TypeVarTuple("badName") # invalid-name

# -- typing_extensions.TypeVarTuple (3.11+) --
# TODO Can't infer typing_extensions.TypeVarTuple for 3.10 # pylint:disable=fixme
Ts = te.TypeVarTuple("Ts")
_Ts = te.TypeVarTuple("_Ts")
GoodNameTs = te.TypeVarTuple("GoodNameTs")
badName = te.TypeVarTuple("badName") # >=3.11:[invalid-name]
2 changes: 2 additions & 0 deletions tests/functional/t/type/typevar_naming_style_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ invalid-name:54:13:54:29::"Type variable name ""a_BadName_contra"" doesn't confo
invalid-name:63:0:63:7::"Type variable name ""badName"" doesn't conform to predefined naming style":HIGH
typevar-double-variance:64:0:64:4::TypeVar cannot be both covariant and contravariant:INFERENCE
typevar-name-incorrect-variance:64:0:64:4::Type variable name does not reflect variance:INFERENCE
invalid-name:71:0:71:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
invalid-name:77:0:77:7::"Parameter specification variable name ""badName"" doesn't conform to predefined naming style":HIGH
8 changes: 7 additions & 1 deletion tests/functional/t/type/typevar_naming_style_py312.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
"""PEP 695 generic typing nodes"""

from collections.abc import Sequence
from collections.abc import Callable, Sequence

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

# Don't report typevar-name-incorrect-variance for type parameter
# The variance is determined by the type checker
type Array[T_co] = Sequence[T_co]

type Call[**_P] = Callable[_P, None]
type Call[**p] = Callable[p, None] # [invalid-name]

type Tpl[*_Ts] = tuple[_Ts]
type Tpl[*ts] = tuple[ts] # [invalid-name]
2 changes: 2 additions & 0 deletions tests/functional/t/type/typevar_naming_style_py312.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
invalid-name:6:11:6:12::"Type variable name ""t"" doesn't conform to predefined naming style":HIGH
invalid-name:13:10:13:13::"Parameter specification variable name ""p"" doesn't conform to predefined naming style":HIGH
invalid-name:16:9:16:12::"Type variable tuple name ""ts"" doesn't conform to predefined naming style":HIGH
8 changes: 8 additions & 0 deletions tests/functional/t/type/typevar_naming_style_rgx.311.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
invalid-name:13:0:13:9::"Type variable name ""GoodNameT"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:14:0:14:12::"Type variable name ""GoodNameT_co"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:15:0:15:16::"Type variable name ""GoodNameT_contra"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:20:0:20:9::"Type variable name ""GoodNameT"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:21:0:21:12::"Type variable name ""GoodNameT_co"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:26:0:26:9::"Parameter specification variable name ""GoodNameP"" doesn't conform to 'ParamSpecShouldBeLikeThis$' pattern":HIGH
invalid-name:30:0:30:9::"Parameter specification variable name ""GoodNameP"" doesn't conform to 'ParamSpecShouldBeLikeThis$' pattern":HIGH
invalid-name:40:0:40:10::"Type variable tuple name ""GoodNameTs"" doesn't conform to 'TypeVarTupleShouldBeLikeThis$' pattern":HIGH
21 changes: 20 additions & 1 deletion tests/functional/t/type/typevar_naming_style_rgx.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Test case for typevar-name-missing-variance with non-default settings"""
from typing import TypeVar
from typing import TypeVar, ParamSpec
import typing_extensions as te

# Name set by regex pattern
Expand All @@ -19,3 +19,22 @@
TypeVarsShouldBeLikeThis = te.TypeVar("TypeVarsShouldBeLikeThis")
GoodNameT = te.TypeVar("GoodNameT") # [invalid-name]
GoodNameT_co = te.TypeVar("GoodNameT_co", covariant=True) # [invalid-name]


# -- typing.ParamSpec --
ParamSpecShouldBeLikeThis = ParamSpec("ParamSpecShouldBeLikeThis")
GoodNameP = ParamSpec("GoodNameP") # [invalid-name]

# -- typing_extensions.ParamSpec --
ParamSpecShouldBeLikeThis = te.ParamSpec("ParamSpecShouldBeLikeThis")
GoodNameP = te.ParamSpec("GoodNameP") # [invalid-name]


# # -- typing.TypeVarTuple (TODO 3.11+) --
# TypeVarTupleShouldBeLikeThis = TypeVarTuple("TypeVarTupleShouldBeLikeThis")
# GoodNameTs = TypeVarTuple("GoodNameTs") # invalid-name

# -- typing_extensions.TypeVarTuple --
# TODO Can't infer typing_extensions.TypeVarTuple for 3.10 # pylint:disable=fixme
TypeVarTupleShouldBeLikeThis = te.TypeVarTuple("TypeVarTupleShouldBeLikeThis")
GoodNameTs = te.TypeVarTuple("GoodNameTs") # >=3.11:[invalid-name]
2 changes: 2 additions & 0 deletions tests/functional/t/type/typevar_naming_style_rgx.rc
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[BASIC]
typevar-rgx=TypeVarsShouldBeLikeThis(_co(ntra)?)?$
paramspec-rgx=ParamSpecShouldBeLikeThis$
typevartuple-rgx=TypeVarTupleShouldBeLikeThis$
2 changes: 2 additions & 0 deletions tests/functional/t/type/typevar_naming_style_rgx.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ invalid-name:14:0:14:12::"Type variable name ""GoodNameT_co"" doesn't conform to
invalid-name:15:0:15:16::"Type variable name ""GoodNameT_contra"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:20:0:20:9::"Type variable name ""GoodNameT"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:21:0:21:12::"Type variable name ""GoodNameT_co"" doesn't conform to 'TypeVarsShouldBeLikeThis(_co(ntra)?)?$' pattern":HIGH
invalid-name:26:0:26:9::"Parameter specification variable name ""GoodNameP"" doesn't conform to 'ParamSpecShouldBeLikeThis$' pattern":HIGH
invalid-name:30:0:30:9::"Parameter specification variable name ""GoodNameP"" doesn't conform to 'ParamSpecShouldBeLikeThis$' pattern":HIGH
4 changes: 3 additions & 1 deletion tests/lint/test_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ def linter_stats() -> LinterStats:
module=10,
variable=11,
typevar=12,
typealias=13,
paramspec=13,
typevartuple=14,
typealias=15,
)
)

Expand Down
Loading