Skip to content

Commit f1b257f

Browse files
committed
Auto merge of #14041 - jonas-schievink:record-lit-signature-help, r=Veykril
feat: show signature help when typing record literal Closes #13910 ![Screenshot_20230127_191848](https://user-images.githubusercontent.com/1786438/215165358-8e51cd1a-d7c3-4c9d-a2d8-c22638bcf500.png)
2 parents 261afbd + cad4cb3 commit f1b257f

File tree

1 file changed

+196
-7
lines changed

1 file changed

+196
-7
lines changed

crates/ide/src/signature_help.rs

+196-7
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
use std::collections::BTreeSet;
55

66
use either::Either;
7-
use hir::{AssocItem, GenericParam, HasAttrs, HirDisplay, Semantics, Trait};
8-
use ide_db::{active_parameter::callable_for_node, base_db::FilePosition};
7+
use hir::{
8+
AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait,
9+
};
10+
use ide_db::{active_parameter::callable_for_node, base_db::FilePosition, FxIndexMap};
911
use stdx::format_to;
1012
use syntax::{
1113
algo,
1214
ast::{self, HasArgList},
13-
match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize,
15+
match_ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, TextSize,
1416
};
1517

1618
use crate::RootDatabase;
@@ -37,14 +39,18 @@ impl SignatureHelp {
3739
}
3840

3941
fn push_call_param(&mut self, param: &str) {
40-
self.push_param('(', param);
42+
self.push_param("(", param);
4143
}
4244

4345
fn push_generic_param(&mut self, param: &str) {
44-
self.push_param('<', param);
46+
self.push_param("<", param);
47+
}
48+
49+
fn push_record_field(&mut self, param: &str) {
50+
self.push_param("{ ", param);
4551
}
4652

47-
fn push_param(&mut self, opening_delim: char, param: &str) {
53+
fn push_param(&mut self, opening_delim: &str, param: &str) {
4854
if !self.signature.ends_with(opening_delim) {
4955
self.signature.push_str(", ");
5056
}
@@ -85,14 +91,23 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
8591
}
8692
return signature_help_for_generics(&sema, garg_list, token);
8793
},
94+
ast::RecordExpr(record) => {
95+
let cursor_outside = record.record_expr_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token);
96+
if cursor_outside {
97+
continue;
98+
}
99+
return signature_help_for_record_lit(&sema, record, token);
100+
},
88101
_ => (),
89102
}
90103
}
91104

92105
// Stop at multi-line expressions, since the signature of the outer call is not very
93106
// helpful inside them.
94107
if let Some(expr) = ast::Expr::cast(node.clone()) {
95-
if expr.syntax().text().contains_char('\n') {
108+
if expr.syntax().text().contains_char('\n')
109+
&& expr.syntax().kind() != SyntaxKind::RECORD_EXPR
110+
{
96111
return None;
97112
}
98113
}
@@ -368,6 +383,86 @@ fn add_assoc_type_bindings(
368383
}
369384
}
370385

