[ty] IDE: only provide declarations and bindings as completions (#18456)

## Summary

Previously, all symbols where provided as possible completions. In an
example like the following, both `foo` and `f` were suggested as
completions, because `f` itself is a symbol.
```py
foo = 1

f<CURSOR>
```
Similarly, in the following example, `hidden_symbol` was suggested, even
though it is not statically visible:
```py
if 1 + 2 != 3:
    hidden_symbol = 1

hidden_<CURSOR>
```

With the change suggested here, we only use statically visible
declarations and bindings as a source for completions.


## Test Plan

- Updated snapshot tests
- New test for statically hidden definitions
- Added test for star import
This commit is contained in:
David Peter 2025-06-04 16:11:05 +02:00 committed by GitHub
parent 11db567b0b
commit f1883d71a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 86 deletions

View file

@ -127,10 +127,7 @@ f<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -143,10 +140,7 @@ g<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
foo
g
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -175,10 +169,7 @@ f<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -208,7 +199,6 @@ def foo():
);
assert_snapshot!(test.completions(), @r"
f
foo
foofoo
");
@ -259,7 +249,6 @@ def foo():
);
assert_snapshot!(test.completions(), @r"
f
foo
foofoo
");
@ -276,7 +265,6 @@ def foo():
);
assert_snapshot!(test.completions(), @r"
f
foo
foofoo
");
@ -295,7 +283,6 @@ def frob(): ...
);
assert_snapshot!(test.completions(), @r"
f
foo
foofoo
frob
@ -315,7 +302,6 @@ def frob(): ...
);
assert_snapshot!(test.completions(), @r"
f
foo
frob
");
@ -334,7 +320,6 @@ def frob(): ...
);
assert_snapshot!(test.completions(), @r"
f
foo
foofoo
foofoofoo
@ -451,15 +436,10 @@ def frob(): ...
",
);
// It's not totally clear why `for` shows up in the
// symbol tables of the detected scopes here. My guess
// is that there's perhaps some sub-optimal behavior
// here because the list comprehension as written is not
// valid.
assert_snapshot!(test.completions(), @r"
bar
for
");
// TODO: it would be good if `bar` was included here, but
// the list comprehension is not yet valid and so we do not
// detect this as a definition of `bar`.
assert_snapshot!(test.completions(), @"<No completions found>");
}
#[test]
@ -470,10 +450,7 @@ def frob(): ...
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -484,10 +461,7 @@ def frob(): ...
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -498,10 +472,7 @@ def frob(): ...
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -512,10 +483,7 @@ def frob(): ...
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -526,10 +494,7 @@ def frob(): ...
",
);
assert_snapshot!(test.completions(), @r"
f
foo
");
assert_snapshot!(test.completions(), @"foo");
}
#[test]
@ -602,7 +567,6 @@ class Foo:
assert_snapshot!(test.completions(), @r"
Foo
b
bar
frob
quux
@ -621,7 +585,6 @@ class Foo:
assert_snapshot!(test.completions(), @r"
Foo
b
bar
quux
");
@ -750,7 +713,6 @@ bar(o<CURSOR>
assert_snapshot!(test.completions(), @r"
bar
foo
o
");
}
@ -788,7 +750,6 @@ class C:
assert_snapshot!(test.completions(), @r"
C
bar
f
foo
self
");
@ -825,7 +786,6 @@ class C:
assert_snapshot!(test.completions(), @r"
C
bar
f
foo
self
");
@ -854,11 +814,55 @@ print(f\"{some<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
print
some
some_symbol
");
assert_snapshot!(test.completions(), @"some_symbol");
}
#[test]
fn statically_invisible_symbols() {
let test = cursor_test(
"\
if 1 + 2 != 3:
hidden_symbol = 1
hidden_<CURSOR>
",
);
assert_snapshot!(test.completions(), @"<No completions found>");
}
#[test]
fn completions_inside_unreachable_sections() {
let test = cursor_test(
"\
import sys
if sys.platform == \"not-my-current-platform\":
only_available_in_this_branch = 1
on<CURSOR>
",
);
// TODO: ideally, `only_available_in_this_branch` should be available here, but we
// currently make no effort to provide a good IDE experience within sections that
// are unreachable
assert_snapshot!(test.completions(), @"sys");
}
#[test]
fn star_import() {
let test = cursor_test(
"\
from typing import *
Re<CURSOR>
",
);
test.assert_completions_include("Reversible");
// `ReadableBuffer` is a symbol in `typing`, but it is not re-exported
test.assert_completions_do_not_include("ReadableBuffer");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@ -921,10 +925,7 @@ Fo<CURSOR> = float
",
);
assert_snapshot!(test.completions(), @r"
Fo
float
");
assert_snapshot!(test.completions(), @"Fo");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@ -999,9 +1000,7 @@ except Type<CURSOR>:
",
);
assert_snapshot!(test.completions(), @r"
Type
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@ -1029,5 +1028,29 @@ def _():
.collect::<Vec<String>>()
.join("\n")
}
#[track_caller]
fn assert_completions_include(&self, expected: &str) {
let completions = completion(&self.db, self.file, self.cursor_offset);
assert!(
completions
.iter()
.any(|completion| completion.label == expected),
"Expected completions to include `{expected}`"
);
}
#[track_caller]
fn assert_completions_do_not_include(&self, unexpected: &str) {
let completions = completion(&self.db, self.file, self.cursor_offset);
assert!(
completions
.iter()
.all(|completion| completion.label != unexpected),
"Expected completions to not include `{unexpected}`",
);
}
}
}