Skip to content

Commit

Permalink
New check: TypeVar name mismatch (#6168)
Browse files Browse the repository at this point in the history
* TypeVar name mismatch check

* Fix double hashes in changelogs

Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
  • Loading branch information
jpy-git and Pierre-Sassoulas committed Apr 4, 2022
1 parent 1534fed commit 7a9234f
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 3 deletions.
6 changes: 5 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ Release date: TBA
Put new features here and also in 'doc/whatsnew/2.14.rst'


* Added new checker ``typevar-name-mismatch``: TypeVar must be assigned to a variable with the same name as its name argument.

Closes #5224

* ``invalid-enum-extension``: Used when a class tries to extend an inherited Enum class.

Closes #5501

* Added new checker ``typevar-double-variance``: The "covariant" and "contravariant" keyword arguments
cannot both be set to "True" in a TypeVar.

Closes ##5895
Closes #5895

* Re-enable checker ``bad-docstring-quotes`` for Python <= 3.7.

Expand Down
3 changes: 3 additions & 0 deletions doc/data/messages/t/typevar-name-mismatch/bad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from typing import TypeVar

X = TypeVar("T") # [typevar-name-mismatch]
3 changes: 3 additions & 0 deletions doc/data/messages/t/typevar-name-mismatch/good.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from typing import TypeVar

T = TypeVar("T")
6 changes: 5 additions & 1 deletion doc/whatsnew/2.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ Summary -- Release highlights
New checkers
============

* Added new checker ``typevar-name-mismatch``: TypeVar must be assigned to a variable with the same name as its name argument.

Closes #5224

* ``invalid-enum-extension``: Used when a class tries to extend an inherited Enum class.

Closes #5501

* Added new checker ``typevar-double-variance``: The "covariant" and "contravariant" keyword arguments
cannot both be set to "True" in a TypeVar.

Closes ##5895
Closes #5895

* Add new check ``unnecessary-dunder-call`` for unnecessary dunder method calls.

Expand Down
26 changes: 25 additions & 1 deletion pylint/checkers/base/name_checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,18 @@ class NameChecker(_BasicChecker):
"do not require a suffix. The message is also emitted when invariant "
"variables do have a suffix.",
),
"C0130": (
"C0131": (
"TypeVar cannot be both covariant and contravariant",
"typevar-double-variance",
'Emitted when both the "covariant" and "contravariant" '
'keyword arguments are set to "True" in a TypeVar.',
),
"C0132": (
'TypeVar name "%s" does not match assigned variable name "%s"',
"typevar-name-mismatch",
"Emitted when a TypeVar is assigned to a variable "
"that does not match its name argument.",
),
"W0111": (
"Name %s will become a keyword in Python %s",
"assign-to-new-keyword",
Expand Down Expand Up @@ -376,6 +382,7 @@ def visit_global(self, node: nodes.Global) -> None:
"assign-to-new-keyword",
"typevar-name-incorrect-variance",
"typevar-double-variance",
"typevar-name-mismatch",
)
def visit_assignname(self, node: nodes.AssignName) -> None:
"""Check module level assigned names."""
Expand Down Expand Up @@ -564,12 +571,15 @@ def _check_typevar(self, name: str, node: nodes.AssignName) -> None:
"""Check for TypeVar lint violations."""
if isinstance(node.parent, nodes.Assign):
keywords = node.assign_type().value.keywords
args = node.assign_type().value.args
elif isinstance(node.parent, nodes.Tuple):
keywords = (
node.assign_type().value.elts[node.parent.elts.index(node)].keywords
)
args = node.assign_type().value.elts[node.parent.elts.index(node)].args

variance = TypeVarVariance.invariant
name_arg = None
for kw in keywords:
if variance == TypeVarVariance.double_variant:
pass
Expand All @@ -586,6 +596,12 @@ def _check_typevar(self, name: str, node: nodes.AssignName) -> None:
else TypeVarVariance.double_variant
)

if kw.arg == "name" and isinstance(kw.value, nodes.Const):
name_arg = kw.value.value

if name_arg is None and args and isinstance(args[0], nodes.Const):
name_arg = args[0].value

if variance == TypeVarVariance.double_variant:
self.add_message(
"typevar-double-variance",
Expand Down Expand Up @@ -624,3 +640,11 @@ def _check_typevar(self, name: str, node: nodes.AssignName) -> None:
args=(f'. "{name}" is invariant, use "{suggest_name}" instead'),
confidence=interfaces.INFERENCE,
)