386+
fn signature_help_for_record_lit(
387+
sema: &Semantics<'_, RootDatabase>,
388+
record: ast::RecordExpr,
389+
token: SyntaxToken,
390+
) -> Option<SignatureHelp> {
391+
let arg_list = record
392+
.syntax()
393+
.ancestors()
394+
.filter_map(ast::RecordExpr::cast)
395+
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
396+
397+
let active_parameter = arg_list
398+
.record_expr_field_list()?
399+
.fields()
400+
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
401+
.count();
402+
403+
let mut res = SignatureHelp {
404+
doc: None,
405+
signature: String::new(),
406+
parameters: vec![],
407+
active_parameter: Some(active_parameter),
408+
};
409+
410+
let fields;
411+
412+
let db = sema.db;
413+
let path_res = sema.resolve_path(&record.path()?)?;
414+
if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res {
415+
fields = variant.fields(db);
416+
let en = variant.parent_enum(db);
417+
418+
res.doc = en.docs(db).map(|it| it.into());
419+
format_to!(res.signature, "enum {}::{} {{ ", en.name(db), variant.name(db));
420+
} else {
421+
let adt = match path_res {
422+
PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?,
423+
PathResolution::Def(ModuleDef::Adt(adt)) => adt,
424+
_ => return None,
425+
};
426+
427+
match adt {
428+
hir::Adt::Struct(it) => {
429+
fields = it.fields(db);
430+
res.doc = it.docs(db).map(|it| it.into());
431+
format_to!(res.signature, "struct {} {{ ", it.name(db));
432+
}
433+
hir::Adt::Union(it) => {
434+
fields = it.fields(db);
435+
res.doc = it.docs(db).map(|it| it.into());
436+
format_to!(res.signature, "union {} {{ ", it.name(db));
437+
}
438+
_ => return None,
439+
}
440+
}
441+
442+
let mut fields =
443+
fields.into_iter().map(|field| (field.name(db), Some(field))).collect::<FxIndexMap<_, _>>();
444+
let mut buf = String::new();
445+
for field in record.record_expr_field_list()?.fields() {
446+
let Some((field, _, ty)) = sema.resolve_record_field(&field) else { continue };
447+
let name = field.name(db);
448+
format_to!(buf, "{name}: {}", ty.display_truncated(db, Some(20)));
449+
res.push_record_field(&buf);
450+
buf.clear();
451+
452+
if let Some(field) = fields.get_mut(&name) {
453+
*field = None;
454+
}
455+
}
456+
for (name, field) in fields {
457+
let Some(field) = field else { continue };
458+
format_to!(buf, "{name}: {}", field.ty(db).display_truncated(db, Some(20)));
459+
res.push_record_field(&buf);
460+
buf.clear();
461+
}
462+
res.signature.push_str(" }");
463+
Some(res)
464+
}
465+
371466
#[cfg(test)]
372467
mod tests {
373468
use std::iter;
@@ -1405,4 +1500,98 @@ fn take<C, Error>(
14051500
"#]],
14061501
);
14071502
}
1503+
1504+
#[test]
1505+
fn record_literal() {
1506+
check(
1507+
r#"
1508+
struct Strukt<T, U = ()> {
1509+
t: T,
1510+
u: U,
1511+
unit: (),
1512+
}
1513+
fn f() {
1514+
Strukt {
1515+
u: 0,
1516+
$0
1517+
}
1518+
}
1519+
"#,
1520+
expect![[r#"
1521+
struct Strukt { u: i32, t: T, unit: () }
1522+
------ ^^^^ --------
1523+
"#]],
1524+
);
1525+
}
1526+
1527+
#[test]
1528+
fn record_literal_nonexistent_field() {
1529+
check(
1530+
r#"
1531+
struct Strukt {
1532+
a: u8,
1533+
}
1534+
fn f() {
1535+
Strukt {
1536+
b: 8,
1537+
$0
1538+
}
1539+
}
1540+
"#,
1541+
expect![[r#"
1542+
struct Strukt { a: u8 }
1543+
-----
1544+
"#]],
1545+
);
1546+
}
1547+
1548+
#[test]
1549+
fn tuple_variant_record_literal() {
1550+
check(
1551+
r#"
1552+
enum Opt {
1553+
Some(u8),
1554+
}
1555+
fn f() {
1556+
Opt::Some {$0}
1557+
}
1558+
"#,
1559+
expect![[r#"
1560+
enum Opt::Some { 0: u8 }
1561+
^^^^^
1562+
"#]],
1563+
);
1564+
check(
1565+
r#"
1566+
enum Opt {
1567+
Some(u8),
1568+
}
1569+
fn f() {
1570+
Opt::Some {0:0,$0}
1571+
}
1572+
"#,
1573+
expect![[r#"
1574+
enum Opt::Some { 0: u8 }
1575+
-----
1576+
"#]],
1577+
);
1578+
}
1579+
1580+
#[test]
1581+
fn record_literal_self() {
1582+
check(
1583+
r#"
1584+
struct S { t: u8 }
1585+
impl S {
1586+
fn new() -> Self {
1587+
Self { $0 }
1588+
}
1589+
}
1590+
"#,
1591+
expect![[r#"
1592+
struct S { t: u8 }
1593+
^^^^^
1594+
"#]],
1595+
);
1596+
}
14081597
}

0 commit comments

Comments
 (0)