Skip to content

Catch the StopIteration that result from PEP-479 or add default value in next #1081

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 2 commits into from
Jun 30, 2021
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
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Release date: TBA

Closes PyCQA/pylint#4633

* Fix unhandled StopIteration during inference, following the implementation
of PEP479 in python 3.7+

Closes PyCQA/pylint#4631
Closes #1080

What's New in astroid 2.6.1?
============================
Release date: 2021-06-29
Expand Down
6 changes: 6 additions & 0 deletions astroid/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ def _unpack_keywords(self, keywords, context=None):
except InferenceError:
values[name] = util.Uninferable
continue
except StopIteration:
continue

if not isinstance(inferred, nodes.Dict):
# Not something we can work with.
Expand All @@ -113,6 +115,8 @@ def _unpack_keywords(self, keywords, context=None):
except InferenceError:
values[name] = util.Uninferable
continue
except StopIteration:
continue
if not isinstance(dict_key, nodes.Const):
values[name] = util.Uninferable
continue
Expand Down Expand Up @@ -140,6 +144,8 @@ def _unpack_args(self, args, context=None):
except InferenceError:
values.append(util.Uninferable)
continue
except StopIteration:
continue

if inferred is util.Uninferable:
values.append(util.Uninferable)
Expand Down
43 changes: 33 additions & 10 deletions astroid/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ def _infer_method_result_truth(instance, method_name, context):
for value in meth.infer_call_result(instance, context=context):
if value is util.Uninferable:
return value

inferred = next(value.infer(context=context))
try:
inferred = next(value.infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
return inferred.bool_value()
except InferenceError:
pass
Expand Down Expand Up @@ -344,7 +346,7 @@ def getitem(self, index, context=None):
node=self,
context=context,
)
return next(method.infer_call_result(self, new_context))
return next(method.infer_call_result(self, new_context), None)


class UnboundMethod(Proxy):
Expand Down Expand Up @@ -434,7 +436,10 @@ def _infer_type_new_call(self, caller, context):
from astroid import node_classes

