Skip to content

Commit

Permalink
WIP removed Unknown as a parent node
Browse files Browse the repository at this point in the history
  • Loading branch information
temyurchenko committed Sep 6, 2024
1 parent 261ca0b commit b693781
Show file tree
Hide file tree
Showing 18 changed files with 138 additions and 137 deletions.
17 changes: 9 additions & 8 deletions astroid/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"""This module contains base classes and functions for the nodes and some
inference utils.
"""
# isort: off

from __future__ import annotations

import collections
Expand Down Expand Up @@ -273,27 +275,26 @@ def igetattr(
try:
context.lookupname = name
# XXX frame should be self._proxied, or not ?
get_attr = self.getattr(name, context, lookupclass=False)
yield from _infer_stmts(
self._wrap_attr(get_attr, context), context, frame=self
)
attrs = self.getattr(name, context, lookupclass=False)
iattrs = self._proxied._infer_attrs(attrs, context, class_context=False)
yield from self._wrap_attr(iattrs)
except AttributeInferenceError:
try:
# fallback to class.igetattr since it has some logic to handle
# descriptors
# But only if the _proxied is the Class.
if self._proxied.__class__.__name__ != "ClassDef":
raise
attrs = self._proxied.igetattr(name, context, class_context=False)
yield from self._wrap_attr(attrs, context)
iattrs = self._proxied.igetattr(name, context, class_context=False)
yield from self._wrap_attr(iattrs, context)
except AttributeInferenceError as error:
raise InferenceError(**vars(error)) from error

def _wrap_attr(
self, attrs: Iterable[InferenceResult], context: InferenceContext | None = None
self, iattrs: Iterable[InferenceResult], context: InferenceContext | None = None
) -> Iterator[InferenceResult]:
"""Wrap bound methods of attrs in a InstanceMethod proxies."""
for attr in attrs:
for attr in iattrs:
if isinstance(attr, UnboundMethod):
if _is_property(attr):
yield from attr.infer_call_result(self, context)
Expand Down
5 changes: 1 addition & 4 deletions astroid/brain/brain_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,10 @@ def infer_namespace(node, context: InferenceContext | None = None):
"Namespace",
lineno=node.lineno,
col_offset=node.col_offset,
parent=nodes.Unknown(),
parent=AstroidManager().adhoc_module, # this class is not real
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
)
# Set parent manually until ClassDef constructor fixed:
# https://github.com/pylint-dev/astroid/issues/1490
class_node.parent = node.parent
for attr in set(callsite.keyword_arguments):
fake_node = nodes.EmptyNode()
fake_node.parent = class_node
Expand Down
3 changes: 3 additions & 0 deletions astroid/brain/brain_builtin_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from astroid.manager import AstroidManager
from astroid.nodes import scoped_nodes
from astroid.raw_building import build_module
from astroid.typing import (
ConstFactoryResult,
InferenceResult,
Expand Down Expand Up @@ -163,6 +164,8 @@ def _extend_builtins(class_transforms):

def on_bootstrap():
"""Called by astroid_bootstrapping()."""
AstroidManager().cache_module(build_module("__astroid_adhoc"))

_extend_builtins(
{
"bytes": partial(_extend_string_class, code=BYTES_CLASS, rvalue="b''"),
Expand Down
49 changes: 25 additions & 24 deletions astroid/brain/brain_namedtuple_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ def _extract_namedtuple_arg_or_keyword( # pylint: disable=inconsistent-return-s

def infer_func_form(
node: nodes.Call,
base_type: list[nodes.NodeNG],
base_type: nodes.NodeNG,
*,
parent: nodes.NodeNG,
context: InferenceContext | None = None,
enum: bool = False,
) -> tuple[nodes.ClassDef, str, list[str]]:
Expand Down Expand Up @@ -146,15 +148,11 @@ def infer_func_form(
col_offset=node.col_offset,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
parent=nodes.Unknown(),
parent=parent,
)
# A typical ClassDef automatically adds its name to the parent scope,
# but doing so causes problems, so defer setting parent until after init
# see: https://github.com/pylint-dev/pylint/issues/5982
class_node.parent = node.parent
class_node.postinit(
# set base class=tuple
bases=base_type,
bases=[base_type],
body=[],
decorators=None,
)
Expand Down Expand Up @@ -194,25 +192,18 @@ def infer_named_tuple(
node: nodes.Call, context: InferenceContext | None = None
) -> Iterator[nodes.ClassDef]:
"""Specific inference function for namedtuple Call node."""
tuple_base_name: list[nodes.NodeNG] = [
nodes.Name(
name="tuple",
parent=node.root(),
lineno=0,
col_offset=0,
end_lineno=None,
end_col_offset=None,
)
]
tuple_base = util.safe_infer(_extract_single_node("tuple"))
assert isinstance(tuple_base, nodes.NodeNG)

class_node, name, attributes = infer_func_form(
node, tuple_base_name, context=context
node, tuple_base, parent=AstroidManager().adhoc_module, context=context
)

call_site = arguments.CallSite.from_call(node, context=context)
node = extract_node("import collections; collections.namedtuple")
try:
func = next(node.infer())
except StopIteration as e:
raise InferenceError(node=node) from e
func = util.safe_infer(
_extract_single_node("import collections; collections.namedtuple")
)
assert isinstance(func, nodes.NodeNG)
try:
rename = next(
call_site.infer_argument(func, "rename", context or InferenceContext())
Expand Down Expand Up @@ -363,7 +354,17 @@ def value(self):
__members__ = ['']
"""
)
class_node = infer_func_form(node, [enum_meta], context=context, enum=True)[0]

# FIXME arguably, the base here shouldn't be the EnumMeta class definition
# itself, but a reference (Name) to it. Otherwise, the invariant that all
# children of a node have that node as their parent is broken.
class_node = infer_func_form(
node,
enum_meta,
parent=AstroidManager().adhoc_module,
context=context,
enum=True,
)[0]
return iter([class_node.instantiate_class()])


Expand Down
3 changes: 1 addition & 2 deletions astroid/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ def safe_infer(


def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef:
proxy = raw_building.build_class(cls_name)
proxy.parent = builtins
proxy = raw_building.build_class(cls_name, builtins)
return proxy


Expand Down
4 changes: 3 additions & 1 deletion astroid/interpreter/objectmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
mechanism.
"""

# isort: off

from __future__ import annotations

import itertools
Expand Down Expand Up @@ -493,7 +495,7 @@ def __init__(self):
super().__init__()

@property
def attr___annotations__(self) -> node_classes.Unkown:
def attr___annotations__(self) -> node_classes.Unknown:
return node_classes.Unknown()

@property
Expand Down
4 changes: 4 additions & 0 deletions astroid/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def unregister_transform(self):
def builtins_module(self) -> nodes.Module:
return self.astroid_cache["builtins"]

@property
def adhoc_module(self) -> nodes.Module:
return self.astroid_cache["__astroid_adhoc"]

@property
def prefer_stubs(self) -> bool:
return AstroidManager.brain["prefer_stubs"]
Expand Down
97 changes: 51 additions & 46 deletions astroid/nodes/scoped_nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
Lambda, GeneratorExp, DictComp and SetComp to some extent).
"""

# isort: skip_file

from __future__ import annotations

import io
Expand Down Expand Up @@ -44,7 +46,6 @@
Arguments,
Const,
NodeNG,
Unknown,
_base_nodes,
const_factory,
node_classes,
Expand Down Expand Up @@ -1166,9 +1167,11 @@ def __init__(
end_col_offset=end_col_offset,
parent=parent,
)
if parent and not isinstance(parent, Unknown):
if parent:
frame = parent.frame()
frame.set_local(name, self)
if frame is parent:
frame._append_node(self)

def postinit(
self,
Expand Down Expand Up @@ -1935,7 +1938,7 @@ def __init__(
end_col_offset=end_col_offset,
parent=parent,
)
if parent and not isinstance(parent, Unknown):
if parent:
parent.frame().set_local(name, self)

for local_name, node in self.implicit_locals():
Expand Down Expand Up @@ -2089,7 +2092,7 @@ def _infer_type_call(self, caller, context):
col_offset=0,
end_lineno=0,
end_col_offset=0,
parent=Unknown(),
parent=caller.parent,
)

# Get the bases of the class.
Expand Down Expand Up @@ -2123,7 +2126,6 @@ def _infer_type_call(self, caller, context):
if isinstance(attr, node_classes.Const) and isinstance(attr.value, str):
result.locals[attr.value] = [value]

result.parent = caller.parent
return result

def infer_call_result(
Expand Down Expand Up @@ -2486,14 +2488,11 @@ def igetattr(
:returns: The inferred possible values.
"""
from astroid import objects # pylint: disable=import-outside-toplevel

# set lookup name since this is necessary to infer on import nodes for
# instance
context = copy_context(context)
context.lookupname = name

metaclass = self.metaclass(context=context)
try:
attributes = self.getattr(name, context, class_context=class_context)
# If we have more than one attribute, make sure that those starting from
Expand All @@ -2516,44 +2515,7 @@ def igetattr(
for a in attributes
if a not in functions or a is last_function or bases._is_property(a)
]

for inferred in bases._infer_stmts(attributes, context, frame=self):
# yield Uninferable object instead of descriptors when necessary
if not isinstance(inferred, node_classes.Const) and isinstance(
inferred, bases.Instance
):
try:
inferred._proxied.getattr("__get__", context)
except AttributeInferenceError:
yield inferred
else:
yield util.Uninferable
elif isinstance(inferred, objects.Property):
function = inferred.function
if not class_context:
if not context.callcontext:
context.callcontext = CallContext(
args=function.args.arguments, callee=function
)
# Through an instance so we can solve the property
yield from function.infer_call_result(
caller=self, context=context
)
# If we're in a class context, we need to determine if the property
# was defined in the metaclass (a derived class must be a subclass of
# the metaclass of all its bases), in which case we can resolve the
# property. If not, i.e. the property is defined in some base class
# instead, then we return the property object
elif metaclass and function.parent.scope() is metaclass:
# Resolve a property as long as it is not accessed through
# the class itself.
yield from function.infer_call_result(
caller=self, context=context
)
else:
yield inferred
else:
yield function_to_method(inferred, self)
yield from self._infer_attrs(attributes, context, class_context)
except AttributeInferenceError as error:
if not name.startswith("__") and self.has_dynamic_getattr(context):
# class handle some dynamic attributes, return a Uninferable object
Expand All @@ -2563,6 +2525,49 @@ def igetattr(
str(error), target=self, attribute=name, context=context
) from error

def _infer_attrs(
self,
attributes: list[InferenceResult],
context: InferenceContext,
class_context: bool = True,
) -> Iterator[InferenceResult]:
from astroid import objects # pylint: disable=import-outside-toplevel

metaclass = self.metaclass(context=context)
for inferred in bases._infer_stmts(attributes, context, frame=self):
# yield Uninferable object instead of descriptors when necessary
if not isinstance(inferred, node_classes.Const) and isinstance(
inferred, bases.Instance
):
try:
inferred._proxied.getattr("__get__", context)
except AttributeInferenceError:
yield inferred
else:
yield util.Uninferable
elif isinstance(inferred, objects.Property):
function = inferred.function
if not class_context:
if not context.callcontext:
context.callcontext = CallContext(
args=function.args.arguments, callee=function
)
# Through an instance so we can solve the property
yield from function.infer_call_result(caller=self, context=context)
# If we're in a class context, we need to determine if the property
# was defined in the metaclass (a derived class must be a subclass of
# the metaclass of all its bases), in which case we can resolve the
# property. If not, i.e. the property is defined in some base class
# instead, then we return the property object
elif metaclass and function.parent.scope() is metaclass:
# Resolve a property as long as it is not accessed through
# the class itself.
yield from function.infer_call_result(caller=self, context=context)
else:
yield inferred
else:
yield function_to_method(inferred, self)

def has_dynamic_getattr(self, context: InferenceContext | None = None) -> bool:
"""Check if the class has a custom __getattr__ or __getattribute__.
Expand Down
2 changes: 1 addition & 1 deletion astroid/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def __init__(self, call, name=None, lineno=None, col_offset=None, parent=None):
name,
lineno=lineno,
col_offset=col_offset,
parent=node_classes.Unknown(),
parent=AstroidManager().adhoc_module,
end_col_offset=0,
end_lineno=0,
)
Expand Down
Loading

0 comments on commit b693781

Please sign in to comment.