From 0ee6bcf5863b3ecd94baf2c6a30a7eeac676a876 Mon Sep 17 00:00:00 2001 From: ostr00000 Date: Fri, 24 Mar 2023 23:27:43 +0100 Subject: [PATCH] create `NodeNG.inferred_best()` and replace `inferred()[0]` --- astroid/nodes/node_ng.py | 15 +++ tests/brain/numpy/test_core_einsumfunc.py | 2 +- tests/brain/numpy/test_core_numeric.py | 2 +- tests/brain/numpy/test_core_numerictypes.py | 4 +- tests/brain/numpy/test_ma.py | 2 +- tests/brain/numpy/test_ndarray.py | 2 +- tests/brain/test_brain.py | 4 +- tests/brain/test_ctypes.py | 8 +- tests/brain/test_enum.py | 2 +- tests/brain/test_qt.py | 6 +- tests/brain/test_signal.py | 2 +- tests/test_inference.py | 111 ++++++++++++++------ tests/test_lookup.py | 2 +- tests/test_scoped_nodes.py | 14 +-- 14 files changed, 118 insertions(+), 58 deletions(-) diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index b85707dafe..00d7a14448 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -612,6 +612,21 @@ def inferred(self): """ return list(self.infer()) + def inferred_best(self) -> InferenceResult: + """Return single inferred value, possibly avoid Uninferable. + + Try to return value of type other than :class:`UninferableBase`. + It is still possible to return :class:`UninferableBase` + if there is no other choice. + + :returns: The best inferred value. + :rtype: InferenceResult + """ + return sorted( + self.infer(), + key=lambda inf: isinstance(inf, util.UninferableBase) + )[0] + def instantiate_class(self): """Instantiate an instance of the defined class. diff --git a/tests/brain/numpy/test_core_einsumfunc.py b/tests/brain/numpy/test_core_einsumfunc.py index 593eeec276..b35b156822 100644 --- a/tests/brain/numpy/test_core_einsumfunc.py +++ b/tests/brain/numpy/test_core_einsumfunc.py @@ -49,7 +49,7 @@ def test_function_parameters() -> None: numpy.einsum #@ """ ) - actual_args = instance.inferred()[0].args + actual_args = instance.inferred_best().args assert actual_args.vararg == "operands" assert [arg.name for arg in actual_args.kwonlyargs] == ["out", "optimize"] diff --git a/tests/brain/numpy/test_core_numeric.py b/tests/brain/numpy/test_core_numeric.py index 2481ecefa3..77ee4554e3 100644 --- a/tests/brain/numpy/test_core_numeric.py +++ b/tests/brain/numpy/test_core_numeric.py @@ -74,5 +74,5 @@ def test_function_parameters(method: str, expected_args: list[str]) -> None: numpy.{method} #@ """ ) - actual_args = instance.inferred()[0].args.args + actual_args = instance.inferred_best().args.args assert [arg.name for arg in actual_args] == expected_args diff --git a/tests/brain/numpy/test_core_numerictypes.py b/tests/brain/numpy/test_core_numerictypes.py index 3cf053e96d..9e79a4f563 100644 --- a/tests/brain/numpy/test_core_numerictypes.py +++ b/tests/brain/numpy/test_core_numerictypes.py @@ -376,7 +376,7 @@ def test_generic_types_are_subscriptables(self): np.{type_}[int] """ node = builder.extract_node(src) - cls_node = node.inferred()[0] + cls_node = node.inferred_best() self.assertIsInstance(cls_node, nodes.ClassDef) self.assertEqual(cls_node.name, type_) @@ -407,5 +407,5 @@ def test_numpy_object_uninferable(self): np.number[int] """ node = builder.extract_node(src) - cls_node = node.inferred()[0] + cls_node = node.inferred_best() self.assertIs(cls_node, Uninferable) diff --git a/tests/brain/numpy/test_ma.py b/tests/brain/numpy/test_ma.py index 1b6bbaead8..daf8a6e72b 100644 --- a/tests/brain/numpy/test_ma.py +++ b/tests/brain/numpy/test_ma.py @@ -20,7 +20,7 @@ class TestBrainNumpyMa: def _assert_maskedarray(self, code): node = builder.extract_node(code) - cls_node = node.inferred()[0] + cls_node = node.inferred_best() assert cls_node.pytype() == "numpy.ma.core.MaskedArray" @pytest.mark.parametrize("alias_import", [True, False]) diff --git a/tests/brain/numpy/test_ndarray.py b/tests/brain/numpy/test_ndarray.py index 1fe0f1e74f..c1a3a1eece 100644 --- a/tests/brain/numpy/test_ndarray.py +++ b/tests/brain/numpy/test_ndarray.py @@ -165,6 +165,6 @@ def test_numpy_ndarray_class_support_type_indexing(self): np.ndarray[int] """ node = builder.extract_node(src) - cls_node = node.inferred()[0] + cls_node = node.inferred_best() self.assertIsInstance(cls_node, nodes.ClassDef) self.assertEqual(cls_node.name, "ndarray") diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index dc12ea28bf..538faf5ae3 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -146,7 +146,7 @@ def test_type_subscript(self): a: type[int] = int """ ) - val_inf = src.annotation.value.inferred()[0] + val_inf = src.annotation.value.inferred_best() self.assertIsInstance(val_inf, astroid.ClassDef) self.assertEqual(val_inf.name, "type") meth_inf = val_inf.getattr("__class_getitem__")[0] @@ -163,7 +163,7 @@ def test_invalid_type_subscript(self): a: str[int] = "abc" """ ) - val_inf = src.annotation.value.inferred()[0] + val_inf = src.annotation.value.inferred_best() self.assertIsInstance(val_inf, astroid.ClassDef) self.assertEqual(val_inf.name, "str") with self.assertRaises(AttributeInferenceError): diff --git a/tests/brain/test_ctypes.py b/tests/brain/test_ctypes.py index fe8b254158..19d38c3524 100644 --- a/tests/brain/test_ctypes.py +++ b/tests/brain/test_ctypes.py @@ -67,7 +67,7 @@ def test_ctypes_redefined_types_members(c_type, builtin_type, type_code): """ node = extract_node(src) assert isinstance(node, nodes.NodeNG) - node_inf = node.inferred()[0] + node_inf = node.inferred_best() assert node_inf.pytype() == f"builtins.{builtin_type}" src = f""" @@ -77,7 +77,7 @@ def test_ctypes_redefined_types_members(c_type, builtin_type, type_code): """ node = extract_node(src) assert isinstance(node, nodes.NodeNG) - node_inf = node.inferred()[0] + node_inf = node.inferred_best() assert isinstance(node_inf, nodes.Const) assert node_inf.value == type_code @@ -95,7 +95,7 @@ def test_cdata_member_access() -> None: """ node = extract_node(src) assert isinstance(node, nodes.NodeNG) - node_inf = node.inferred()[0] + node_inf = node.inferred_best() assert node_inf.display_type() == "Class" assert node_inf.qname() == "_ctypes._SimpleCData._objects" @@ -111,6 +111,6 @@ def test_other_ctypes_member_untouched() -> None: """ node = extract_node(src) assert isinstance(node, nodes.NodeNG) - node_inf = node.inferred()[0] + node_inf = node.inferred_best() assert isinstance(node_inf, nodes.Const) assert node_inf.value == 6 diff --git a/tests/brain/test_enum.py b/tests/brain/test_enum.py index 9d95d2ffbb..14ff15406e 100644 --- a/tests/brain/test_enum.py +++ b/tests/brain/test_enum.py @@ -482,7 +482,7 @@ def pear(self): # Test that both of the successfully inferred `Name` & `Attribute` # nodes refer to the user-defined Enum class. - for inferred in (attribute_nodes[0].inferred()[0], name_nodes[0].inferred()[0]): + for inferred in (attribute_nodes[0].inferred_best(), name_nodes[0].inferred_best()): assert isinstance(inferred, astroid.Instance) assert inferred.name == "Enum" assert inferred.qname() == "module_with_class_named_enum.Enum" diff --git a/tests/brain/test_qt.py b/tests/brain/test_qt.py index 2d029e024c..06d90cffc3 100644 --- a/tests/brain/test_qt.py +++ b/tests/brain/test_qt.py @@ -31,7 +31,7 @@ def test_value_of_lambda_instance_attrs_is_list(): printsupport.QPrintPreviewDialog.paintRequested #@ """ node = extract_node(src) - attribute_node = node.inferred()[0] + attribute_node = node.inferred_best() if attribute_node is Uninferable: pytest.skip("PyQt6 C bindings may not be installed?") assert isinstance(attribute_node, UnboundMethod) @@ -47,7 +47,7 @@ def test_implicit_parameters() -> None: timer.timeout.connect #@ """ node = extract_node(src) - attribute_node = node.inferred()[0] + attribute_node = node.inferred_best() if attribute_node is Uninferable: pytest.skip("PyQt6 C bindings may not be installed?") assert isinstance(attribute_node, FunctionDef) @@ -65,7 +65,7 @@ def test_slot_disconnect_no_args() -> None: timer.timeout.disconnect #@ """ node = extract_node(src) - attribute_node = node.inferred()[0] + attribute_node = node.inferred_best() if attribute_node is Uninferable: pytest.skip("PyQt6 C bindings may not be installed?") assert isinstance(attribute_node, FunctionDef) diff --git a/tests/brain/test_signal.py b/tests/brain/test_signal.py index fdd4f4270c..6e84fac189 100644 --- a/tests/brain/test_signal.py +++ b/tests/brain/test_signal.py @@ -30,7 +30,7 @@ def test_enum(enum_name): # Check the extracted node assert isinstance(node, nodes.NodeNG) - node_inf = node.inferred()[0] + node_inf = node.inferred_best() assert isinstance(node_inf, nodes.ClassDef) assert node_inf.display_type() == "Class" assert node_inf.is_subtype_of("enum.IntEnum") diff --git a/tests/test_inference.py b/tests/test_inference.py index 6ac55a429d..f3b76b0063 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -391,7 +391,7 @@ def f(): raise __(NotImplementedError) """ error = extract_node(code, __name__) - nie = error.inferred()[0] + nie = error.inferred_best() self.assertIsInstance(nie, nodes.ClassDef) nie_ancestors = [c.name for c in nie.ancestors()] expected = ["RuntimeError", "Exception", "BaseException", "object"] @@ -1242,7 +1242,7 @@ class B: ... for n in ast_nodes: assert n.inferred() == [util.Uninferable] else: - i0 = ast_nodes[0].inferred()[0] + i0 = ast_nodes[0].inferred_best() assert isinstance(i0, UnionType) assert isinstance(i0.left, nodes.ClassDef) assert i0.left.name == "int" @@ -1257,10 +1257,10 @@ class B: ... assert str(i0) == "UnionType(UnionType)" assert repr(i0) == f"" - i1 = ast_nodes[1].inferred()[0] + i1 = ast_nodes[1].inferred_best() assert isinstance(i1, UnionType) - i2 = ast_nodes[2].inferred()[0] + i2 = ast_nodes[2].inferred_best() assert isinstance(i2, UnionType) assert isinstance(i2.left, UnionType) assert isinstance(i2.left.left, nodes.ClassDef) @@ -1270,22 +1270,22 @@ class B: ... assert isinstance(i2.right, nodes.Const) assert i2.right.value is None - i3 = ast_nodes[3].inferred()[0] + i3 = ast_nodes[3].inferred_best() assert isinstance(i3, UnionType) assert isinstance(i3.left, nodes.ClassDef) assert i3.left.name == "A" assert isinstance(i3.right, nodes.ClassDef) assert i3.right.name == "B" - i4 = ast_nodes[4].inferred()[0] + i4 = ast_nodes[4].inferred_best() assert isinstance(i4, UnionType) - i5 = ast_nodes[5].inferred()[0] + i5 = ast_nodes[5].inferred_best() assert isinstance(i5, UnionType) assert isinstance(i5.left, nodes.ClassDef) assert i5.left.name == "List" - i6 = ast_nodes[6].inferred()[0] + i6 = ast_nodes[6].inferred_best() assert isinstance(i6, UnionType) assert isinstance(i6.left, nodes.ClassDef) assert i6.left.name == "tuple" @@ -1305,18 +1305,18 @@ class B: ... for n in ast_nodes: assert n.inferred() == [util.Uninferable] else: - i0 = ast_nodes[0].inferred()[0] + i0 = ast_nodes[0].inferred_best() assert isinstance(i0, UnionType) assert isinstance(i0.left, nodes.ClassDef) assert i0.left.name == "List" - i1 = ast_nodes[1].inferred()[0] + i1 = ast_nodes[1].inferred_best() assert isinstance(i1, UnionType) assert isinstance(i1.left, UnionType) assert isinstance(i1.left.left, nodes.ClassDef) assert i1.left.left.name == "str" - i2 = ast_nodes[2].inferred()[0] + i2 = ast_nodes[2].inferred_best() assert isinstance(i2, UnionType) assert isinstance(i2.left, nodes.ClassDef) assert i2.left.name == "List" @@ -1367,7 +1367,7 @@ def __init__(self): self.assertEqual(len(bar_class.instance_attrs["attr"]), 1) self.assertEqual(bar_class.instance_attrs, {"attr": [assattr]}) # call 'instance_attr' via 'Instance.getattr' to trigger the bug: - instance = bar_self.inferred()[0] + instance = bar_self.inferred_best() instance.getattr("attr") self.assertEqual(len(bar_class.instance_attrs["attr"]), 1) self.assertEqual(len(foo_class.instance_attrs["attr"]), 1) @@ -1384,7 +1384,7 @@ def test_nonregr_multi_referential_addition(self) -> None: a #@ """ variable_a = extract_node(code) - self.assertEqual(variable_a.inferred()[0].value, 2) + self.assertEqual(variable_a.inferred_best().value, 2) def test_nonregr_layed_dictunpack(self) -> None: """Regression test for https://github.com/PyCQA/astroid/issues/483 @@ -1397,7 +1397,7 @@ def test_nonregr_layed_dictunpack(self) -> None: new3 #@ """ ass = extract_node(code) - self.assertIsInstance(ass.inferred()[0], nodes.Dict) + self.assertIsInstance(ass.inferred_best(), nodes.Dict) def test_nonregr_inference_modifying_col_offset(self) -> None: """Make sure inference doesn't improperly modify col_offset. @@ -1646,11 +1646,11 @@ def test_pluggable_inference(self) -> None: B = namedtuple('B', 'a b') """ ast = parse(code, __name__) - aclass = ast["A"].inferred()[0] + aclass = ast["A"].inferred_best() self.assertIsInstance(aclass, nodes.ClassDef) self.assertIn("a", aclass.instance_attrs) self.assertIn("b", aclass.instance_attrs) - bclass = ast["B"].inferred()[0] + bclass = ast["B"].inferred_best() self.assertIsInstance(bclass, nodes.ClassDef) self.assertIn("a", bclass.instance_attrs) self.assertIn("b", bclass.instance_attrs) @@ -1678,18 +1678,18 @@ def empty_method(self): empty_list = A().empty_method() """ ast = parse(code, __name__) - int_node = ast["x"].inferred()[0] + int_node = ast["x"].inferred_best() self.assertIsInstance(int_node, nodes.Const) self.assertEqual(int_node.value, 1) - list_node = ast["y"].inferred()[0] + list_node = ast["y"].inferred_best() self.assertIsInstance(list_node, nodes.List) - int_node = ast["z"].inferred()[0] + int_node = ast["z"].inferred_best() self.assertIsInstance(int_node, nodes.Const) self.assertEqual(int_node.value, 1) - empty = ast["empty"].inferred()[0] + empty = ast["empty"].inferred_best() self.assertIsInstance(empty, nodes.Const) self.assertEqual(empty.value, 2) - empty_list = ast["empty_list"].inferred()[0] + empty_list = ast["empty_list"].inferred_best() self.assertIsInstance(empty_list, nodes.List) def test_infer_variable_arguments(self) -> None: @@ -1703,11 +1703,11 @@ def test(*args, **kwargs): vararg = func.body[0].value kwarg = func.body[1].value - kwarg_inferred = kwarg.inferred()[0] + kwarg_inferred = kwarg.inferred_best() self.assertIsInstance(kwarg_inferred, nodes.Dict) self.assertIs(kwarg_inferred.parent, func.args) - vararg_inferred = vararg.inferred()[0] + vararg_inferred = vararg.inferred_best() self.assertIsInstance(vararg_inferred, nodes.Tuple) self.assertIs(vararg_inferred.parent, func.args) @@ -1725,7 +1725,7 @@ def __init__(self): ast = parse(code, __name__) callfunc = next(ast.nodes_of_class(nodes.Call)) func = callfunc.func - inferred = func.inferred()[0] + inferred = func.inferred_best() self.assertIsInstance(inferred, UnboundMethod) def test_instance_binary_operations(self) -> None: @@ -1739,8 +1739,8 @@ def __mul__(self, other): mul = a * b """ ast = parse(code, __name__) - sub = ast["sub"].inferred()[0] - mul = ast["mul"].inferred()[0] + sub = ast["sub"].inferred_best() + mul = ast["mul"].inferred_best() self.assertIs(sub, util.Uninferable) self.assertIsInstance(mul, nodes.Const) self.assertEqual(mul.value, 42) @@ -1758,8 +1758,8 @@ class B(A): mul = a * b """ ast = parse(code, __name__) - sub = ast["sub"].inferred()[0] - mul = ast["mul"].inferred()[0] + sub = ast["sub"].inferred_best() + mul = ast["mul"].inferred_best() self.assertIs(sub, util.Uninferable) self.assertIsInstance(mul, nodes.Const) self.assertEqual(mul.value, 42) @@ -1778,8 +1778,8 @@ def __mul__(self, other): mul = a * b """ ast = parse(code, __name__) - sub = ast["sub"].inferred()[0] - mul = ast["mul"].inferred()[0] + sub = ast["sub"].inferred_best() + mul = ast["mul"].inferred_best() self.assertIs(sub, util.Uninferable) self.assertIsInstance(mul, nodes.List) self.assertIsInstance(mul.elts[0], nodes.Const) @@ -2198,7 +2198,7 @@ def test_dict_inference_unpack_repeated_key(self) -> None: ast = extract_node(code) final_values = ("{'data': 1}", "{'data': 0}", "{'data': 3}", "{'data': 0}") for node, final_value in zip(ast, final_values): - assert node.targets[0].inferred()[0].as_string() == final_value + assert node.targets[0].inferred_best().as_string() == final_value def test_dict_invalid_args(self) -> None: invalid_values = ["dict(*1)", "dict(**lala)", "dict(**[])"] @@ -4195,7 +4195,7 @@ def __call__(self): Clazz() #@ """ ) - .inferred()[0] + .inferred_best() .value ) assert val == 1 @@ -4211,7 +4211,7 @@ class Clazz(metaclass=_Meta): pass Clazz() #@ """ - ).inferred()[0] + ).inferred_best() assert isinstance(cls, nodes.ClassDef) and cls.name == "Clazz" def test_infer_subclass_attr_outer_class(self) -> None: @@ -4465,6 +4465,51 @@ def test_uninferable_type_subscript(self) -> None: with self.assertRaises(InferenceError): _ = next(node.infer()) + def test_inferred_best(self) -> None: + node = extract_node( + """ + if hasattr(typing, 'some_future_function'): + future_function = typing.some_future_function + else: + def future_function(arg: int) -> bool: + return False + future_function + """ + ) + inferred_all = node.inferred() + inferred_best = node.inferred_best() + assert inferred_best is not util.Uninferable + assert inferred_best in inferred_all + + def test_inferred_best_no_choice(self) -> None: + node = extract_node( + """ + if hasattr(typing, 'future_function'): + future_function = typing.future_function + else: + future_function = typing.old_function + future_function + """ + ) + inferred_all = node.inferred() + inferred_best = node.inferred_best() + assert inferred_best is util.Uninferable + assert inferred_best in inferred_all + + def test_inferred_best_any_value(self) -> None: + node = extract_node( + """ + if hasattr(typing, 'future_function'): + future_function = 1 + else: + future_function = '1' + future_function + """ + ) + inferred_all = node.inferred() + inferred_best = node.inferred_best() + assert inferred_best is not util.Uninferable + assert inferred_best in inferred_all class GetattrTest(unittest.TestCase): def test_yes_when_unknown(self) -> None: diff --git a/tests/test_lookup.py b/tests/test_lookup.py index cc882e6221..9952ec7e5e 100644 --- a/tests/test_lookup.py +++ b/tests/test_lookup.py @@ -334,7 +334,7 @@ def count(): """ ) next_node = tree.body[2].value.func - gener = next_node.expr.inferred()[0] + gener = next_node.expr.inferred_best() self.assertIsInstance(gener.getattr("__next__")[0], nodes.FunctionDef) self.assertIsInstance(gener.getattr("send")[0], nodes.FunctionDef) self.assertIsInstance(gener.getattr("throw")[0], nodes.FunctionDef) diff --git a/tests/test_scoped_nodes.py b/tests/test_scoped_nodes.py index a69983c65c..87ea4d655e 100644 --- a/tests/test_scoped_nodes.py +++ b/tests/test_scoped_nodes.py @@ -613,10 +613,10 @@ def test(): test() """ astroid = builder.parse(data, "mod") - func = astroid.body[2].value.func.inferred()[0] + func = astroid.body[2].value.func.inferred_best() self.assertIsInstance(func, nodes.FunctionDef) self.assertEqual(func.name, "test") - one = func.getattr("bar")[0].inferred()[0] + one = func.getattr("bar")[0].inferred_best() self.assertIsInstance(one, nodes.Const) self.assertEqual(one.value, 1) @@ -2543,7 +2543,7 @@ class Veg(Enum): Veg.TOMATO.value """ ) - inferred_member_value = node.inferred()[0] + inferred_member_value = node.inferred_best() assert isinstance(inferred_member_value, nodes.Const) assert inferred_member_value.value == "sweet" @@ -2563,7 +2563,7 @@ class Veg(Enum): Veg.TOMATO.value """ ) - inferred_member_value = node.inferred()[0] + inferred_member_value = node.inferred_best() assert inferred_member_value.value is None @@ -2578,7 +2578,7 @@ class Veg(Enum): Veg """ ) - inferred_class = node.inferred()[0] + inferred_class = node.inferred_best() assert "_value2member_map_" in inferred_class.locals @@ -2599,7 +2599,7 @@ class Veg(Enum): Veg.TOMATO.value """ ) - inferred_member_value = node.inferred()[0] + inferred_member_value = node.inferred_best() assert isinstance(inferred_member_value, nodes.Const) assert inferred_member_value.value == value @@ -2629,7 +2629,7 @@ class Veg(Enum): """ ) - inferred_member_value = member.inferred()[0] + inferred_member_value = member.inferred_best() assert not isinstance(inferred_member_value, nodes.Const) assert inferred_member_value.as_string() == repr(value)