]> git.lizzy.rs Git - rust.git/blob - crates/ide-completion/src/render/variant.rs
Auto merge of #12827 - Veykril:be-lazy, r=Veykril
[rust.git] / crates / ide-completion / src / render / variant.rs
1 //! Code common to structs, unions, and enum variants.
2
3 use crate::context::CompletionContext;
4 use hir::{db::HirDatabase, HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind};
5 use ide_db::SnippetCap;
6 use itertools::Itertools;
7 use syntax::SmolStr;
8
9 /// A rendered struct, union, or enum variant, split into fields for actual
10 /// auto-completion (`literal`, using `field: ()`) and display in the
11 /// completions menu (`detail`, using `field: type`).
12 pub(crate) struct RenderedLiteral {
13     pub(crate) literal: String,
14     pub(crate) detail: String,
15 }
16
17 /// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
18 /// the `name` argument for an anonymous type.
19 pub(crate) fn render_record_lit(
20     db: &dyn HirDatabase,
21     snippet_cap: Option<SnippetCap>,
22     fields: &[hir::Field],
23     path: &str,
24 ) -> RenderedLiteral {
25     let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
26         if snippet_cap.is_some() {
27             f(&format_args!("{}: ${{{}:()}}", field.name(db).escaped(), idx + 1))
28         } else {
29             f(&format_args!("{}: ()", field.name(db).escaped()))
30         }
31     });
32
33     let types = fields.iter().format_with(", ", |field, f| {
34         f(&format_args!("{}: {}", field.name(db), field.ty(db).display(db)))
35     });
36
37     RenderedLiteral {
38         literal: format!("{} {{ {} }}", path, completions),
39         detail: format!("{} {{ {} }}", path, types),
40     }
41 }
42
43 /// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
44 /// the `name` argument for an anonymous type.
45 pub(crate) fn render_tuple_lit(
46     db: &dyn HirDatabase,
47     snippet_cap: Option<SnippetCap>,
48     fields: &[hir::Field],
49     path: &str,
50 ) -> RenderedLiteral {
51     let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| {
52         if snippet_cap.is_some() {
53             f(&format_args!("${{{}:()}}", idx + 1))
54         } else {
55             f(&format_args!("()"))
56         }
57     });
58
59     let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db)));
60
61     RenderedLiteral {
62         literal: format!("{}({})", path, completions),
63         detail: format!("{}({})", path, types),
64     }
65 }
66
67 /// Find all the visible fields in a given list. Returns the list of visible
68 /// fields, plus a boolean for whether the list is comprehensive (contains no
69 /// private fields and its item is not marked `#[non_exhaustive]`).
70 pub(crate) fn visible_fields(
71     ctx: &CompletionContext<'_>,
72     fields: &[hir::Field],
73     item: impl HasAttrs + HasCrate + Copy,
74 ) -> Option<(Vec<hir::Field>, bool)> {
75     let module = ctx.module;
76     let n_fields = fields.len();
77     let fields = fields
78         .iter()
79         .filter(|field| field.is_visible_from(ctx.db, module))
80         .copied()
81         .collect::<Vec<_>>();
82     let has_invisible_field = n_fields - fields.len() > 0;
83     let is_foreign_non_exhaustive = item.attrs(ctx.db).by_key("non_exhaustive").exists()
84         && item.krate(ctx.db) != module.krate();
85     let fields_omitted = has_invisible_field || is_foreign_non_exhaustive;
86     Some((fields, fields_omitted))
87 }
88
89 /// Format a struct, etc. literal option for display in the completions menu.
90 pub(crate) fn format_literal_label(name: &str, kind: StructKind) -> SmolStr {
91     match kind {
92         StructKind::Tuple => SmolStr::from_iter([name, "(…)"]),
93         StructKind::Record => SmolStr::from_iter([name, " {…}"]),
94         StructKind::Unit => name.into(),
95     }
96 }