mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] fix GotoTargets for keyword args in nested function calls (#20013)
While implementing similar logic for initializers I noticed that this code appeared to be walking the ancestors in the wrong direction, and so if you have nested function calls it would always grab the outermost one instead of the closest-ancestor. The four copies of the test are because there's something really evil in our caching that can't seem to be demonstrated in our cursor testing framework, which I'm filing a followup for.
This commit is contained in:
parent
c68ff8d90b
commit
fc5321e000
3 changed files with 158 additions and 5 deletions
|
@ -116,10 +116,10 @@ impl<'a> CoveringNode<'a> {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the ancestor nodes, starting from the root
|
||||
/// and ending with the covering node.
|
||||
pub(crate) fn ancestors(&self) -> impl Iterator<Item = AnyNodeRef<'a>> + '_ {
|
||||
self.nodes.iter().copied()
|
||||
/// Returns an iterator over the ancestor nodes, starting with the node itself
|
||||
/// and walking towards the root.
|
||||
pub(crate) fn ancestors(&self) -> impl DoubleEndedIterator<Item = AnyNodeRef<'a>> + '_ {
|
||||
self.nodes.iter().copied().rev()
|
||||
}
|
||||
|
||||
/// Finds the index of the node that fully covers the range and
|
||||
|
|
|
@ -630,6 +630,158 @@ class MyClass: ...
|
|||
");
|
||||
}
|
||||
|
||||
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
||||
///
|
||||
/// In this case they ultimately have different signatures.
|
||||
#[test]
|
||||
fn goto_definition_nested_keyword_arg1() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
def my_func(ab, y, z = None): ...
|
||||
def my_other_func(ab, y): ...
|
||||
|
||||
my_other_func(my_func(a<CURSOR>b=5, y=2), 0)
|
||||
my_func(my_other_func(ab=5, y=2), 0)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | def my_func(ab, y, z = None): ...
|
||||
| ^^
|
||||
3 | def my_other_func(ab, y): ...
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:23
|
||||
|
|
||||
3 | def my_other_func(ab, y): ...
|
||||
4 |
|
||||
5 | my_other_func(my_func(ab=5, y=2), 0)
|
||||
| ^^
|
||||
6 | my_func(my_other_func(ab=5, y=2), 0)
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
||||
///
|
||||
/// In this case they ultimately have different signatures.
|
||||
#[test]
|
||||
fn goto_definition_nested_keyword_arg2() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
def my_func(ab, y, z = None): ...
|
||||
def my_other_func(ab, y): ...
|
||||
|
||||
my_other_func(my_func(ab=5, y=2), 0)
|
||||
my_func(my_other_func(a<CURSOR>b=5, y=2), 0)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:3:19
|
||||
|
|
||||
2 | def my_func(ab, y, z = None): ...
|
||||
3 | def my_other_func(ab, y): ...
|
||||
| ^^
|
||||
4 |
|
||||
5 | my_other_func(my_func(ab=5, y=2), 0)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:23
|
||||
|
|
||||
5 | my_other_func(my_func(ab=5, y=2), 0)
|
||||
6 | my_func(my_other_func(ab=5, y=2), 0)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
||||
///
|
||||
/// In this case they have identical signatures.
|
||||
#[test]
|
||||
fn goto_definition_nested_keyword_arg3() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
def my_func(ab, y): ...
|
||||
def my_other_func(ab, y): ...
|
||||
|
||||
my_other_func(my_func(a<CURSOR>b=5, y=2), 0)
|
||||
my_func(my_other_func(ab=5, y=2), 0)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | def my_func(ab, y): ...
|
||||
| ^^
|
||||
3 | def my_other_func(ab, y): ...
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:23
|
||||
|
|
||||
3 | def my_other_func(ab, y): ...
|
||||
4 |
|
||||
5 | my_other_func(my_func(ab=5, y=2), 0)
|
||||
| ^^
|
||||
6 | my_func(my_other_func(ab=5, y=2), 0)
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// goto-definition on a nested call using a keyword arg where both funcs have that arg name
|
||||
///
|
||||
/// In this case they have identical signatures.
|
||||
#[test]
|
||||
fn goto_definition_nested_keyword_arg4() {
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
def my_func(ab, y): ...
|
||||
def my_other_func(ab, y): ...
|
||||
|
||||
my_other_func(my_func(ab=5, y=2), 0)
|
||||
my_func(my_other_func(a<CURSOR>b=5, y=2), 0)
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-definition]: Definition
|
||||
--> main.py:3:19
|
||||
|
|
||||
2 | def my_func(ab, y): ...
|
||||
3 | def my_other_func(ab, y): ...
|
||||
| ^^
|
||||
4 |
|
||||
5 | my_other_func(my_func(ab=5, y=2), 0)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:23
|
||||
|
|
||||
5 | my_other_func(my_func(ab=5, y=2), 0)
|
||||
6 | my_func(my_other_func(ab=5, y=2), 0)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn goto_definition(&self) -> String {
|
||||
let Some(targets) = goto_definition(&self.db, self.cursor.file, self.cursor.offset)
|
||||
|
|
|
@ -14,7 +14,8 @@ pub fn selection_range(db: &dyn Db, file: File, offset: TextSize) -> Vec<TextRan
|
|||
let covering = covering_node(parsed.syntax().into(), range);
|
||||
|
||||
let mut ranges = Vec::new();
|
||||
for node in covering.ancestors() {
|
||||
// Start with the largest range (the root), so iterate ancestors backwards
|
||||
for node in covering.ancestors().rev() {
|
||||
if should_include_in_selection(node) {
|
||||
let range = node.range();
|
||||
// Eliminate duplicates when parent and child nodes have the same range
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue