Repository: pylint-dev/pylint
Pull request #10170: Consistency between  ``is``/``is not`` and ``==``/``!=`` when comparing types for ``unidiomatic-typecheck``
Author: login: nedelcu-ioan
Labels: name: False Positive 🦟
description: A message is emitted but nothing is wrong with the code
name: Backported
Created: 2025-01-07T20:48:53Z | Merged: 2025-05-04T13:57:55Z

<!--
Thank you for submitting a PR to pylint!

To ease the process of reviewing your PR, do make sure to complete the following boxes.

- [ ] Document your change, if it is a non-trivial one.
  - A maintainer might label the issue ``skip-news`` if the change does not need to be in the changelog.
  - Otherwise, create a news fragment with ``towncrier create <IssueNumber>.<type>`` which will be
    included in the changelog. ``<type>`` can be one of the types defined in `./towncrier.toml`.
    If necessary you can write details or offer examples on how the new change is supposed to work.
  - Generating the doc is done with ``tox -e docs``
- [ ] Relate your change to an issue in the tracker if such an issue exists (Refs #1234, Closes #1234)
- [ ] Write comprehensive commit messages and/or a good description of what the PR does.
- [ ] Keep the change small, separate the consensual changes from the opinionated one.
  Don't hesitate to open multiple PRs if the change requires it. If your review is so
  big it requires to actually plan and allocate time to review, it's more likely
  that it's going to go stale.
- [ ] If you used multiple emails or multiple names when contributing, add your mails
      and preferred name in ``script/.contributors_aliases.json``
-->

## Type of Changes

<!-- Leave the corresponding lines for the applicable type of change: -->

|     | Type                   |
| --- | ---------------------- |
| ✓   | :bug: Bug fix          |

## Description

<!-- If this PR fixes an issue, use the following to automatically close when we merge: -->

Closes #10161


Linked issue #1234: incorrect analysing of etree
```
from lxml import etree

print(etree.tostring)
```
pylint says that
`Module 'lxml.etree' has no 'tostring' member (no-member)`

but this is wrong.

### pylint --version output
```
No config file found, using default configuration
pylint 1.6.4,
astroid 1.4.8
Python 3.5.2 (default, Oct 11 2016, 04:59:56)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)]
```


Linked issue comments: user: login: PCManticore
created_at: 2016-12-21T19:15:29Z
body: Try with ``pylint --extension-pkg-whitelist=lxml``. This library is a C library, pylint can't know out of the box what members does it have.
author_association: CONTRIBUTOR

Diff:
diff --git a/doc/whatsnew/fragments/10161.false_positive b/doc/whatsnew/fragments/10161.false_positive
new file mode 100644
index 0000000000..dc343e05ae
--- /dev/null
+++ b/doc/whatsnew/fragments/10161.false_positive
@@ -0,0 +1,3 @@
+Comparisons between two calls to `type()` won't raise an ``unidiomatic-typecheck`` warning anymore, consistent with the behavior applied only for ``==`` previously.
+
+Closes #10161
diff --git a/pylint/checkers/base/comparison_checker.py b/pylint/checkers/base/comparison_checker.py
index 353f74383e..a2c5b10f11 100644
--- a/pylint/checkers/base/comparison_checker.py
+++ b/pylint/checkers/base/comparison_checker.py
@@ -323,18 +323,13 @@ def _check_unidiomatic_typecheck(self, node: nodes.Compare) -> None:
         if operator in TYPECHECK_COMPARISON_OPERATORS:
             left = node.left
             if _is_one_arg_pos_call(left):
-                self._check_type_x_is_y(node, left, operator, right)
+                self._check_type_x_is_y(node=node, left=left, right=right)
             elif isinstance(left, nodes.Name) and _is_one_arg_pos_call(right):
-                self._check_type_x_is_y(
-                    node=node, left=right, operator=operator, right=left
-                )  # transforming Y == type(x) case to type(x) == Y
+                # transforming Y == type(x) case to type(x) == Y
+                self._check_type_x_is_y(node=node, left=right, right=left)
 
     def _check_type_x_is_y(
-        self,
-        node: nodes.Compare,
-        left: nodes.NodeNG,
-        operator: str,
-        right: nodes.NodeNG,
+        self, node: nodes.Compare, left: nodes.NodeNG, right: nodes.NodeNG
     ) -> None:
         """Check for expressions like type(x) == Y."""
         left_func = utils.safe_infer(left.func)
@@ -343,7 +338,7 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
diff --git a/tests/functional/u/unidiomatic_typecheck.py b/tests/functional/u/unidiomatic_typecheck.py
index 1a87280cae..784668b8a6 100644
--- a/tests/functional/u/unidiomatic_typecheck.py
+++ b/tests/functional/u/unidiomatic_typecheck.py
@@ -61,14 +61,28 @@ def parameter_shadowing_inference_negatives(type):
     type(42) in [int]
     type(42) not in [int]
 
-def deliberate_subclass_check_negatives(b):
+
+def deliberate_subclass_check_negatives(a, b):
     type(42) is type(b)
     type(42) is not type(b)
+    type(42) == type(b)
+    type(42) != type(b)
+    type(a) is type(b)
+    type(a) is not type(b)
+    type(a) == type(b)
+    type(a) != type(b)
+
 
 def type_of_literals_positives(a):
-    type(a) is type([]) # [unidiomatic-typecheck]
-    type(a) is not type([]) # [unidiomatic-typecheck]
-    type(a) is type({}) # [unidiomatic-typecheck]
-    type(a) is not type({}) # [unidiomatic-typecheck]
-    type(a) is type("") # [unidiomatic-typecheck]
-    type(a) is not type("") # [unidiomatic-typecheck]
+    type(a) is type([])  # [unidiomatic-typecheck]
+    type(a) is not type([])  # [unidiomatic-typecheck]
+    type(a) is type({})  # [unidiomatic-typecheck]
+    type(a) is not type({})  # [unidiomatic-typecheck]
+    type(a) is type("")  # [unidiomatic-typecheck]
+    type(a) is not type("")  # [unidiomatic-typecheck]
+    type(a) == type([])  # [unidiomatic-typecheck]
+    type(a) != type([])  # [unidiomatic-typecheck]
+    type(a) == type({})  # [unidiomatic-typecheck]
+    type(a) != type({})  # [unidiomatic-typecheck]
+    type(a) == type("")  # [unidiomatic-typecheck]
+    type(a) != type("")  # [unidiomatic-typecheck]
diff --git a/tests/functional/u/unidiomatic_typecheck.txt b/tests/functional/u/unidiomatic_typecheck.txt
index 5916407bd9..76369310a6 100644
--- a/tests/functional/u/unidiomatic_typecheck.txt
+++ b/tests/functional/u/unidiomatic_typecheck.txt
@@ -10,9 +10,15 @@ unidiomatic-typecheck:16:4:16:20:simple_inference_positives:Use isinstance() rat
 unidiomatic-typecheck:17:4:17:24:simple_inference_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
 unidiomatic-typecheck:18:4:18:20:simple_inference_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
 unidiomatic-typecheck:19:4:19:20:simple_inference_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
-unidiomatic-typecheck:69:4:69:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
-unidiomatic-typecheck:70:4:70:27:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
-unidiomatic-typecheck:71:4:71:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
-unidiomatic-typecheck:72:4:72:27:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
-unidiomatic-typecheck:73:4:73:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
-unidiomatic-typecheck:74:4:74:27:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:77:4:77:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:78:4:78:27:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:79:4:79:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:80:4:80:27:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:81:4:81:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:82:4:82:27:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:83:4:83:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:84:4:84:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:85:4:85:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:86:4:86:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:87:4:87:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED
+unidiomatic-typecheck:88:4:88:23:type_of_literals_positives:Use isinstance() rather than type() for a typecheck.:UNDEFINED


Reviews:
user: login: DanielNoord
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-01-09T13:08:28Z
user: login: jacobtylerwalls
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-02T14:01:06Z
user: login: zenlyj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-05T07:43:52Z
user: login: zenlyj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-05T07:44:00Z
user: login: shivanshbohra-tcpl
state: APPROVED
author_association: NONE
submitted_at: 2025-02-13T18:13:08Z
user: login: Pierre-Sassoulas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T08:55:34Z
user: login: Pierre-Sassoulas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T09:17:21Z
user: login: zenlyj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T08:05:48Z
user: login: zenlyj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T08:07:17Z
user: login: Pierre-Sassoulas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T08:50:37Z
user: login: jacobtylerwalls
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-03T09:20:23Z
user: login: Pierre-Sassoulas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T20:14:43Z
user: login: Pierre-Sassoulas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T20:15:44Z
user: login: Pierre-Sassoulas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T20:16:33Z
user: login: DanielNoord
body: LGTM!
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-05-04T10:30:40Z
user: login: jacobtylerwalls
body: Thanks, all!
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-05-04T10:42:36Z
user: login: jacobtylerwalls
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T10:58:51Z
user: login: zenlyj
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T13:49:44Z

Review comments:
diff_hunk: @@ -61,10 +61,22 @@ def deliberate_subclass_check_negatives(b):
     type(42) is type(b)
     type(42) is not type(b)
 
-def type_of_literals_positives(a):
-    type(a) is type([]) # [unidiomatic-typecheck]
-    type(a) is not type([]) # [unidiomatic-typecheck]
-    type(a) is type({}) # [unidiomatic-typecheck]
-    type(a) is not type({}) # [unidiomatic-typecheck]
-    type(a) is type("") # [unidiomatic-typecheck]
-    type(a) is not type("") # [unidiomatic-typecheck]
+def type_of_literals_negatives(a):
+    type(a) is type([])
+    type(a) is not type([])
+    type(a) is type({})
+    type(a) is not type({})
+    type(a) is type("")
+    type(a) is not type("")
+    type(a) == type([])
path: tests/functional/u/unidiomatic_typecheck.py
user: login: DanielNoord
body: Doesn't the issue say that we don't want to suggest this but only exclude the `is` and `is not` comparisons?
created_at: 2025-01-09T13:08:24Z
author_association: COLLABORATOR
diff_hunk: @@ -61,10 +61,22 @@ def deliberate_subclass_check_negatives(b):
     type(42) is type(b)
     type(42) is not type(b)
 
-def type_of_literals_positives(a):
-    type(a) is type([]) # [unidiomatic-typecheck]
-    type(a) is not type([]) # [unidiomatic-typecheck]
-    type(a) is type({}) # [unidiomatic-typecheck]
-    type(a) is not type({}) # [unidiomatic-typecheck]
-    type(a) is type("") # [unidiomatic-typecheck]
-    type(a) is not type("") # [unidiomatic-typecheck]
+def type_of_literals_negatives(a):
+    type(a) is type([])
+    type(a) is not type([])
+    type(a) is type({})
+    type(a) is not type({})
+    type(a) is type("")
+    type(a) is not type("")
+    type(a) == type([])
path: tests/functional/u/unidiomatic_typecheck.py
user: login: jacobtylerwalls
body: I think @zenlyj wanted to treat `==` similarly to `is`.
created_at: 2025-02-02T13:57:48Z
author_association: MEMBER
diff_hunk: @@ -339,15 +339,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
                 and right_func.qname() == TYPE_QNAME
             ):
                 # type(x) == type(a)
