11210: feat: Deprioritize ops methods in completion r=Veykril a=Veykril

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/10593



Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2022-01-11 09:39:12 +00:00 committed by GitHub
commit 5a711d4f3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 38 deletions

View file

@ -1610,6 +1610,12 @@ pub struct Trait {
} }
impl Trait { impl Trait {
pub fn lang(db: &dyn HirDatabase, krate: Crate, name: &Name) -> Option<Trait> {
db.lang_item(krate.into(), name.to_smol_str())
.and_then(LangItemTarget::as_trait)
.map(Into::into)
}
pub fn module(self, db: &dyn HirDatabase) -> Module { pub fn module(self, db: &dyn HirDatabase) -> Module {
Module { id: self.id.lookup(db.upcast()).container } Module { id: self.id.lookup(db.upcast()).container }
} }

View file

@ -255,6 +255,10 @@ impl Attrs {
} }
} }
pub fn lang(&self) -> Option<&SmolStr> {
self.by_key("lang").string_value()
}
pub fn docs(&self) -> Option<Documentation> { pub fn docs(&self) -> Option<Documentation> {
let docs = self.by_key("doc").attrs().flat_map(|attr| match attr.input.as_deref()? { let docs = self.by_key("doc").attrs().flat_map(|attr| match attr.input.as_deref()? {
AttrInput::Literal(s) => Some(s), AttrInput::Literal(s) => Some(s),
@ -775,20 +779,20 @@ impl Attr {
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct AttrQuery<'a> { pub struct AttrQuery<'attr> {
attrs: &'a Attrs, attrs: &'attr Attrs,
key: &'static str, key: &'static str,
} }
impl<'a> AttrQuery<'a> { impl<'attr> AttrQuery<'attr> {
pub fn tt_values(self) -> impl Iterator<Item = &'a Subtree> { pub fn tt_values(self) -> impl Iterator<Item = &'attr Subtree> {
self.attrs().filter_map(|attr| match attr.input.as_deref()? { self.attrs().filter_map(|attr| match attr.input.as_deref()? {
AttrInput::TokenTree(it, _) => Some(it), AttrInput::TokenTree(it, _) => Some(it),
_ => None, _ => None,
}) })
} }
pub fn string_value(self) -> Option<&'a SmolStr> { pub fn string_value(self) -> Option<&'attr SmolStr> {
self.attrs().find_map(|attr| match attr.input.as_deref()? { self.attrs().find_map(|attr| match attr.input.as_deref()? {
AttrInput::Literal(it) => Some(it), AttrInput::Literal(it) => Some(it),
_ => None, _ => None,
@ -799,7 +803,7 @@ impl<'a> AttrQuery<'a> {
self.attrs().next().is_some() self.attrs().next().is_some()
} }
pub fn attrs(self) -> impl Iterator<Item = &'a Attr> + Clone { pub fn attrs(self) -> impl Iterator<Item = &'attr Attr> + Clone {
let key = self.key; let key = self.key;
self.attrs self.attrs
.iter() .iter()

View file

@ -144,8 +144,8 @@ impl LangItems {
let _p = profile::span("lang_item_query"); let _p = profile::span("lang_item_query");
let lang_items = db.crate_lang_items(start_crate); let lang_items = db.crate_lang_items(start_crate);
let start_crate_target = lang_items.items.get(&item); let start_crate_target = lang_items.items.get(&item);
if let Some(target) = start_crate_target { if let Some(&target) = start_crate_target {
return Some(*target); return Some(target);
} }
db.crate_graph()[start_crate] db.crate_graph()[start_crate]
.dependencies .dependencies

View file

@ -309,26 +309,6 @@ pub mod known {
wrapping_mul, wrapping_mul,
wrapping_sub, wrapping_sub,
// known methods of lang items // known methods of lang items
add,
mul,
sub,
div,
rem,
shl,
shr,
bitxor,
bitor,
bitand,
add_assign,
mul_assign,
sub_assign,
div_assign,
rem_assign,
shl_assign,
shr_assign,
bitxor_assign,
bitor_assign,
bitand_assign,
eq, eq,
ne, ne,
ge, ge,
@ -336,12 +316,38 @@ pub mod known {
le, le,
lt, lt,
// lang items // lang items
not, add_assign,
neg, add,
bitand_assign,
bitand,
bitor_assign,
bitor,
bitxor_assign,
bitxor,
deref_mut,
deref,
div_assign,
div,
fn_mut,
fn_once,
future_trait, future_trait,
owned_box,
index, index,
partial_ord index_mut,
mul_assign,
mul,
neg,
not,
owned_box,
partial_ord,
r#fn,
rem_assign,
rem,
shl_assign,
shl,
shr_assign,
shr,
sub_assign,
sub,
); );
// self/Self cannot be used as an identifier // self/Self cannot be used as an identifier

View file

@ -3,7 +3,7 @@
use std::iter; use std::iter;
use base_db::SourceDatabaseExt; use base_db::SourceDatabaseExt;
use hir::{Local, Name, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo}; use hir::{HasAttrs, Local, Name, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo};
use ide_db::{ use ide_db::{
active_parameter::ActiveParameter, active_parameter::ActiveParameter,
base_db::{FilePosition, SourceDatabase}, base_db::{FilePosition, SourceDatabase},
@ -85,6 +85,7 @@ pub(crate) enum ParamKind {
Function, Function,
Closure, Closure,
} }
/// `CompletionContext` is created early during completion to figure out, where /// `CompletionContext` is created early during completion to figure out, where
/// exactly is the cursor, syntax-wise. /// exactly is the cursor, syntax-wise.
#[derive(Debug)] #[derive(Debug)]
@ -120,6 +121,7 @@ pub(crate) struct CompletionContext<'a> {
pub(super) lifetime_ctx: Option<LifetimeContext>, pub(super) lifetime_ctx: Option<LifetimeContext>,
pub(super) pattern_ctx: Option<PatternContext>, pub(super) pattern_ctx: Option<PatternContext>,
pub(super) path_context: Option<PathCompletionContext>, pub(super) path_context: Option<PathCompletionContext>,
pub(super) locals: Vec<(Name, Local)>, pub(super) locals: Vec<(Name, Local)>,
no_completion_required: bool, no_completion_required: bool,
@ -308,6 +310,14 @@ impl<'a> CompletionContext<'a> {
self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL) self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL)
} }
/// Whether the given trait is an operator trait or not.
pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
match trait_.attrs(self.db).lang() {
Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang.as_str()),
None => false,
}
}
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items. /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) { pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let _p = profile::span("CompletionContext::process_all_names"); let _p = profile::span("CompletionContext::process_all_names");
@ -388,6 +398,7 @@ impl<'a> CompletionContext<'a> {
locals.push((name, local)); locals.push((name, local));
} }
}); });
let mut ctx = CompletionContext { let mut ctx = CompletionContext {
sema, sema,
scope, scope,
@ -889,6 +900,7 @@ fn pattern_context_for(pat: ast::Pat) -> PatternContext {
}); });
PatternContext { refutability, is_param, has_type_ascription } PatternContext { refutability, is_param, has_type_ascription }
} }
fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
syntax.covering_element(range).ancestors().find_map(N::cast) syntax.covering_element(range).ancestors().find_map(N::cast)
} }
@ -915,6 +927,37 @@ fn has_ref(token: &SyntaxToken) -> bool {
token.kind() == T![&] token.kind() == T![&]
} }
const OP_TRAIT_LANG_NAMES: &[&str] = &[
"add_assign",
"add",
"bitand_assign",
"bitand",
"bitor_assign",
"bitor",
"bitxor_assign",
"bitxor",
"deref_mut",
"deref",
"div_assign",
"div",
"fn_mut",
"fn_once",
"fn",
"index_mut",
"index",
"mul_assign",
"mul",
"neg",
"not",
"rem_assign",
"rem",
"shl_assign",
"shl",
"shr_assign",
"shr",
"sub",
"sub",
];
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};