# Verify the metaclass
mcs = next(caller.args[0].infer(context=context))
try:
mcs = next(caller.args[0].infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
if mcs.__class__.__name__ != "ClassDef":
# Not a valid first argument.
return None
Expand All @@ -443,7 +448,10 @@ def _infer_type_new_call(self, caller, context):
return None

# Verify the name
name = next(caller.args[1].infer(context=context))
try:
name = next(caller.args[1].infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
if name.__class__.__name__ != "Const":
# Not a valid name, needs to be a const.
return None
Expand All @@ -452,24 +460,39 @@ def _infer_type_new_call(self, caller, context):
return None

# Verify the bases
bases = next(caller.args[2].infer(context=context))
try:
bases = next(caller.args[2].infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
if bases.__class__.__name__ != "Tuple":
# Needs to be a tuple.
return None
inferred_bases = [next(elt.infer(context=context)) for elt in bases.elts]
try:
inferred_bases = [next(elt.infer(context=context)) for elt in bases.elts]
except StopIteration as e:
raise InferenceError(context=context) from e
if any(base.__class__.__name__ != "ClassDef" for base in inferred_bases):
# All the bases needs to be Classes
return None

# Verify the attributes.
attrs = next(caller.args[3].infer(context=context))
try:
attrs = next(caller.args[3].infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
if attrs.__class__.__name__ != "Dict":
# Needs to be a dictionary.
return None
cls_locals = collections.defaultdict(list)
for key, value in attrs.items:
key = next(key.infer(context=context))
value = next(value.infer(context=context))
try:
key = next(key.infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
try:
value = next(value.infer(context=context))
except StopIteration as e:
raise InferenceError(context=context) from e
# Ignore non string keys
if key.__class__.__name__ == "Const" and isinstance(key.value, str):
cls_locals[key.value].append(value)
Expand Down
34 changes: 20 additions & 14 deletions astroid/brain/brain_builtin_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def is_iterable(n):

try:
inferred = next(arg.infer(context))
except (InferenceError, NameInferenceError) as exc:
except (InferenceError, NameInferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc
if isinstance(inferred, nodes.Dict):
items = inferred.items
Expand Down Expand Up @@ -432,11 +432,11 @@ def infer_super(node, context=None):
else:
try:
mro_pointer = next(node.args[0].infer(context=context))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc
try:
mro_type = next(node.args[1].infer(context=context))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if mro_pointer is util.Uninferable or mro_type is util.Uninferable:
Expand All @@ -458,7 +458,7 @@ def _infer_getattr_args(node, context):
try:
obj = next(node.args[0].infer(context=context))
attr = next(node.args[1].infer(context=context))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if obj is util.Uninferable or attr is util.Uninferable:
Expand Down Expand Up @@ -496,7 +496,7 @@ def infer_getattr(node, context=None):
# Try to infer the default and return it instead.
try:
return next(node.args[2].infer(context=context))
except InferenceError as exc:
except (StopIteration, InferenceError) as exc:
raise UseInferenceDefault from exc

raise UseInferenceDefault
Expand Down Expand Up @@ -544,7 +544,7 @@ def infer_callable(node, context=None):
argument = node.args[0]
try:
inferred = next(argument.infer(context=context))
except InferenceError:
except (InferenceError, StopIteration):
return util.Uninferable
if inferred is util.Uninferable:
return util.Uninferable
Expand All @@ -564,7 +564,7 @@ def infer_property(node, context=None):
getter = node.args[0]
try:
inferred = next(getter.infer(context=context))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if not isinstance(inferred, (nodes.FunctionDef, nodes.Lambda)):
Expand Down Expand Up @@ -592,7 +592,7 @@ def infer_bool(node, context=None):
argument = node.args[0]
try:
inferred = next(argument.infer(context=context))
except InferenceError:
except (InferenceError, StopIteration):
return util.Uninferable
if inferred is util.Uninferable:
return util.Uninferable
Expand Down Expand Up @@ -682,7 +682,7 @@ def infer_issubclass(callnode, context=None):

try:
obj_type = next(obj_node.infer(context=context))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc
if not isinstance(obj_type, nodes.ClassDef):
raise UseInferenceDefault("TypeError: arg 1 must be class")
Expand Down Expand Up @@ -749,13 +749,19 @@ def _class_or_tuple_to_container(node, context=None):
# Move inferences results into container
# to simplify later logic
# raises InferenceError if any of the inferences fall through
node_infer = next(node.infer(context=context))
try:
node_infer = next(node.infer(context=context))
except StopIteration as e:
raise InferenceError(node=node, context=context) from e
# arg2 MUST be a type or a TUPLE of types
# for isinstance
if isinstance(node_infer, nodes.Tuple):
class_container = [
next(node.infer(context=context)) for node in node_infer.elts
]
try:
class_container = [
next(node.infer(context=context)) for node in node_infer.elts
]
except StopIteration as e:
raise InferenceError(node=node, context=context) from e
class_container = [
klass_node for klass_node in class_container if klass_node is not None
]
Expand Down Expand Up @@ -865,7 +871,7 @@ def _build_dict_with_elements(elements):
values = call.positional_arguments[0]
try:
inferred_values = next(values.infer(context=context))
except InferenceError:
except (InferenceError, StopIteration):
return _build_dict_with_elements([])
if inferred_values is util.Uninferable:
return _build_dict_with_elements([])
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _functools_partial_inference(node, context=None):
partial_function = call.positional_arguments[0]
try:
inferred_wrapped_function = next(partial_function.infer(context=context))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc
if inferred_wrapped_function is Uninferable:
raise UseInferenceDefault("Cannot infer the wrapped function")
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def Manager():
try:
context = next(node["default"].infer())
base = next(node["base"].infer())
except InferenceError:
except (InferenceError, StopIteration):
return module

for node in (context, base):
Expand Down
22 changes: 15 additions & 7 deletions astroid/brain/brain_namedtuple_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ def _infer_first(node, context):
raise UseInferenceDefault
try:
value = next(node.infer(context=context))
if value is util.Uninferable:
raise UseInferenceDefault()
return value
except StopIteration as exc:
raise InferenceError from exc
if value is util.Uninferable:
raise UseInferenceDefault()
return value


def _find_func_form_arguments(node, context):
Expand Down Expand Up @@ -184,10 +184,15 @@ def infer_named_tuple(node, context=None):
node, tuple_base_name, context=context
)
call_site = arguments.CallSite.from_call(node, context=context)
func = next(extract_node("import collections; collections.namedtuple").infer())
node = extract_node("import collections; collections.namedtuple")
try:

func = next(node.infer())
except StopIteration as e:
raise InferenceError(node=node) from e
try:
rename = next(call_site.infer_argument(func, "rename", context)).bool_value()
except InferenceError:
except (InferenceError, StopIteration):
rename = False

try:
Expand Down Expand Up @@ -471,7 +476,10 @@ def infer_typing_namedtuple_class(class_node, context=None):
"""
).format(typename=class_node.name, fields=",".join(annassigns_fields))
node = extract_node(code)
generated_class_node = next(infer_named_tuple(node, context))
try:
generated_class_node = next(infer_named_tuple(node, context))
except StopIteration as e:
raise InferenceError(node=node, context=context) from e
for method in class_node.mymethods():
generated_class_node.locals[method.name] = [method]

Expand Down Expand Up @@ -507,7 +515,7 @@ def infer_typing_namedtuple(node, context=None):
# so we extract the args and infer a named tuple.
try:
func = next(node.func.infer())
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if func.qname() != "typing.NamedTuple":
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_nose.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Test(unittest.TestCase):
)
try:
case = next(module["a"].infer())
except InferenceError:
except (InferenceError, StopIteration):
return
for method in case.methods():
if method.name.startswith("assert") and "_" not in method.name:
Expand Down
2 changes: 1 addition & 1 deletion astroid/brain/brain_six.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def transform_six_add_metaclass(node): # pylint: disable=inconsistent-return-st

try:
func = next(decorator.func.infer())
except InferenceError:
except (InferenceError, StopIteration):
continue
if func.qname() == SIX_ADD_METACLASS and decorator.args:
metaclass = decorator.args[0]
Expand Down
15 changes: 11 additions & 4 deletions astroid/brain/brain_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def infer_typing_typevar_or_newtype(node, context_itton=None):
"""Infer a typing.TypeVar(...) or typing.NewType(...) call"""
try:
func = next(node.func.infer(context=context_itton))
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if func.qname() not in TYPING_TYPEVARS_QUALIFIED:
Expand Down Expand Up @@ -145,7 +145,7 @@ def infer_typing_attr(
"""Infer a typing.X[...] subscript"""
try:
value = next(node.value.infer())
except InferenceError as exc:
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc

if (
Expand Down Expand Up @@ -269,7 +269,11 @@ def infer_typing_alias(
or not isinstance(node.parent.targets[0], AssignName)
):
return None
res = next(node.args[0].infer(context=ctx))
try:
res = next(node.args[0].infer(context=ctx))
except StopIteration as e:
raise InferenceError(node=node.args[0], context=context) from e

assign_name = node.parent.targets[0]

class_def = ClassDef(
Expand Down Expand Up @@ -333,7 +337,10 @@ def infer_tuple_alias(
node: Call, ctx: context.InferenceContext = None
) -> typing.Iterator[ClassDef]:
"""Infer call to tuple alias as new subscriptable class typing.Tuple."""
res = next(node.args[0].infer(context=ctx))
try:
res = next(node.args[0].infer(context=ctx))
except StopIteration as e:
raise InferenceError(node=node.args[0], context=context) from e
class_def = ClassDef(
name="Tuple",
parent=node.parent,
Expand Down
Loading