-                right_arg = utils.safe_infer(right.args[0])
-                if not isinstance(right_arg, LITERAL_NODE_TYPES):
-                    # not e.g. type(x) == type([])
-                    return
+                return
path: pylint/checkers/base/comparison_checker.py
user: login: jacobtylerwalls
body: I'm not sure I would change this.
created_at: 2025-02-02T13:58:37Z
author_association: MEMBER
diff_hunk: @@ -61,10 +61,22 @@ def deliberate_subclass_check_negatives(b):
     type(42) is type(b)
     type(42) is not type(b)
 
-def type_of_literals_positives(a):
-    type(a) is type([]) # [unidiomatic-typecheck]
-    type(a) is not type([]) # [unidiomatic-typecheck]
-    type(a) is type({}) # [unidiomatic-typecheck]
-    type(a) is not type({}) # [unidiomatic-typecheck]
-    type(a) is type("") # [unidiomatic-typecheck]
-    type(a) is not type("") # [unidiomatic-typecheck]
+def type_of_literals_negatives(a):
+    type(a) is type([])
+    type(a) is not type([])
+    type(a) is type({})
+    type(a) is not type({})
+    type(a) is type("")
+    type(a) is not type("")
+    type(a) == type([])
+    type(a) != type([])
+    type(a) == type({})
+    type(a) != type({})
+    type(a) == type("")
+    type(a) != type("")
+
+def double_type_check_negatives(a, b):
+    type(a) == type(b)
+    type(a) != type(b)
+    type(a) is type(b)
+    type(a) is not type(b)
path: tests/functional/u/unidiomatic_typecheck.py
user: login: zenlyj
body: instead of adding a new set of tests here, perhaps we can extend `deliberate_subclass_check_negatives()`? by adding the `==` / `!=` test cases in that block
created_at: 2025-02-05T07:43:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -339,15 +339,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
                 and right_func.qname() == TYPE_QNAME
             ):
                 # type(x) == type(a)
