From 7cbf34a296c228f34e3741b63128d7cd36b47b92 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 13 Apr 2023 22:40:30 +0530 Subject: [PATCH 01/12] Use `safe-infer` to infer starred value --- astroid/arguments.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/astroid/arguments.py b/astroid/arguments.py index cc1b7359a9..feb94c84a8 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -134,14 +134,7 @@ def _unpack_args(self, args, context: InferenceContext | None = None): context.extra_context = self.argument_context_map for arg in args: if isinstance(arg, nodes.Starred): - try: - inferred = next(arg.value.infer(context=context)) - except InferenceError: - values.append(Uninferable) - continue - except StopIteration: - continue - + inferred = safe_infer(arg.value, context=context) if isinstance(inferred, UninferableBase): values.append(Uninferable) continue From 015500fc90921a7496c760f5a8a2c81feeeed00f Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 13 Apr 2023 22:43:55 +0530 Subject: [PATCH 02/12] Import `safe_infer` --- astroid/arguments.py | 1 + 1 file changed, 1 insertion(+) diff --git a/astroid/arguments.py b/astroid/arguments.py index feb94c84a8..ece138980d 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -8,6 +8,7 @@ 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.util import Uninferable, UninferableBase From b98df65b541823c17a8230151581497699d8661c Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 13 Apr 2023 23:07:26 +0530 Subject: [PATCH 03/12] Add test --- tests/test_inference.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index 84017ea1de..5e51cb4597 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -17,7 +17,7 @@ import pytest -from astroid import Slice, arguments, helpers, nodes, objects, test_utils, util +from astroid import Slice, Uninferable, arguments, helpers, nodes, objects, test_utils, util from astroid import decorators as decoratorsmod from astroid.arguments import CallSite from astroid.bases import BoundMethod, Instance, UnboundMethod, UnionType @@ -5296,6 +5296,29 @@ def test_duplicated_keyword_arguments(self) -> None: site = self._call_site_from_call(ast_node) self.assertIn("f", site.duplicated_keywords) + def test_call_site_uninferable(self) -> None: + code = """ + def get_nums(): + nums = () + if x == '1': + nums = (1, 2) + return nums + + def add(x, y): + return x + y + + nums = get_nums() + if nums: + add(*nums) + """ + # Test that `*nums` argument should be Uninferable + ast = parse(code, __name__) + add_call = list(ast.nodes_of_class(nodes.Call))[-1] + nums_arg = add_call.args[0] + call_site = self._call_site_from_call(add_call) + assert call_site._unpack_args([nums_arg]) == [Uninferable] + + class ObjectDunderNewTest(unittest.TestCase): def test_object_dunder_new_is_inferred_if_decorator(self) -> None: From c05dc9fd8a243c02a39e46bdbc13cd1fbac34fc6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 17:38:54 +0000 Subject: [PATCH 04/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_inference.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index 5e51cb4597..557165db22 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -17,7 +17,16 @@ import pytest -from astroid import Slice, Uninferable, arguments, helpers, nodes, objects, test_utils, util +from astroid import ( + Slice, + Uninferable, + arguments, + helpers, + nodes, + objects, + test_utils, + util, +) from astroid import decorators as decoratorsmod from astroid.arguments import CallSite from astroid.bases import BoundMethod, Instance, UnboundMethod, UnionType @@ -5319,7 +5328,6 @@ def add(x, y): assert call_site._unpack_args([nums_arg]) == [Uninferable] - class ObjectDunderNewTest(unittest.TestCase): def test_object_dunder_new_is_inferred_if_decorator(self) -> None: node = extract_node( From 278b0f6ea671d04bbab37bce8ef7801eefb74c75 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 14 Apr 2023 10:16:15 +0530 Subject: [PATCH 05/12] Fix code in `_unpack_keywords` as well --- astroid/arguments.py | 17 ++--------------- tests/test_inference.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/astroid/arguments.py b/astroid/arguments.py index ece138980d..0096cee243 100644 --- a/astroid/arguments.py +++ b/astroid/arguments.py @@ -92,27 +92,14 @@ def _unpack_keywords(self, keywords, context: InferenceContext | None = None): for name, value in keywords: if name is None: # Then it's an unpacking operation (**) - try: - inferred = next(value.infer(context=context)) - except InferenceError: - values[name] = Uninferable - continue - except StopIteration: - continue - + inferred = safe_infer(value, context=context) if not isinstance(inferred, nodes.Dict): # Not something we can work with. values[name] = Uninferable continue for dict_key, dict_value in inferred.items: - try: - dict_key = next(dict_key.infer(context=context)) - except InferenceError: - values[name] = Uninferable - continue - except StopIteration: - continue + dict_key = safe_infer(dict_key, context=context) if not isinstance(dict_key, nodes.Const): values[name] = Uninferable continue diff --git a/tests/test_inference.py b/tests/test_inference.py index 557165db22..96820e1784 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -5317,15 +5317,23 @@ def add(x, y): return x + y nums = get_nums() + + kwargs = {foo: bar, 1: baz} + if nums: add(*nums) + print(**kwargs) """ # Test that `*nums` argument should be Uninferable ast = parse(code, __name__) - add_call = list(ast.nodes_of_class(nodes.Call))[-1] + *_, add_call, print_call = list(ast.nodes_of_class(nodes.Call)) nums_arg = add_call.args[0] - call_site = self._call_site_from_call(add_call) - assert call_site._unpack_args([nums_arg]) == [Uninferable] + add_call_site = self._call_site_from_call(add_call) + assert add_call_site._unpack_args([nums_arg]) == [Uninferable] + + print_call_site = self._call_site_from_call(print_call) + keywords = CallContext(print_call.args, print_call.keywords).keywords + assert print_call_site._unpack_keywords(keywords) == {None: Uninferable} class ObjectDunderNewTest(unittest.TestCase): From 4c6041dfcff0d9a03713a2955d02377fb2ccf695 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 14 Apr 2023 10:18:11 +0530 Subject: [PATCH 06/12] Import CallContext --- tests/test_inference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index 96820e1784..123ca69796 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -32,7 +32,7 @@ from astroid.bases import BoundMethod, Instance, UnboundMethod, UnionType from astroid.builder import AstroidBuilder, _extract_single_node, extract_node, parse from astroid.const import PY38_PLUS, PY39_PLUS, PY310_PLUS -from astroid.context import InferenceContext +from astroid.context import CallContext, InferenceContext from astroid.exceptions import ( AstroidTypeError, AttributeInferenceError, From 739673d24a638e3666bc20e4d09a890dbc9ae29b Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 14 Apr 2023 17:47:07 +0530 Subject: [PATCH 07/12] Add ambiguity --- tests/test_inference.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index 123ca69796..5591fb3c9b 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -5318,7 +5318,10 @@ def add(x, y): nums = get_nums() - kwargs = {foo: bar, 1: baz} + if x: + kwargs = {1: bar} + else: + kwargs = {} if nums: add(*nums) From 767919734fa595058ab426506f6abac57a5930de Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 14 Apr 2023 17:48:41 +0530 Subject: [PATCH 08/12] Use assertEqual --- tests/test_inference.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index 5591fb3c9b..d55d42fb47 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -5332,11 +5332,11 @@ def add(x, y): *_, add_call, print_call = list(ast.nodes_of_class(nodes.Call)) nums_arg = add_call.args[0] add_call_site = self._call_site_from_call(add_call) - assert add_call_site._unpack_args([nums_arg]) == [Uninferable] + self.assertEqual(add_call_site._unpack_args([nums_arg]), [Uninferable]) print_call_site = self._call_site_from_call(print_call) keywords = CallContext(print_call.args, print_call.keywords).keywords - assert print_call_site._unpack_keywords(keywords) == {None: Uninferable} + self.assertEqual(print_call_site._unpack_keywords(keywords), {None: Uninferable}) class ObjectDunderNewTest(unittest.TestCase): From 43285cd535997657bf1f18e56633f3480ad11068 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 12:18:59 +0000 Subject: [PATCH 09/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_inference.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index d55d42fb47..b11541bc6c 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -5336,7 +5336,9 @@ def add(x, y): print_call_site = self._call_site_from_call(print_call) keywords = CallContext(print_call.args, print_call.keywords).keywords - self.assertEqual(print_call_site._unpack_keywords(keywords), {None: Uninferable}) + self.assertEqual( + print_call_site._unpack_keywords(keywords), {None: Uninferable} + ) class ObjectDunderNewTest(unittest.TestCase): From 8d1c66284ccc832abc9fe9ee839e9180605ced28 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Apr 2023 08:27:15 -0400 Subject: [PATCH 10/12] Show improved test result --- tests/test_inference.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index b11541bc6c..37a48f089d 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -1452,10 +1452,9 @@ def get_context_data(self, **kwargs): """ node = extract_node(code) assert isinstance(node, nodes.NodeNG) - result = node.inferred() - assert len(result) == 2 - assert isinstance(result[0], nodes.Dict) - assert result[1] is util.Uninferable + results = node.inferred() + assert len(results) == 2 + assert all(isinstance(result, nodes.Dict) for result in results) def test_python25_no_relative_import(self) -> None: ast = resources.build_file("data/package/absimport.py") From 34e0334b5bc432affed54c124892417a3f925693 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Sat, 15 Apr 2023 19:27:22 +0530 Subject: [PATCH 11/12] Add changelog entry --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index f6608caced..6f08822291 100644 --- a/ChangeLog +++ b/ChangeLog @@ -52,6 +52,8 @@ Release date: TBA * Remove dependency on ``wrapt``. +* ``CallSite._unpack_args`` and ``CallSite._unpack_keywords`` now use ``safe_infer()`` for + better inference and fewer false positives. What's New in astroid 2.15.3? ============================= From 1f4d9a132400264054093343cb46bc90b65814ca Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Apr 2023 09:59:51 -0400 Subject: [PATCH 12/12] Update ChangeLog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 6f08822291..460ef30507 100644 --- a/ChangeLog +++ b/ChangeLog @@ -55,6 +55,8 @@ Release date: TBA * ``CallSite._unpack_args`` and ``CallSite._unpack_keywords`` now use ``safe_infer()`` for better inference and fewer false positives. + Closes pylint-dev/pylint#8544 + What's New in astroid 2.15.3? ============================= Release date: TBA