Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit e993072

Browse files
author
Jonas Schievink
committed
Provide signature help when typing record literal
1 parent 261afbd commit e993072

File tree

1 file changed

+173
-7
lines changed

1 file changed

+173
-7
lines changed

crates/ide/src/signature_help.rs

Lines changed: 173 additions & 7 deletions
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,81 @@ 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+
match sema.resolve_path(&record.path()?)? {
414+
PathResolution::Def(ModuleDef::Adt(adt)) => match adt {
415+
hir::Adt::Struct(it) => {
416+
fields = it.fields(db);
417+
res.doc = it.docs(db).map(|it| it.into());
418+
format_to!(res.signature, "struct {} {{ ", it.name(db));
419+
}
420+
hir::Adt::Union(it) => {
421+
fields = it.fields(db);
422+
res.doc = it.docs(db).map(|it| it.into());
423+
format_to!(res.signature, "union {} {{ ", it.name(db));
424+
}
425+
_ => return None,
426+
},
427+
PathResolution::Def(ModuleDef::Variant(variant)) => {
428+
fields = variant.fields(db);
429+
let en = variant.parent_enum(db);
430+
431+
res.doc = en.docs(db).map(|it| it.into());
432+
format_to!(res.signature, "enum {}::{} {{ ", en.name(db), variant.name(db));
433+
}
434+
_ => return None,
435+
}
436+
437+
let mut fields =
438+
fields.into_iter().map(|field| (field.name(db), Some(field))).collect::<FxIndexMap<_, _>>();
439+
let mut buf = String::new();
440+
for field in record.record_expr_field_list()?.fields() {
441+
let Some((field, _, ty)) = sema.resolve_record_field(&field) else { continue };
442+
let name = field.name(db);
443+
format_to!(buf, "{name}: {}", ty.display_truncated(db, Some(20)));
444+
res.push_record_field(&buf);
445+
buf.clear();
446+
447+
if let Some(field) = fields.get_mut(&name) {
448+
*field = None;
449+
}
450+
}
451+
for (name, field) in fields {
452+
let Some(field) = field else { continue };
453+
format_to!(buf, "{name}: {}", field.ty(db).display_truncated(db, Some(20)));
454+
res.push_record_field(&buf);
455+
buf.clear();
456+
}
457+
res.signature.push_str(" }");
458+
Some(res)
459+
}
460+
371461
#[cfg(test)]
372462
mod tests {
373463
use std::iter;
@@ -1405,4 +1495,80 @@ fn take<C, Error>(
14051495
"#]],
14061496
);
14071497
}
1498+
1499+
#[test]
1500+
fn record_literal() {
1501+
check(
1502+
r#"
1503+
struct Strukt<T, U = ()> {
1504+
t: T,
1505+
u: U,
1506+
unit: (),
1507+
}
1508+
fn f() {
1509+
Strukt {
1510+
u: 0,
1511+
$0
1512+
}
1513+
}
1514+
"#,
1515+
expect![[r#"
1516+
struct Strukt { u: i32, t: T, unit: () }
1517+
------ ^^^^ --------
1518+
"#]],
1519+
);
1520+
}
1521+
1522+
#[test]
1523+
fn record_literal_nonexistent_field() {
1524+
check(
1525+
r#"
1526+
struct Strukt {
1527+
a: u8,
1528+
}
1529+
fn f() {
1530+
Strukt {
1531+
b: 8,
1532+
$0
1533+
}
1534+
}
1535+
"#,
1536+
expect![[r#"
1537+
struct Strukt { a: u8 }
1538+
-----
1539+
"#]],
1540+
);
1541+
}
1542+
1543+
#[test]
1544+
fn tuple_variant_record_literal() {
1545+
check(
1546+
r#"
1547+
enum Opt {
1548+
Some(u8),
1549+
}
1550+
fn f() {
1551+
Opt::Some {$0}
1552+
}
1553+
"#,
1554+
expect![[r#"
1555+
enum Opt::Some { 0: u8 }
1556+
^^^^^
1557+
"#]],
1558+
);
1559+
check(
1560+
r#"
1561+
enum Opt {
1562+
Some(u8),
1563+
}
1564+
fn f() {
1565+
Opt::Some {0:0,$0}
1566+
}
1567+
"#,
1568+
expect![[r#"
1569+
enum Opt::Some { 0: u8 }
1570+
-----
1571+
"#]],
1572+
);
1573+
}
14081574
}

0 commit comments

Comments
 (0)