Skip to content

Commit ff7e057

Browse files
bors[bot]m0rg-dev
andauthored
Merge #11691
11691: feat: Suggest union literals, suggest union fields within an empty union literal r=Veykril a=m0rg-dev Adds a `Union {…}` completion in contexts where a union is expected, expanding to a choice of available fields (if snippets are supported): ![image](https://user-images.githubusercontent.com/38578268/158023335-84c03e39-daf0-4a52-b969-f40b01501cc8.png) ![image](https://user-images.githubusercontent.com/38578268/158023354-db49d0bb-034c-49d3-bc02-07414179cb61.png) Also, adds support for listing possible fields in an empty union literal. ![image](https://user-images.githubusercontent.com/38578268/158023398-4695ae34-ce64-4f40-8494-68731a3030c6.png) ![image](https://user-images.githubusercontent.com/38578268/158023406-be96dd95-125a-47ac-9628-0bce634ca2eb.png) Closes #11568. Co-authored-by: Morgan Thomas <[email protected]>
2 parents 421d964 + f922b80 commit ff7e057

File tree

5 files changed

+179
-28
lines changed

5 files changed

+179
-28
lines changed

crates/ide_completion/src/completions.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use crate::{
3535
render_field, render_resolution, render_tuple_field,
3636
struct_literal::render_struct_literal,
3737
type_alias::{render_type_alias, render_type_alias_with_eq},
38+
union_literal::render_union_literal,
3839
RenderContext,
3940
},
4041
CompletionContext, CompletionItem, CompletionItemKind,
@@ -234,6 +235,17 @@ impl Completions {
234235
self.add_opt(item);
235236
}
236237

238+
pub(crate) fn add_union_literal(
239+
&mut self,
240+
ctx: &CompletionContext,
241+
un: hir::Union,
242+
path: Option<hir::ModPath>,
243+
local_name: Option<hir::Name>,
244+
) {
245+
let item = render_union_literal(RenderContext::new(ctx, false), un, path, local_name);
246+
self.add_opt(item);
247+
}
248+
237249
pub(crate) fn add_tuple_field(
238250
&mut self,
239251
ctx: &CompletionContext,

crates/ide_completion/src/completions/record.rs

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,51 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
1414
| ImmediateLocation::RecordExprUpdate(record_expr),
1515
) => {
1616
let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
17-
let default_trait = ctx.famous_defs().core_default_Default();
18-
let impl_default_trait = default_trait.zip(ty).map_or(false, |(default_trait, ty)| {
19-
ty.original.impls_trait(ctx.db, default_trait, &[])
20-
});
21-
22-
let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
23-
if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
24-
let completion_text = "..Default::default()";
25-
let mut item =
26-
CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
27-
let completion_text =
28-
completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
29-
item.insert_text(completion_text).set_relevance(CompletionRelevance {
30-
exact_postfix_snippet_match: true,
31-
..Default::default()
32-
});
33-
item.add_to(acc);
34-
}
35-
if ctx.previous_token_is(T![.]) {
36-
let mut item =
37-
CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
38-
item.insert_text(".");
39-
item.add_to(acc);
40-
return None;
17+
18+
if let Some(hir::Adt::Union(un)) = ty.as_ref().and_then(|t| t.original.as_adt()) {
19+
// ctx.sema.record_literal_missing_fields will always return
20+
// an empty Vec on a union literal. This is normally
21+
// reasonable, but here we'd like to present the full list
22+
// of fields if the literal is empty.
23+
let were_fields_specified = record_expr
24+
.record_expr_field_list()
25+
.and_then(|fl| fl.fields().next())
26+
.is_some();
27+
28+
match were_fields_specified {
29+
false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
30+
true => vec![],
31+
}
32+
} else {
33+
let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
34+
35+
let default_trait = ctx.famous_defs().core_default_Default();
36+
let impl_default_trait =
37+
default_trait.zip(ty.as_ref()).map_or(false, |(default_trait, ty)| {
38+
ty.original.impls_trait(ctx.db, default_trait, &[])
39+
});
40+
41+
if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
42+
let completion_text = "..Default::default()";
43+
let mut item =
44+
CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
45+
let completion_text =
46+
completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
47+
item.insert_text(completion_text).set_relevance(CompletionRelevance {
48+
exact_postfix_snippet_match: true,
49+
..Default::default()
50+
});
51+
item.add_to(acc);
52+
}
53+
if ctx.previous_token_is(T![.]) {
54+
let mut item =
55+
CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
56+
item.insert_text(".");
57+
item.add_to(acc);
58+
return None;
59+
}
60+
missing_fields
4161
}
42-
missing_fields
4362
}
4463
Some(ImmediateLocation::RecordPat(record_pat)) => {
4564
ctx.sema.record_pattern_missing_fields(record_pat)
@@ -62,14 +81,21 @@ pub(crate) fn complete_record_literal(
6281
return None;
6382
}
6483

65-
if let hir::Adt::Struct(strukt) = ctx.expected_type.as_ref()?.as_adt()? {
66-
if ctx.path_qual().is_none() {
84+
match ctx.expected_type.as_ref()?.as_adt()? {
85+
hir::Adt::Struct(strukt) if ctx.path_qual().is_none() => {
6786
let module = if let Some(module) = ctx.module { module } else { strukt.module(ctx.db) };
6887
let path = module.find_use_path(ctx.db, hir::ModuleDef::from(strukt));
6988

7089
acc.add_struct_literal(ctx, strukt, path, None);
7190
}
72-
}
91+
hir::Adt::Union(un) if ctx.path_qual().is_none() => {
92+
let module = if let Some(module) = ctx.module { module } else { un.module(ctx.db) };
93+
let path = module.find_use_path(ctx.db, hir::ModuleDef::from(un));
94+
95+
acc.add_union_literal(ctx, un, path, None);
96+
}
97+
_ => {}
98+
};
7399

74100
Some(())
75101
}

crates/ide_completion/src/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub(crate) mod pattern;
99
pub(crate) mod type_alias;
1010
pub(crate) mod struct_literal;
1111
pub(crate) mod compound;
12+
pub(crate) mod union_literal;
1213

1314
use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
1415
use ide_db::{helpers::item_name, RootDatabase, SnippetCap, SymbolKind};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! Renderer for `union` literals.
2+
3+
use hir::{HirDisplay, Name, StructKind};
4+
use itertools::Itertools;
5+
6+
use crate::{
7+
render::{
8+
compound::{format_literal_label, visible_fields},
9+
RenderContext,
10+
},
11+
CompletionItem, CompletionItemKind,
12+
};
13+
14+
pub(crate) fn render_union_literal(
15+
ctx: RenderContext,
16+
un: hir::Union,
17+
path: Option<hir::ModPath>,
18+
local_name: Option<Name>,
19+
) -> Option<CompletionItem> {
20+
let name = local_name.unwrap_or_else(|| un.name(ctx.db())).to_smol_str();
21+
22+
let qualified_name = match path {
23+
Some(p) => p.to_string(),
24+
None => name.to_string(),
25+
};
26+
27+
let mut item = CompletionItem::new(
28+
CompletionItemKind::Snippet,
29+
ctx.source_range(),
30+
format_literal_label(&name, StructKind::Record),
31+
);
32+
33+
let fields = un.fields(ctx.db());
34+
let (fields, fields_omitted) = visible_fields(&ctx, &fields, un)?;
35+
36+
if fields.is_empty() {
37+
return None;
38+
}
39+
40+
let literal = if ctx.snippet_cap().is_some() {
41+
format!(
42+
"{} {{ ${{1|{}|}}: ${{2:()}} }}$0",
43+
qualified_name,
44+
fields.iter().map(|field| field.name(ctx.db())).format(",")
45+
)
46+
} else {
47+
format!(
48+
"{} {{ {} }}",
49+
qualified_name,
50+
fields
51+
.iter()
52+
.format_with(", ", |field, f| { f(&format_args!("{}: ()", field.name(ctx.db()))) })
53+
)
54+
};
55+
56+
let detail = format!(
57+
"{} {{ {}{} }}",
58+
qualified_name,
59+
fields.iter().format_with(", ", |field, f| {
60+
f(&format_args!("{}: {}", field.name(ctx.db()), field.ty(ctx.db()).display(ctx.db())))
61+
}),
62+
if fields_omitted { ", .." } else { "" }
63+
);
64+
65+
item.set_documentation(ctx.docs(un))
66+
.set_deprecated(ctx.is_deprecated(un))
67+
.detail(&detail)
68+
.set_relevance(ctx.completion_relevance());
69+
70+
match ctx.snippet_cap() {
71+
Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
72+
None => item.insert_text(literal),
73+
};
74+
75+
Some(item.build())
76+
}

crates/ide_completion/src/tests/record.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,39 @@ fn main() {
204204
"#]],
205205
);
206206
}
207+
208+
#[test]
209+
fn empty_union_literal() {
210+
check(
211+
r#"
212+
union Union { foo: u32, bar: f32 }
213+
214+
fn foo() {
215+
let other = Union {
216+
$0
217+
};
218+
}
219+
"#,
220+
expect![[r#"
221+
fd foo u32
222+
fd bar f32
223+
"#]],
224+
)
225+
}
226+
227+
#[test]
228+
fn dont_suggest_additional_union_fields() {
229+
check(
230+
r#"
231+
union Union { foo: u32, bar: f32 }
232+
233+
fn foo() {
234+
let other = Union {
235+
foo: 1,
236+
$0
237+
};
238+
}
239+
"#,
240+
expect![[r#""#]],
241+
)
242+
}

0 commit comments

Comments
 (0)