diff --git a/astroid/inference.py b/astroid/inference.py index 4dadc11698..06651f475b 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -40,6 +40,7 @@ AttributeInferenceError, InferenceError, NameInferenceError, + UseInferenceDefault, _NonDeducibleTypeHierarchy, ) from astroid.interpreter import dunder_lookup @@ -85,15 +86,6 @@ def infer_end( yield self -# We add ignores to all assignments to methods -# See https://github.com/python/mypy/issues/2427 -nodes.Module._infer = infer_end -nodes.ClassDef._infer = infer_end -nodes.Lambda._infer = infer_end # type: ignore[assignment] -nodes.Const._infer = infer_end # type: ignore[assignment] -nodes.Slice._infer = infer_end # type: ignore[assignment] - - def _infer_sequence_helper( node: _BaseContainerT, context: InferenceContext | None = None ) -> list[SuccessfulInferenceResult]: @@ -139,11 +131,6 @@ def infer_sequence( yield self -nodes.List._infer = infer_sequence # type: ignore[assignment] -nodes.Tuple._infer = infer_sequence # type: ignore[assignment] -nodes.Set._infer = infer_sequence # type: ignore[assignment] - - def infer_map( self: nodes.Dict, context: InferenceContext | None = None ) -> Iterator[nodes.Dict]: @@ -206,9 +193,6 @@ def _infer_map( return values -nodes.Dict._infer = infer_map # type: ignore[assignment] - - def _higher_function_scope(node: nodes.NodeNG) -> nodes.FunctionDef | None: """Search for the first function which encloses the given scope. This can be used for looking up in that function's @@ -254,12 +238,18 @@ def infer_name( return bases._infer_stmts(stmts, context, frame) -# pylint: disable=no-value-for-parameter # The order of the decorators here is important # See https://github.com/pylint-dev/astroid/commit/0a8a75db30da060a24922e05048bc270230f5 -nodes.Name._infer = decorators.raise_if_nothing_inferred( - decorators.path_wrapper(infer_name) -) +@decorators.raise_if_nothing_inferred +@decorators.path_wrapper +def _infer_name( + self: nodes.Name | nodes.AssignName, + context: InferenceContext | None = None, + **kwargs: Any, +) -> Generator[InferenceResult, None, None]: + return infer_name(self, context, **kwargs) + + nodes.AssignName.infer_lhs = infer_name # won't work with a path wrapper @@ -289,9 +279,6 @@ def infer_call( return InferenceErrorInfo(node=self, context=context) -nodes.Call._infer = infer_call # type: ignore[assignment] - - @decorators.raise_if_nothing_inferred @decorators.path_wrapper def infer_import( @@ -315,9 +302,6 @@ def infer_import( raise InferenceError(node=self, context=context) from exc -nodes.Import._infer = infer_import - - @decorators.raise_if_nothing_inferred @decorators.path_wrapper def infer_import_from( @@ -353,9 +337,6 @@ def infer_import_from( ) from error -nodes.ImportFrom._infer = infer_import_from # type: ignore[assignment] - - def infer_attribute( self: nodes.Attribute | nodes.AssignAttr, context: InferenceContext | None = None, @@ -390,9 +371,16 @@ def infer_attribute( # The order of the decorators here is important # See https://github.com/pylint-dev/astroid/commit/0a8a75db30da060a24922e05048bc270230f5 -nodes.Attribute._infer = decorators.raise_if_nothing_inferred( - decorators.path_wrapper(infer_attribute) -) +@decorators.raise_if_nothing_inferred +@decorators.path_wrapper +def _infer_attribute( + self: nodes.Attribute | nodes.AssignAttr, + context: InferenceContext | None = None, + **kwargs: Any, +) -> Generator[InferenceResult, None, InferenceErrorInfo]: + return infer_attribute(self, context, **kwargs) + + # won't work with a path wrapper nodes.AssignAttr.infer_lhs = decorators.raise_if_nothing_inferred(infer_attribute) @@ -412,9 +400,6 @@ def infer_global( ) from error -nodes.Global._infer = infer_global # type: ignore[assignment] - - _SUBSCRIPT_SENTINEL = object() @@ -479,9 +464,14 @@ def infer_subscript( # The order of the decorators here is important # See https://github.com/pylint-dev/astroid/commit/0a8a75db30da060a24922e05048bc270230f5 -nodes.Subscript._infer = decorators.raise_if_nothing_inferred( # type: ignore[assignment] - decorators.path_wrapper(infer_subscript) -) +@decorators.raise_if_nothing_inferred +@decorators.path_wrapper +def _infer_subscript( + self: nodes.Subscript, context: InferenceContext | None = None, **kwargs: Any +) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: + return infer_subscript(self, context=context, **kwargs) + + nodes.Subscript.infer_lhs = decorators.raise_if_nothing_inferred(infer_subscript) @@ -540,9 +530,6 @@ def _infer_boolop( return InferenceErrorInfo(node=self, context=context) -nodes.BoolOp._infer = _infer_boolop - - # UnaryOp, BinOp and AugAssign inferences @@ -639,7 +626,6 @@ def infer_unaryop( nodes.UnaryOp._infer_unaryop = _infer_unaryop -nodes.UnaryOp._infer = infer_unaryop def _is_not_implemented(const) -> bool: @@ -991,7 +977,6 @@ def infer_binop( nodes.BinOp._infer_binop = _infer_binop -nodes.BinOp._infer = infer_binop COMPARE_OPS: dict[str, Callable[[Any, Any], bool]] = { "==": operator.eq, @@ -1089,9 +1074,6 @@ def _infer_compare( yield nodes.Const(retval) -nodes.Compare._infer = _infer_compare # type: ignore[assignment] - - def _infer_augassign( self: nodes.AugAssign, context: InferenceContext | None = None ) -> Generator[InferenceResult | util.BadBinaryOperationMessage, None, None]: @@ -1131,7 +1113,6 @@ def infer_augassign( nodes.AugAssign._infer_augassign = _infer_augassign -nodes.AugAssign._infer = infer_augassign # End of binary operation inference. @@ -1145,9 +1126,6 @@ def infer_arguments( return protocols._arguments_infer_argname(self, context.lookupname, context) -nodes.Arguments._infer = infer_arguments # type: ignore[assignment] - - @decorators.raise_if_nothing_inferred @decorators.path_wrapper def infer_assign( @@ -1165,10 +1143,6 @@ def infer_assign( return bases._infer_stmts(stmts, context) -nodes.AssignName._infer = infer_assign -nodes.AssignAttr._infer = infer_assign - - @decorators.raise_if_nothing_inferred @decorators.path_wrapper def infer_empty_node( @@ -1185,9 +1159,6 @@ def infer_empty_node( yield util.Uninferable -nodes.EmptyNode._infer = infer_empty_node # type: ignore[assignment] - - def _populate_context_lookup(call: nodes.Call, context: InferenceContext | None): # Allows context to be saved for later # for inference inside a function @@ -1240,9 +1211,6 @@ def infer_ifexp( yield from self.orelse.infer(context=rhs_context) -nodes.IfExp._infer = infer_ifexp # type: ignore[assignment] - - def infer_functiondef( self: _FunctionDefT, context: InferenceContext | None = None, **kwargs: Any ) -> Generator[Property | _FunctionDefT, None, InferenceErrorInfo]: @@ -1277,4 +1245,126 @@ def infer_functiondef( return InferenceErrorInfo(node=self, context=context) -nodes.FunctionDef._infer = infer_functiondef +# pylint: disable-next=too-many-return-statements +def _infer_node( + node: nodes.NodeNG, context: InferenceContext | None = None, **kwargs: Any +) -> Generator[InferenceResult, None, None]: + """Find the infer method for the given node and call it.""" + if isinstance( + node, + ( + nodes.Module, + nodes.ClassDef, + nodes.Lambda, + nodes.Const, + nodes.Slice, + objects.Property, + objects.FrozenSet, + objects.Super, + ), + ): + return infer_end(node, context=context, **kwargs) + if isinstance(node, (nodes.List, nodes.Tuple, nodes.Set)): + return infer_sequence(node, context=context, **kwargs) + if isinstance(node, nodes.Dict): + return infer_map(node, context=context, **kwargs) + if isinstance(node, nodes.Name): + return _infer_name(node, context=context, **kwargs) + if isinstance(node, nodes.Call): + return infer_call(node, context=context, **kwargs) + if isinstance(node, nodes.Import): + return infer_import(node, context=context, **kwargs) + if isinstance(node, nodes.ImportFrom): + return infer_import_from(node, context=context, **kwargs) + if isinstance(node, nodes.Attribute): + return _infer_attribute(node, context=context, **kwargs) + if isinstance(node, nodes.Global): + return infer_global(node, context=context, **kwargs) + if isinstance(node, nodes.Subscript): + return _infer_subscript(node, context=context, **kwargs) + if isinstance(node, nodes.BoolOp): + return _infer_boolop(node, context=context, **kwargs) + if isinstance(node, nodes.UnaryOp): + return infer_unaryop(node, context=context, **kwargs) + if isinstance(node, nodes.BinOp): + return infer_binop(node, context=context, **kwargs) + if isinstance(node, nodes.Compare): + return _infer_compare(node, context=context, **kwargs) + if isinstance(node, nodes.AugAssign): + return infer_augassign(node, context=context, **kwargs) + if isinstance(node, nodes.Arguments): + return infer_arguments(node, context=context, **kwargs) + if isinstance(node, (nodes.AssignName, nodes.AssignAttr)): + return infer_assign(node, context=context, **kwargs) + if isinstance(node, nodes.EmptyNode): + return infer_empty_node(node, context=context, **kwargs) + if isinstance(node, nodes.IfExp): + return infer_ifexp(node, context=context, **kwargs) + if isinstance(node, nodes.FunctionDef): + return infer_functiondef(node, context=context, **kwargs) + if isinstance(node, nodes.Unknown): + return iter((util.Uninferable,)) + if isinstance(node, nodes.EvaluatedObject): + return iter((node.value,)) + raise InferenceError( + "No inference function for {node!r}.", node=node, context=context + ) + + +def infer_object( + node: nodes.NodeNG, context: InferenceContext | None = None, **kwargs: Any +) -> Generator[InferenceResult, None, None]: + """Get a generator of the inferred values. + + This is the main entry point to the inference system. + + .. seealso:: :ref:`inference` + + If the instance has some explicit inference function set, it will be + called instead of the default interface. + + :returns: The inferred values. + """ + if context is not None: + context = context.extra_context.get(node, context) + if node._explicit_inference is not None: + # explicit_inference is not bound, give it self explicitly + try: + if context is None: + yield from node._explicit_inference(node, context, **kwargs) + return + for result in node._explicit_inference(node, context, **kwargs): + context.nodes_inferred += 1 + yield result + return + except UseInferenceDefault: + pass + + if not context: + # nodes_inferred? + yield from _infer_node(node, context=context, **kwargs) + return + + key = (node, context.lookupname, context.callcontext, context.boundnode) + if key in context.inferred: + yield from context.inferred[key] + return + + results = [] + + # Limit inference amount to help with performance issues with + # exponentially exploding possible results. + limit = AstroidManager.max_inferable_values + for i, result in enumerate(_infer_node(node, context=context, **kwargs)): + if i >= limit or (context.nodes_inferred > context.max_inferred): + results.append(util.Uninferable) + yield util.Uninferable + break + results.append(result) + yield result + context.nodes_inferred += 1 + + # Cache generated results for subsequent inferences of the + # same node using the same context + context.inferred[key] = tuple(results) + return diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 787fbc5f02..60395cc7b3 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -3932,10 +3932,6 @@ def __init__( def qname(self) -> Literal["Unknown"]: return "Unknown" - def _infer(self, context: InferenceContext | None = None, **kwargs): - """Inference on an Unknown node immediately terminates.""" - yield util.Uninferable - class EvaluatedObject(NodeNG): """Contains an object that has already been inferred @@ -3963,11 +3959,6 @@ def __init__(self, original: NodeNG, value: NodeNG | util.UninferableBase) -> No end_col_offset=self.original.end_col_offset, ) - def _infer( - self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[NodeNG | util.UninferableBase, None, None]: - yield self.value - # Pattern matching ####################################################### diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index de5dec77d7..9d55823846 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -26,16 +26,13 @@ from astroid.context import InferenceContext from astroid.exceptions import ( AstroidError, - InferenceError, ParentMissingError, StatementMissing, - UseInferenceDefault, ) -from astroid.manager import AstroidManager from astroid.nodes.as_string import AsStringVisitor from astroid.nodes.const import OP_PRECEDENCE from astroid.nodes.utils import Position -from astroid.typing import InferenceErrorInfo, InferenceResult, InferFn +from astroid.typing import InferenceResult, InferFn if TYPE_CHECKING: from astroid import nodes @@ -131,49 +128,10 @@ def infer( :returns: The inferred values. :rtype: iterable """ - if context is not None: - context = context.extra_context.get(self, context) - if self._explicit_inference is not None: - # explicit_inference is not bound, give it self explicitly - try: - if context is None: - yield from self._explicit_inference(self, context, **kwargs) - return - for result in self._explicit_inference(self, context, **kwargs): - context.nodes_inferred += 1 - yield result - return - except UseInferenceDefault: - pass - - if not context: - # nodes_inferred? - yield from self._infer(context=context, **kwargs) - return - - key = (self, context.lookupname, context.callcontext, context.boundnode) - if key in context.inferred: - yield from context.inferred[key] - return + # pylint: disable-next=import-outside-toplevel + from astroid import inference - results = [] - - # Limit inference amount to help with performance issues with - # exponentially exploding possible results. - limit = AstroidManager.max_inferable_values - for i, result in enumerate(self._infer(context=context, **kwargs)): - if i >= limit or (context.nodes_inferred > context.max_inferred): - results.append(util.Uninferable) - yield util.Uninferable - break - results.append(result) - yield result - context.nodes_inferred += 1 - - # Cache generated results for subsequent inferences of the - # same node using the same context - context.inferred[key] = tuple(results) - return + return inference.infer_object(self, context, **kwargs) def _repr_name(self) -> str: """Get a name for nice representation. @@ -578,15 +536,6 @@ def _infer_name(self, frame, name): # overridden for ImportFrom, Import, Global, TryExcept, TryStar and Arguments pass - def _infer( - self, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - """We don't know how to resolve a statement by default.""" - # this method is overridden by most concrete classes - raise InferenceError( - "No inference function for {node!r}.", node=self, context=context - ) - def inferred(self): """Get a list of the inferred values. diff --git a/astroid/objects.py b/astroid/objects.py index f1e4cb69d2..e10ad9f30c 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -13,9 +13,9 @@ from __future__ import annotations -from collections.abc import Generator, Iterator +from collections.abc import Iterator from functools import cached_property -from typing import Any, Literal, NoReturn, TypeVar +from typing import Literal, NoReturn, TypeVar from astroid import bases, decorators, util from astroid.context import InferenceContext @@ -39,9 +39,6 @@ class FrozenSet(node_classes.BaseContainer): def pytype(self) -> Literal["builtins.frozenset"]: return "builtins.frozenset" - def _infer(self, context: InferenceContext | None = None, **kwargs: Any): - yield self - @cached_property def _proxied(self): # pylint: disable=method-hidden ast_builtins = AstroidManager().builtins_module @@ -84,9 +81,6 @@ def __init__( end_col_offset=scope.end_col_offset, ) - def _infer(self, context: InferenceContext | None = None, **kwargs: Any): - yield self - def super_mro(self): """Get the MRO which will be used to lookup attributes in this super.""" if not isinstance(self.mro_pointer, scoped_nodes.ClassDef): @@ -366,8 +360,3 @@ def infer_call_result( context: InferenceContext | None = None, ) -> NoReturn: raise InferenceError("Properties are not callable") - - def _infer( - self: _T, context: InferenceContext | None = None, **kwargs: Any - ) -> Generator[_T, None, None]: - yield self diff --git a/tests/test_inference.py b/tests/test_inference.py index 3de2c17b00..6c34ee37fb 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -40,6 +40,7 @@ NotFoundError, ) from astroid.inference import infer_end as inference_infer_end +from astroid.inference import infer_object from astroid.objects import ExceptionInstance from . import resources @@ -7022,7 +7023,7 @@ def patch(cls): def test_function_def_cached_generator() -> None: """Regression test for https://github.com/pylint-dev/astroid/issues/817.""" funcdef: nodes.FunctionDef = extract_node("def func(): pass") - next(funcdef._infer()) + next(infer_object(funcdef)) class TestOldStyleStringFormatting: diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py index 59d344b954..140a709d03 100644 --- a/tests/test_regrtest.py +++ b/tests/test_regrtest.py @@ -444,7 +444,7 @@ def test_max_inferred_for_complicated_class_hierarchy() -> None: @mock.patch( - "astroid.nodes.ImportFrom._infer", + "astroid.inference.infer_import_from", side_effect=RecursionError, ) def test_recursion_during_inference(mocked) -> None: