Skip to content

Move safe_infer() to util #2232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ Release date: TBA

* Move ``LookupMixIn`` to ``astroid.nodes._base_nodes`` and make it private.

* Remove the shims for ``OperationError``, ``BinaryOperationError``, and ``UnaryOperationError``
in ``exceptions``. They were moved to ``util`` in astroid 1.5.0.

* Move ``safe_infer()`` from ``helpers`` to ``util``. This avoids some circular imports.

* Reduce file system access in ``ast_from_file()``.

* Reduce time to ``import astroid`` by delaying ``astroid_bootstrapping()`` until
Expand Down
3 changes: 0 additions & 3 deletions astroid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
AstroidTypeError,
AstroidValueError,
AttributeInferenceError,
BinaryOperationError,
DuplicateBasesError,
InconsistentMroError,
InferenceError,
Expand All @@ -66,14 +65,12 @@
NameInferenceError,
NoDefault,
NotFoundError,
OperationError,
ParentMissingError,
ResolveError,
StatementMissing,
SuperArgumentTypeError,
SuperError,
TooManyLevelsError,
UnaryOperationError,
UnresolvableName,
UseInferenceDefault,
)
Expand Down
3 changes: 1 addition & 2 deletions astroid/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
from astroid.bases import Instance
from astroid.context import CallContext, InferenceContext
from astroid.exceptions import InferenceError, NoDefault
from astroid.helpers import safe_infer
from astroid.typing import InferenceResult
from astroid.util import Uninferable, UninferableBase
from astroid.util import Uninferable, UninferableBase, safe_infer


class CallSite:
Expand Down
6 changes: 2 additions & 4 deletions astroid/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
InferenceResult,
SuccessfulInferenceResult,
)
from astroid.util import Uninferable, UninferableBase
from astroid.util import Uninferable, UninferableBase, safe_infer

if TYPE_CHECKING:
from astroid.constraint import Constraint
Expand Down Expand Up @@ -68,8 +68,6 @@
def _is_property(
meth: nodes.FunctionDef | UnboundMethod, context: InferenceContext | None = None
) -> bool:
from astroid import helpers # pylint: disable=import-outside-toplevel

