Skip to content

Commit a7178ca

Browse files
bors[bot]Afourcat
andauthored
Merge #9785
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>
2 parents 80f5220 + 9beefef commit a7178ca

File tree

6 files changed

+259
-0
lines changed

6 files changed

+259
-0
lines changed

crates/ide_completion/src/completions.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::{
2929
macro_::render_macro,
3030
pattern::{render_struct_pat, render_variant_pat},
3131
render_field, render_resolution, render_tuple_field,
32+
struct_literal::render_struct_literal,
3233
type_alias::{render_type_alias, render_type_alias_with_eq},
3334
RenderContext,
3435
},
@@ -168,6 +169,16 @@ impl Completions {
168169
self.add(item);
169170
}
170171

172+
pub(crate) fn add_struct_literal(
173+
&mut self,
174+
ctx: &CompletionContext,
175+
strukt: hir::Struct,
176+
local_name: Option<hir::Name>,
177+
) {
178+
let item = render_struct_literal(RenderContext::new(ctx), strukt, local_name);
179+
self.add_opt(item);
180+
}
181+
171182
pub(crate) fn add_tuple_field(
172183
&mut self,
173184
ctx: &CompletionContext,

crates/ide_completion/src/completions/record.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,81 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
4545
Some(())
4646
}
4747

