Skip to content

Commit cd9f27d

Browse files
bors[bot]Veykril
andauthored
Merge #10382
10382: fix: Fix inline_call breaking RecordExprField shorthands r=Veykril a=Veykril Fixes #10349 bors r+ Co-authored-by: Lukas Wirth <[email protected]>
2 parents c6d9565 + 774a8cf commit cd9f27d

File tree

3 files changed

+112
-20
lines changed

3 files changed

+112
-20
lines changed

crates/ide_assists/src/handlers/inline_call.rs

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ use hir::{db::HirDatabase, HasSource, PathResolution, Semantics, TypeInfo};
44
use ide_db::{
55
base_db::{FileId, FileRange},
66
defs::Definition,
7-
helpers::insert_use::remove_path_if_in_use_stmt,
7+
helpers::{insert_use::remove_path_if_in_use_stmt, node_ext::expr_as_name_ref},
88
path_transform::PathTransform,
99
search::{FileReference, SearchScope},
1010
RootDatabase,
1111
};
1212
use itertools::{izip, Itertools};
1313
use syntax::{
14-
ast::{self, edit_in_place::Indent, HasArgList},
14+
ast::{self, edit_in_place::Indent, HasArgList, PathExpr},
1515
ted, AstNode, SyntaxNode,
1616
};
1717

