ide_completion/
item.rs

1//! See `CompletionItem` structure.
2
3use std::{fmt, mem};
4
5use hir::Mutability;
6use ide_db::text_edit::TextEdit;
7use ide_db::{
8    RootDatabase, SnippetCap, SymbolKind, documentation::Documentation,
9    imports::import_assets::LocatedImport,
10};
11use itertools::Itertools;
12use macros::UpmapFromRaFixture;
13use smallvec::SmallVec;
14use stdx::{format_to, impl_from, never};
15use syntax::{Edition, SmolStr, TextRange, TextSize, format_smolstr};
16
17use crate::{
18    context::{CompletionContext, PathCompletionCtx},
19    render::{RenderContext, render_path_resolution},
20};
21
22/// `CompletionItem` describes a single completion entity which expands to 1 or more entries in the
23/// editor pop-up.
24///
25/// It is basically a POD with various properties. To construct a [`CompletionItem`],
26/// use the [`Builder`] struct.
27#[derive(Clone, UpmapFromRaFixture)]
28#[non_exhaustive]
29pub struct CompletionItem {
30    /// Label in the completion pop up which identifies completion.
31    pub label: CompletionItemLabel,
32
33    /// Range of identifier that is being completed.
34    ///
35    /// It should be used primarily for UI, but we also use this to convert
36    /// generic TextEdit into LSP's completion edit (see conv.rs).
37    ///
38    /// `source_range` must contain the completion offset. `text_edit` should
39    /// start with what `source_range` points to, or VSCode will filter out the
40    /// completion silently.
41    pub source_range: TextRange,
42    /// What happens when user selects this item.
43    ///
44    /// Typically, replaces `source_range` with new identifier.
45    pub text_edit: TextEdit,
46    pub is_snippet: bool,
47
48    /// What item (struct, function, etc) are we completing.
49    pub kind: CompletionItemKind,
50
51    /// Lookup is used to check if completion item indeed can complete current
52    /// ident.
53    ///
54    /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
55    /// contains `bar` sub sequence), and `quux` will rejected.
56    pub lookup: SmolStr,
57
58    /// Additional info to show in the UI pop up.
59    pub detail: Option<String>,
60    // FIXME: Make this with `'db` lifetime.
61    pub documentation: Option<Documentation<'static>>,
62
63    /// Whether this item is marked as deprecated
64    pub deprecated: bool,
65
66    /// If completing a function call, ask the editor to show parameter popup
67    /// after completion.
68    pub trigger_call_info: bool,
69
70    /// We use this to sort completion. Relevance records facts like "do the
71    /// types align precisely?". We can't sort by relevances directly, they are
72    /// only partially ordered.
73    ///
74    /// Note that Relevance ignores fuzzy match score. We compute Relevance for
75    /// all possible items, and then separately build an ordered completion list
76    /// based on relevance and fuzzy matching with the already typed identifier.
77    pub relevance: CompletionRelevance,
78
79    /// Indicates that a reference or mutable reference to this variable is a
80    /// possible match.
81    // FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
82    // until we have more splitting completions in which case we should think about
83    // generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
84    pub ref_match: Option<(CompletionItemRefMode, TextSize)>,
85
86    /// The import data to add to completion's edits.
87    pub import_to_add: SmallVec<[String; 1]>,
88}
89
90#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
91pub struct CompletionItemLabel {
92    /// The primary label for the completion item.
93    pub primary: SmolStr,
94    /// The left detail for the completion item, usually rendered right next to the primary label.
95    pub detail_left: Option<String>,
96    /// The right detail for the completion item, usually rendered right aligned at the end of the completion item.
97    pub detail_right: Option<String>,
98}
99// We use custom debug for CompletionItem to make snapshot tests more readable.
100impl fmt::Debug for CompletionItem {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        let mut s = f.debug_struct("CompletionItem");
103        s.field("label", &self.label.primary)
104            .field("detail_left", &self.label.detail_left)
105            .field("detail_right", &self.label.detail_right)
106            .field("source_range", &self.source_range);
107        if self.text_edit.len() == 1 {
108            let atom = self.text_edit.iter().next().unwrap();
109            s.field("delete", &atom.delete);
110            s.field("insert", &atom.insert);
111        } else {
112            s.field("text_edit", &self.text_edit);
113        }
114        s.field("kind", &self.kind);
115        if self.lookup() != self.label.primary {
116            s.field("lookup", &self.lookup());
117        }
118        if let Some(detail) = &self.detail {
119            s.field("detail", &detail);
120        }
121        if let Some(documentation) = &self.documentation {
122            s.field("documentation", &documentation);
123        }
124        if self.deprecated {
125            s.field("deprecated", &true);
126        }
127
128        if self.relevance != CompletionRelevance::default() {
129            s.field("relevance", &self.relevance);
130        }
131
132        if let Some((ref_mode, offset)) = self.ref_match {
133            let prefix = match ref_mode {
134                CompletionItemRefMode::Reference(mutability) => match mutability {
135                    Mutability::Shared => "&",
136                    Mutability::Mut => "&mut ",
137                },
138                CompletionItemRefMode::Dereference => "*",
139            };
140            s.field("ref_match", &format!("{prefix}@{offset:?}"));
141        }
142        if self.trigger_call_info {
143            s.field("trigger_call_info", &true);
144        }
145        s.finish()
146    }
147}
148
149#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
150pub struct CompletionRelevance {
151    /// This is set when the identifier being completed matches up with the name that is expected,
152    /// like in a function argument.
153    ///
154    /// ```ignore
155    /// fn f(spam: String) {}
156    /// fn main() {
157    ///     let spam = 92;
158    ///     f($0) // name of local matches the name of param
159    /// }
160    /// ```
161    pub exact_name_match: bool,
162    /// See [`CompletionRelevanceTypeMatch`].
163    pub type_match: Option<CompletionRelevanceTypeMatch>,
164    /// Set for local variables.
165    ///
166    /// ```ignore
167    /// fn foo(a: u32) {
168    ///     let b = 0;
169    ///     $0 // `a` and `b` are local
170    /// }
171    /// ```
172    pub is_local: bool,
173    /// Populated when the completion item comes from a trait (impl).
174    pub trait_: Option<CompletionRelevanceTraitInfo>,
175    /// This is set when an import is suggested in a use item whose name is already imported.
176    pub is_name_already_imported: bool,
177    /// This is set for completions that will insert a `use` item.
178    pub requires_import: bool,
179    /// Set for item completions that are private but in the workspace.
180    pub is_private_editable: bool,
181    /// Set for postfix snippet item completions
182    pub postfix_match: Option<CompletionRelevancePostfixMatch>,
183    /// This is set for items that are function (associated or method)
184    pub function: Option<CompletionRelevanceFn>,
185    /// true when there is an `await.method()` or `iter().method()` completion.
186    pub is_skipping_completion: bool,
187}
188#[derive(Debug, Clone, Copy, Eq, PartialEq)]
189pub struct CompletionRelevanceTraitInfo {
190    /// The trait this item is from is a `#[doc(notable_trait)]`
191    pub notable_trait: bool,
192    /// Set for method completions of the `core::ops` and `core::cmp` family.
193    pub is_op_method: bool,
194}
195
196#[derive(Debug, Clone, Copy, Eq, PartialEq)]
197pub enum CompletionRelevanceTypeMatch {
198    /// This is set in cases like these:
199    ///
200    /// ```ignore
201    /// enum Option<T> { Some(T), None }
202    /// fn f(a: Option<u32>) {}
203    /// fn main {
204    ///     f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
205    /// }
206    /// ```
207    CouldUnify,
208    /// This is set in cases where the type matches the expected type, like:
209    ///
210    /// ```ignore
211    /// fn f(spam: String) {}
212    /// fn main() {
213    ///     let foo = String::new();
214    ///     f($0) // type of local matches the type of param
215    /// }
216    /// ```
217    Exact,
218}
219
220#[derive(Debug, Clone, Copy, Eq, PartialEq)]
221pub enum CompletionRelevancePostfixMatch {
222    /// Set in cases when item is postfix, but not exact
223    NonExact,
224    /// This is set in cases like these:
225    ///
226    /// ```ignore
227    /// (a > b).not$0
228    /// ```
229    ///
230    /// Basically, we want to guarantee that postfix snippets always takes
231    /// precedence over everything else.
232    Exact,
233}
234
235#[derive(Debug, Clone, Copy, Eq, PartialEq)]
236pub struct CompletionRelevanceFn {
237    pub has_params: bool,
238    pub has_self_param: bool,
239    pub return_type: CompletionRelevanceReturnType,
240}
241
242#[derive(Debug, Clone, Copy, Eq, PartialEq)]
243pub enum CompletionRelevanceReturnType {
244    Other,
245    /// Returns the Self type of the impl/trait
246    DirectConstructor,
247    /// Returns something that indirectly constructs the `Self` type of the impl/trait e.g. `Result<Self, ()>`, `Option<Self>`
248    Constructor,
249    /// Returns a possible builder for the type
250    Builder,
251}
252
253impl CompletionRelevance {
254    /// Provides a relevance score. Higher values are more relevant.
255    ///
256    /// The absolute value of the relevance score is not meaningful, for
257    /// example a value of BASE_SCORE doesn't mean "not relevant", rather
258    /// it means "least relevant". The score value should only be used
259    /// for relative ordering.
260    ///
261    /// See is_relevant if you need to make some judgement about score
262    /// in an absolute sense.
263    const BASE_SCORE: u32 = u32::MAX / 2;
264
265    pub fn score(self) -> u32 {
266        let mut score = Self::BASE_SCORE;
267        let CompletionRelevance {
268            exact_name_match,
269            type_match,
270            is_local,
271            is_name_already_imported,
272            requires_import,
273            is_private_editable,
274            postfix_match,
275            trait_,
276            function,
277            is_skipping_completion,
278        } = self;
279
280        // only applicable for completions within use items
281        // lower rank for conflicting import names
282        if is_name_already_imported {
283            score -= 1;
284        }
285        // slightly prefer locals
286        if is_local {
287            score += 1;
288        }
289
290        // lower rank private things
291        if !is_private_editable {
292            score += 1;
293        }
294
295        if let Some(trait_) = trait_ {
296            // lower rank trait methods unless its notable
297            if !trait_.notable_trait {
298                score -= 5;
299            }
300            // lower rank trait op methods
301            if trait_.is_op_method {
302                score -= 5;
303            }
304        }
305
306        // Lower rank for completions that skip `await` and `iter()`.
307        if is_skipping_completion {
308            score -= 7;
309        }
310
311        // lower rank for items that need an import
312        if requires_import {
313            score -= 1;
314        }
315        if exact_name_match {
316            score += 20;
317        }
318        match postfix_match {
319            Some(CompletionRelevancePostfixMatch::Exact) => score += 100,
320            Some(CompletionRelevancePostfixMatch::NonExact) => score -= 5,
321            None => (),
322        };
323        score += match type_match {
324            Some(CompletionRelevanceTypeMatch::Exact) => 18,
325            Some(CompletionRelevanceTypeMatch::CouldUnify) => 5,
326            None => 0,
327        };
328        if let Some(function) = function {
329            let mut fn_score = match function.return_type {
330                CompletionRelevanceReturnType::DirectConstructor => 15,
331                CompletionRelevanceReturnType::Builder => 10,
332                CompletionRelevanceReturnType::Constructor => 5,
333                CompletionRelevanceReturnType::Other => 0u32,
334            };
335
336            // When a fn is bumped due to return type:
337            // Bump Constructor or Builder methods with no arguments,
338            // over them than with self arguments
339            if function.has_params {
340                // bump associated functions
341                fn_score = fn_score.saturating_sub(1);
342            } else if function.has_self_param {
343                // downgrade methods (below Constructor)
344                fn_score = fn_score.min(1);
345            }
346
347            score += fn_score;
348        };
349
350        score
351    }
352
353    /// Returns true when the score for this threshold is above
354    /// some threshold such that we think it is especially likely
355    /// to be relevant.
356    pub fn is_relevant(&self) -> bool {
357        self.score() > Self::BASE_SCORE
358    }
359}
360
361/// The type of the completion item.
362#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
363pub enum CompletionItemKind {
364    SymbolKind(SymbolKind),
365    Binding,
366    BuiltinType,
367    InferredType,
368    Keyword,
369    Snippet,
370    UnresolvedReference,
371    Expression,
372}
373
374impl_from!(SymbolKind for CompletionItemKind);
375
376impl CompletionItemKind {
377    pub fn tag(self) -> &'static str {
378        match self {
379            CompletionItemKind::SymbolKind(kind) => match kind {
380                SymbolKind::Attribute => "at",
381                SymbolKind::BuiltinAttr => "ba",
382                SymbolKind::Const => "ct",
383                SymbolKind::ConstParam => "cp",
384                SymbolKind::CrateRoot => "cr",
385                SymbolKind::Derive => "de",
386                SymbolKind::DeriveHelper => "dh",
387                SymbolKind::Enum => "en",
388                SymbolKind::Field => "fd",
389                SymbolKind::Function => "fn",
390                SymbolKind::Impl => "im",
391                SymbolKind::InlineAsmRegOrRegClass => "ar",
392                SymbolKind::Label => "lb",
393                SymbolKind::LifetimeParam => "lt",
394                SymbolKind::Local => "lc",
395                SymbolKind::Macro => "ma",
396                SymbolKind::Method => "me",
397                SymbolKind::ProcMacro => "pm",
398                SymbolKind::Module => "md",
399                SymbolKind::SelfParam => "sp",
400                SymbolKind::SelfType => "sy",
401                SymbolKind::Static => "sc",
402                SymbolKind::Struct => "st",
403                SymbolKind::ToolModule => "tm",
404                SymbolKind::Trait => "tt",
405                SymbolKind::TypeAlias => "ta",
406                SymbolKind::TypeParam => "tp",
407                SymbolKind::Union => "un",
408                SymbolKind::ValueParam => "vp",
409                SymbolKind::Variant => "ev",
410            },
411            CompletionItemKind::Binding => "bn",
412            CompletionItemKind::BuiltinType => "bt",
413            CompletionItemKind::InferredType => "it",
414            CompletionItemKind::Keyword => "kw",
415            CompletionItemKind::Snippet => "sn",
416            CompletionItemKind::UnresolvedReference => "??",
417            CompletionItemKind::Expression => "ex",
418        }
419    }
420}
421
422#[derive(Copy, Clone, Debug)]
423pub enum CompletionItemRefMode {
424    Reference(Mutability),
425    Dereference,
426}
427
428impl CompletionItem {
429    pub(crate) fn new(
430        kind: impl Into<CompletionItemKind>,
431        source_range: TextRange,
432        label: impl Into<SmolStr>,
433        edition: Edition,
434    ) -> Builder {
435        let label = label.into();
436        Builder {
437            source_range,
438            label,
439            insert_text: None,
440            is_snippet: false,
441            trait_name: None,
442            detail: None,
443            documentation: None,
444            lookup: None,
445            kind: kind.into(),
446            text_edit: None,
447            deprecated: false,
448            trigger_call_info: false,
449            relevance: CompletionRelevance::default(),
450            ref_match: None,
451            imports_to_add: Default::default(),
452            doc_aliases: vec![],
453            edition,
454        }
455    }
456
457    /// What string is used for filtering.
458    pub fn lookup(&self) -> &str {
459        self.lookup.as_str()
460    }
461
462    pub fn ref_match(&self) -> Option<(String, ide_db::text_edit::Indel, CompletionRelevance)> {
463        // Relevance of the ref match should be the same as the original
464        // match, but with exact type match set because self.ref_match
465        // is only set if there is an exact type match.
466        let mut relevance = self.relevance;
467        relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
468
469        self.ref_match.map(|(mode, offset)| {
470            let prefix = match mode {
471                CompletionItemRefMode::Reference(Mutability::Shared) => "&",
472                CompletionItemRefMode::Reference(Mutability::Mut) => "&mut ",
473                CompletionItemRefMode::Dereference => "*",
474            };
475            let label = format!("{prefix}{}", self.label.primary);
476            (label, ide_db::text_edit::Indel::insert(offset, String::from(prefix)), relevance)
477        })
478    }
479}
480
481/// A helper to make `CompletionItem`s.
482#[must_use]
483#[derive(Clone)]
484pub(crate) struct Builder {
485    source_range: TextRange,
486    imports_to_add: SmallVec<[LocatedImport; 1]>,
487    trait_name: Option<SmolStr>,
488    doc_aliases: Vec<SmolStr>,
489    label: SmolStr,
490    insert_text: Option<String>,
491    is_snippet: bool,
492    detail: Option<String>,
493    // FIXME: Make this with `'db` lifetime.
494    documentation: Option<Documentation<'static>>,
495    lookup: Option<SmolStr>,
496    kind: CompletionItemKind,
497    text_edit: Option<TextEdit>,
498    deprecated: bool,
499    trigger_call_info: bool,
500    relevance: CompletionRelevance,
501    ref_match: Option<(CompletionItemRefMode, TextSize)>,
502    edition: Edition,
503}
504
505impl Builder {
506    pub(crate) fn from_resolution(
507        ctx: &CompletionContext<'_>,
508        path_ctx: &PathCompletionCtx<'_>,
509        local_name: hir::Name,
510        resolution: hir::ScopeDef,
511    ) -> Self {
512        let doc_aliases = ctx.doc_aliases_in_scope(resolution);
513        render_path_resolution(
514            RenderContext::new(ctx).doc_aliases(doc_aliases),
515            path_ctx,
516            local_name,
517            resolution,
518        )
519    }
520
521    pub(crate) fn build(self, db: &RootDatabase) -> CompletionItem {
522        let _p = tracing::info_span!("item::Builder::build").entered();
523
524        let label = self.label;
525        let mut lookup = self.lookup.unwrap_or_else(|| label.clone());
526        let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
527
528        let mut detail_left = None;
529        if !self.doc_aliases.is_empty() {
530            let doc_aliases = self.doc_aliases.iter().join(", ");
531            detail_left = Some(format!("(alias {doc_aliases})"));
532            let lookup_doc_aliases = self
533                .doc_aliases
534                .iter()
535                // Don't include aliases in `lookup` that aren't valid identifiers as including
536                // them results in weird completion filtering behavior e.g. `Partial>` matching
537                // `PartialOrd` because it has an alias of ">".
538                .filter(|alias| {
539                    let mut chars = alias.chars();
540                    chars.next().is_some_and(char::is_alphabetic)
541                        && chars.all(|c| c.is_alphanumeric() || c == '_')
542                })
543                // Deliberately concatenated without separators as adding separators e.g.
544                // `alias1, alias2` results in LSP clients continuing to display the completion even
545                // after typing a comma or space.
546                .join("");
547            if !lookup_doc_aliases.is_empty() {
548                lookup = format_smolstr!("{lookup}{lookup_doc_aliases}");
549            }
550        }
551        if let [import_edit] = &*self.imports_to_add {
552            // snippets can have multiple imports, but normal completions only have up to one
553            let detail_left = detail_left.get_or_insert_with(String::new);
554            format_to!(
555                detail_left,
556                "{}(use {})",
557                if detail_left.is_empty() { "" } else { " " },
558                import_edit.import_path.display(db, self.edition)
559            );
560        } else if let Some(trait_name) = self.trait_name {
561            let detail_left = detail_left.get_or_insert_with(String::new);
562            format_to!(
563                detail_left,
564                "{}(as {trait_name})",
565                if detail_left.is_empty() { "" } else { " " },
566            );
567        }
568
569        let text_edit = match self.text_edit {
570            Some(it) => it,
571            None => TextEdit::replace(self.source_range, insert_text),
572        };
573
574        let import_to_add = self
575            .imports_to_add
576            .into_iter()
577            .map(|import| import.import_path.display(db, self.edition).to_string())
578            .collect();
579
580        CompletionItem {
581            source_range: self.source_range,
582            label: CompletionItemLabel {
583                primary: label,
584                detail_left,
585                detail_right: self.detail.clone(),
586            },
587            text_edit,
588            is_snippet: self.is_snippet,
589            detail: self.detail,
590            documentation: self.documentation,
591            lookup,
592            kind: self.kind,
593            deprecated: self.deprecated,
594            trigger_call_info: self.trigger_call_info,
595            relevance: self.relevance,
596            ref_match: self.ref_match,
597            import_to_add,
598        }
599    }
600    pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
601        self.lookup = Some(lookup.into());
602        self
603    }
604    pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
605        self.label = label.into();
606        self
607    }
608    pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
609        self.trait_name = Some(trait_name);
610        self
611    }
612    pub(crate) fn doc_aliases(&mut self, doc_aliases: Vec<SmolStr>) -> &mut Builder {
613        self.doc_aliases = doc_aliases;
614        self
615    }
616    pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
617        self.insert_text = Some(insert_text.into());
618        self
619    }
620    pub(crate) fn insert_snippet(
621        &mut self,
622        cap: SnippetCap,
623        snippet: impl Into<String>,
624    ) -> &mut Builder {
625        let _ = cap;
626        self.is_snippet = true;
627        self.insert_text(snippet)
628    }
629    pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
630        self.text_edit = Some(edit);
631        self
632    }
633    pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
634        self.is_snippet = true;
635        self.text_edit(edit)
636    }
637    pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
638        self.set_detail(Some(detail))
639    }
640    pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
641        self.detail = detail.map(Into::into);
642        if let Some(detail) = &self.detail
643            && never!(detail.contains('\n'), "multiline detail:\n{}", detail)
644        {
645            self.detail = Some(detail.split('\n').next().unwrap().to_owned());
646        }
647        self
648    }
649    #[allow(unused)]
650    pub(crate) fn documentation(&mut self, docs: Documentation<'_>) -> &mut Builder {
651        self.set_documentation(Some(docs))
652    }
653    pub(crate) fn set_documentation(&mut self, docs: Option<Documentation<'_>>) -> &mut Builder {
654        self.documentation = docs.map(Documentation::into_owned);
655        self
656    }
657    pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
658        self.deprecated = deprecated;
659        self
660    }
661    pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
662        self.relevance = relevance;
663        self
664    }
665    pub(crate) fn with_relevance(
666        &mut self,
667        relevance: impl FnOnce(CompletionRelevance) -> CompletionRelevance,
668    ) -> &mut Builder {
669        self.relevance = relevance(mem::take(&mut self.relevance));
670        self
671    }
672    pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
673        self.trigger_call_info = true;
674        self
675    }
676    pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
677        self.imports_to_add.push(import_to_add);
678        self
679    }
680    pub(crate) fn ref_match(
681        &mut self,
682        ref_mode: CompletionItemRefMode,
683        offset: TextSize,
684    ) -> &mut Builder {
685        self.ref_match = Some((ref_mode, offset));
686        self
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use itertools::Itertools;
693    use test_utils::assert_eq_text;
694
695    use super::{
696        CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
697    };
698
699    /// Check that these are CompletionRelevance are sorted in ascending order
700    /// by their relevance score.
701    ///
702    /// We want to avoid making assertions about the absolute score of any
703    /// item, but we do want to assert whether each is >, <, or == to the
704    /// others.
705    ///
706    /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
707    ///     a.score < b.score == c.score < d.score
708    fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
709        let expected = format!("{expected_relevance_order:#?}");
710
711        let actual_relevance_order = expected_relevance_order
712            .into_iter()
713            .flatten()
714            .map(|r| (r.score(), r))
715            .sorted_by_key(|(score, _r)| *score)
716            .fold(
717                (u32::MIN, vec![vec![]]),
718                |(mut currently_collecting_score, mut out), (score, r)| {
719                    if currently_collecting_score == score {
720                        out.last_mut().unwrap().push(r);
721                    } else {
722                        currently_collecting_score = score;
723                        out.push(vec![r]);
724                    }
725                    (currently_collecting_score, out)
726                },
727            )
728            .1;
729
730        let actual = format!("{actual_relevance_order:#?}");
731
732        assert_eq_text!(&expected, &actual);
733    }
734
735    #[test]
736    fn relevance_score() {
737        use CompletionRelevance as Cr;
738        let default = Cr::default();
739        // This test asserts that the relevance score for these items is ascending, and
740        // that any items in the same vec have the same score.
741        let expected_relevance_order = vec![
742            vec![],
743            vec![Cr {
744                trait_: Some(crate::item::CompletionRelevanceTraitInfo {
745                    notable_trait: false,
746                    is_op_method: true,
747                }),
748                is_private_editable: true,
749                ..default
750            }],
751            vec![Cr {
752                trait_: Some(crate::item::CompletionRelevanceTraitInfo {
753                    notable_trait: false,
754                    is_op_method: true,
755                }),
756                ..default
757            }],
758            vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
759            vec![Cr { is_private_editable: true, ..default }],
760            vec![default],
761            vec![Cr { is_local: true, ..default }],
762            vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
763            vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
764            vec![Cr { exact_name_match: true, ..default }],
765            vec![Cr { exact_name_match: true, is_local: true, ..default }],
766            vec![Cr {
767                exact_name_match: true,
768                type_match: Some(CompletionRelevanceTypeMatch::Exact),
769                ..default
770            }],
771            vec![Cr {
772                exact_name_match: true,
773                type_match: Some(CompletionRelevanceTypeMatch::Exact),
774                is_local: true,
775                ..default
776            }],
777            vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
778        ];
779
780        check_relevance_score_ordered(expected_relevance_order);
781    }
782}