48+
pub(crate) fn complete_record_literal(
49+
acc: &mut Completions,
50+
ctx: &CompletionContext,
51+
) -> Option<()> {
52+
if !ctx.expects_expression() {
53+
return None;
54+
}
55+
56+
if let hir::Adt::Struct(strukt) = ctx.expected_type.as_ref()?.as_adt()? {
57+
acc.add_struct_literal(ctx, strukt, None);
58+
}
59+
60+
Some(())
61+
}
62+
4863
#[cfg(test)]
4964
mod tests {
5065
use crate::tests::check_edit;
5166

67+
#[test]
68+
fn literal_struct_completion_edit() {
69+
check_edit(
70+
"FooDesc {…}",
71+
r#"
72+
struct FooDesc { pub bar: bool }
73+
74+
fn create_foo(foo_desc: &FooDesc) -> () { () }
75+
76+
fn baz() {
77+
let foo = create_foo(&$0);
78+
}
79+
"#,
80+
r#"
81+
struct FooDesc { pub bar: bool }
82+
83+
fn create_foo(foo_desc: &FooDesc) -> () { () }
84+
85+
fn baz() {
86+
let foo = create_foo(&FooDesc { bar: ${1:()} }$0);
87+
}
88+
"#,
89+
)
90+
}
91+
92+
#[test]
93+
fn literal_struct_complexion_module() {
94+
check_edit(
95+
"FooDesc {…}",
96+
r#"
97+
mod _69latrick {
98+
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
99+
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
100+
}
101+
102+
fn baz() {
103+
use _69latrick::*;
104+
105+
let foo = create_foo(&$0);
106+
}
107+
"#,
108+
r#"
109+
mod _69latrick {
110+
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
111+
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
112+
}
113+
114+
fn baz() {
115+
use _69latrick::*;
116+
117+
let foo = create_foo(&FooDesc { six: ${1:()}, neuf: ${2:()}, bar: ${3:()} }$0);
118+
}
119+
"#,
120+
);
121+
}
122+
52123
#[test]
53124
fn default_completion_edit() {
54125
check_edit(

crates/ide_completion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ pub fn completions(
156156
completions::unqualified_path::complete_unqualified_path(&mut acc, &ctx);
157157
completions::dot::complete_dot(&mut acc, &ctx);
158158
completions::record::complete_record(&mut acc, &ctx);
159+
completions::record::complete_record_literal(&mut acc, &ctx);
159160
completions::pattern::complete_pattern(&mut acc, &ctx);
160161
completions::postfix::complete_postfix(&mut acc, &ctx);
161162
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);

crates/ide_completion/src/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub(crate) mod enum_variant;
77
pub(crate) mod const_;
88
pub(crate) mod pattern;
99
pub(crate) mod type_alias;
10+
pub(crate) mod struct_literal;
1011

1112
mod builder_ext;
1213

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! Renderer for `struct` literal.
2+
3+
use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
4+
use ide_db::helpers::SnippetCap;
5+
use itertools::Itertools;
6+
7+
use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
8+
9+
pub(crate) fn render_struct_literal(
10+
ctx: RenderContext<'_>,
11+
strukt: hir::Struct,
12+
local_name: Option<Name>,
13+
) -> Option<CompletionItem> {
14+
let _p = profile::span("render_struct_literal");
15+
16+
let fields = strukt.fields(ctx.db());
17+
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
18+
19+
if fields_omitted {
20+
// If some fields are private you can't make `struct` literal.
21+
return None;
22+
}
23+
24+
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
25+
let literal = render_literal(&ctx, &name, strukt.kind(ctx.db()), &visible_fields)?;
26+
27+
Some(build_completion(ctx, name, literal, strukt))
28+
}
29+
30+
fn build_completion(
31+
ctx: RenderContext<'_>,
32+
name: String,
33+
literal: String,
34+
def: impl HasAttrs + Copy,
35+
) -> CompletionItem {
36+
let mut item = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name + " {…}");
37+
item.kind(CompletionItemKind::Snippet)
38+
.set_documentation(ctx.docs(def))
39+
.set_deprecated(ctx.is_deprecated(def))
40+
.detail(&literal);
41+
if let Some(snippet_cap) = ctx.snippet_cap() {
42+
item.insert_snippet(snippet_cap, literal);
43+
} else {
44+
item.insert_text(literal);
45+
};
46+
item.build()
47+
}
48+
49+
fn render_literal(
50+
ctx: &RenderContext<'_>,
51+
name: &str,
52+
kind: StructKind,
53+
fields: &[hir::Field],
54+
) -> Option<String> {
55+
let mut literal = match kind {
56+
StructKind::Tuple if ctx.snippet_cap().is_some() => render_tuple_as_literal(fields, name),
57+
StructKind::Record => render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, name),
58+
_ => return None,
59+
};
60+
61+
if ctx.completion.is_param {
62+
literal.push(':');
63+
literal.push(' ');
64+
literal.push_str(name);
65+
}
66+
if ctx.snippet_cap().is_some() {
67+
literal.push_str("$0");
68+
}
69+
Some(literal)
70+
}
71+
72+
fn render_record_as_literal(
73+
db: &dyn HirDatabase,
74+
snippet_cap: Option<SnippetCap>,
75+
fields: &[hir::Field],
76+
name: &str,
77+
) -> String {
78+
let fields = fields.iter();
79+
if snippet_cap.is_some() {
80+
format!(
81+
"{name} {{ {} }}",
82+
fields
83+
.enumerate()
84+
.map(|(idx, field)| format!("{}: ${{{}:()}}", field.name(db), idx + 1))
85+
.format(", "),
86+
name = name
87+
)
88+
} else {
89+
format!(
90+
"{name} {{ {} }}",
91+
fields.map(|field| format!("{}: ()", field.name(db))).format(", "),
92+
name = name
93+
)
94+
}
95+
}
96+
97+
fn render_tuple_as_literal(fields: &[hir::Field], name: &str) -> String {
98+
format!(
99+
"{name}({})",
100+
fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
101+
name = name
102+
)
103+
}
104+
105+
fn visible_fields(
106+
ctx: &RenderContext<'_>,
107+
fields: &[hir::Field],
108+
item: impl HasAttrs,
109+
) -> Option<(Vec<hir::Field>, bool)> {
110+
let module = ctx.completion.scope.module()?;
111+
let n_fields = fields.len();
112+
let fields = fields
113+
.iter()
114+
.filter(|field| field.is_visible_from(ctx.db(), module))
115+
.copied()
116+
.collect::<Vec<_>>();
117+
118+
let fields_omitted =
119+
n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
120+
Some((fields, fields_omitted))
121+
}

crates/ide_completion/src/tests/expression.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,60 @@ fn check_empty(ra_fixture: &str, expect: Expect) {
1313
expect.assert_eq(&actual);
1414
}
1515

16+
#[test]
17+
fn complete_literal_struct_with_a_private_field() {
18+
// `FooDesc.bar` is private, the completion should not be triggered.
19+
check(
20+
r#"
21+
mod _69latrick {
22+
pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, bar: bool }
23+
pub fn create_foo(foo_desc: &FooDesc) -> () { () }
24+
}
25+
26+
fn baz() {
27+
use _69latrick::*;
28+
29+
let foo = create_foo(&$0);
30+
}
31+
"#,
32+
// This should not contain `FooDesc {…}`.
33+
expect![[r##"
34+
kw unsafe
35+
kw match
36+
kw while
37+
kw while let
38+
kw loop
39+
kw if
40+
kw if let
41+
kw for
42+
kw true
43+
kw false
44+
kw mut
45+
kw return
46+
kw self
47+
kw super
48+
kw crate
49+
st FooDesc
50+
fn create_foo(…) fn(&FooDesc)
51+
bt u32
52+
tt Trait
53+
en Enum
54+
st Record
55+
st Tuple
56+
md module
57+
fn baz() fn()
58+
st Unit
59+
md _69latrick
60+
ma makro!(…) #[macro_export] macro_rules! makro
61+
fn function() fn()
62+
sc STATIC
63+
un Union
64+
ev TupleV(…) (u32)
65+
ct CONST
66+
"##]],
67+
)
68+
}
69+
1670
#[test]
1771
fn completes_various_bindings() {
1872
check_empty(

0 commit comments

Comments
 (0)