-                right_arg = utils.safe_infer(right.args[0])
-                if not isinstance(right_arg, LITERAL_NODE_TYPES):
-                    # not e.g. type(x) == type([])
-                    return
+                return
path: pylint/checkers/base/comparison_checker.py
user: login: zenlyj
body: agree with jacob, shall we keep this guard block that checks for literal node types?
created_at: 2025-02-05T07:44:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -61,10 +61,22 @@ def deliberate_subclass_check_negatives(b):
     type(42) is type(b)
     type(42) is not type(b)
 
-def type_of_literals_positives(a):
-    type(a) is type([]) # [unidiomatic-typecheck]
-    type(a) is not type([]) # [unidiomatic-typecheck]
-    type(a) is type({}) # [unidiomatic-typecheck]
-    type(a) is not type({}) # [unidiomatic-typecheck]
-    type(a) is type("") # [unidiomatic-typecheck]
-    type(a) is not type("") # [unidiomatic-typecheck]
+def type_of_literals_negatives(a):
+    type(a) is type([])
+    type(a) is not type([])
+    type(a) is type({})
+    type(a) is not type({})
+    type(a) is type("")
+    type(a) is not type("")
+    type(a) == type([])
+    type(a) != type([])
+    type(a) == type({})
+    type(a) != type({})
+    type(a) == type("")
+    type(a) != type("")
+
+def double_type_check_negatives(a, b):
+    type(a) == type(b)
+    type(a) != type(b)
+    type(a) is type(b)
+    type(a) is not type(b)
path: tests/functional/u/unidiomatic_typecheck.py
user: login: Pierre-Sassoulas
body: Done in [0a09606](https://github.com/pylint-dev/pylint/pull/10170/commits/0a0960630ac9ee1fdef52a3f46169bd816f856dc)
created_at: 2025-05-02T08:55:34Z
author_association: MEMBER
diff_hunk: @@ -339,15 +339,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
                 and right_func.qname() == TYPE_QNAME
             ):
                 # type(x) == type(a)