@@ -359,33 +359,37 @@ fn inline(
359359
}
360360
// Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
361361
for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arguments).rev() {
362-
let expr_is_name_ref = matches!(&expr,
363-
ast::Expr::PathExpr(expr)
364-
if expr.path().and_then(|path| path.as_single_name_ref()).is_some()
365-
);
366-
match &*usages {
362+
let inline_direct = |usage, replacement: &ast::Expr| {
363+
if let Some(field) = path_expr_as_record_field(usage) {
364+
cov_mark::hit!(inline_call_inline_direct_field);
365+
field.replace_expr(replacement.clone_for_update());
366+
} else {
367+
ted::replace(usage.syntax(), &replacement.syntax().clone_for_update());
368+
}
369+
};
370+
// izip confuses RA due to our lack of hygiene info currently losing us typeinfo
371+
let usages: &[ast::PathExpr] = &*usages;
372+
match usages {
367373
// inline single use closure arguments
368374
[usage]
369375
if matches!(expr, ast::Expr::ClosureExpr(_))
370376
&& usage.syntax().parent().and_then(ast::Expr::cast).is_some() =>
371377
{
372378
cov_mark::hit!(inline_call_inline_closure);
373379
let expr = make::expr_paren(expr.clone());
374-
ted::replace(usage.syntax(), expr.syntax().clone_for_update());
380+
inline_direct(usage, &expr);
375381
}
376382
// inline single use literals
377383
[usage] if matches!(expr, ast::Expr::Literal(_)) => {
378384
cov_mark::hit!(inline_call_inline_literal);
379-
ted::replace(usage.syntax(), expr.syntax().clone_for_update());
385+
inline_direct(usage, &expr);
380386
}
381387
// inline direct local arguments
382-
[_, ..] if expr_is_name_ref => {
388+
[_, ..] if expr_as_name_ref(&expr).is_some() => {
383389
cov_mark::hit!(inline_call_inline_locals);
384-
usages.into_iter().for_each(|usage| {
385-
ted::replace(usage.syntax(), &expr.syntax().clone_for_update());
386-
});
390+
usages.into_iter().for_each(|usage| inline_direct(usage, &expr));
387391
}
388-
// cant inline, emit a let statement
392+
// can't inline, emit a let statement
389393
_ => {
390394
let ty =
391395
sema.type_of_expr(expr).filter(TypeInfo::has_adjustment).and(param_ty.clone());
@@ -421,6 +425,12 @@ fn inline(
421425
}
422426
}
423427

428+
fn path_expr_as_record_field(usage: &PathExpr) -> Option<ast::RecordExprField> {
429+
let path = usage.path()?;
430+
let name_ref = path.as_single_name_ref()?;
431+
ast::RecordExprField::for_name_ref(&name_ref)
432+
}
433+
424434
#[cfg(test)]
425435
mod tests {
426436
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -1022,6 +1032,61 @@ fn foo$0() {
10221032
fn foo() {
10231033
foo$0();
10241034
}
1035+
"#,
1036+
);
1037+
}
1038+
1039+
#[test]
1040+
fn inline_call_field_shorthand() {
1041+
cov_mark::check!(inline_call_inline_direct_field);
1042+
check_assist(
1043+
inline_call,
1044+
r#"
1045+
struct Foo {
1046+
field: u32,
1047+
field1: u32,
1048+
field2: u32,
1049+
field3: u32,
1050+
}
1051+
fn foo(field: u32, field1: u32, val2: u32, val3: u32) -> Foo {
1052+
Foo {
1053+
field,
1054+
field1,
1055+
field2: val2,
1056+
field3: val3,
1057+
}
1058+
}
1059+
fn main() {
1060+
let bar = 0;
1061+
let baz = 0;
1062+
foo$0(bar, 0, baz, 0);
1063+
}
1064+
"#,
1065+
r#"
1066+
struct Foo {
1067+
field: u32,
1068+
field1: u32,
1069+
field2: u32,
1070+
field3: u32,
1071+
}
1072+
fn foo(field: u32, field1: u32, val2: u32, val3: u32) -> Foo {
1073+
Foo {
1074+
field,
1075+
field1,
1076+
field2: val2,
1077+
field3: val3,
1078+
}
1079+
}
1080+
fn main() {
1081+
let bar = 0;
1082+
let baz = 0;
1083+
Foo {
1084+
field: bar,
1085+
field1: 0,
1086+
field2: baz,
1087+
field3: 0,
1088+
};
1089+
}
10251090
"#,
10261091
);
10271092
}

crates/ide_db/src/helpers/node_ext.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ use syntax::{
77
pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
88
if let ast::Expr::PathExpr(expr) = expr {
99
let path = expr.path()?;
10-
let segment = path.segment()?;
11-
let name_ref = segment.name_ref()?;
12-
if path.qualifier().is_none() {
13-
return Some(name_ref);
14-
}
10+
path.as_single_name_ref()
11+
} else {
12+
None
1513
}
16-
None
1714
}
1815

1916
pub fn block_as_lone_tail(block: &ast::BlockExpr) -> Option<ast::Expr> {
2017
block.statements().next().is_none().then(|| block.tail_expr()).flatten()
2118
}
19+
2220
/// Preorder walk all the expression's child expressions.
2321
pub fn walk_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) {
2422
preorder_expr(expr, &mut |ev| {

crates/syntax/src/ast/edit_in_place.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,35 @@ impl ast::RecordExprFieldList {
451451
}
452452
}
453453

454+
impl ast::RecordExprField {
455+
/// This will either replace the initializer, or in the case that this is a shorthand convert
456+
/// the initializer into the name ref and insert the expr as the new initializer.
457+
pub fn replace_expr(&self, expr: ast::Expr) {
458+
if let Some(_) = self.name_ref() {
459+
match self.expr() {
460+
Some(prev) => ted::replace(prev.syntax(), expr.syntax()),
461+
None => ted::append_child(self.syntax(), expr.syntax()),
462+
}
463+
return;
464+
}
465+
// this is a shorthand
466+
if let Some(ast::Expr::PathExpr(path_expr)) = self.expr() {
467+
if let Some(path) = path_expr.path() {
468+
if let Some(name_ref) = path.as_single_name_ref() {
469+
path_expr.syntax().detach();
470+
let children = vec![
471+
name_ref.syntax().clone().into(),
472+
ast::make::token(T![:]).into(),
473+
ast::make::tokens::single_space().into(),
474+
expr.syntax().clone().into(),
475+
];
476+
ted::insert_all_raw(Position::last_child_of(self.syntax()), children);
477+
}
478+
}
479+
}
480+
}
481+
}
482+
454483
impl ast::StmtList {
455484
pub fn push_front(&self, statement: ast::Stmt) {
456485
ted::insert(Position::after(self.l_curly_token().unwrap()), statement.syntax());

0 commit comments

Comments
 (0)