//! 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, 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, 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, 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::>(); 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, ) -> 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(), } }