summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Drozd <nicholasdrozd@gmail.com>2023-02-14 16:39:57 -0500
committerGitHub <noreply@github.com>2023-02-14 22:39:57 +0100
commite64f0437388298c7f8514a6755c3c27a0d9a35f2 (patch)
tree115bd308fcad5e5665b37b32455393c529fed738
parentb8f2b5498656b5fe5b94b6c9da90f76677478701 (diff)
downloadpylint-git-e64f0437388298c7f8514a6755c3c27a0d9a35f2.tar.gz
Fix invalid type false positive (#8206)
-rw-r--r--doc/whatsnew/fragments/8205.false_positive3
-rw-r--r--pylint/checkers/typecheck.py15
-rw-r--r--pylint/constants.py1
-rw-r--r--tests/functional/d/dataclass/dataclass_typecheck.txt2
-rw-r--r--tests/functional/ext/typing/redundant_typehint_argument.py2
-rw-r--r--tests/functional/ext/typing/redundant_typehint_argument_py310.py2
-rw-r--r--tests/functional/i/isinstance_second_argument.txt10
-rw-r--r--tests/functional/i/isinstance_second_argument_py310.py27
-rw-r--r--tests/functional/i/isinstance_second_argument_py310.rc2
-rw-r--r--tests/functional/i/isinstance_second_argument_py310.txt3
10 files changed, 58 insertions, 9 deletions
diff --git a/doc/whatsnew/fragments/8205.false_positive b/doc/whatsnew/fragments/8205.false_positive
new file mode 100644
index 000000000..6bf596592
--- /dev/null
+++ b/doc/whatsnew/fragments/8205.false_positive
@@ -0,0 +1,3 @@
+Fix false positive for isinstance-second-argument-not-valid-type with union types.
+
+Closes #8205
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index bfd415923..8448a0f13 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -48,6 +48,7 @@ from pylint.checkers.utils import (
supports_membership_test,
supports_setitem,
)
+from pylint.constants import PY310_PLUS
from pylint.interfaces import HIGH, INFERENCE
from pylint.typing import MessageDefinitionTuple
@@ -796,6 +797,10 @@ def _is_c_extension(module_node: InferenceResult) -> bool:
def _is_invalid_isinstance_type(arg: nodes.NodeNG) -> bool:
# Return True if we are sure that arg is not a type
+ if PY310_PLUS and isinstance(arg, nodes.BinOp) and arg.op == "|":
+ return _is_invalid_isinstance_type(arg.left) or _is_invalid_isinstance_type(
+ arg.right
+ )
inferred = utils.safe_infer(arg)
if not inferred:
# Cannot infer it so skip it.
@@ -806,6 +811,10 @@ def _is_invalid_isinstance_type(arg: nodes.NodeNG) -> bool:
return False
if isinstance(inferred, astroid.Instance) and inferred.qname() == BUILTIN_TUPLE:
return False
+ if PY310_PLUS and isinstance(inferred, bases.UnionType):
+ return _is_invalid_isinstance_type(
+ inferred.left
+ ) or _is_invalid_isinstance_type(inferred.right)
return True
@@ -1398,7 +1407,11 @@ accessed. Python regular expressions are accepted.",
second_arg = node.args[1]
if _is_invalid_isinstance_type(second_arg):
- self.add_message("isinstance-second-argument-not-valid-type", node=node)
+ self.add_message(
+ "isinstance-second-argument-not-valid-type",
+ node=node,
+ confidence=INFERENCE,
+ )
# pylint: disable = too-many-branches, too-many-locals, too-many-statements
def visit_call(self, node: nodes.Call) -> None:
diff --git a/pylint/constants.py b/pylint/constants.py
index 3bbda93d0..92a9990d1 100644
--- a/pylint/constants.py
+++ b/pylint/constants.py
@@ -18,6 +18,7 @@ from pylint.typing import MessageTypesFullName
PY38_PLUS = sys.version_info[:2] >= (3, 8)
PY39_PLUS = sys.version_info[:2] >= (3, 9)
+PY310_PLUS = sys.version_info[:2] >= (3, 10)
IS_PYPY = platform.python_implementation() == "PyPy"
diff --git a/tests/functional/d/dataclass/dataclass_typecheck.txt b/tests/functional/d/dataclass/dataclass_typecheck.txt
index 5afac5849..1873dc122 100644
--- a/tests/functional/d/dataclass/dataclass_typecheck.txt
+++ b/tests/functional/d/dataclass/dataclass_typecheck.txt
@@ -9,4 +9,4 @@ unsupported-delete-operation:72:4:72:13::'obj.attr1' does not support item delet
not-context-manager:97:0:98:8::Context manager 'str' doesn't implement __enter__ and __exit__.:UNDEFINED
invalid-metaclass:105:0:105:11:Test2:Invalid metaclass 'Instance of builtins.int' used:UNDEFINED
unhashable-member:111:0:111:2::'obj.attr5' is unhashable and can't be used as a key in a dict:INFERENCE
-isinstance-second-argument-not-valid-type:121:6:121:30::Second argument of isinstance is not a type:UNDEFINED
+isinstance-second-argument-not-valid-type:121:6:121:30::Second argument of isinstance is not a type:INFERENCE
diff --git a/tests/functional/ext/typing/redundant_typehint_argument.py b/tests/functional/ext/typing/redundant_typehint_argument.py
index e4814ed5f..fe2cc4a2e 100644
--- a/tests/functional/ext/typing/redundant_typehint_argument.py
+++ b/tests/functional/ext/typing/redundant_typehint_argument.py
@@ -1,4 +1,4 @@
-""""Checks for redundant Union typehints in assignments"""
+"""Checks for redundant Union typehints in assignments"""
# pylint: disable=deprecated-typing-alias,consider-alternative-union-syntax,consider-using-alias,invalid-name,unused-argument,missing-function-docstring
from __future__ import annotations
diff --git a/tests/functional/ext/typing/redundant_typehint_argument_py310.py b/tests/functional/ext/typing/redundant_typehint_argument_py310.py
index 599f88283..72a4cc836 100644
--- a/tests/functional/ext/typing/redundant_typehint_argument_py310.py
+++ b/tests/functional/ext/typing/redundant_typehint_argument_py310.py
@@ -1,4 +1,4 @@
-""""Checks for redundant Union typehints in assignments"""
+"""Checks for redundant Union typehints in assignments"""
# pylint: disable=deprecated-typing-alias,consider-alternative-union-syntax,consider-using-alias,invalid-name,unused-argument,missing-function-docstring
from __future__ import annotations
diff --git a/tests/functional/i/isinstance_second_argument.txt b/tests/functional/i/isinstance_second_argument.txt
index 20c13499e..22d8b196a 100644
--- a/tests/functional/i/isinstance_second_argument.txt
+++ b/tests/functional/i/isinstance_second_argument.txt
@@ -1,5 +1,5 @@
-isinstance-second-argument-not-valid-type:27:0:27:23::Second argument of isinstance is not a type:UNDEFINED
-isinstance-second-argument-not-valid-type:28:0:28:19::Second argument of isinstance is not a type:UNDEFINED
-isinstance-second-argument-not-valid-type:29:0:29:34::Second argument of isinstance is not a type:UNDEFINED
-isinstance-second-argument-not-valid-type:30:0:30:54::Second argument of isinstance is not a type:UNDEFINED
-isinstance-second-argument-not-valid-type:31:0:31:18::Second argument of isinstance is not a type:UNDEFINED
+isinstance-second-argument-not-valid-type:27:0:27:23::Second argument of isinstance is not a type:INFERENCE
+isinstance-second-argument-not-valid-type:28:0:28:19::Second argument of isinstance is not a type:INFERENCE
+isinstance-second-argument-not-valid-type:29:0:29:34::Second argument of isinstance is not a type:INFERENCE
+isinstance-second-argument-not-valid-type:30:0:30:54::Second argument of isinstance is not a type:INFERENCE
+isinstance-second-argument-not-valid-type:31:0:31:18::Second argument of isinstance is not a type:INFERENCE
diff --git a/tests/functional/i/isinstance_second_argument_py310.py b/tests/functional/i/isinstance_second_argument_py310.py
new file mode 100644
index 000000000..8a0c17af5
--- /dev/null
+++ b/tests/functional/i/isinstance_second_argument_py310.py
@@ -0,0 +1,27 @@
+'''Tests for invalid isinstance with compound types'''
+
+# True negatives
+isinstance(0, int | str)
+isinstance(0, int | int | int)
+isinstance(0, int | str | list | float)
+isinstance(0, (int | str) | (list | float))
+
+IntOrStr = int | str
+isinstance(0, IntOrStr)
+ListOrDict = list | dict
+isinstance(0, (float | ListOrDict) | IntOrStr)
+
+# True positives
+isinstance(0, int | 5) # [isinstance-second-argument-not-valid-type]
+isinstance(0, str | 5 | int) # [isinstance-second-argument-not-valid-type]
+INT = 5
+isinstance(0, INT | int) # [isinstance-second-argument-not-valid-type]
+
+
+# FALSE NEGATIVES
+
+# Parameterized generics will raise type errors at runtime.
+# Warnings should be raised, but aren't (yet).
+isinstance(0, list[int])
+ListOfInts = list[int]
+isinstance(0, ListOfInts)
diff --git a/tests/functional/i/isinstance_second_argument_py310.rc b/tests/functional/i/isinstance_second_argument_py310.rc
new file mode 100644
index 000000000..68a8c8ef1
--- /dev/null
+++ b/tests/functional/i/isinstance_second_argument_py310.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.10
diff --git a/tests/functional/i/isinstance_second_argument_py310.txt b/tests/functional/i/isinstance_second_argument_py310.txt
new file mode 100644
index 000000000..776bf3c2e
--- /dev/null
+++ b/tests/functional/i/isinstance_second_argument_py310.txt
@@ -0,0 +1,3 @@
+isinstance-second-argument-not-valid-type:15:0:15:22::Second argument of isinstance is not a type:INFERENCE
+isinstance-second-argument-not-valid-type:16:0:16:28::Second argument of isinstance is not a type:INFERENCE
+isinstance-second-argument-not-valid-type:18:0:18:24::Second argument of isinstance is not a type:INFERENCE