-
-
Notifications
You must be signed in to change notification settings - Fork 277
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
Bug pylint 4206 #921
Bug pylint 4206 #921
Conversation
… a class may use the __class_getitem__ method of the current class instead of looking for __getitem__ in the metaclass.
…ded and thus have no metaclass but starting from python3.9 it supports subscripting thanks to the __class_getitem__ method.
…he class scope. The brain_typing module does not add a ABCMeta_typing class thus there is no need to test it. Moreover it doesn't add neither a __getitem__ to the metaclass
…'t have a metaclass.
… type 're.Pattern' is not an acceptable base type
…rived => Iterator => Iterable => object
… support subscripting thanks to __class_getitem__ method. However the wat it is implemented is not straigthforward and instead of complexifying the way __class_getitem__ is handled inside the getitem method of the ClassDef class, we prefer to hack a bit.
…Before python3.9 the objects of the typing module that are alias of the same objects in the collections.abc module have subscripting possibility thanks to the _GenericAlias metaclass. To mock the subscripting capability we add __class_getitem__ method on those objects.
…e which is a pure C lib. While letting those class mocks inside collections module is fair for astroid it leds to pylint acceptance tests fail.
@cdce8p if you could have a look to this PR it would be cool! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some surface comment, because to be fair I'm not understanding a lot of what is happening here 😄 At least I learned about MutableSet 😄 ! Fixing the typing acceptance fail is nice and I don't think we can avoid making special case for python 3.9. It would be the right time to release astroid 2.5.2 after we merge this, right ?
@hippo91 I'll definitely take a look at it, hopefully later today. Thanks for taking the time to work on it! |
I had some time to take a closer look and do some tests. The good news first, I really like the use of However, it doesn't solve the problem completely. Since you add # a.py
import typing
from collections.abc import Iterable
var: Iterable[int] # python38
pylint a.py
************* Module a
a.py:1:0: W0611: Unused import typing (unused-import)
# python38, without the typing import
pylint a.py
************* Module a
a.py:3:5: E1136: Value 'Iterable' is unsubscriptable (unsubscriptable-object) Regarding a solution
-- |
@cdce8p thanks a lot for taking time to review this.
is a bit weird. I don't see any reason to use a container of the collections module if the typing module is already imported. |
@hippo91 PEP 585 deprecated the use of the the generic aliases from the I've been testing the approach (1) a bit today and it looks promising. I hope to be able to upload it later today or tomorrow. |
@cdce8p you are right concerning PEP 585. But the deprecation should start with python3.9. Isn't it? If yes can we consider that mixing typing and collections with python3.8 and lower is bad practice? |
…ltin types is made directly inside the getitem method
…between typing module and others (collections or builtin)
@Pierre-Sassoulas @cdce8p i think it is almost good now!
@cdce8p in pylint-dev/pylint#4239 i needed to change in With this PR all pylint's acceptance tests are fines (at least on my machine :-) ). Moreover it seems that the problem with mixing For example linting:
gives, with
and with
I think it is correct. |
@hippo91 Unfortunately the latest change doesn't improve much on my part. I'll need more time to figure out what exactly the issue are, but most tests aren't passing. Maybe fetch the latest changes? As for the file you posted, for D = collections.abc.List[int] # this should be `unsubscriptable-object`, not `no-member` and for D = collections.abc.List[int] # no issue, just like `collections.abc.Iterable` |
@cdce8p it seems that there is not
Thus i think that |
True, my mistake. It's a builtin |
@hippo91 I think I've found something now. The strange part was that when I ran the tests with pytest tests/test_functional.py -k generic_alias I saw a whole lot of errors that miraculously disappeared when I only ran a single file. -- Try this example (Python 3.7 / 3.8) import typing
A = list[int] # [unsubscriptable-object]
B = typing.List[int] The result is test2.py:3:4: E1136: Value 'list' is unsubscriptable (unsubscriptable-object) If you now switch lines 3 and 4, the error will disappear. -- I'd guess the root cause here might be caching on -- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the use of inference_tip
! It seems this was the missing piece.
Nevertheless, I think intermediate
classes solve a lot of the remaining issues ;)
@cdce8p i took into account your remarks. Thanks! def supports_getitem(
value: astroid.node_classes.NodeNG, node: astroid.node_classes.NodeNG
) -> bool:
def is_typing_imported(node: astroid.node_classes.NodeNG):
"""
Returns True if the typing module is imported
"""
rt = node.root()
import_nodes = rt.nodes_of_class((astroid.Import,))
for i_node in import_nodes:
if 'typing' in i_node.names[0]:
return True
return False
def is_name_imported_from_collections(node: astroid.node_classes.NodeNG):
"""
Returns True if the Name node is imported from collections module
"""
cls_name = node.value
if isinstance(cls_name, astroid.Name):
lookup_res = cls_name.lookup(cls_name.name)
if lookup_res:
for l_res in lookup_res[1]:
if l_res.modname.startswith("collections"):
return True
return False
def is_attribute_from_collections(node: astroid.node_classes.NodeNG):
"""
Returns True if the Attribute node is linked to collections module
"""
cls_name = node.value
if isinstance(cls_name, astroid.Attribute):
for lmod in cls_name.expr.inferred():
if lmod.name.startswith('collections'):
return True
return False
if is_typing_imported(node) and PY39_LESS and isinstance(node, astroid.Subscript):
if is_name_imported_from_collections(node) or is_attribute_from_collections(node):
return False
if isinstance(value, astroid.ClassDef):
if _supports_protocol_method(value, CLASS_GETITEM_METHOD):
return True
if is_class_subscriptable_pep585_with_postponed_evaluation_enabled(value, node):
return True
return _supports_protocol(value, _supports_getitem_protocol) And it seems to give good results even if it should probably be improved. |
@hippo91 Unfortunately your suggestion doesn't work in As for the intermediate classes. The implementation is much simpler in It might seem like these intermediate classes are a hack, but honestly it's it more similar to what Lastly, it would also enable the possibility to do deprecation detection for the |
@cdce8p you convinced me! You are right concerning the fact that your implementation is not so complex and it mimics the cpython trick with |
@hippo91 That sound like a plan! I'm ready to get this finally done 🚀 |
Steps
Description
This PR:
__class_getitem__
method. If a class implements this method then the class is subscriptable without the need that its metaclass implements__getitem__
method. In this PR only FunctionDef nodes are taken into account. Maybe it should be enlarge to other node types such as AssignName but it should be made in another PRcollections
objects implementing__class_getitem__
to be detected as subscriptable. This is done in thecollections
brain because the implementation is a bit tricky and without the hack in the brain it would be necessary to improve the way__class_getitem__
is inferred (see point 1).typing
types that are alias ofcollections
object to be detected as subscriptable. This is done without modifying inheritance tree and without hacking the metaclass. The way chosen here is to add__class_getitem__
method on the types that are detected as subscriptable. Original way of adding__getitem__
on the metaclass is not a good idea, because the same metaclass instance is shared between subscriptable and not subscriptable types.collections
brain hacks are registered so thatpylint
s acceptance tests are ok.Points 1, 2, and 3 are necessary to fix the bug specified in pylint-dev/pylint#4206 but it is not sufficient to make the acceptance test ok. That's why point 4 exists.
I would like to highlight the fact
deque
,defaultdict
andOrderedDict
are defined in the_collections
module which is a C library. In order message dealing with this module (in fact its representation in the brain) to be displayed correctly (with no crash) i have specified the file path as/tmp/unknown
. It is uggly but i dont know what to do otherwise. Moreover it wont impact many users, except those executingpylint
againsttyping
module (as thepylint
s acceptance test does).One last thing, for python versions lower thant 3.9, the case where both
typing
andcollections
modules are imported in the same module may lead to spurious false positive. This is because parsing thetyping
module while give subscript ability to some objects also defined in collection module that are not subscriptable. I don't think it matters because it is not wise, IMHO, to mix those modules. Instead the user should choose between object in collection that are not subscriptable or the same in typing that are subscriptable.Type of Changes
| ✓ | 🐛 Bug fix |
Related Issue
First step towards: pylint-dev/pylint#4206