diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 95721489d3..dea8a49509 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -122,8 +122,6 @@ impl<'db> Completions<'db> { /// sequence of completions. fn into_completions(mut self) -> Vec> { self.items.sort_unstable(); - self.items - .dedup_by(|c1, c2| (&c1.0.name, c1.0.module_name) == (&c2.0.name, c2.0.module_name)); self.items.truncate(Completions::LIMIT); self.items .into_iter() @@ -134,8 +132,6 @@ impl<'db> Completions<'db> { // Convert this collection into a list of "import..." fixes fn into_imports(mut self) -> Vec { self.items.sort_unstable(); - self.items - .dedup_by(|c1, c2| (&c1.0.name, c1.0.module_name) == (&c2.0.name, c2.0.module_name)); self.items .into_iter() .map(|CompletionRanker(c)| c) @@ -151,8 +147,6 @@ impl<'db> Completions<'db> { // Convert this collection into a list of "qualify..." fixes fn into_qualifications(mut self, range: TextRange) -> Vec { self.items.sort_unstable(); - self.items - .dedup_by(|c1, c2| (&c1.0.name, c1.0.module_name) == (&c2.0.name, c2.0.module_name)); self.items .into_iter() .map(|CompletionRanker(c)| c) @@ -208,11 +202,8 @@ impl<'db> Completions<'db> { if self.context.exclude(self.db, &builder) { return false; } - self.items.push(CompletionRanker(builder.build( - self.db, - &self.context, - &self.query, - ))); + let completion = builder.build(self.db, &self.context, &self.query); + self.items.push(CompletionRanker(completion)); true } } @@ -1409,11 +1400,11 @@ fn add_function_arg_completions<'db>( let Some(sig_help) = signature_help(db, file, cursor.offset) else { return; }; - let set_function_args = detect_set_function_args(cursor); + let mut set_function_args = detect_set_function_args(cursor); for sig in &sig_help.signatures { for p in &sig.parameters { - if p.is_positional_only || set_function_args.contains(&p.name.as_str()) { + if p.is_positional_only || !set_function_args.insert(p.name.as_str()) { continue; } let mut builder = CompletionBuilder::argument(&p.name).ty(p.ty); @@ -8170,6 +8161,20 @@ def f(x: Intersection[int, Any] | str): ); } + #[test] + fn no_duplicate_keyword_arg() { + let builder = completion_test_builder( + r#" +import re +re.match('', '', fla +"#, + ); + assert_snapshot!( + builder.skip_auto_import().skip_builtins().build().snapshot(), + @"flags=", + ); + } + /// A way to create a simple single-file (named `main.py`) completion test /// builder. /// diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index aaeacb2cd6..7ae87d78ad 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -251,6 +251,11 @@ impl<'db> SemanticModel<'db> { // Builtins are available in all scopes. let builtins = ModuleName::new_static("builtins").expect("valid module name"); completions.extend(self.module_completions(&builtins)); + + // The above can sometimes result in duplicates. Get rid of them. + completions.sort_by(|c1, c2| c1.name.cmp(&c2.name)); + completions.dedup_by(|c1, c2| c1.name == c2.name); + completions }