if name_arg is not None and name_arg != name:
self.add_message(
"typevar-name-mismatch",
node=node,
args=(name_arg, name),
confidence=interfaces.INFERENCE,
)
47 changes: 47 additions & 0 deletions tests/functional/t/typevar_name_mismatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Test case for TypeVar name not matching assigned variable name."""
from typing import TypeVar

# Control examples
T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)
T_contra = TypeVar("T_contra", contravariant=True)

# Mismatching names
X = TypeVar("T") # [typevar-name-mismatch]
X_co = TypeVar("T_co", covariant=True) # [typevar-name-mismatch]
X_contra = TypeVar("T_contra", contravariant=True) # [typevar-name-mismatch]
X_co, X_contra = ( # [typevar-name-mismatch,typevar-name-mismatch]
TypeVar("T", covariant=True),
TypeVar("T", contravariant=True),
)

# The user may also violate typevar-double-variance
# or typevar-name-incorrect-variance
# on top of not matching the variable and TypeVar names.
# This rule does not suggest what the correct name is
# (that's already handled by the aforementioned rules),
# it just highlights that the names don't match.
X = TypeVar("T", contravariant=True) # [typevar-name-mismatch,typevar-name-incorrect-variance]
X = TypeVar( # [typevar-name-mismatch,typevar-name-incorrect-variance,typevar-double-variance]
"T",
covariant=True,
contravariant=True
)
X_co = TypeVar("T_co") # [typevar-name-mismatch,typevar-name-incorrect-variance]
X_contra = TypeVar( # [typevar-name-mismatch,typevar-name-incorrect-variance]
"T_contra",
covariant=True
)

# name can be specified as a keyword argument as well.
T = TypeVar(name="T")
T_co = TypeVar(name="T_co", covariant=True)
T_contra = TypeVar(name="T_contra", contravariant=True)
T_co = TypeVar(covariant=True, name="T_co")
T_contra = TypeVar(contravariant=True, name="T_contra")

X = TypeVar(name="T") # [typevar-name-mismatch]
X_co = TypeVar(name="T_co", covariant=True) # [typevar-name-mismatch]
X_contra = TypeVar(name="T_contra", contravariant=True) # [typevar-name-mismatch]
X_co = TypeVar(covariant=True, name="T_co") # [typevar-name-mismatch]
X_contra = TypeVar(contravariant=True, name="T_contra") # [typevar-name-mismatch]
19 changes: 19 additions & 0 deletions tests/functional/t/typevar_name_mismatch.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
typevar-name-mismatch:10:0:10:1::"TypeVar name ""T"" does not match assigned variable name ""X""":INFERENCE
typevar-name-mismatch:11:0:11:4::"TypeVar name ""T_co"" does not match assigned variable name ""X_co""":INFERENCE
typevar-name-mismatch:12:0:12:8::"TypeVar name ""T_contra"" does not match assigned variable name ""X_contra""":INFERENCE
typevar-name-mismatch:13:0:13:4::"TypeVar name ""T"" does not match assigned variable name ""X_co""":INFERENCE
typevar-name-mismatch:13:6:13:14::"TypeVar name ""T"" does not match assigned variable name ""X_contra""":INFERENCE
typevar-name-incorrect-variance:24:0:24:1::"Type variable name does not reflect variance. ""X"" is contravariant, use ""X_contra"" instead":INFERENCE
typevar-name-mismatch:24:0:24:1::"TypeVar name ""T"" does not match assigned variable name ""X""":INFERENCE
typevar-double-variance:25:0:25:1::TypeVar cannot be both covariant and contravariant:INFERENCE
typevar-name-incorrect-variance:25:0:25:1::Type variable name does not reflect variance:INFERENCE
typevar-name-mismatch:25:0:25:1::"TypeVar name ""T"" does not match assigned variable name ""X""":INFERENCE
typevar-name-incorrect-variance:30:0:30:4::"Type variable name does not reflect variance. ""X_co"" is invariant, use ""X"" instead":INFERENCE
typevar-name-mismatch:30:0:30:4::"TypeVar name ""T_co"" does not match assigned variable name ""X_co""":INFERENCE
typevar-name-incorrect-variance:31:0:31:8::"Type variable name does not reflect variance. ""X_contra"" is covariant, use ""X_co"" instead":INFERENCE
typevar-name-mismatch:31:0:31:8::"TypeVar name ""T_contra"" does not match assigned variable name ""X_contra""":INFERENCE
typevar-name-mismatch:43:0:43:1::"TypeVar name ""T"" does not match assigned variable name ""X""":INFERENCE
typevar-name-mismatch:44:0:44:4::"TypeVar name ""T_co"" does not match assigned variable name ""X_co""":INFERENCE
typevar-name-mismatch:45:0:45:8::"TypeVar name ""T_contra"" does not match assigned variable name ""X_contra""":INFERENCE
typevar-name-mismatch:46:0:46:4::"TypeVar name ""T_co"" does not match assigned variable name ""X_co""":INFERENCE
typevar-name-mismatch:47:0:47:8::"TypeVar name ""T_contra"" does not match assigned variable name ""X_contra""":INFERENCE

0 comments on commit 7a9234f

Please sign in to comment.