Skip to content

Commit

Permalink
Make constants have synthetic root as their parent (#2602)
Browse files Browse the repository at this point in the history
that fixes a bunch of tests in pylint.

We also make the synthetic root a singleton, as opposed to a module
   built in the astroid manager. That allows us to use it as a default
   value in Const constructors.
  • Loading branch information
temyurchenko authored Oct 13, 2024
1 parent 0954cd4 commit 7710b7b
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 63 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Release date: TBA
* Removed internal functions ``infer_numpy_member``, ``name_looks_like_numpy_member``, and
``attribute_looks_like_numpy_member`` from ``astroid.brain.brain_numpy_utils``.

* Constants now have a parent of ``nodes.SYNTHETIC_ROOT``.

* Fix crashes with large positive and negative list multipliers.

Closes #2521
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def infer_namespace(node, context: InferenceContext | None = None):
"Namespace",
lineno=node.lineno,
col_offset=node.col_offset,
parent=AstroidManager().synthetic_root, # this class is not real
parent=nodes.SYNTHETIC_ROOT, # this class is not real
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
)
Expand Down
5 changes: 1 addition & 4 deletions astroid/brain/brain_builtin_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from astroid.inference_tip import inference_tip
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 @@ -165,8 +164,6 @@ def _extend_builtins(class_transforms):

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

