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