-                right_arg = utils.safe_infer(right.args[0])
-                if not isinstance(right_arg, LITERAL_NODE_TYPES):
-                    # not e.g. type(x) == type([])
-                    return
+                return
path: pylint/checkers/base/comparison_checker.py
user: login: Pierre-Sassoulas
body: At this point we already know that the left arg is type(something), once we know the right argument is starting by ``type()`` we don't have to check what's inside anymore ? Or we want to remove the whole "type_of_literals_negatives" test block ?
created_at: 2025-05-02T09:17:20Z
author_association: MEMBER
diff_hunk: @@ -339,15 +339,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
                 and right_func.qname() == TYPE_QNAME
             ):
                 # type(x) == type(a)
-                right_arg = utils.safe_infer(right.args[0])
-                if not isinstance(right_arg, LITERAL_NODE_TYPES):
-                    # not e.g. type(x) == type([])
-                    return
+                return
path: pylint/checkers/base/comparison_checker.py
user: login: zenlyj
body: i think it depends on whether we want to retain current behavior of raising a message for `type(a) is type(LITERAL)`, where LITERAL: `[] | {} | "" | ...`. These cases are covered by the `type_of_literals_positives` test block, introduced from #299.

Couldn't dig into the related PR as it was merged >10 years ago in a different repository, but the separation of `deliberate_subclass_check_negatives` and `type_of_literals_positives` test blocks seems to indicate that the author wanted to raise a message because `type(a) is type([])` is equivalent to `type(a) is list`, which should be refactored to `isinstance(a, list)`?
created_at: 2025-05-03T08:05:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -343,15 +343,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
path: pylint/checkers/base/comparison_checker.py
user: login: zenlyj
body: ```suggestion
        if _is_one_arg_pos_call(right):
```