_extend_builtins(
{
"bytes": partial(_extend_string_class, code=BYTES_CLASS, rvalue="b''"),
Expand Down Expand Up @@ -653,7 +650,7 @@ def infer_property(
# node.frame. It's somewhere in the builtins module, but we are special
# casing it for each "property()" call, so we are making up the
# definition on the spot, ad-hoc.
parent=AstroidManager().synthetic_root,
parent=scoped_nodes.SYNTHETIC_ROOT,
)
prop_func.postinit(
body=[],
Expand Down
5 changes: 3 additions & 2 deletions astroid/brain/brain_namedtuple_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
from astroid.nodes.scoped_nodes.scoped_nodes import SYNTHETIC_ROOT

ENUM_QNAME: Final[str] = "enum.Enum"
TYPING_NAMEDTUPLE_QUALIFIED: Final = {
Expand Down Expand Up @@ -194,7 +195,7 @@ def infer_named_tuple(
"""Specific inference function for namedtuple Call node."""
tuple_base: nodes.Name = _extract_single_node("tuple")
class_node, name, attributes = infer_func_form(
node, tuple_base, parent=AstroidManager().synthetic_root, context=context
node, tuple_base, parent=SYNTHETIC_ROOT, context=context
)

call_site = arguments.CallSite.from_call(node, context=context)
Expand Down Expand Up @@ -360,7 +361,7 @@ def value(self):
class_node = infer_func_form(
node,
enum_meta,
parent=AstroidManager().synthetic_root,
parent=SYNTHETIC_ROOT,
context=context,
enum=True,
)[0]
Expand Down
4 changes: 0 additions & 4 deletions astroid/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@ def unregister_transform(self):
def builtins_module(self) -> nodes.Module:
return self.astroid_cache["builtins"]

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

@property
def prefer_stubs(self) -> bool:
return AstroidManager.brain["prefer_stubs"]
Expand Down
2 changes: 2 additions & 0 deletions astroid/nodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
unpack_infer,
)
from astroid.nodes.scoped_nodes import (
SYNTHETIC_ROOT,
AsyncFunctionDef,
ClassDef,
ComprehensionScope,
Expand Down Expand Up @@ -276,6 +277,7 @@
"Position",
"Raise",
"Return",
"SYNTHETIC_ROOT",
"Set",
"SetComp",
"Slice",
Expand Down
9 changes: 5 additions & 4 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from astroid.nodes import _base_nodes
from astroid.nodes.const import OP_PRECEDENCE
from astroid.nodes.node_ng import NodeNG
from astroid.nodes.scoped_nodes import SYNTHETIC_ROOT
from astroid.typing import (
ConstFactoryResult,
InferenceErrorInfo,
Expand Down Expand Up @@ -2038,7 +2039,7 @@ def __init__(
value: Any,
lineno: int | None = None,
col_offset: int | None = None,
parent: NodeNG | None = None,
parent: NodeNG = SYNTHETIC_ROOT,
kind: str | None = None,
*,
end_lineno: int | None = None,
Expand Down Expand Up @@ -2550,7 +2551,7 @@ def __init__(
self,
lineno: None = None,
col_offset: None = None,
parent: None = None,
parent: NodeNG = SYNTHETIC_ROOT,
*,
end_lineno: None = None,
end_col_offset: None = None,
Expand Down Expand Up @@ -5535,7 +5536,7 @@ def const_factory(value: Any) -> ConstFactoryResult:
instance = initializer_cls(
lineno=None,
col_offset=None,
parent=None,
parent=SYNTHETIC_ROOT,
end_lineno=None,
end_col_offset=None,
)
Expand All @@ -5545,7 +5546,7 @@ def const_factory(value: Any) -> ConstFactoryResult:
instance = initializer_cls(
lineno=None,
col_offset=None,
parent=None,
parent=SYNTHETIC_ROOT,
end_lineno=None,
end_col_offset=None,
)
Expand Down
2 changes: 2 additions & 0 deletions astroid/nodes/scoped_nodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
from astroid.nodes.scoped_nodes.scoped_nodes import (
SYNTHETIC_ROOT,
AsyncFunctionDef,
ClassDef,
DictComp,
Expand All @@ -37,6 +38,7 @@
"ListComp",
"LocalsDictNodeNG",
"Module",
"SYNTHETIC_ROOT",
"SetComp",
"builtin_lookup",
"function_to_method",
Expand Down
64 changes: 28 additions & 36 deletions astroid/nodes/scoped_nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,7 @@
from astroid.interpreter.dunder_lookup import lookup
from astroid.interpreter.objectmodel import ClassModel, FunctionModel, ModuleModel
from astroid.manager import AstroidManager
from astroid.nodes import (
Arguments,
Const,
NodeNG,
_base_nodes,
const_factory,
node_classes,
)
from astroid.nodes import _base_nodes, node_classes
from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
from astroid.nodes.scoped_nodes.utils import builtin_lookup
from astroid.nodes.utils import Position
Expand All @@ -60,6 +53,7 @@

if TYPE_CHECKING:
from astroid import nodes, objects
from astroid.nodes import Arguments, Const, NodeNG
from astroid.nodes._base_nodes import LookupMixIn


Expand Down Expand Up @@ -177,6 +171,15 @@ def function_to_method(n, klass):
return n


def _infer_last(
arg: SuccessfulInferenceResult, context: InferenceContext
) -> InferenceResult:
res = util.Uninferable
for b in arg.infer(context=context.clone()):
res = b
return res


class Module(LocalsDictNodeNG):
"""Class representing an :class:`ast.Module` node.
Expand Down Expand Up @@ -353,7 +356,7 @@ def getattr(
if name in self.special_attributes and not ignore_locals and not name_in_locals:
result = [self.special_attributes.lookup(name)]
if name == "__name__":
main_const = const_factory("__main__")
main_const = node_classes.const_factory("__main__")
main_const.parent = AstroidManager().builtins_module
result.append(main_const)
elif not ignore_locals and name_in_locals:
Expand Down Expand Up @@ -607,6 +610,14 @@ def _infer(
yield self


class __SyntheticRoot(Module):
def __init__(self):
super().__init__("__astroid_synthetic", pure_python=False)


SYNTHETIC_ROOT = __SyntheticRoot()


class GeneratorExp(ComprehensionScope):
"""Class representing an :class:`ast.GeneratorExp` node.
Expand Down Expand Up @@ -1538,10 +1549,7 @@ def infer_yield_result(self, context: InferenceContext | None = None):
"""
for yield_ in self.nodes_of_class(node_classes.Yield):
if yield_.value is None:
const = node_classes.Const(None)
const.parent = yield_
const.lineno = yield_.lineno
yield const
yield node_classes.Const(None, parent=yield_, lineno=yield_.lineno)
elif yield_.scope() == self:
yield from yield_.value.infer(context=context)

Expand All @@ -1551,6 +1559,8 @@ def infer_call_result(
context: InferenceContext | None = None,
) -> Iterator[InferenceResult]:
"""Infer what the function returns when called."""
if context is None:
context = InferenceContext()
if self.is_generator():
if isinstance(self, AsyncFunctionDef):
generator_cls: type[bases.Generator] = bases.AsyncGenerator
Expand All @@ -1572,7 +1582,7 @@ def infer_call_result(
and len(self.args.args) == 1
and self.args.vararg is not None
):
if isinstance(caller.args, Arguments):
if isinstance(caller.args, node_classes.Arguments):
assert caller.args.args is not None
metaclass = next(caller.args.args[0].infer(context), None)
elif isinstance(caller.args, list):
Expand All @@ -1582,27 +1592,14 @@ def infer_call_result(
f"caller.args was neither Arguments nor list; got {type(caller.args)}"
)
if isinstance(metaclass, ClassDef):
try:
class_bases = [
# Find the first non-None inferred base value
next(
b
for b in arg.infer(
context=context.clone() if context else context
)
if not (isinstance(b, Const) and b.value is None)
)
for arg in caller.args[1:]
]
except StopIteration as e:
raise InferenceError(node=caller.args[1:], context=context) from e
class_bases = [_infer_last(x, context) for x in caller.args[1:]]
new_class = ClassDef(
name="temporary_class",
lineno=0,
col_offset=0,
end_lineno=0,
end_col_offset=0,
parent=AstroidManager().synthetic_root,
parent=SYNTHETIC_ROOT,
)
new_class.hide = True
new_class.postinit(
Expand Down Expand Up @@ -2827,13 +2824,8 @@ def _inferred_bases(self, context: InferenceContext | None = None):

for stmt in self.bases:
try:
# Find the first non-None inferred base value
baseobj = next(
b
for b in stmt.infer(context=context.clone())
if not (isinstance(b, Const) and b.value is None)
)
except (InferenceError, StopIteration):
baseobj = _infer_last(stmt, context)
except InferenceError:
continue
if isinstance(baseobj, bases.Instance):
baseobj = baseobj._proxied
Expand Down
2 changes: 1 addition & 1 deletion astroid/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def __init__(self, call, name=None, lineno=None, col_offset=None, parent=None):
name,
lineno=lineno,
col_offset=col_offset,
parent=AstroidManager().synthetic_root,
parent=scoped_nodes.SYNTHETIC_ROOT,
end_col_offset=0,
end_lineno=0,
)
Expand Down
22 changes: 12 additions & 10 deletions tests/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@
AstroidBuildingError,
AstroidSyntaxError,
AttributeInferenceError,
ParentMissingError,
StatementMissing,
)
from astroid.manager import AstroidManager
from astroid.nodes.node_classes import (
AssignAttr,
AssignName,
Expand All @@ -46,7 +44,13 @@
ImportFrom,
Tuple,
)
from astroid.nodes.scoped_nodes import ClassDef, FunctionDef, GeneratorExp, Module
from astroid.nodes.scoped_nodes import (
SYNTHETIC_ROOT,
ClassDef,
FunctionDef,
GeneratorExp,
Module,
)
from tests.testdata.python3.recursion_error import LONG_CHAINED_METHOD_CALL

from . import resources
Expand Down Expand Up @@ -623,12 +627,10 @@ def _test(self, value: Any) -> None:
with self.assertRaises(StatementMissing):
node.statement()

with self.assertRaises(ParentMissingError):
with pytest.warns(DeprecationWarning) as records:
node.frame(future=True)
assert len(records) == 1
with self.assertRaises(ParentMissingError):
node.frame()
with pytest.warns(DeprecationWarning) as records:
assert node.frame(future=True) is SYNTHETIC_ROOT
assert len(records) == 1
assert node.frame() is SYNTHETIC_ROOT

def test_none(self) -> None:
self._test(None)
Expand Down Expand Up @@ -1962,7 +1964,7 @@ def test_str_repr_no_warnings(node):
continue

if name == "parent" and "NodeNG" in param_type.annotation:
args[name] = AstroidManager().synthetic_root
args[name] = SYNTHETIC_ROOT
elif "int" in param_type.annotation:
args[name] = random.randint(0, 50)
elif (
Expand Down
2 changes: 1 addition & 1 deletion tests/test_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
return type.__new__(metaclass, 'temporary_class', (), {})
import lala
Expand Down

0 comments on commit 7710b7b

Please sign in to comment.