From b0ffd336cd61178a662751cedfd03e12da97090f Mon Sep 17 00:00:00 2001 From: AryanRG Date: Sat, 17 Oct 2026 14:01:37 +0530 Subject: [PATCH] Recognize walrus targets in call arguments --- AUTHORS.txt | 1 + parso/python/tree.py | 4 ++++ test/test_parser_tree.py | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 451918f..4ede3f9 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -6,6 +6,7 @@ David Halter (@davidhalter) Code Contributors ================= Alisdair Robertson (@robodair) +AryanRG (@AryanRG) Bryan Forbes (@bryanforbes) Likio3000 (@Likio3000) — documentation diff --git a/parso/python/tree.py b/parso/python/tree.py index 60a982d..da89f42 100644 --- a/parso/python/tree.py +++ b/parso/python/tree.py @@ -229,6 +229,10 @@ def get_definition(self, import_name_always=False, include_setitem=False): return node.parent # The try_stmt. return None + # Unparenthesized assignment expressions in calls use argument nodes. + if type_ == 'argument' and node.children[1] == ':=': + return node if node.children[0] is self else None + while node is not None: if node.type == 'suite': return None diff --git a/test/test_parser_tree.py b/test/test_parser_tree.py index b994b9b..d3cf90d 100644 --- a/test/test_parser_tree.py +++ b/test/test_parser_tree.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 # This file contains Unicode characters. +import sys from textwrap import dedent import pytest @@ -216,6 +217,47 @@ def test_is_definition(code, name_index, is_definition, include_setitem): assert name.is_definition(include_setitem=include_setitem) == is_definition +@pytest.mark.parametrize('code', [ + 'f(target := source)\n', + 'f(target := target)\n', + 'f(0, target := source)\n', + 'result = f(target := source)\n', + 'def outer():\n return f(target := source)\n', + 'f(target := source.attr)\n', + 'f(target := g(source))\n', + 'obj.f(target := source)\n', +]) +def test_walrus_argument_definition(code, version_ge_py38): + if sys.version_info >= (3, 8): + compile(code, '', 'exec') + module = parse(code, version=version_ge_py38, error_recovery=False) + names = module.get_used_names() + target = names['target'][0] + + assert target.get_definition() is target.parent + assert target.is_definition() + assert target.is_definition(include_setitem=True) + assert target.get_definition(import_name_always=True) is target.parent + for name in names.get('source', []) + names['target'][1:]: + assert name.get_definition() is None + assert not name.is_definition() + assert module.get_code() == code + + +@pytest.mark.parametrize('code, expected', [ + ('f(target=source)\n', [False]), + ('f(target)\n', [False]), + ('f(*target)\n', [False]), + ('f(**target)\n', [False]), + ('f(target for target in source)\n', [False, True]), + ('(target := source)\n', [True]), + ('f((target := source))\n', [True]), +]) +def test_argument_definition_controls(code, expected, version_ge_py38): + module = parse(code, version=version_ge_py38, error_recovery=False) + assert [name.is_definition() for name in module.get_used_names()['target']] == expected + + def test_iter_funcdefs(): code = dedent(''' def normal(): ...