Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Code common to structs, unions, and enum variants.

use crate::context::CompletionContext;
use hir::{HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind};
use ide_db::SnippetCap;
use itertools::Itertools;
use syntax::SmolStr;

/// A rendered struct, union, or enum variant, split into fields for actual
/// auto-completion (`literal`, using `field: ()`) and display in the
/// completions menu (`detail`, using `field: type`).
pub(crate) struct RenderedLiteral {
    pub(crate) literal: String,
    pub(crate) detail: String,
}

/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
/// the `name` argument for an anonymous type.
pub(crate) fn render_record_lit(
    ctx: &CompletionContext<'_>,
    snippet_cap: Option<SnippetCap>,
    fields: &[hir::Field],
    path: &str,
) -> RenderedLiteral {
    if snippet_cap.is_none() {
        return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() };
    }
    let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
        let mut fmt_field = |fill, tab| {
            let field_name = field.name(ctx.db);

            if let Some(local) = ctx.locals.get(&field_name)
                && local
                    .ty(ctx.db)
                    .could_unify_with_deeply(ctx.db, &field.ty(ctx.db).to_type(ctx.db))
            {
                f(&format_args!("{}{tab}", field_name.display(ctx.db, ctx.edition)))
            } else {
                f(&format_args!("{}: {fill}", field_name.display(ctx.db, ctx.edition)))
            }
        };
        if snippet_cap.is_some() {
            fmt_field(format_args!("${{{}:()}}", idx + 1), format_args!("${}", idx + 1))
        } else {
            fmt_field(format_args!("()"), format_args!(""))
        }
    });

    let types = fields.iter().format_with(", ", |field, f| {
        f(&format_args!(
            "{}: {}",
            field.name(ctx.db).display(ctx.db, ctx.edition),
            field.ty(ctx.db).display(ctx.db, ctx.display_target)
        ))
    });

    RenderedLiteral {
        literal: format!("{path} {{ {completions} }}"),
        detail: format!("{path} {{ {types} }}"),
    }
}

/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
/// the `name` argument for an anonymous type.
pub(crate) fn render_tuple_lit(
    ctx: &CompletionContext<'_>,
    snippet_cap: Option<SnippetCap>,
    fields: &[hir::Field],
    path: &str,
) -> RenderedLiteral {
    if snippet_cap.is_none() {
        return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() };
    }
    let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| {
        if snippet_cap.is_some() {
            f(&format_args!("${{{}:()}}", idx + 1))
        } else {
            f(&format_args!("()"))
        }
    });

    let types = fields
        .iter()
        .format_with(", ", |field, f| f(&field.ty(ctx.db).display(ctx.db, ctx.display_target)));

    RenderedLiteral {
        literal: format!("{path}({completions})"),
        detail: format!("{path}({types})"),
    }
}

/// Find all the visible fields in a given list. Returns the list of visible
/// fields, plus a boolean for whether the list is comprehensive (contains no
/// private fields and its item is not marked `#[non_exhaustive]`).
pub(crate) fn visible_fields(
    ctx: &CompletionContext<'_>,
    fields: &[hir::Field],
    item: impl HasAttrs + HasCrate + Copy,
) -> Option<(Vec<hir::Field>, bool)> {
    let module = ctx.module;
    let n_fields = fields.len();
    let fields = fields
        .iter()
        .filter(|field| field.is_visible_from(ctx.db, module))
        .copied()
        .collect::<Vec<_>>();
    let has_invisible_field = n_fields - fields.len() > 0;
    let is_foreign_non_exhaustive =
        item.attrs(ctx.db).is_non_exhaustive() && item.krate(ctx.db) != module.krate(ctx.db);
    let fields_omitted = has_invisible_field || is_foreign_non_exhaustive;
    Some((fields, fields_omitted))
}

/// Format a struct, etc. literal option for display in the completions menu.
pub(crate) fn format_literal_label(
    name: &str,
    kind: StructKind,
    snippet_cap: Option<SnippetCap>,
) -> SmolStr {
    if snippet_cap.is_none() {
        return name.into();
    }
    match kind {
        StructKind::Tuple => SmolStr::from_iter([name, "(…)"]),
        StructKind::Record => SmolStr::from_iter([name, " {…}"]),
        StructKind::Unit => name.into(),
    }
}

/// Format a struct, etc. literal option for lookup used in completions filtering.
pub(crate) fn format_literal_lookup(name: &str, kind: StructKind) -> SmolStr {
    match kind {
        StructKind::Tuple => SmolStr::from_iter([name, "()"]),
        StructKind::Record => SmolStr::from_iter([name, "{}"]),
        StructKind::Unit => name.into(),
    }
}