the operator check may not be necessary here as it is already performed upstream at `_check_unidiomatic_typecheck`?
created_at: 2025-05-03T08:06:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -343,15 +343,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
path: pylint/checkers/base/comparison_checker.py
user: login: Pierre-Sassoulas
body: You're right, nice catch !
created_at: 2025-05-03T08:50:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,3 @@
+Comparisons of types won't raise an ``unidiomatic-typecheck`` warning anymore, consistent with the behavior applied only for ``==`` previously.
path: doc/whatsnew/fragments/10161.false_positive
user: login: jacobtylerwalls
body: Let's be careful to say we aren't changing the behavior of `type(x) == list`

```suggestion
Comparisons between two calls to `type()` won't raise an ``unidiomatic-typecheck`` warning anymore, consistent with the behavior applied only for ``==`` previously.
```

created_at: 2025-05-03T09:09:16Z
author_association: MEMBER
diff_hunk: @@ -339,15 +339,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
                 and right_func.qname() == TYPE_QNAME
             ):
                 # type(x) == type(a)
-                right_arg = utils.safe_infer(right.args[0])
-                if not isinstance(right_arg, LITERAL_NODE_TYPES):
-                    # not e.g. type(x) == type([])
-                    return
+                return
path: pylint/checkers/base/comparison_checker.py
user: login: jacobtylerwalls
body: I re-read the issue. Here's my context:

