[ty] Refactor ranking
This moves the information we want to use to rank completions into `Completion` itself. (This was the primary motivation for creating a `CompletionBuilder` in the first place.) The principal advantage here is that we now only need to compute the relevance information for each completion exactly once. Previously, we were computing it on every comparison, which might end up doing redundant work for a not insignifcant number of completions. The relevance information is also specifically constructed from the builder so that, in the future, we might choose to short-circuit construction if we know we'll never send it back to the client (e.g., its ranking is worse than the lowest ranked completion). But we don't implement that optimization yet.
This commit is contained in:
committed by
Andrew Gallant
parent
e9cc2f6f42
commit
56862f8241
@@ -118,7 +118,7 @@ impl<'db> Completions<'db> {
|
||||
/// Convert this collection into a simple
|
||||
/// sequence of completions.
|
||||
fn into_completions(mut self) -> Vec<Completion<'db>> {
|
||||
self.items.sort_by(|c1, c2| self.context.compare(c1, c2));
|
||||
self.items.sort_by(rank);
|
||||
self.items
|
||||
.dedup_by(|c1, c2| (&c1.name, c1.module_name) == (&c2.name, c2.module_name));
|
||||
// A user should refine its completion request if the searched symbol doesn't appear in the first 1k results.
|
||||
@@ -129,7 +129,7 @@ impl<'db> Completions<'db> {
|
||||
|
||||
// Convert this collection into a list of "import..." fixes
|
||||
fn into_imports(mut self) -> Vec<ImportEdit> {
|
||||
self.items.sort_by(|c1, c2| self.context.compare(c1, c2));
|
||||
self.items.sort_by(rank);
|
||||
self.items
|
||||
.dedup_by(|c1, c2| (&c1.name, c1.module_name) == (&c2.name, c2.module_name));
|
||||
self.items
|
||||
@@ -145,7 +145,7 @@ impl<'db> Completions<'db> {
|
||||
|
||||
// Convert this collection into a list of "qualify..." fixes
|
||||
fn into_qualifications(mut self, range: TextRange) -> Vec<ImportEdit> {
|
||||
self.items.sort_by(|c1, c2| self.context.compare(c1, c2));
|
||||
self.items.sort_by(rank);
|
||||
self.items
|
||||
.dedup_by(|c1, c2| (&c1.name, c1.module_name) == (&c2.name, c2.module_name));
|
||||
self.items
|
||||
@@ -284,6 +284,9 @@ pub struct Completion<'db> {
|
||||
/// The documentation associated with this item, if
|
||||
/// available.
|
||||
pub documentation: Option<Docstring>,
|
||||
/// Information used to sort this completion relative to others
|
||||
/// in the same collection.
|
||||
relevance: Relevance,
|
||||
}
|
||||
|
||||
impl<'db> Completion<'db> {
|
||||
@@ -374,7 +377,7 @@ impl<'db> CompletionBuilder<'db> {
|
||||
mut self,
|
||||
db: &'db dyn Db,
|
||||
ctx: &CollectionContext<'db>,
|
||||
_query: &QueryPattern,
|
||||
query: &QueryPattern,
|
||||
) -> Completion<'db> {
|
||||
// Tags completions with context-specific if they are
|
||||
// known to be usable in a `raise` context and we have
|
||||
@@ -394,6 +397,7 @@ impl<'db> CompletionBuilder<'db> {
|
||||
let kind = self
|
||||
.kind
|
||||
.or_else(|| self.ty.and_then(|ty| completion_kind_from_type(db, ty)));
|
||||
let relevance = Relevance::new(ctx, query, &self);
|
||||
Completion {
|
||||
name: self.name,
|
||||
qualified: self.qualified,
|
||||
@@ -406,6 +410,7 @@ impl<'db> CompletionBuilder<'db> {
|
||||
is_type_check_only: self.is_type_check_only,
|
||||
is_context_specific: self.is_context_specific,
|
||||
documentation: self.documentation,
|
||||
relevance,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,49 +998,6 @@ impl<'db> CollectionContext<'db> {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Return an ordering relating the two completions.
|
||||
///
|
||||
/// A `Ordering::Less` is returned when `c1` should be ranked
|
||||
/// above `c2`. A `Ordering::Greater` is returned when `c1` should be
|
||||
/// ranked below `c2`. In other words, a standard ascending sort
|
||||
/// used with this comparison routine will yields the "best ranked"
|
||||
/// completions first.
|
||||
fn compare(&self, c1: &Completion<'_>, c2: &Completion<'_>) -> Ordering {
|
||||
self.rank(c1).cmp(&self.rank(c2))
|
||||
}
|
||||
|
||||
/// Return a rank for the given completion.
|
||||
///
|
||||
/// A smaller rank means the completion should appear higher in the
|
||||
/// results shown to end users.
|
||||
// Not currently using `self`, but we intend to in the future.
|
||||
// i.e., the context should be able to influence ranking in
|
||||
// some way.
|
||||
#[allow(clippy::unused_self)]
|
||||
fn rank<'c>(&self, c: &'c Completion<'_>) -> Relevance<'c> {
|
||||
Relevance {
|
||||
definitively_usable: if c.is_context_specific {
|
||||
Sort::Higher
|
||||
} else {
|
||||
Sort::Even
|
||||
},
|
||||
current_module: c.module_name.map(|_| Sort::Lower).unwrap_or(Sort::Higher),
|
||||
keyword: if c.kind == Some(CompletionKind::Keyword) {
|
||||
Sort::Higher
|
||||
} else {
|
||||
Sort::Even
|
||||
},
|
||||
builtin: if c.builtin { Sort::Lower } else { Sort::Even },
|
||||
name_kind: NameKind::classify(&c.name),
|
||||
type_check_only: if c.is_type_check_only {
|
||||
Sort::Lower
|
||||
} else {
|
||||
Sort::Even
|
||||
},
|
||||
name: &c.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Relevance information assigned to a single completion.
|
||||
@@ -1050,7 +1012,7 @@ impl<'db> CollectionContext<'db> {
|
||||
/// matter. The most important or overriding criteria should appear
|
||||
/// first.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
struct Relevance<'a> {
|
||||
struct Relevance {
|
||||
/// This is set when we know that a symbol in the current context
|
||||
/// is affirmatively usable or not. That is, other symbols in the
|
||||
/// results may not be usable (we may not know for sure), but
|
||||
@@ -1082,10 +1044,35 @@ struct Relevance<'a> {
|
||||
/// Sorts based on whether this symbol is only available during
|
||||
/// type checking and not at runtime.
|
||||
type_check_only: Sort,
|
||||
/// The name of a symbol. This is kind of a last ditch thing that
|
||||
/// we fallback to in order to provide some stable and predictable
|
||||
/// ordering, but otherwise means we've mostly given up.
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
impl Relevance {
|
||||
/// Return a rank for the given completion.
|
||||
///
|
||||
/// A smaller rank means the completion should appear higher in the
|
||||
/// results shown to end users.
|
||||
fn new(_ctx: &CollectionContext, _query: &QueryPattern, c: &CompletionBuilder) -> Relevance {
|
||||
Relevance {
|
||||
definitively_usable: if c.is_context_specific {
|
||||
Sort::Higher
|
||||
} else {
|
||||
Sort::Even
|
||||
},
|
||||
current_module: c.module_name.map(|_| Sort::Lower).unwrap_or(Sort::Higher),
|
||||
keyword: if c.kind == Some(CompletionKind::Keyword) {
|
||||
Sort::Higher
|
||||
} else {
|
||||
Sort::Even
|
||||
},
|
||||
builtin: if c.builtin { Sort::Lower } else { Sort::Even },
|
||||
name_kind: NameKind::classify(&c.name),
|
||||
type_check_only: if c.is_type_check_only {
|
||||
Sort::Lower
|
||||
} else {
|
||||
Sort::Even
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An instruction to indicate an ordering preference.
|
||||
@@ -2224,6 +2211,21 @@ fn completion_kind_from_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Comp
|
||||
imp(db, ty, &CompletionKindVisitor::default())
|
||||
}
|
||||
|
||||
/// Return an ordering relating the two completions.
|
||||
///
|
||||
/// A `Ordering::Less` is returned when `c1` should be ranked above
|
||||
/// `c2`. A `Ordering::Greater` is returned when `c1` should be ranked
|
||||
/// below `c2`. In other words, a standard ascending sort used with
|
||||
/// this comparison routine will yields the "best ranked" completions
|
||||
/// first.
|
||||
///
|
||||
/// Note that this could have been implemented via `Eq` and `Ord`
|
||||
/// impls on `Completion`, but is instead a separate function to avoid
|
||||
/// conflating relevance ranking with identity.
|
||||
fn rank(c1: &Completion<'_>, c2: &Completion<'_>) -> Ordering {
|
||||
(&c1.relevance, &c1.name).cmp(&(&c2.relevance, &c2.name))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_snapshot;
|
||||
|
||||
Reference in New Issue
Block a user