From e017b039df5f17102e7ccfd7820814e029563102 Mon Sep 17 00:00:00 2001 From: Matthew Mckee Date: Mon, 3 Nov 2025 14:33:05 +0000 Subject: [PATCH] [ty] Favor in scope completions (#21194) ## 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. --- .../completion-evaluation-tasks.csv | 8 ++--- crates/ty_ide/src/completion.rs | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/crates/ty_completion_eval/completion-evaluation-tasks.csv b/crates/ty_completion_eval/completion-evaluation-tasks.csv index 01b8ca4373..4bea881bf6 100644 --- a/crates/ty_completion_eval/completion-evaluation-tasks.csv +++ b/crates/ty_completion_eval/completion-evaluation-tasks.csv @@ -1,5 +1,5 @@ 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 higher-level-symbols-preferred,main.py,0, 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,3,2 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 numpy-array,main.py,0, numpy-array,main.py,1,1 object-attr-instance-methods,main.py,0,1 object-attr-instance-methods,main.py,1,1 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-simple-long-identifier,main.py,0,1 tstring-completions,main.py,0,1 ty-extensions-lower-stdlib,main.py,0,8 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 diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 7ca15362b0..5b84e199f0 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -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" /// attributes, and all single-underscore attributes after dunder attributes. 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)] @@ -3440,8 +3447,8 @@ from os. .build() .snapshot(); assert_snapshot!(snapshot, @r" - AbraKadabra :: Unavailable :: package Kadabra :: Literal[1] :: Current module + AbraKadabra :: Unavailable :: package "); } @@ -4168,6 +4175,27 @@ type assert!(!builder.build().completions().is_empty()); } + #[test] + fn favour_symbols_currently_imported() { + let snapshot = CursorTest::builder() + .source("main.py", "long_nameb = 1\nlong_name") + .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 /// builder. ///