[ty] Favor in scope completions (#21194)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

Resolves https://github.com/astral-sh/ty/issues/1464

We sort the completions before we add the unimported ones, meaning that
imported completions show up before unimported ones.

This is also spoken about in
https://github.com/astral-sh/ty/issues/1274, and this is probably a
duplicate of that.

@AlexWaygood mentions this
[here](https://github.com/astral-sh/ty/issues/1274#issuecomment-3345942698)
too.

## Test Plan

Add a test showing even if an unimported completion "should"
(alphabetically before) come first, we favor the imported one.
This commit is contained in:
Matthew Mckee 2025-11-03 14:33:05 +00:00 committed by GitHub
parent 0dfd55babf
commit e017b039df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 35 additions and 7 deletions

View File

@ -1,5 +1,5 @@
name,file,index,rank name,file,index,rank
auto-import-skips-current-module,main.py,0,4 auto-import-skips-current-module,main.py,0,1
fstring-completions,main.py,0,1 fstring-completions,main.py,0,1
higher-level-symbols-preferred,main.py,0, higher-level-symbols-preferred,main.py,0,
higher-level-symbols-preferred,main.py,1,1 higher-level-symbols-preferred,main.py,1,1
@ -10,17 +10,17 @@ import-deprioritizes-type_check_only,main.py,1,1
import-deprioritizes-type_check_only,main.py,2,1 import-deprioritizes-type_check_only,main.py,2,1
import-deprioritizes-type_check_only,main.py,3,2 import-deprioritizes-type_check_only,main.py,3,2
import-deprioritizes-type_check_only,main.py,4,3 import-deprioritizes-type_check_only,main.py,4,3
internal-typeshed-hidden,main.py,0,4 internal-typeshed-hidden,main.py,0,5
none-completion,main.py,0,11 none-completion,main.py,0,11
numpy-array,main.py,0, numpy-array,main.py,0,
numpy-array,main.py,1,1 numpy-array,main.py,1,1
object-attr-instance-methods,main.py,0,1 object-attr-instance-methods,main.py,0,1
object-attr-instance-methods,main.py,1,1 object-attr-instance-methods,main.py,1,1
raise-uses-base-exception,main.py,0,2 raise-uses-base-exception,main.py,0,2
scope-existing-over-new-import,main.py,0,474 scope-existing-over-new-import,main.py,0,13
scope-prioritize-closer,main.py,0,2 scope-prioritize-closer,main.py,0,2
scope-simple-long-identifier,main.py,0,1 scope-simple-long-identifier,main.py,0,1
tstring-completions,main.py,0,1 tstring-completions,main.py,0,1
ty-extensions-lower-stdlib,main.py,0,8 ty-extensions-lower-stdlib,main.py,0,8
type-var-typing-over-ast,main.py,0,3 type-var-typing-over-ast,main.py,0,3
type-var-typing-over-ast,main.py,1,270 type-var-typing-over-ast,main.py,1,277

1 name file index rank
2 auto-import-skips-current-module main.py 0 4 1
3 fstring-completions main.py 0 1
4 higher-level-symbols-preferred main.py 0
5 higher-level-symbols-preferred main.py 1 1
10 import-deprioritizes-type_check_only main.py 2 1
11 import-deprioritizes-type_check_only main.py 3 2
12 import-deprioritizes-type_check_only main.py 4 3
13 internal-typeshed-hidden main.py 0 4 5
14 none-completion main.py 0 11
15 numpy-array main.py 0
16 numpy-array main.py 1 1
17 object-attr-instance-methods main.py 0 1
18 object-attr-instance-methods main.py 1 1
19 raise-uses-base-exception main.py 0 2
20 scope-existing-over-new-import main.py 0 474 13
21 scope-prioritize-closer main.py 0 2
22 scope-simple-long-identifier main.py 0 1
23 tstring-completions main.py 0 1
24 ty-extensions-lower-stdlib main.py 0 8
25 type-var-typing-over-ast main.py 0 3
26 type-var-typing-over-ast main.py 1 270 277

View File

@ -883,9 +883,16 @@ fn is_in_definition_place(db: &dyn Db, tokens: &[Token], file: File) -> bool {
/// This has the effect of putting all dunder attributes after "normal" /// This has the effect of putting all dunder attributes after "normal"
/// attributes, and all single-underscore attributes after dunder attributes. /// attributes, and all single-underscore attributes after dunder attributes.
fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering { fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
let (kind1, kind2) = (NameKind::classify(&c1.name), NameKind::classify(&c2.name)); fn key<'a>(completion: &'a Completion) -> (bool, NameKind, bool, &'a Name) {
(
completion.module_name.is_some(),
NameKind::classify(&completion.name),
completion.is_type_check_only,
&completion.name,
)
}
(kind1, c1.is_type_check_only, &c1.name).cmp(&(kind2, c2.is_type_check_only, &c2.name)) key(c1).cmp(&key(c2))
} }
#[cfg(test)] #[cfg(test)]
@ -3440,8 +3447,8 @@ from os.<CURSOR>
.build() .build()
.snapshot(); .snapshot();
assert_snapshot!(snapshot, @r" assert_snapshot!(snapshot, @r"
AbraKadabra :: Unavailable :: package
Kadabra :: Literal[1] :: Current module Kadabra :: Literal[1] :: Current module
AbraKadabra :: Unavailable :: package
"); ");
} }
@ -4168,6 +4175,27 @@ type <CURSOR>
assert!(!builder.build().completions().is_empty()); assert!(!builder.build().completions().is_empty());
} }
#[test]
fn favour_symbols_currently_imported() {
let snapshot = CursorTest::builder()
.source("main.py", "long_nameb = 1\nlong_name<CURSOR>")
.source("foo.py", "def long_namea(): ...")
.completion_test_builder()
.type_signatures()
.auto_import()
.module_names()
.filter(|c| c.name.contains("long_name"))
.build()
.snapshot();
// Even though long_namea is alphabetically before long_nameb,
// long_nameb is currently imported and should be preferred.
assert_snapshot!(snapshot, @r"
long_nameb :: Literal[1] :: Current module
long_namea :: Unavailable :: foo
");
}
/// A way to create a simple single-file (named `main.py`) completion test /// A way to create a simple single-file (named `main.py`) completion test
/// builder. /// builder.
/// ///