Skip to content

Commit 9beefef

Browse files
committed
Add completion for struct literal in which all fields are visible.
Fix ide_completion tests. Move 'complete_record_literal' call to the main completion function. Fix a rendering bug when snippet not available. Checks if an expression is expected before adding completion for struct literal. Move 'completion struct literal with private field' test to 'expressions.rs' test file. Update 'expect' tests with new check in 'complete record literal'.
1 parent 8a84311 commit 9beefef

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)