View file

@ -139,6 +139,8 @@ pub struct CompletionRelevance {
/// } /// }
/// ``` /// ```
pub is_local: bool, pub is_local: bool,
/// Set for method completions of the `core::ops` family.
pub is_op_method: bool,
/// This is set in cases like these: /// This is set in cases like these:
/// ///
/// ``` /// ```
@ -175,6 +177,7 @@ pub enum CompletionRelevanceTypeMatch {
} }
impl CompletionRelevance { impl CompletionRelevance {
const BASE_LINE: u32 = 1;
/// Provides a relevance score. Higher values are more relevant. /// Provides a relevance score. Higher values are more relevant.
/// ///
/// The absolute value of the relevance score is not meaningful, for /// The absolute value of the relevance score is not meaningful, for
@ -185,7 +188,7 @@ impl CompletionRelevance {
/// See is_relevant if you need to make some judgement about score /// See is_relevant if you need to make some judgement about score
/// in an absolute sense. /// in an absolute sense.
pub fn score(&self) -> u32 { pub fn score(&self) -> u32 {
let mut score = 0; let mut score = Self::BASE_LINE;
if self.exact_name_match { if self.exact_name_match {
score += 1; score += 1;
@ -198,6 +201,9 @@ impl CompletionRelevance {
if self.is_local { if self.is_local {
score += 1; score += 1;
} }
if self.is_op_method {
score -= 1;
}
if self.exact_postfix_snippet_match { if self.exact_postfix_snippet_match {
score += 100; score += 100;
} }
@ -208,7 +214,7 @@ impl CompletionRelevance {
/// some threshold such that we think it is especially likely /// some threshold such that we think it is especially likely
/// to be relevant. /// to be relevant.
pub fn is_relevant(&self) -> bool { pub fn is_relevant(&self) -> bool {
self.score() > 0 self.score() > (Self::BASE_LINE + 1)
} }
} }
@ -558,6 +564,7 @@ mod tests {
// This test asserts that the relevance score for these items is ascending, and // This test asserts that the relevance score for these items is ascending, and
// that any items in the same vec have the same score. // that any items in the same vec have the same score.
let expected_relevance_order = vec![ let expected_relevance_order = vec![
vec![CompletionRelevance { is_op_method: true, ..CompletionRelevance::default() }],
vec![CompletionRelevance::default()], vec![CompletionRelevance::default()],
vec![ vec![
CompletionRelevance { exact_name_match: true, ..CompletionRelevance::default() }, CompletionRelevance { exact_name_match: true, ..CompletionRelevance::default() },
@ -588,10 +595,8 @@ mod tests {
..CompletionRelevance::default() ..CompletionRelevance::default()
}], }],
vec![CompletionRelevance { vec![CompletionRelevance {
exact_name_match: false,
type_match: None,
is_local: false,
exact_postfix_snippet_match: true, exact_postfix_snippet_match: true,
..CompletionRelevance::default()
}], }],
]; ];

View file

@ -400,6 +400,7 @@ mod tests {
(relevance.exact_name_match, "name"), (relevance.exact_name_match, "name"),
(relevance.is_local, "local"), (relevance.is_local, "local"),
(relevance.exact_postfix_snippet_match, "snippet"), (relevance.exact_postfix_snippet_match, "snippet"),
(relevance.is_op_method, "op_method"),
] ]
.into_iter() .into_iter()
.filter_map(|(cond, desc)| if cond { Some(desc) } else { None }) .filter_map(|(cond, desc)| if cond { Some(desc) } else { None })
@ -580,6 +581,7 @@ fn main() { let _: m::Spam = S$0 }
Exact, Exact,
), ),
is_local: false, is_local: false,
is_op_method: false,
exact_postfix_snippet_match: false, exact_postfix_snippet_match: false,
}, },
trigger_call_info: true, trigger_call_info: true,
@ -600,6 +602,7 @@ fn main() { let _: m::Spam = S$0 }
Exact, Exact,
), ),
is_local: false, is_local: false,
is_op_method: false,
exact_postfix_snippet_match: false, exact_postfix_snippet_match: false,
}, },
}, },
@ -685,6 +688,7 @@ fn foo() { A { the$0 } }
CouldUnify, CouldUnify,
), ),
is_local: false, is_local: false,
is_op_method: false,
exact_postfix_snippet_match: false, exact_postfix_snippet_match: false,
}, },
}, },
@ -1349,6 +1353,23 @@ fn main() {
) )
} }
#[test]
fn op_method_relevances() {
check_relevance(
r#"
#[lang = "sub"]
trait Sub {
fn sub(self, other: Self) -> Self { self }
}
impl Sub for u32 {}
fn foo(a: u32) { a.$0 }
"#,
expect![[r#"
me sub() (as Sub) [op_method]
"#]],
)
}
#[test] #[test]
fn struct_field_method_ref() { fn struct_field_method_ref() {
check_kinds( check_kinds(

View file

@ -70,6 +70,13 @@ fn render(
item.set_relevance(CompletionRelevance { item.set_relevance(CompletionRelevance {
type_match: compute_type_match(completion, &ret_type), type_match: compute_type_match(completion, &ret_type),
exact_name_match: compute_exact_name_match(completion, &call), exact_name_match: compute_exact_name_match(completion, &call),
is_op_method: match func_type {
FuncType::Method(_) => func
.as_assoc_item(ctx.db())
.and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
.map_or(false, |trait_| completion.is_ops_trait(trait_)),
_ => false,
},
..CompletionRelevance::default() ..CompletionRelevance::default()
}); });