decoratornames = meth.decoratornames(context=context)
if PROPERTIES.intersection(decoratornames):
return True
Expand All @@ -85,7 +83,7 @@ def _is_property(
if not meth.decorators:
return False
for decorator in meth.decorators.nodes or ():
inferred = helpers.safe_infer(decorator, context=context)
inferred = safe_infer(decorator, context=context)
if inferred is None or isinstance(inferred, UninferableBase):
continue
if isinstance(inferred, nodes.ClassDef):
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
Without this hook pylint reports unsupported-assignment-operation
for attrs classes
"""
from astroid.helpers import safe_infer
from astroid.manager import AstroidManager
from astroid.nodes.node_classes import AnnAssign, Assign, AssignName, Call, Unknown
from astroid.nodes.scoped_nodes import ClassDef
from astroid.util import safe_infer

ATTRIB_NAMES = frozenset(
(
Expand Down
12 changes: 6 additions & 6 deletions astroid/brain/brain_builtin_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def _container_generic_transform(
for element in arg.elts:
if not element:
continue
inferred = helpers.safe_infer(element, context=context)
inferred = util.safe_infer(element, context=context)
if inferred:
evaluated_object = nodes.EvaluatedObject(
original=element, value=inferred
Expand Down Expand Up @@ -690,7 +690,7 @@ def infer_slice(node, context: InferenceContext | None = None):
if not 0 < len(args) <= 3:
raise UseInferenceDefault

infer_func = partial(helpers.safe_infer, context=context)
infer_func = partial(util.safe_infer, context=context)
args = [infer_func(arg) for arg in args]
for arg in args:
if not arg or isinstance(arg, util.UninferableBase):
Expand Down Expand Up @@ -1006,7 +1006,7 @@ def _is_str_format_call(node: nodes.Call) -> bool:
return False

if isinstance(node.func.expr, nodes.Name):
value = helpers.safe_infer(node.func.expr)
value = util.safe_infer(node.func.expr)
else:
value = node.func.expr

Expand All @@ -1022,7 +1022,7 @@ def _infer_str_format_call(

value: nodes.Const
if isinstance(node.func.expr, nodes.Name):
if not (inferred := helpers.safe_infer(node.func.expr)) or not isinstance(
if not (inferred := util.safe_infer(node.func.expr)) or not isinstance(
inferred, nodes.Const
):
return iter([util.Uninferable])
Expand All @@ -1037,7 +1037,7 @@ def _infer_str_format_call(
# Get the positional arguments passed
inferred_positional: list[nodes.Const] = []
for i in call.positional_arguments:
one_inferred = helpers.safe_infer(i, context)
one_inferred = util.safe_infer(i, context)
if not isinstance(one_inferred, nodes.Const):
return iter([util.Uninferable])
inferred_positional.append(one_inferred)
Expand All @@ -1047,7 +1047,7 @@ def _infer_str_format_call(
# Get the keyword arguments passed
inferred_keyword: dict[str, nodes.Const] = {}
for k, v in call.keyword_arguments.items():
one_inferred = helpers.safe_infer(v, context)
one_inferred = util.safe_infer(v, context)
if not isinstance(one_inferred, nodes.Const):
return iter([util.Uninferable])
inferred_keyword[k] = one_inferred
Expand Down
6 changes: 3 additions & 3 deletions astroid/brain/brain_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
from collections.abc import Iterator
from typing import Literal, Tuple, Union

from astroid import bases, context, helpers, nodes
from astroid import bases, context, nodes
from astroid.builder import parse
from astroid.const import PY39_PLUS, PY310_PLUS
from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
from astroid.typing import InferenceResult
from astroid.util import Uninferable, UninferableBase
from astroid.util import Uninferable, UninferableBase, safe_infer

_FieldDefaultReturn = Union[
None,
Expand Down Expand Up @@ -561,7 +561,7 @@ def _is_keyword_only_sentinel(node: nodes.NodeNG) -> bool:
"""Return True if node is the KW_ONLY sentinel."""
if not PY310_PLUS:
return False
inferred = helpers.safe_infer(node)
inferred = safe_infer(node)
return (
isinstance(inferred, bases.Instance)
and inferred.qname() == "dataclasses._KW_ONLY_TYPE"
Expand Down
6 changes: 3 additions & 3 deletions astroid/brain/brain_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from functools import partial
from itertools import chain

from astroid import BoundMethod, arguments, extract_node, helpers, nodes, objects
from astroid import BoundMethod, arguments, extract_node, nodes, objects
from astroid.context import InferenceContext
from astroid.exceptions import InferenceError, UseInferenceDefault
from astroid.inference_tip import inference_tip
Expand All @@ -19,7 +19,7 @@
from astroid.nodes.node_classes import AssignName, Attribute, Call, Name
from astroid.nodes.scoped_nodes import FunctionDef
from astroid.typing import InferenceResult, SuccessfulInferenceResult
from astroid.util import UninferableBase
from astroid.util import UninferableBase, safe_infer

LRU_CACHE = "functools.lru_cache"

Expand Down Expand Up @@ -50,7 +50,7 @@ def infer_call_result(
caller: SuccessfulInferenceResult | None,
context: InferenceContext | None = None,
) -> Iterator[InferenceResult]:
res = helpers.safe_infer(cache_info)
res = safe_infer(cache_info)
assert res is not None
yield res

Expand Down
6 changes: 3 additions & 3 deletions astroid/brain/brain_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import random

from astroid import helpers
from astroid.context import InferenceContext
from astroid.exceptions import UseInferenceDefault
from astroid.inference_tip import inference_tip
Expand All @@ -21,6 +20,7 @@
Set,
Tuple,
)
from astroid.util import safe_infer

ACCEPTED_ITERABLES_FOR_SAMPLE = (List, Set, Tuple)

Expand Down Expand Up @@ -51,13 +51,13 @@ def infer_random_sample(node, context: InferenceContext | None = None):
if len(node.args) != 2:
raise UseInferenceDefault

inferred_length = helpers.safe_infer(node.args[1], context=context)
inferred_length = safe_infer(node.args[1], context=context)
if not isinstance(inferred_length, Const):
raise UseInferenceDefault
if not isinstance(inferred_length.value, int):
raise UseInferenceDefault

inferred_sequence = helpers.safe_infer(node.args[0], context=context)
inferred_sequence = safe_infer(node.args[0], context=context)
if not inferred_sequence:
raise UseInferenceDefault

Expand Down
9 changes: 0 additions & 9 deletions astroid/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from collections.abc import Iterator
from typing import TYPE_CHECKING, Any

from astroid import util
from astroid.typing import InferenceResult, SuccessfulInferenceResult

if TYPE_CHECKING:
Expand All @@ -26,7 +25,6 @@
"AstroidTypeError",
"AstroidValueError",
"AttributeInferenceError",
"BinaryOperationError",
"DuplicateBasesError",
"InconsistentMroError",
"InferenceError",
Expand All @@ -35,14 +33,12 @@
"NameInferenceError",
"NoDefault",
"NotFoundError",
"OperationError",
"ParentMissingError",
"ResolveError",
"StatementMissing",
"SuperArgumentTypeError",
"SuperError",
"TooManyLevelsError",
"UnaryOperationError",
"UnresolvableName",
"UseInferenceDefault",
)
Expand Down Expand Up @@ -416,11 +412,6 @@ def __init__(self, target: nodes.NodeNG) -> None:
)


# Backwards-compatibility aliases
OperationError = util.BadOperationMessage
UnaryOperationError = util.BadUnaryOperationMessage
BinaryOperationError = util.BadBinaryOperationMessage

SuperArgumentTypeError = SuperError
UnresolvableName = NameInferenceError
NotFoundError = AttributeInferenceError
Expand Down
44 changes: 17 additions & 27 deletions astroid/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import warnings
from collections.abc import Generator

from astroid import bases, manager, nodes, objects, raw_building, util
Expand All @@ -19,6 +20,20 @@
)
from astroid.nodes import scoped_nodes
from astroid.typing import InferenceResult
from astroid.util import safe_infer as real_safe_infer


def safe_infer(
node: nodes.NodeNG | bases.Proxy | util.UninferableBase,
context: InferenceContext | None = None,
) -> InferenceResult | None:
# When removing, also remove the real_safe_infer alias
warnings.warn(
"Import safe_infer from astroid.util; this shim in astroid.helpers will be removed.",
DeprecationWarning,
stacklevel=2,
)
return real_safe_infer(node, context=context)


def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef:
Expand Down Expand Up @@ -155,39 +170,14 @@ def object_issubclass(
return _object_type_is_subclass(node, class_or_seq, context=context)


def safe_infer(
node: nodes.NodeNG | bases.Proxy | util.UninferableBase,
context: InferenceContext | None = None,
) -> InferenceResult | None:
"""Return the inferred value for the given node.

Return None if inference failed or if there is some ambiguity (more than
one node has been inferred).
"""
if isinstance(node, util.UninferableBase):
return node
try:
inferit = node.infer(context=context)
value = next(inferit)
except (InferenceError, StopIteration):
return None
try:
next(inferit)
return None # None if there is ambiguity on the inferred node
except InferenceError:
return None # there is some kind of ambiguity
except StopIteration:
return value


def has_known_bases(klass, context: InferenceContext | None = None) -> bool:
"""Return whether all base classes of a class could be inferred."""
try:
return klass._all_bases_known
except AttributeError:
pass
for base in klass.bases:
result = safe_infer(base, context=context)
result = real_safe_infer(base, context=context)
# TODO: check for A->B->A->B pattern in class structure too?
if (
not isinstance(result, scoped_nodes.ClassDef)
Expand Down Expand Up @@ -262,7 +252,7 @@ def object_len(node, context: InferenceContext | None = None):
# pylint: disable=import-outside-toplevel; circular import
from astroid.objects import FrozenSet

inferred_node = safe_infer(node, context=context)
inferred_node = real_safe_infer(node, context=context)

# prevent self referential length calls from causing a recursion error
# see https://github.com/pylint-dev/astroid/issues/777
Expand Down
8 changes: 3 additions & 5 deletions astroid/nodes/_base_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,23 +351,21 @@ def _infer_old_style_string_formatting(
TODO: Instead of returning Uninferable we should rely
on the call to '%' to see if the result is actually uninferable.
"""
from astroid import helpers # pylint: disable=import-outside-toplevel

if isinstance(other, nodes.Tuple):
if util.Uninferable in other.elts:
return (util.Uninferable,)
inferred_positional = [helpers.safe_infer(i, context) for i in other.elts]
inferred_positional = [util.safe_infer(i, context) for i in other.elts]
if all(isinstance(i, nodes.Const) for i in inferred_positional):
values = tuple(i.value for i in inferred_positional)
else:
values = None
elif isinstance(other, nodes.Dict):
values: dict[Any, Any] = {}
for pair in other.items:
key = helpers.safe_infer(pair[0], context)
key = util.safe_infer(pair[0], context)
if not isinstance(key, nodes.Const):
return (util.Uninferable,)
value = helpers.safe_infer(pair[1], context)
value = util.safe_infer(pair[1], context)
if not isinstance(value, nodes.Const):
return (util.Uninferable,)
values[key.value] = value.value
Expand Down
Loading