From 3969f5ddf1151f3d3b43b6af20de93e41305dcda Mon Sep 17 00:00:00 2001 From: Artem Yurchenko Date: Mon, 7 Oct 2024 02:00:31 -0400 Subject: [PATCH 1/3] make constants have synthetic root as their parent 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. Note the changes to the test. Before, we in fact tested that constants don't have a parent. Is that reasonable? In fact, it seems like there are a variety of interpretations and we shouldn't commit to one by explicitly testing for a certain parent or lack thereof. --- astroid/brain/brain_argparse.py | 2 +- astroid/brain/brain_builtin_inference.py | 5 +-- astroid/brain/brain_namedtuple_enum.py | 5 ++- astroid/manager.py | 4 -- astroid/nodes/__init__.py | 2 + astroid/nodes/node_classes.py | 9 +++-- astroid/nodes/scoped_nodes/__init__.py | 2 + astroid/nodes/scoped_nodes/scoped_nodes.py | 44 +++++++++++----------- astroid/objects.py | 2 +- tests/test_nodes.py | 22 ++++++----- 10 files changed, 48 insertions(+), 49 deletions(-) diff --git a/astroid/brain/brain_argparse.py b/astroid/brain/brain_argparse.py index f7ba5f92e7..6bde22f2e5 100644 --- a/astroid/brain/brain_argparse.py +++ b/astroid/brain/brain_argparse.py @@ -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, ) diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index 7d11f8815a..a56b1525c9 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -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, @@ -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''"), @@ -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=[], diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py index 34f0501bc3..67d706d90e 100644 --- a/astroid/brain/brain_namedtuple_enum.py +++ b/astroid/brain/brain_namedtuple_enum.py @@ -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 = { @@ -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) @@ -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] diff --git a/astroid/manager.py b/astroid/manager.py index aed586c04b..ee4024448d 100644 --- a/astroid/manager.py +++ b/astroid/manager.py @@ -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"] diff --git a/astroid/nodes/__init__.py b/astroid/nodes/__init__.py index 769cf278e4..d34a54292f 100644 --- a/astroid/nodes/__init__.py +++ b/astroid/nodes/__init__.py @@ -93,6 +93,7 @@ unpack_infer, ) from astroid.nodes.scoped_nodes import ( + SYNTHETIC_ROOT, AsyncFunctionDef, ClassDef, ComprehensionScope, @@ -276,6 +277,7 @@ "Position", "Raise", "Return", + "SYNTHETIC_ROOT", "Set", "SetComp", "Slice", diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 91d7b0fa49..fde3239899 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -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, @@ -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, @@ -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, @@ -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, ) @@ -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, ) diff --git a/astroid/nodes/scoped_nodes/__init__.py b/astroid/nodes/scoped_nodes/__init__.py index f00dc3093e..92a0f9a95a 100644 --- a/astroid/nodes/scoped_nodes/__init__.py +++ b/astroid/nodes/scoped_nodes/__init__.py @@ -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, @@ -37,6 +38,7 @@ "ListComp", "LocalsDictNodeNG", "Module", + "SYNTHETIC_ROOT", "SetComp", "builtin_lookup", "function_to_method", diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 0a80eb8aac..271f9501df 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -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 @@ -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 @@ -353,7 +347,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: @@ -607,6 +601,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. @@ -1572,7 +1574,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): @@ -1583,17 +1585,13 @@ def infer_call_result( ) 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:] - ] + # Find the first non-None inferred base value + get_base = lambda arg: next( + b + for b in arg.infer(context=context.clone() if context else None) + if not (isinstance(b, node_classes.Const) and b.value is None) + ) + class_bases = [get_base(arg) for arg in caller.args[1:]] except StopIteration as e: raise InferenceError(node=caller.args[1:], context=context) from e new_class = ClassDef( @@ -1602,7 +1600,7 @@ def infer_call_result( col_offset=0, end_lineno=0, end_col_offset=0, - parent=AstroidManager().synthetic_root, + parent=SYNTHETIC_ROOT, ) new_class.hide = True new_class.postinit( @@ -2831,7 +2829,7 @@ def _inferred_bases(self, context: InferenceContext | None = None): baseobj = next( b for b in stmt.infer(context=context.clone()) - if not (isinstance(b, Const) and b.value is None) + if not (isinstance(b, node_classes.Const) and b.value is None) ) except (InferenceError, StopIteration): continue diff --git a/astroid/objects.py b/astroid/objects.py index 829cbf1900..9f638d436e 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -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, ) diff --git a/tests/test_nodes.py b/tests/test_nodes.py index deb3b7fef5..7f2133ed96 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -34,10 +34,8 @@ AstroidBuildingError, AstroidSyntaxError, AttributeInferenceError, - ParentMissingError, StatementMissing, ) -from astroid.manager import AstroidManager from astroid.nodes.node_classes import ( AssignAttr, AssignName, @@ -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 @@ -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) @@ -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 ( From 17c2f641e9aee1482ec41961b83936d4a49fd8bd Mon Sep 17 00:00:00 2001 From: Artem Yurchenko Date: Mon, 7 Oct 2024 02:17:37 -0400 Subject: [PATCH 2/3] fixup! make constants have synthetic root as their parent --- astroid/nodes/scoped_nodes/scoped_nodes.py | 36 +++++++++------------- tests/test_regrtest.py | 2 +- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 271f9501df..f94404086f 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -171,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. @@ -1540,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) @@ -1553,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 @@ -1584,16 +1592,7 @@ def infer_call_result( f"caller.args was neither Arguments nor list; got {type(caller.args)}" ) if isinstance(metaclass, ClassDef): - try: - # Find the first non-None inferred base value - get_base = lambda arg: next( - b - for b in arg.infer(context=context.clone() if context else None) - if not (isinstance(b, node_classes.Const) and b.value is None) - ) - class_bases = [get_base(arg) 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, @@ -2825,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, node_classes.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 diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py index f7383d25fb..2f6684124b 100644 --- a/tests/test_regrtest.py +++ b/tests/test_regrtest.py @@ -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 From c68607adc26e1f547a1c081e2dc1623f35175fe3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 13 Oct 2024 14:10:07 -0700 Subject: [PATCH 3/3] Add changelog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 73cc908ebc..bade6f0667 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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