diff --git a/astroid/__init__.py b/astroid/__init__.py index 29e052e1f7..6e446a765b 100644 --- a/astroid/__init__.py +++ b/astroid/__init__.py @@ -16,7 +16,7 @@ Instance attributes are added by a builder object, which can either generate extended ast (let's call them astroid ;) by visiting an existent ast tree or by inspecting living -object. Methods are added by monkey patching ast classes. +object. Main modules are: diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py index d6d80986a9..53b296e0c1 100644 --- a/astroid/nodes/_base_nodes.py +++ b/astroid/nodes/_base_nodes.py @@ -14,25 +14,23 @@ from functools import cached_property, lru_cache, partial from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Union -from astroid import bases, decorators, nodes, util +from astroid import bases, nodes, util from astroid.const import PY310_PLUS from astroid.context import ( CallContext, InferenceContext, bind_context_to_node, - copy_context, ) from astroid.exceptions import ( AttributeInferenceError, InferenceError, - NameInferenceError, ) from astroid.interpreter import dunder_lookup from astroid.nodes.node_ng import NodeNG -from astroid.typing import InferenceErrorInfo, InferenceResult +from astroid.typing import InferenceResult if TYPE_CHECKING: - from astroid.nodes.node_classes import AssignedStmtsPossibleNode, LocalsDictNodeNG + from astroid.nodes.node_classes import LocalsDictNodeNG GetFlowFactory = Callable[ [ @@ -331,7 +329,7 @@ def _filter_operation_errors( for result in infer_callable(context): if isinstance(result, error): # For the sake of .infer(), we don't care about operation - # errors, which is the job of pylint. So return something + # errors, which is the job of a linter. So return something # which shows that we can't infer the result. yield util.Uninferable else: @@ -670,106 +668,3 @@ def _infer_binary_operation( # The operation doesn't seem to be supported so let the caller know about it yield util.BadBinaryOperationMessage(left_type, binary_opnode.op, right_type) - - -class AttributeNode(NodeNG): - expr: NodeNG - """The name that this node represents.""" - attrname: str - - @decorators.raise_if_nothing_inferred - def _infer_attribute( - self, - context: InferenceContext | None = None, - **kwargs: Any, - ) -> Generator[InferenceResult, None, InferenceErrorInfo]: - """Infer an Attribute node by using getattr on the associated object.""" - # pylint: disable=import-outside-toplevel - from astroid.constraint import get_constraints - - for owner in self.expr.infer(context): - if isinstance(owner, util.UninferableBase): - yield owner - continue - - context = copy_context(context) - old_boundnode = context.boundnode - try: - context.boundnode = owner - if isinstance(owner, (nodes.ClassDef, bases.Instance)): - frame = ( - owner if isinstance(owner, nodes.ClassDef) else owner._proxied - ) - context.constraints[self.attrname] = get_constraints( - self, frame=frame - ) - yield from owner.igetattr(self.attrname, context) - except ( - AttributeInferenceError, - InferenceError, - AttributeError, - ): - pass - finally: - context.boundnode = old_boundnode - return InferenceErrorInfo(node=self, context=context) - - -class AssignNode(NodeNG): - @decorators.raise_if_nothing_inferred - @decorators.path_wrapper - def _infer_assign( - self, - context: InferenceContext | None = None, - **kwargs: Any, - ) -> Generator[InferenceResult, None, None]: - """Infer a AssignName/AssignAttr: need to inspect the RHS part of the - assign node. - """ - if isinstance(self.parent, nodes.AugAssign): - return self.parent.infer(context) - - stmts = list(self.assigned_stmts(context=context)) - return bases._infer_stmts(stmts, context) - - def assigned_stmts( - self: nodes.AssignName | nodes.AssignAttr, - node: AssignedStmtsPossibleNode = None, - context: InferenceContext | None = None, - assign_path: list[int] | None = None, - ) -> Any: - """Returns the assigned statement (non inferred) according to the assignment type.""" - return self.parent.assigned_stmts(node=self, context=context) - - -class NameNode(LookupMixIn): - name: str - - @decorators.raise_if_nothing_inferred - def _infer_name_node( - self, - context: InferenceContext | None = None, - **kwargs: Any, - ) -> Generator[InferenceResult, None, None]: - """Infer a Name: use name lookup rules.""" - # pylint: disable=import-outside-toplevel - from astroid.constraint import get_constraints - from astroid.helpers import _higher_function_scope - - frame, stmts = self.lookup(self.name) - if not stmts: - # Try to see if the name is enclosed in a nested function - # and use the higher (first function) scope for searching. - parent_function = _higher_function_scope(self.scope()) - if parent_function: - _, stmts = parent_function.lookup(self.name) - - if not stmts: - raise NameInferenceError( - name=self.name, scope=self.scope(), context=context - ) - context = copy_context(context) - context.lookupname = self.name - context.constraints[self.name] = get_constraints(self, frame) - - return bases._infer_stmts(stmts, context, frame) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 4f2bc949fe..1e1c31c4ec 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -36,6 +36,7 @@ AstroidValueError, AttributeInferenceError, InferenceError, + NameInferenceError, NoDefault, ParentMissingError, _NonDeducibleTypeHierarchy, @@ -396,8 +397,6 @@ def _infer_sequence_helper( class AssignName( - _base_nodes.NameNode, - _base_nodes.AssignNode, _base_nodes.NoChildrenNode, _base_nodes.LookupMixIn, _base_nodes.ParentAssignNode, @@ -445,16 +444,48 @@ def __init__( See astroid/protocols.py for actual implementation. """ + @decorators.raise_if_nothing_inferred + @decorators.path_wrapper def _infer( self, context: InferenceContext | None = None, **kwargs: Any ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - return self._infer_assign(context, **kwargs) + """Infer an AssignName: need to inspect the RHS part of the + assign node. + """ + if isinstance(self.parent, AugAssign): + return self.parent.infer(context) + + stmts = list(self.assigned_stmts(context=context)) + return _infer_stmts(stmts, context) @decorators.raise_if_nothing_inferred def infer_lhs( self, context: InferenceContext | None = None, **kwargs: Any ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - return self._infer_name_node(context, **kwargs) + """Infer a Name: use name lookup rules. + + Same implementation as Name._infer.""" + # pylint: disable=import-outside-toplevel + from astroid.constraint import get_constraints + from astroid.helpers import _higher_function_scope + + frame, stmts = self.lookup(self.name) + if not stmts: + # Try to see if the name is enclosed in a nested function + # and use the higher (first function) scope for searching. + parent_function = _higher_function_scope(self.scope()) + if parent_function: + _, stmts = parent_function.lookup(self.name) + + if not stmts: + raise NameInferenceError( + name=self.name, scope=self.scope(), context=context + ) + context = copy_context(context) + context.lookupname = self.name + context.constraints[self.name] = get_constraints(self, frame) + + return _infer_stmts(stmts, context, frame) class DelName( @@ -496,7 +527,7 @@ def __init__( ) -class Name(_base_nodes.NameNode, _base_nodes.NoChildrenNode): +class Name(_base_nodes.LookupMixIn, _base_nodes.NoChildrenNode): """Class representing an :class:`ast.Name` node. A :class:`Name` node is something that is named, but not covered by @@ -545,7 +576,30 @@ def _get_name_nodes(self): def _infer( self, context: InferenceContext | None = None, **kwargs: Any ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - return self._infer_name_node(context, **kwargs) + """Infer a Name: use name lookup rules + + Same implementation as AssignName._infer_lhs.""" + # pylint: disable=import-outside-toplevel + from astroid.constraint import get_constraints + from astroid.helpers import _higher_function_scope + + frame, stmts = self.lookup(self.name) + if not stmts: + # Try to see if the name is enclosed in a nested function + # and use the higher (first function) scope for searching. + parent_function = _higher_function_scope(self.scope()) + if parent_function: + _, stmts = parent_function.lookup(self.name) + + if not stmts: + raise NameInferenceError( + name=self.name, scope=self.scope(), context=context + ) + context = copy_context(context) + context.lookupname = self.name + context.constraints[self.name] = get_constraints(self, frame) + + return _infer_stmts(stmts, context, frame) DEPRECATED_ARGUMENT_DEFAULT = object() @@ -987,9 +1041,7 @@ def _format_args( return ", ".join(values) -class AssignAttr( - _base_nodes.AttributeNode, _base_nodes.AssignNode, _base_nodes.ParentAssignNode -): +class AssignAttr(_base_nodes.LookupMixIn, _base_nodes.ParentAssignNode): """Variation of :class:`ast.Assign` representing assignment to an attribute. >>> import astroid @@ -1002,6 +1054,8 @@ class AssignAttr( 'self.attribute' """ + expr: NodeNG + _astroid_fields = ("expr",) _other_fields = ("attrname",) @@ -1037,15 +1091,19 @@ def postinit(self, expr: NodeNG) -> None: def get_children(self): yield self.expr + @decorators.raise_if_nothing_inferred + @decorators.path_wrapper def _infer( self, context: InferenceContext | None = None, **kwargs: Any ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - return self._infer_assign(context, **kwargs) + """Infer an AssignAttr: need to inspect the RHS part of the + assign node. + """ + if isinstance(self.parent, AugAssign): + return self.parent.infer(context) - def infer_lhs( - self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - return self._infer_attribute(context, **kwargs) + stmts = list(self.assigned_stmts(context=context)) + return _infer_stmts(stmts, context) class Assert(_base_nodes.Statement): @@ -2727,9 +2785,11 @@ def _infer( ) from error -class Attribute(_base_nodes.AttributeNode): +class Attribute(NodeNG): """Class representing an :class:`ast.Attribute` node.""" + expr: NodeNG + _astroid_fields = ("expr",) _other_fields = ("attrname",) @@ -2760,11 +2820,40 @@ def postinit(self, expr: NodeNG) -> None: def get_children(self): yield self.expr + @decorators.raise_if_nothing_inferred @decorators.path_wrapper def _infer( self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, None]: - return self._infer_attribute(context, **kwargs) + ) -> Generator[InferenceResult, None, InferenceErrorInfo]: + """Infer an Attribute node by using getattr on the associated object.""" + # pylint: disable=import-outside-toplevel + from astroid.constraint import get_constraints + from astroid.nodes import ClassDef + + for owner in self.expr.infer(context): + if isinstance(owner, util.UninferableBase): + yield owner + continue + + context = copy_context(context) + old_boundnode = context.boundnode + try: + context.boundnode = owner + if isinstance(owner, (ClassDef, Instance)): + frame = owner if isinstance(owner, ClassDef) else owner._proxied + context.constraints[self.attrname] = get_constraints( + self, frame=frame + ) + yield from owner.igetattr(self.attrname, context) + except ( + AttributeInferenceError, + InferenceError, + AttributeError, + ): + pass + finally: + context.boundnode = old_boundnode + return InferenceErrorInfo(node=self, context=context) class Global(_base_nodes.NoChildrenNode, _base_nodes.Statement):