diff --git a/CHANGELOG.md b/CHANGELOG.md index ff6f27ed8..522298584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # **Upcoming release** - ... +- Fix inlining parameters used as indices in subscript assignments (#827). # Release 1.15.0 diff --git a/rope/base/worder.py b/rope/base/worder.py index 94089b5a8..092cbe68d 100644 --- a/rope/base/worder.py +++ b/rope/base/worder.py @@ -486,6 +486,12 @@ def get_assignment_type(self, offset): if double not in ("==", "<=", ">=", "!="): for op in [single, double, triple]: if op.endswith("="): + for end in range(next_char, next_char + len(op) - 1): + if self.code[end] in ")]": + # A subscript index is read, unlike an unpacking target. + parens_start = self._find_parens_start(end) + if self._find_primary_start(end) < parens_start: + return None return op def get_primary_range(self, offset): diff --git a/ropetest/codeanalyzetest.py b/ropetest/codeanalyzetest.py index 6dd16ab3b..874bb9193 100644 --- a/ropetest/codeanalyzetest.py +++ b/ropetest/codeanalyzetest.py @@ -387,6 +387,56 @@ def test_is_assigned_here_for_not_equal_test(self): word_finder = worder.Worder(code) self.assertFalse(word_finder.is_assigned_here(0)) + def test_subscript_assignment_does_not_assign_its_index(self): + for code in ( + "get_registry()[name] = value\n", + "registry[name]=value\n", + "registry[name]+=value\n", + "registry[(name)]=value\n", + "registry[left + name] = value\n", + "registry[:name] = value\n", + "registry[name:]=value\n", + "registry[registry[name]]=value\n", + "[registry[name]]=values\n", + ): + with self.subTest(code=code): + word_finder = worder.Worder(code) + self.assertFalse(word_finder.is_assigned_here(code.index("name"))) + + def test_grouped_and_list_assignment_targets_are_assigned(self): + for code in ( + "(name)=value\n", + "(name) = value\n", + "[name]=value\n", + "[name] = value\n", + "[(name)]=value\n", + "([name])=value\n", + ): + with self.subTest(code=code): + word_finder = worder.Worder(code) + self.assertTrue(word_finder.is_assigned_here(code.index("name"))) + + def test_assignment_operator_types(self): + for operator in ( + "=", "+=", "-=", "*=", "/=", "//=", "%=", "**=", "@=", + "&=", "|=", "^=", "<<=", ">>=", ":=", + ): + code = "name " + operator + " value\n" + if operator == ":=": + code = "(" + code.rstrip() + ")\n" + with self.subTest(operator=operator): + word_finder = worder.Worder(code) + self.assertEqual( + operator, word_finder.get_assignment_type(code.index("name")) + ) + + def test_comparison_operators_do_not_assign(self): + for operator in ("==", "!=", "<=", ">="): + code = "name " + operator + " value\n" + with self.subTest(operator=operator): + word_finder = worder.Worder(code) + self.assertFalse(word_finder.is_assigned_here(0)) + # XXX: is_assigned_here should work for tuple assignments def xxx_test_is_assigned_here_for_tuple_assignment(self): code = "a, b = (1, 2)\n" diff --git a/ropetest/refactor/inlinetest.py b/ropetest/refactor/inlinetest.py index 585214b41..13c4f3ae2 100644 --- a/ropetest/refactor/inlinetest.py +++ b/ropetest/refactor/inlinetest.py @@ -354,6 +354,26 @@ def a_func(param): self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1)\n", self.mod.read()) + def test_parameters_used_as_subscript_assignment_indices(self): + code = dedent("""\ + registry = {} + def get_registry(): + return registry + def register(name, value): + get_registry()[name] = value + register("cache", 42) + """) + refactored = self._inline(code, code.index("register") + 1) + self.assertEqual( + dedent("""\ + registry = {} + def get_registry(): + return registry + get_registry()["cache"] = 42 + """), + refactored, + ) + def test_parameters_renaming_for_passed_statements(self): self.mod.write(dedent("""\ def a_func(param):