Skip to content

Commit

Permalink
WIP 3
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed Mar 18, 2021
1 parent 1ea872e commit 9817886
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 74 deletions.
42 changes: 42 additions & 0 deletions astroid/brain/brain_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import sys

import astroid
from astroid import nodes


PY39 = sys.version_info >= (3, 9)
Expand Down Expand Up @@ -83,3 +84,44 @@ def move_to_end(self, key, last=False): pass"""
astroid.register_module_extender(
astroid.MANAGER, "_collections", _collections_transform
)


def _looks_like_subscriptable(node: nodes.ClassDef) -> bool:
"""
Returns True if the node corresponds to a ClassDef of the Collections.abc module that
supports subscripting
:param node: ClassDef node
"""
if node.qname().startswith("_collections") or node.qname().startswith(
"collections"
):
try:
node.getattr("__class_getitem__")
return True
except astroid.AttributeInferenceError:
pass
return False


CLASS_GET_ITEM_TEMPLATE = """
@classmethod
def __class_getitem__(cls, item):
return cls
"""


def easy_class_getitem_inference(node, context=None):
# Here __class_getitem__ exists but is quite a mess to infer thus
# put instead an easy inference tip
func_to_add = astroid.extract_node(CLASS_GET_ITEM_TEMPLATE)
node.locals["__class_getitem__"] = [func_to_add]


if PY39:
# Starting with Python39 some objects of the collection module are subscriptable
# thanks to the __class_getitem__ method but the way it is implemented in
# _collection_abc makes it difficult to infer. (We would have to handle AssignName inference in the
# getitem method of the ClassDef class) Instead we put here a mock of the __class_getitem__ method
astroid.MANAGER.register_transform(
astroid.nodes.ClassDef, easy_class_getitem_inference, _looks_like_subscriptable
)
60 changes: 17 additions & 43 deletions astroid/brain/brain_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"""Astroid hooks for typing.py support."""
import sys
import typing
from functools import lru_cache

from astroid import (
MANAGER,
Expand Down Expand Up @@ -116,42 +115,11 @@ def infer_typedDict( # pylint: disable=invalid-name
node.root().locals["TypedDict"] = [class_def]


GET_ITEM_TEMPLATE = """
@classmethod
def __getitem__(cls, value):
return cls
"""

CLASS_GETITEM_TEMPLATE = """
@classmethod
def __class_getitem__(cls, item): return cls
"""

ABC_METACLASS_TEMPLATE = """
from abc import ABCMeta
ABCMeta
"""


@lru_cache()
def create_typing_metaclass():
#  Needs to mock the __getitem__ class method so that
#  MutableSet[T] is acceptable
func_to_add = extract_node(GET_ITEM_TEMPLATE)

abc_meta = next(extract_node(ABC_METACLASS_TEMPLATE).infer())
typing_meta = nodes.ClassDef(
name="ABCMeta_typing",
lineno=abc_meta.lineno,
col_offset=abc_meta.col_offset,
parent=abc_meta.parent,
)
typing_meta.postinit(
bases=[extract_node(ABC_METACLASS_TEMPLATE)], body=[], decorators=None
)
typing_meta.locals["__getitem__"] = [func_to_add]
return typing_meta


def _looks_like_typing_alias(node: nodes.Call) -> bool:
"""
Expand All @@ -166,7 +134,7 @@ def _looks_like_typing_alias(node: nodes.Call) -> bool:
isinstance(node, nodes.Call)
and isinstance(node.func, nodes.Name)
and node.func.name == "_alias"
and isinstance(node.args[0], nodes.Attribute)
and isinstance(node.args[0], (nodes.Attribute, nodes.Name))
)


Expand All @@ -193,14 +161,20 @@ def infer_typing_alias(
class_def = nodes.ClassDef(
name=assign_name.name,
lineno=assign_name.lineno,
# tolineno=assign_name.lineno,
col_offset=assign_name.col_offset,
parent=node.parent,
)
class_def.postinit(bases=[res], body=[], decorators=None)
if not PY39:
func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE)
class_def.locals["__class_getitem__"] = [func_to_add]
if res.root().name == "_collections":
if not PY39 or assign_name.name == "Type":
maybe_type_var = node.args[1]
if not (
isinstance(maybe_type_var, node_classes.Tuple)
and not maybe_type_var.elts
):
func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE)
class_def.locals["__class_getitem__"] = [func_to_add]
if res.root().file is None and res.root().name == "_collections":
res.root().file = "/tmp/unknown"
return class_def

Expand All @@ -212,9 +186,8 @@ def infer_typing_alias(
parent=node.parent,
)
class_def.postinit(bases=[], body=[], decorators=None)
if not PY39:
func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE)
class_def.locals["__class_getitem__"] = [func_to_add]
func_to_add = astroid.extract_node(CLASS_GETITEM_TEMPLATE)
class_def.locals["__class_getitem__"] = [func_to_add]
return class_def

return None
Expand All @@ -225,9 +198,10 @@ def infer_typing_alias(
inference_tip(infer_typing_typevar_or_newtype),
looks_like_typing_typevar_or_newtype,
)
MANAGER.register_transform(
nodes.Subscript, inference_tip(infer_typing_attr), _looks_like_typing_subscript
)
if not PY37:
MANAGER.register_transform(
nodes.Subscript, inference_tip(infer_typing_attr), _looks_like_typing_subscript
)

if PY39:
MANAGER.register_transform(
Expand Down
20 changes: 19 additions & 1 deletion astroid/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2617,7 +2617,25 @@ def getitem(self, index, context=None):
try:
methods = dunder_lookup.lookup(self, "__getitem__")
except exceptions.AttributeInferenceError as exc:
raise exceptions.AstroidTypeError(node=self, context=context) from exc
if isinstance(self, ClassDef):
# subscripting a class definition may be
# achieved thanks to __class_getitem__ method
# which is a classmethod defined in the class
# that supports subscript and not in the metaclass
try:
methods = self.getattr("__class_getitem__")
# Here it is assumed that the __class_getitem__ node is
# a FunctionDef. One possible improvment would be to deal
# with more generic inference.
if not isinstance(methods[0], FunctionDef):
raise exceptions.AttributeInferenceError
except exceptions.AttributeInferenceError:
raise exceptions.AstroidTypeError(
node=self, context=context
) from exc
else:
raise exceptions.AstroidTypeError(node=self, context=context) from exc
# raise exceptions.AstroidTypeError(node=self, context=context) from exc

method = methods[0]

Expand Down

0 comments on commit 9817886

Please sign in to comment.