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