9785: feature: Add completion for struct literals in which all fields are visible. r=Veykril a=Afourcat

This PR adds a new completion for struct literal.

It Implements the feature discussed in the issue #9610.

![RAExample3](https://user-images.githubusercontent.com/35599359/128211142-116361e9-7a69-425f-83ea-473c6ea47b26.gif)

This PR introduce a repetition in the source files `crates/ide_completion/render/pattern.rs` and `crates/ide_completion/render/struct_literal.rs` that may be fix in another PR.

Co-authored-by: Alexandre Fourcat <afourcat@gmail.com>
This commit is contained in:
bors[bot] 2021-08-05 16:17:38 +00:00 committed by GitHub
commit a7178cabf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 259 additions and 0 deletions

View File

@ -29,6 +29,7 @@
macro_::render_macro,
pattern::{render_struct_pat, render_variant_pat},
render_field, render_resolution, render_tuple_field,
struct_literal::render_struct_literal,
type_alias::{render_type_alias, render_type_alias_with_eq},
RenderContext,
},
@ -168,6 +169,16 @@ pub(crate) fn add_field(
self.add(item);
}
pub(crate) fn add_struct_literal(
&mut self,
ctx: &CompletionContext,
strukt: hir::Struct,
local_name: Option<hir::Name>,
) {
let item = render_struct_literal(RenderContext::new(ctx), strukt, local_name);
self.add_opt(item);
}
pub(crate) fn add_tuple_field(
&mut self,
ctx: &CompletionContext,

View File

@ -45,10 +45,81 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
Some(())
}
pub(crate) fn complete_record_literal(
acc: &mut Completions,
ctx: &CompletionContext,
) -> Option<()> {
if !ctx.expects_expression() {
return None;
}
if let hir::Adt::Struct(strukt) = ctx.expected_type.as_ref()?.as_adt()? {
acc.add_struct_literal(ctx, strukt, None);
}
Some(())
}
#[cfg(test)]
mod tests {
use crate::tests::check_edit;
#[test]
fn literal_struct_completion_edit() {
check_edit(
"FooDesc {…}",
r#"
struct FooDesc { pub bar: bool }
fn create_foo(foo_desc: &FooDesc) -> () { () }
fn baz() {
let foo = create_foo(&$0);
}
"#,
r#"
struct FooDesc { pub bar: bool }
fn create_foo(foo_desc: &FooDesc) -> () { () }
fn baz() {
let foo = create_foo(&FooDesc { bar: ${1:()} }$0);
}
"#,
)
}
#[test]
fn literal_struct_complexion_module() {
check_edit(
"FooDesc {…}",
r#"
mod _69latrick {
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
}
fn baz() {
use _69latrick::*;
let foo = create_foo(&$0);
}
"#,
r#"
mod _69latrick {
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
}
fn baz() {
use _69latrick::*;
let foo = create_foo(&FooDesc { six: ${1:()}, neuf: ${2:()}, bar: ${3:()} }$0);
}
"#,
);
}
#[test]
fn default_completion_edit() {
check_edit(

View File

@ -156,6 +156,7 @@ pub fn completions(
completions::unqualified_path::complete_unqualified_path(&mut acc, &ctx);
completions::dot::complete_dot(&mut acc, &ctx);
completions::record::complete_record(&mut acc, &ctx);
completions::record::complete_record_literal(&mut acc, &ctx);
completions::pattern::complete_pattern(&mut acc, &ctx);
completions::postfix::complete_postfix(&mut acc, &ctx);
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);

View File

@ -7,6 +7,7 @@
pub(crate) mod const_;
pub(crate) mod pattern;
pub(crate) mod type_alias;
pub(crate) mod struct_literal;
mod builder_ext;

View File

@ -0,0 +1,121 @@
//! Renderer for `struct` literal.
use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
use ide_db::helpers::SnippetCap;
use itertools::Itertools;
use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
pub(crate) fn render_struct_literal(
ctx: RenderContext<'_>,
strukt: hir::Struct,
local_name: Option<Name>,
) -> Option<CompletionItem> {
let _p = profile::span("render_struct_literal");
let fields = strukt.fields(ctx.db());
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
if fields_omitted {
// If some fields are private you can't make `struct` literal.
return None;
}
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
let literal = render_literal(&ctx, &name, strukt.kind(ctx.db()), &visible_fields)?;
Some(build_completion(ctx, name, literal, strukt))
}
fn build_completion(
ctx: RenderContext<'_>,
name: String,
literal: String,
def: impl HasAttrs + Copy,
) -> CompletionItem {
let mut item = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name + " {…}");
item.kind(CompletionItemKind::Snippet)
.set_documentation(ctx.docs(def))
.set_deprecated(ctx.is_deprecated(def))
.detail(&literal);
if let Some(snippet_cap) = ctx.snippet_cap() {
item.insert_snippet(snippet_cap, literal);
} else {
item.insert_text(literal);
};
item.build()
}
fn render_literal(
ctx: &RenderContext<'_>,
name: &str,
kind: StructKind,
fields: &[hir::Field],
) -> Option<String> {
let mut literal = match kind {
StructKind::Tuple if ctx.snippet_cap().is_some() => render_tuple_as_literal(fields, name),
StructKind::Record => render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, name),
_ => return None,
};
if ctx.completion.is_param {
literal.push(':');
literal.push(' ');
literal.push_str(name);
}
if ctx.snippet_cap().is_some() {
literal.push_str("$0");
}
Some(literal)
}
fn render_record_as_literal(
db: &dyn HirDatabase,
snippet_cap: Option<SnippetCap>,
fields: &[hir::Field],
name: &str,
) -> String {
let fields = fields.iter();
if snippet_cap.is_some() {
format!(
"{name} {{ {} }}",
fields
.enumerate()
.map(|(idx, field)| format!("{}: ${{{}:()}}", field.name(db), idx + 1))
.format(", "),
name = name
)
} else {
format!(
"{name} {{ {} }}",
fields.map(|field| format!("{}: ()", field.name(db))).format(", "),
name = name
)
}
}
fn render_tuple_as_literal(fields: &[hir::Field], name: &str) -> String {
format!(
"{name}({})",
fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
name = name
)
}
fn visible_fields(
ctx: &RenderContext<'_>,
fields: &[hir::Field],
item: impl HasAttrs,
) -> Option<(Vec<hir::Field>, bool)> {
let module = ctx.completion.scope.module()?;
let n_fields = fields.len();
let fields = fields
.iter()
.filter(|field| field.is_visible_from(ctx.db(), module))
.copied()
.collect::<Vec<_>>();
let fields_omitted =
n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
Some((fields, fields_omitted))
}

View File

@ -13,6 +13,60 @@ fn check_empty(ra_fixture: &str, expect: Expect) {
expect.assert_eq(&actual);
}
#[test]
fn complete_literal_struct_with_a_private_field() {
// `FooDesc.bar` is private, the completion should not be triggered.
check(
r#"
mod _69latrick {
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, bar: bool }
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
}
fn baz() {
use _69latrick::*;
let foo = create_foo(&$0);
}
"#,
// This should not contain `FooDesc {…}`.
expect![[r##"
kw unsafe
kw match
kw while
kw while let
kw loop
kw if
kw if let
kw for
kw true
kw false
kw mut
kw return
kw self
kw super
kw crate
st FooDesc
fn create_foo() fn(&FooDesc)
bt u32
tt Trait
en Enum
st Record
st Tuple
md module
fn baz() fn()
st Unit
md _69latrick
ma makro!() #[macro_export] macro_rules! makro
fn function() fn()
sc STATIC
un Union
ev TupleV() (u32)
ct CONST
"##]],
)
}
#[test]
fn completes_various_bindings() {
check_empty(