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)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the ancestor nodes, starting from the root
|
/// Returns an iterator over the ancestor nodes, starting with the node itself
|
||||||
/// and ending with the covering node.
|
/// and walking towards the root.
|
||||||
pub(crate) fn ancestors(&self) -> impl Iterator<Item = AnyNodeRef<'a>> + '_ {
|
pub(crate) fn ancestors(&self) -> impl DoubleEndedIterator<Item = AnyNodeRef<'a>> + '_ {
|
||||||
self.nodes.iter().copied()
|
self.nodes.iter().copied().rev()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the index of the node that fully covers the range and
|
/// 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 {
|
impl CursorTest {
|
||||||
fn goto_definition(&self) -> String {
|
fn goto_definition(&self) -> String {
|
||||||
let Some(targets) = goto_definition(&self.db, self.cursor.file, self.cursor.offset)
|
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 covering = covering_node(parsed.syntax().into(), range);
|
||||||
|
|
||||||
let mut ranges = Vec::new();
|
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) {
|
if should_include_in_selection(node) {
|
||||||
let range = node.range();
|
let range = node.range();
|
||||||
// Eliminate duplicates when parent and child nodes have the same range
|
// Eliminate duplicates when parent and child nodes have the same range
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue