Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix invalid type false positive #8206

3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/8205.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix false positive for isinstance-second-argument-not-valid-type with union types.

Closes #8205
9 changes: 9 additions & 0 deletions pylint/checkers/typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
supports_membership_test,
supports_setitem,
)
from pylint.constants import PY310_PLUS
from pylint.interfaces import HIGH, INFERENCE
from pylint.typing import MessageDefinitionTuple

Expand Down Expand Up @@ -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.
Expand All @@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if PY310_PLUS and isinstance(inferred, bases.UnionType):
if isinstance(inferred, bases.UnionType):

Is the bases.UnionType the legacy Union/Optional type? if so it is supported from 3.7+ I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, Union[int, str] is an instance of ClassDef and not UnionType. I'm not sure what exactly UnionType corresponds to.

return _is_invalid_isinstance_type(
inferred.left
) or _is_invalid_isinstance_type(inferred.right)
return True


Expand Down
1 change: 1 addition & 0 deletions pylint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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"

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
27 changes: 27 additions & 0 deletions tests/functional/i/isinstance_second_argument_py310.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions tests/functional/i/isinstance_second_argument_py310.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.10
Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you could allow the test to run also for earlier versions like was done in redundant-typehint-argument:

Suggested change
[testoptions]
min_pyver=3.10
[testoptions]
min_pyver=3.7
[TYPING]
runtime-typing=no

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the runtime-typing option do? Also, is there a need to specify 3.7 as the min version, since that's already the min version being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, this check is different because it doesn't deal with type annotations. In this case the types involved will actually be called, so I think this option won't apply.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't have to explicitly write min_pyver=3.7 but it would be just clearer for the test reader to understand why runtime-typing was used. There is some documentation runtime-typing at pylint/extensions/typing.py. It will identify type annotations if it can for python versions 3.7-3.9 with the | operation.

Now that I think about it, this check is different because it doesn't deal with type annotations. In this case the types involved will actually be called, so I think this option won't apply.

I think you are right. These are not annotations it the actual code. so if someone uses the | operation they can only do that in py3.10+.

3 changes: 3 additions & 0 deletions tests/functional/i/isinstance_second_argument_py310.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
isinstance-second-argument-not-valid-type:15:0:15:22::Second argument of isinstance is not a type:UNDEFINED
isinstance-second-argument-not-valid-type:16:0:16:28::Second argument of isinstance is not a type:UNDEFINED
isinstance-second-argument-not-valid-type:18:0:18:24::Second argument of isinstance is not a type:UNDEFINED