- OP wanted no message for `type(x) == type(y)` on the theory that users know what they're doing (me: I'd probably wontfix that -- but I won't stand in the way right now)
- then @zenlyj noticed we have a discrepancy between `==` and `is` we should fix. (me: I'm mostly interested in fixing that alone)
- then in PR review we face the question of whether to remove the cases that check `type(x) == type([])`. This seems to pull even further in the wrong direction, linter should catch this.

Let's just fix the is/is not thing and keep these test cases and the open follow-up issues for:
- suggesting `type(x) is type(y)` to be rewritten as `isinstance(x, type(y))`, going against OP
- [extending](https://github.com/pylint-dev/pylint/issues/10161#issuecomment-2573157330) `comparison-with-callable` to cover `== list`
created_at: 2025-05-03T09:18:37Z
author_association: MEMBER
diff_hunk: @@ -343,15 +343,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
path: pylint/checkers/base/comparison_checker.py
user: login: jacobtylerwalls
body: I think this is the only change that should be in this PR.
created_at: 2025-05-03T09:20:05Z
author_association: MEMBER
diff_hunk: @@ -343,15 +343,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
path: pylint/checkers/base/comparison_checker.py
user: login: Pierre-Sassoulas
body: https://github.com/pylint-dev/pylint/pull/10170/commits/986d1b40881d51769fa8a2c82d92e85ad361f563 is a simplification that follows from it (we're not using the operator parameter anymore)
created_at: 2025-05-03T20:14:43Z
author_association: MEMBER
diff_hunk: @@ -61,10 +61,22 @@ def deliberate_subclass_check_negatives(b):
     type(42) is type(b)
     type(42) is not type(b)
 
-def type_of_literals_positives(a):
-    type(a) is type([]) # [unidiomatic-typecheck]
-    type(a) is not type([]) # [unidiomatic-typecheck]
-    type(a) is type({}) # [unidiomatic-typecheck]
-    type(a) is not type({}) # [unidiomatic-typecheck]
-    type(a) is type("") # [unidiomatic-typecheck]
-    type(a) is not type("") # [unidiomatic-typecheck]
+def type_of_literals_negatives(a):
+    type(a) is type([])
+    type(a) is not type([])
+    type(a) is type({})
+    type(a) is not type({})
+    type(a) is type("")
+    type(a) is not type("")
+    type(a) == type([])
path: tests/functional/u/unidiomatic_typecheck.py
user: login: Pierre-Sassoulas
body: Let's discuss in https://github.com/pylint-dev/pylint/pull/10170#discussion_r1938498563 if required.
created_at: 2025-05-03T20:15:44Z
author_association: MEMBER
diff_hunk: @@ -354,4 +349,5 @@ def _check_type_x_is_y(
                 if not isinstance(right_arg, LITERAL_NODE_TYPES):
                     # not e.g. type(x) == type([])
                     return
+
path: pylint/checkers/base/comparison_checker.py
user: login: Pierre-Sassoulas
body: ```suggestion
```
created_at: 2025-05-03T20:16:33Z
author_association: MEMBER
diff_hunk: @@ -339,15 +339,12 @@ def _check_type_x_is_y(
         ):
             return
 
-        if operator in {"is", "is not"} and _is_one_arg_pos_call(right):
+        if operator in {"!=", "==", "is", "is not"} and _is_one_arg_pos_call(right):
             right_func = utils.safe_infer(right.func)
             if (
                 isinstance(right_func, nodes.ClassDef)
                 and right_func.qname() == TYPE_QNAME
             ):
                 # type(x) == type(a)
-                right_arg = utils.safe_infer(right.args[0])
-                if not isinstance(right_arg, LITERAL_NODE_TYPES):
-                    # not e.g. type(x) == type([])
-                    return
+                return
path: pylint/checkers/base/comparison_checker.py
user: login: jacobtylerwalls
body: Opened #10364 and #10365
created_at: 2025-05-04T10:58:51Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-01-07T20:57:02Z
body: ## [Codecov](https://app.codecov.io/gh/pylint-dev/pylint/pull/10170?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.83%. Comparing base [(`6456374`)](https://app.codecov.io/gh/pylint-dev/pylint/commit/6456374d239cca989c6601ea894bd0222036d111?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev) to head [(`226c110`)](https://app.codecov.io/gh/pylint-dev/pylint/commit/226c11075d94463085061ca165d8d6be70cca21b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev).
> Report is 10 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/pylint-dev/pylint/pull/10170/graphs/tree.svg?width=650&height=150&src=pr&token=ZETEzayrfk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev)](https://app.codecov.io/gh/pylint-dev/pylint/pull/10170?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev)

```diff
@@            Coverage Diff             @@
##             main   #10170      +/-   ##
==========================================
- Coverage   95.83%   95.83%   -0.01%     
==========================================
  Files         174      174              
  Lines       18995    18993       -2     
==========================================
- Hits        18204    18202       -2     
  Misses        791      791              
```

| [Files with missing lines](https://app.codecov.io/gh/pylint-dev/pylint/pull/10170?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev) | Coverage Δ | |
|---|---|---|
| [pylint/checkers/base/comparison\_checker.py](https://app.codecov.io/gh/pylint-dev/pylint/pull/10170?src=pr&el=tree&filepath=pylint%2Fcheckers%2Fbase%2Fcomparison_checker.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pylint-dev#diff-cHlsaW50L2NoZWNrZXJzL2Jhc2UvY29tcGFyaXNvbl9jaGVja2VyLnB5) | `100.00% <100.00%> (ø)` | |

</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-01-07T21:12:47Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/arrays/interval.py#L337
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/arrays/interval.py#L992
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/arrays/base.py#L1510
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/arrays/masked.py#L1112
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/315b54908bd0dc8a7bc913a38fb22f03377eb6eb/pandas/core/indexes/base.py#L5538

</details>

*This comment was generated for commit 226c11075d94463085061ca165d8d6be70cca21b*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-02-16T20:23:24Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/arrays/masked.py#L1112
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/arrays/interval.py#L337
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/arrays/interval.py#L992
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/arrays/base.py#L1510
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/6bcd30397d67c3887288c7a82c2c235ce8bc3c7f/pandas/core/indexes/base.py#L5538

</details>

*This comment was generated for commit 323fb824ceaeef58f2331d461b2d1ddc4c24cd07*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-02T09:17:28Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/masked.py#L1108
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L337
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L992
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/base.py#L1510
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/indexes/base.py#L5545

</details>

*This comment was generated for commit 0a0960630ac9ee1fdef52a3f46169bd816f856dc*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-02T09:40:32Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/masked.py#L1108
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L337
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L992
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/base.py#L1510
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/indexes/base.py#L5545

</details>

*This comment was generated for commit 4380fbe4a8ca604a6eeeb81c0f35e21d6fadb19a*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-03T09:13:25Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/masked.py#L1108
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L337
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L992
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/base.py#L1510
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/indexes/base.py#L5545

</details>

*This comment was generated for commit 59a0a5d726ab5843952b0b79969788a30defb6c5*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-03T20:40:33Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/masked.py#L1108
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L337
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L992
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/base.py#L1510
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/indexes/base.py#L5545

</details>

*This comment was generated for commit 59f975c58158a804121fd6cee1ed3566e0dabbef*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-04T13:11:24Z
body: 🤖 **Effect of this PR on checked open source code:** 🤖



**Effect on [pandas](https://github.com/pandas-dev/pandas):**
The following messages are no longer emitted:

<details>

1) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/masked.py#L1108
2) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L337
3) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/interval.py#L992
4) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/base.py#L1510
5) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L43
6) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/arrays/arrow/extension_types.py#L97
7) unidiomatic-typecheck:
*Use isinstance() rather than type() for a typecheck.*
https://github.com/pandas-dev/pandas/blob/e55d90783bac30b75e7288380b15a62ab6e43f78/pandas/core/indexes/base.py#L5545

</details>

*This comment was generated for commit 74c2fa7a88192937cc3119092993303ef8f3ef64*
author_association: CONTRIBUTOR
user: login: zenlyj
created_at: 2025-05-04T13:47:54Z
body: LGTM 👌, thanks!
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-04T13:58:07Z
body: The backport to `maintenance/3.3.x` failed:
```
The process '/usr/bin/git' failed with exit code 1
```
To backport manually, run these commands in your terminal:
```bash
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-maintenance/3.3.x maintenance/3.3.x
# Navigate to the new working tree
cd .worktrees/backport-maintenance/3.3.x
# Create a new branch
git switch --create backport-10170-to-maintenance/3.3.x
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 d396616db59977540ab32496e324c7aaca352ab2
# Push it to GitHub
git push --set-upstream origin backport-10170-to-maintenance/3.3.x
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-maintenance/3.3.x
```
Then, create a pull request where the `base` branch is `maintenance/3.3.x` and the `compare`/`head` branch is `backport-10170-to-maintenance/3.3.x`.
author_association: CONTRIBUTOR