aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/lib.rs4
-rw-r--r--crates/hir_ty/src/chalk_ext.rs36
-rw-r--r--crates/ide/src/references.rs64
-rw-r--r--crates/ide/src/references/rename.rs17
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs2
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs102
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs198
-rw-r--r--crates/ide_completion/src/context.rs23
-rw-r--r--crates/ide_db/src/search.rs177
-rw-r--r--crates/parser/src/grammar/patterns.rs2
-rw-r--r--crates/syntax/src/algo.rs122
-rw-r--r--crates/syntax/src/ast/edit.rs12
-rw-r--r--crates/syntax/src/ast/make.rs3
13 files changed, 420 insertions, 342 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ac23e385e..c9ef4b420 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2071,6 +2071,10 @@ impl Type {
2071 Some(adt.into()) 2071 Some(adt.into())
2072 } 2072 }
2073 2073
2074 pub fn as_builtin(&self) -> Option<BuiltinType> {
2075 self.ty.as_builtin().map(|inner| BuiltinType { inner })
2076 }
2077
2074 pub fn as_dyn_trait(&self) -> Option<Trait> { 2078 pub fn as_dyn_trait(&self) -> Option<Trait> {
2075 self.ty.dyn_trait().map(Into::into) 2079 self.ty.dyn_trait().map(Into::into)
2076 } 2080 }
diff --git a/crates/hir_ty/src/chalk_ext.rs b/crates/hir_ty/src/chalk_ext.rs
index 8c4542956..5232a7d80 100644
--- a/crates/hir_ty/src/chalk_ext.rs
+++ b/crates/hir_ty/src/chalk_ext.rs
@@ -1,8 +1,10 @@
1//! Various extensions traits for Chalk types. 1//! Various extensions traits for Chalk types.
2 2
3use chalk_ir::Mutability; 3use chalk_ir::{FloatTy, IntTy, Mutability, Scalar, UintTy};
4use hir_def::{ 4use hir_def::{
5 type_ref::Rawness, AssocContainerId, FunctionId, GenericDefId, HasModule, Lookup, TraitId, 5 builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType, BuiltinUint},
6 type_ref::Rawness,
7 AssocContainerId, FunctionId, GenericDefId, HasModule, Lookup, TraitId,
6}; 8};
7 9
8use crate::{ 10use crate::{
@@ -18,6 +20,7 @@ pub trait TyExt {
18 fn is_unknown(&self) -> bool; 20 fn is_unknown(&self) -> bool;
19 21
20 fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>; 22 fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>;
23 fn as_builtin(&self) -> Option<BuiltinType>;
21 fn as_tuple(&self) -> Option<&Substitution>; 24 fn as_tuple(&self) -> Option<&Substitution>;
22 fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId>; 25 fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId>;
23 fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>; 26 fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>;
@@ -59,6 +62,35 @@ impl TyExt for Ty {
59 } 62 }
60 } 63 }
61 64
65 fn as_builtin(&self) -> Option<BuiltinType> {
66 match self.kind(&Interner) {
67 TyKind::Str => Some(BuiltinType::Str),
68 TyKind::Scalar(Scalar::Bool) => Some(BuiltinType::Bool),
69 TyKind::Scalar(Scalar::Char) => Some(BuiltinType::Char),
70 TyKind::Scalar(Scalar::Float(fty)) => Some(BuiltinType::Float(match fty {
71 FloatTy::F64 => BuiltinFloat::F64,
72 FloatTy::F32 => BuiltinFloat::F32,
73 })),
74 TyKind::Scalar(Scalar::Int(ity)) => Some(BuiltinType::Int(match ity {
75 IntTy::Isize => BuiltinInt::Isize,
76 IntTy::I8 => BuiltinInt::I8,
77 IntTy::I16 => BuiltinInt::I16,
78 IntTy::I32 => BuiltinInt::I32,
79 IntTy::I64 => BuiltinInt::I64,
80 IntTy::I128 => BuiltinInt::I128,
81 })),
82 TyKind::Scalar(Scalar::Uint(ity)) => Some(BuiltinType::Uint(match ity {
83 UintTy::Usize => BuiltinUint::Usize,
84 UintTy::U8 => BuiltinUint::U8,
85 UintTy::U16 => BuiltinUint::U16,
86 UintTy::U32 => BuiltinUint::U32,
87 UintTy::U64 => BuiltinUint::U64,
88 UintTy::U128 => BuiltinUint::U128,
89 })),
90 _ => None,
91 }
92 }
93
62 fn as_tuple(&self) -> Option<&Substitution> { 94 fn as_tuple(&self) -> Option<&Substitution> {
63 match self.kind(&Interner) { 95 match self.kind(&Interner) {
64 TyKind::Tuple(_, substs) => Some(substs), 96 TyKind::Tuple(_, substs) => Some(substs),
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 11ca7ec6b..ae492a264 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -65,7 +65,7 @@ pub(crate) fn find_all_refs(
65 (find_def(&sema, &syntax, position)?, false) 65 (find_def(&sema, &syntax, position)?, false)
66 }; 66 };
67 67
68 let mut usages = def.usages(sema).set_scope(search_scope).all(); 68 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all();
69 if is_literal_search { 69 if is_literal_search {
70 // filter for constructor-literals 70 // filter for constructor-literals
71 let refs = usages.references.values_mut(); 71 let refs = usages.references.values_mut();
@@ -1163,22 +1163,76 @@ fn foo<const FOO$0: usize>() -> usize {
1163 } 1163 }
1164 1164
1165 #[test] 1165 #[test]
1166 fn test_find_self_ty_in_trait_def() { 1166 fn test_trait() {
1167 check( 1167 check(
1168 r#" 1168 r#"
1169trait Foo { 1169trait Foo$0 where Self: {}
1170 fn f() -> Self$0; 1170
1171impl Foo for () {}
1172"#,
1173 expect![[r#"
1174 Foo Trait FileId(0) 0..24 6..9
1175
1176 FileId(0) 31..34
1177 "#]],
1178 );
1179 }
1180
1181 #[test]
1182 fn test_trait_self() {
1183 check(
1184 r#"
1185trait Foo where Self$0 {
1186 fn f() -> Self;
1171} 1187}
1188
1189impl Foo for () {}
1172"#, 1190"#,
1173 expect![[r#" 1191 expect![[r#"
1174 Self TypeParam FileId(0) 6..9 6..9 1192 Self TypeParam FileId(0) 6..9 6..9
1175 1193
1176 FileId(0) 26..30 1194 FileId(0) 16..20
1195 FileId(0) 37..41
1177 "#]], 1196 "#]],
1178 ); 1197 );
1179 } 1198 }
1180 1199
1181 #[test] 1200 #[test]
1201 fn test_self_ty() {
1202 check(
1203 r#"
1204 struct $0Foo;
1205
1206 impl Foo where Self: {
1207 fn f() -> Self;
1208 }
1209 "#,
1210 expect![[r#"
1211 Foo Struct FileId(0) 0..11 7..10
1212
1213 FileId(0) 18..21
1214 FileId(0) 28..32
1215 FileId(0) 50..54
1216 "#]],
1217 );
1218 check(
1219 r#"
1220struct Foo;
1221
1222impl Foo where Self: {
1223 fn f() -> Self$0;
1224}
1225"#,
1226 expect![[r#"
1227 impl Impl FileId(0) 13..57 18..21
1228
1229 FileId(0) 18..21
1230 FileId(0) 28..32
1231 FileId(0) 50..54
1232 "#]],
1233 );
1234 }
1235 #[test]
1182 fn test_self_variant_with_payload() { 1236 fn test_self_variant_with_payload() {
1183 check( 1237 check(
1184 r#" 1238 r#"
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 175e7a31d..2bf953305 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1888,4 +1888,21 @@ impl Foo {
1888 "error: Cannot rename `Self`", 1888 "error: Cannot rename `Self`",
1889 ); 1889 );
1890 } 1890 }
1891
1892 #[test]
1893 fn test_rename_ignores_self_ty() {
1894 check(
1895 "Fo0",
1896 r#"
1897struct $0Foo;
1898
1899impl Foo where Self: {}
1900"#,
1901 r#"
1902struct Fo0;
1903
1904impl Fo0 where Self: {}
1905"#,
1906 );
1907 }
1891} 1908}
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index b5b5ada5e..70949ca35 100644
--- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -107,7 +107,7 @@ fn edit_struct_references(
107 names: &[ast::Name], 107 names: &[ast::Name],
108) { 108) {
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); 109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110 let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all(); 110 let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
111 111
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { 112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
113 match_ast! { 113 match_ast! {
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 93b28370c..4116985ae 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -10,7 +10,6 @@ use ide_db::{
10use itertools::Itertools; 10use itertools::Itertools;
11use stdx::format_to; 11use stdx::format_to;
12use syntax::{ 12use syntax::{
13 algo::SyntaxRewriter,
14 ast::{ 13 ast::{
15 self, 14 self,
16 edit::{AstNodeEdit, IndentLevel}, 15 edit::{AstNodeEdit, IndentLevel},
@@ -1362,7 +1361,8 @@ fn rewrite_body_segment(
1362 syntax: &SyntaxNode, 1361 syntax: &SyntaxNode,
1363) -> SyntaxNode { 1362) -> SyntaxNode {
1364 let syntax = fix_param_usages(ctx, params, syntax); 1363 let syntax = fix_param_usages(ctx, params, syntax);
1365 update_external_control_flow(handler, &syntax) 1364 update_external_control_flow(handler, &syntax);
1365 syntax
1366} 1366}
1367 1367
1368/// change all usages to account for added `&`/`&mut` for some params 1368/// change all usages to account for added `&`/`&mut` for some params
@@ -1415,75 +1415,65 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode)
1415 res 1415 res
1416} 1416}
1417 1417
1418fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode { 1418fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) {
1419 let mut rewriter = SyntaxRewriter::default();
1420
1421 let mut nested_loop = None; 1419 let mut nested_loop = None;
1422 let mut nested_scope = None; 1420 let mut nested_scope = None;
1423 for event in syntax.preorder() { 1421 for event in syntax.preorder() {
1424 let node = match event { 1422 match event {
1425 WalkEvent::Enter(e) => { 1423 WalkEvent::Enter(e) => match e.kind() {
1426 match e.kind() { 1424 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => {
1427 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => { 1425 if nested_loop.is_none() {
1428 if nested_loop.is_none() { 1426 nested_loop = Some(e.clone());
1429 nested_loop = Some(e.clone());
1430 }
1431 } 1427 }
1432 SyntaxKind::FN 1428 }
1433 | SyntaxKind::CONST 1429 SyntaxKind::FN
1434 | SyntaxKind::STATIC 1430 | SyntaxKind::CONST
1435 | SyntaxKind::IMPL 1431 | SyntaxKind::STATIC
1436 | SyntaxKind::MODULE => { 1432 | SyntaxKind::IMPL
1437 if nested_scope.is_none() { 1433 | SyntaxKind::MODULE => {
1438 nested_scope = Some(e.clone()); 1434 if nested_scope.is_none() {
1439 } 1435 nested_scope = Some(e.clone());
1440 } 1436 }
1441 _ => {}
1442 } 1437 }
1443 e 1438 _ => {}
1444 } 1439 },
1445 WalkEvent::Leave(e) => { 1440 WalkEvent::Leave(e) => {
1441 if nested_scope.is_none() {
1442 if let Some(expr) = ast::Expr::cast(e.clone()) {
1443 match expr {
1444 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1445 let expr = return_expr.expr();
1446 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1447 ted::replace(return_expr.syntax(), replacement.syntax())
1448 }
1449 }
1450 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1451 let expr = break_expr.expr();
1452 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1453 ted::replace(break_expr.syntax(), replacement.syntax())
1454 }
1455 }
1456 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1457 if let Some(replacement) = make_rewritten_flow(handler, None) {
1458 ted::replace(continue_expr.syntax(), replacement.syntax())
1459 }
1460 }
1461 _ => {
1462 // do nothing
1463 }
1464 }
1465 }
1466 }
1467
1446 if nested_loop.as_ref() == Some(&e) { 1468 if nested_loop.as_ref() == Some(&e) {
1447 nested_loop = None; 1469 nested_loop = None;
1448 } 1470 }
1449 if nested_scope.as_ref() == Some(&e) { 1471 if nested_scope.as_ref() == Some(&e) {
1450 nested_scope = None; 1472 nested_scope = None;
1451 } 1473 }
1452 continue;
1453 } 1474 }
1454 }; 1475 };
1455 if nested_scope.is_some() {
1456 continue;
1457 }
1458 let expr = match ast::Expr::cast(node) {
1459 Some(e) => e,
1460 None => continue,
1461 };
1462 match expr {
1463 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1464 let expr = return_expr.expr();
1465 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1466 rewriter.replace_ast(&return_expr.into(), &replacement);
1467 }
1468 }
1469 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1470 let expr = break_expr.expr();
1471 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1472 rewriter.replace_ast(&break_expr.into(), &replacement);
1473 }
1474 }
1475 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1476 if let Some(replacement) = make_rewritten_flow(handler, None) {
1477 rewriter.replace_ast(&continue_expr.into(), &replacement);
1478 }
1479 }
1480 _ => {
1481 // do nothing
1482 }
1483 }
1484 } 1476 }
1485
1486 rewriter.rewrite(syntax)
1487} 1477}
1488 1478
1489fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> { 1479fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> {
@@ -1502,7 +1492,7 @@ fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Op
1502 make::expr_call(make::expr_path(make_path_from_text("Err")), args) 1492 make::expr_call(make::expr_path(make_path_from_text("Err")), args)
1503 } 1493 }
1504 }; 1494 };
1505 Some(make::expr_return(Some(value))) 1495 Some(make::expr_return(Some(value)).clone_for_update())
1506} 1496}
1507 1497
1508#[cfg(test)] 1498#[cfg(test)]
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs
index 04bae4e58..28d14b9c3 100644
--- a/crates/ide_assists/src/handlers/pull_assignment_up.rs
+++ b/crates/ide_assists/src/handlers/pull_assignment_up.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, edit::AstNodeEdit, make}, 2 ast::{self, make},
3 AstNode, 3 ted, AstNode,
4}; 4};
5 5
6use crate::{ 6use crate::{
@@ -37,103 +37,101 @@ use crate::{
37// ``` 37// ```
38pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 38pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; 39 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
40 let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment { 40
41 assign_expr.lhs()? 41 let op_kind = assign_expr.op_kind()?;
42 } else { 42 if op_kind != ast::BinOp::Assignment {
43 cov_mark::hit!(test_cant_pull_non_assignments);
43 return None; 44 return None;
45 }
46
47 let mut collector = AssignmentsCollector {
48 sema: &ctx.sema,
49 common_lhs: assign_expr.lhs()?,
50 assignments: Vec::new(),
44 }; 51 };
45 52
46 let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { 53 let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
47 ( 54 collector.collect_if(&if_expr)?;
48 ast::Expr::cast(if_expr.syntax().to_owned())?, 55 if_expr.into()
49 exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()),
50 )
51 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { 56 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
52 ( 57 collector.collect_match(&match_expr)?;
53 ast::Expr::cast(match_expr.syntax().to_owned())?, 58 match_expr.into()
54 exprify_match(&match_expr, &ctx.sema, &name_expr)?,
55 )
56 } else { 59 } else {
57 return None; 60 return None;
58 }; 61 };
59 62
60 let expr_stmt = make::expr_stmt(new_stmt);
61
62 acc.add( 63 acc.add(
63 AssistId("pull_assignment_up", AssistKind::RefactorExtract), 64 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
64 "Pull assignment up", 65 "Pull assignment up",
65 old_stmt.syntax().text_range(), 66 tgt.syntax().text_range(),
66 move |edit| { 67 move |edit| {
67 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt)); 68 let assignments: Vec<_> = collector
69 .assignments
70 .into_iter()
71 .map(|(stmt, rhs)| (edit.make_ast_mut(stmt), rhs.clone_for_update()))
72 .collect();
73
74 let tgt = edit.make_ast_mut(tgt);
75
76 for (stmt, rhs) in assignments {
77 ted::replace(stmt.syntax(), rhs.syntax());
78 }
79 let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
80 let assign_stmt = make::expr_stmt(assign_expr);
81
82 ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update());
68 }, 83 },
69 ) 84 )
70} 85}
71 86
72fn exprify_match( 87struct AssignmentsCollector<'a> {
73 match_expr: &ast::MatchExpr, 88 sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
74 sema: &hir::Semantics<ide_db::RootDatabase>, 89 common_lhs: ast::Expr,
75 name: &ast::Expr, 90 assignments: Vec<(ast::ExprStmt, ast::Expr)>,
76) -> Option<ast::Expr> {
77 let new_arm_list = match_expr
78 .match_arm_list()?
79 .arms()
80 .map(|arm| {
81 if let ast::Expr::BlockExpr(block) = arm.expr()? {
82 let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level());
83 Some(arm.replace_descendant(block, new_block))
84 } else {
85 None
86 }
87 })
88 .collect::<Option<Vec<_>>>()?;
89 let new_arm_list = match_expr
90 .match_arm_list()?
91 .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list));
92 Some(make::expr_match(match_expr.expr()?, new_arm_list))
93} 91}
94 92
95fn exprify_if( 93impl<'a> AssignmentsCollector<'a> {
96 statement: &ast::IfExpr, 94 fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
97 sema: &hir::Semantics<ide_db::RootDatabase>, 95 for arm in match_expr.match_arm_list()?.arms() {
98 name: &ast::Expr, 96 match arm.expr()? {
99) -> Option<ast::Expr> { 97 ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
100 let then_branch = exprify_block(&statement.then_branch()?, sema, name)?; 98 _ => return None,
101 let else_branch = match statement.else_branch()? { 99 }
102 ast::ElseBranch::Block(ref block) => {
103 ast::ElseBranch::Block(exprify_block(block, sema, name)?)
104 }
105 ast::ElseBranch::IfExpr(expr) => {
106 cov_mark::hit!(test_pull_assignment_up_chained_if);
107 ast::ElseBranch::IfExpr(ast::IfExpr::cast(
108 exprify_if(&expr, sema, name)?.syntax().to_owned(),
109 )?)
110 } 100 }
111 };
112 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
113}
114 101
115fn exprify_block( 102 Some(())
116 block: &ast::BlockExpr,
117 sema: &hir::Semantics<ide_db::RootDatabase>,
118 name: &ast::Expr,
119) -> Option<ast::BlockExpr> {
120 if block.tail_expr().is_some() {
121 return None;
122 } 103 }
104 fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> {
105 let then_branch = if_expr.then_branch()?;
106 self.collect_block(&then_branch)?;
107
108 match if_expr.else_branch()? {
109 ast::ElseBranch::Block(block) => self.collect_block(&block),
110 ast::ElseBranch::IfExpr(expr) => {
111 cov_mark::hit!(test_pull_assignment_up_chained_if);
112 self.collect_if(&expr)
113 }
114 }
115 }
116 fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
117 if block.tail_expr().is_some() {
118 return None;
119 }
123 120
124 let mut stmts: Vec<_> = block.statements().collect(); 121 let last_stmt = block.statements().last()?;
125 let stmt = stmts.pop()?; 122 if let ast::Stmt::ExprStmt(stmt) = last_stmt {
126 123 if let ast::Expr::BinExpr(expr) = stmt.expr()? {
127 if let ast::Stmt::ExprStmt(stmt) = stmt { 124 if expr.op_kind()? == ast::BinOp::Assignment
128 if let ast::Expr::BinExpr(expr) = stmt.expr()? { 125 && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
129 if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) 126 {
130 { 127 self.assignments.push((stmt, expr.rhs()?));
131 // The last statement in the block is an assignment to the name we want 128 return Some(());
132 return Some(make::block_expr(stmts, Some(expr.rhs()?))); 129 }
133 } 130 }
134 } 131 }
132
133 None
135 } 134 }
136 None
137} 135}
138 136
139fn is_equivalent( 137fn is_equivalent(
@@ -243,6 +241,38 @@ fn foo() {
243 } 241 }
244 242
245 #[test] 243 #[test]
244 #[ignore]
245 fn test_pull_assignment_up_assignment_expressions() {
246 check_assist(
247 pull_assignment_up,
248 r#"
249fn foo() {
250 let mut a = 1;
251
252 match 1 {
253 1 => { $0a = 2; },
254 2 => a = 3,
255 3 => {
256 a = 4
257 }
258 }
259}"#,
260 r#"
261fn foo() {
262 let mut a = 1;
263
264 a = match 1 {
265 1 => { 2 },
266 2 => 3,
267 3 => {
268 4
269 }
270 };
271}"#,
272 );
273 }
274
275 #[test]
246 fn test_pull_assignment_up_not_last_not_applicable() { 276 fn test_pull_assignment_up_not_last_not_applicable() {
247 check_assist_not_applicable( 277 check_assist_not_applicable(
248 pull_assignment_up, 278 pull_assignment_up,
@@ -439,4 +469,24 @@ fn foo() {
439"#, 469"#,
440 ) 470 )
441 } 471 }
472
473 #[test]
474 fn test_cant_pull_non_assignments() {
475 cov_mark::check!(test_cant_pull_non_assignments);
476 check_assist_not_applicable(
477 pull_assignment_up,
478 r#"
479fn foo() {
480 let mut a = 1;
481 let b = &mut a;
482
483 if true {
484 $0*b + 2;
485 } else {
486 *b + 3;
487 }
488}
489"#,
490 )
491 }
442} 492}
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 62ef40818..787eb2fd3 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -313,7 +313,8 @@ impl<'a> CompletionContext<'a> {
313 cov_mark::hit!(expected_type_let_with_leading_char); 313 cov_mark::hit!(expected_type_let_with_leading_char);
314 cov_mark::hit!(expected_type_let_without_leading_char); 314 cov_mark::hit!(expected_type_let_without_leading_char);
315 let ty = it.pat() 315 let ty = it.pat()
316 .and_then(|pat| self.sema.type_of_pat(&pat)); 316 .and_then(|pat| self.sema.type_of_pat(&pat))
317 .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)));
317 let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { 318 let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
318 ident.name().map(NameOrNameRef::Name) 319 ident.name().map(NameOrNameRef::Name)
319 } else { 320 } else {
@@ -720,6 +721,26 @@ fn foo() {
720 } 721 }
721 722
722 #[test] 723 #[test]
724 fn expected_type_let_pat() {
725 check_expected_type_and_name(
726 r#"
727fn foo() {
728 let x$0 = 0u32;
729}
730"#,
731 expect![[r#"ty: u32, name: ?"#]],
732 );
733 check_expected_type_and_name(
734 r#"
735fn foo() {
736 let $0 = 0u32;
737}
738"#,
739 expect![[r#"ty: u32, name: ?"#]],
740 );
741 }
742
743 #[test]
723 fn expected_type_fn_param_without_leading_char() { 744 fn expected_type_fn_param_without_leading_char() {
724 cov_mark::check!(expected_type_fn_param_without_leading_char); 745 cov_mark::check!(expected_type_fn_param_without_leading_char);
725 check_expected_type_and_name( 746 check_expected_type_and_name(
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index 8f899ea56..67840602b 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -233,6 +233,13 @@ impl Definition {
233 }; 233 };
234 } 234 }
235 235
236 if let Definition::SelfType(impl_) = self {
237 return match impl_.source(db).map(|src| src.value.syntax().text_range()) {
238 Some(range) => SearchScope::file_range(FileRange { file_id, range }),
239 None => SearchScope::single_file(file_id),
240 };
241 }
242
236 if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self { 243 if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
237 let range = match param.parent(db) { 244 let range = match param.parent(db) {
238 hir::GenericDef::Function(it) => { 245 hir::GenericDef::Function(it) => {
@@ -297,7 +304,7 @@ impl Definition {
297 } 304 }
298 305
299 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> { 306 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
300 FindUsages { def: self, sema, scope: None, include_self_kw_refs: false } 307 FindUsages { def: self, sema, scope: None, include_self_kw_refs: None }
301 } 308 }
302} 309}
303 310
@@ -305,12 +312,13 @@ pub struct FindUsages<'a> {
305 def: &'a Definition, 312 def: &'a Definition,
306 sema: &'a Semantics<'a, RootDatabase>, 313 sema: &'a Semantics<'a, RootDatabase>,
307 scope: Option<SearchScope>, 314 scope: Option<SearchScope>,
308 include_self_kw_refs: bool, 315 include_self_kw_refs: Option<hir::Type>,
309} 316}
310 317
311impl<'a> FindUsages<'a> { 318impl<'a> FindUsages<'a> {
312 pub fn include_self_kw_refs(mut self, include: bool) -> FindUsages<'a> { 319 /// Enable searching for `Self` when the definition is a type.
313 self.include_self_kw_refs = include; 320 pub fn include_self_refs(mut self) -> FindUsages<'a> {
321 self.include_self_kw_refs = def_to_ty(self.sema, self.def);
314 self 322 self
315 } 323 }
316 324
@@ -354,13 +362,18 @@ impl<'a> FindUsages<'a> {
354 } 362 }
355 }; 363 };
356 364
357 let name = match self.def.name(sema.db) { 365 let name = self.def.name(sema.db).or_else(|| {
358 Some(it) => it.to_string(), 366 self.include_self_kw_refs.as_ref().and_then(|ty| {
367 ty.as_adt()
368 .map(|adt| adt.name(self.sema.db))
369 .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
370 })
371 });
372 let name = match name {
373 Some(name) => name.to_string(),
359 None => return, 374 None => return,
360 }; 375 };
361 376 let name = name.as_str();
362 let pat = name.as_str();
363 let search_for_self = self.include_self_kw_refs;
364 377
365 for (file_id, search_range) in search_scope { 378 for (file_id, search_range) in search_scope {
366 let text = sema.db.file_text(file_id); 379 let text = sema.db.file_text(file_id);
@@ -369,51 +382,63 @@ impl<'a> FindUsages<'a> {
369 382
370 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); 383 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
371 384
372 let mut handle_match = |idx: usize| -> bool { 385 for (idx, _) in text.match_indices(name) {
373 let offset: TextSize = idx.try_into().unwrap(); 386 let offset: TextSize = idx.try_into().unwrap();
374 if !search_range.contains_inclusive(offset) { 387 if !search_range.contains_inclusive(offset) {
375 return false; 388 continue;
376 } 389 }
377 390
378 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) { 391 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
379 match name { 392 if match name {
380 ast::NameLike::NameRef(name_ref) => { 393 ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
381 if self.found_name_ref(&name_ref, sink) { 394 ast::NameLike::Name(name) => self.found_name(&name, sink),
382 return true; 395 ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
383 } 396 } {
384 } 397 return;
385 ast::NameLike::Name(name) => {
386 if self.found_name(&name, sink) {
387 return true;
388 }
389 }
390 ast::NameLike::Lifetime(lifetime) => {
391 if self.found_lifetime(&lifetime, sink) {
392 return true;
393 }
394 }
395 } 398 }
396 } 399 }
397
398 return false;
399 };
400
401 for (idx, _) in text.match_indices(pat) {
402 if handle_match(idx) {
403 return;
404 }
405 } 400 }
406 401 if let Some(self_ty) = &self.include_self_kw_refs {
407 if search_for_self {
408 for (idx, _) in text.match_indices("Self") { 402 for (idx, _) in text.match_indices("Self") {
409 if handle_match(idx) { 403 let offset: TextSize = idx.try_into().unwrap();
410 return; 404 if !search_range.contains_inclusive(offset) {
405 continue;
406 }
407
408 if let Some(ast::NameLike::NameRef(name_ref)) =
409 sema.find_node_at_offset_with_descend(&tree, offset)
410 {
411 if self.found_self_ty_name_ref(&self_ty, &name_ref, sink) {
412 return;
413 }
411 } 414 }
412 } 415 }
413 } 416 }
414 } 417 }
415 } 418 }
416 419
420 fn found_self_ty_name_ref(
421 &self,
422 self_ty: &hir::Type,
423 name_ref: &ast::NameRef,
424 sink: &mut dyn FnMut(FileId, FileReference) -> bool,
425 ) -> bool {
426 match NameRefClass::classify(self.sema, &name_ref) {
427 Some(NameRefClass::Definition(Definition::SelfType(impl_)))
428 if impl_.self_ty(self.sema.db) == *self_ty =>
429 {
430 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
431 let reference = FileReference {
432 range,
433 name: ast::NameLike::NameRef(name_ref.clone()),
434 access: None,
435 };
436 sink(file_id, reference)
437 }
438 _ => false,
439 }
440 }
441
417 fn found_lifetime( 442 fn found_lifetime(
418 &self, 443 &self,
419 lifetime: &ast::Lifetime, 444 lifetime: &ast::Lifetime,
@@ -429,7 +454,7 @@ impl<'a> FindUsages<'a> {
429 }; 454 };
430 sink(file_id, reference) 455 sink(file_id, reference)
431 } 456 }
432 _ => false, // not a usage 457 _ => false,
433 } 458 }
434 } 459 }
435 460
@@ -448,42 +473,35 @@ impl<'a> FindUsages<'a> {
448 }; 473 };
449 sink(file_id, reference) 474 sink(file_id, reference)
450 } 475 }
451 Some(NameRefClass::Definition(Definition::SelfType(impl_))) => { 476 Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => {
452 let ty = impl_.self_ty(self.sema.db); 477 if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
453 478 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
454 if let Some(adt) = ty.as_adt() { 479 let reference = FileReference {
455 if &Definition::ModuleDef(ModuleDef::Adt(adt)) == self.def { 480 range,
456 let FileRange { file_id, range } = 481 name: ast::NameLike::NameRef(name_ref.clone()),
457 self.sema.original_range(name_ref.syntax()); 482 access: reference_access(&def, &name_ref),
458 let reference = FileReference { 483 };
459 range, 484 sink(file_id, reference)
460 name: ast::NameLike::NameRef(name_ref.clone()), 485 } else {
461 access: None, 486 false
462 };
463 return sink(file_id, reference);
464 }
465 } 487 }
466
467 false
468 } 488 }
469 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { 489 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
470 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); 490 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
471 let reference = match self.def { 491 let access = match self.def {
472 Definition::Field(_) if &field == self.def => FileReference { 492 Definition::Field(_) if &field == self.def => {
473 range, 493 reference_access(&field, &name_ref)
474 name: ast::NameLike::NameRef(name_ref.clone()), 494 }
475 access: reference_access(&field, &name_ref), 495 Definition::Local(l) if &local == l => {
476 }, 496 reference_access(&Definition::Local(local), &name_ref)
477 Definition::Local(l) if &local == l => FileReference { 497 }
478 range, 498 _ => return false,
479 name: ast::NameLike::NameRef(name_ref.clone()),
480 access: reference_access(&Definition::Local(local), &name_ref),
481 },
482 _ => return false, // not a usage
483 }; 499 };
500 let reference =
501 FileReference { range, name: ast::NameLike::NameRef(name_ref.clone()), access };
484 sink(file_id, reference) 502 sink(file_id, reference)
485 } 503 }
486 _ => false, // not a usage 504 _ => false,
487 } 505 }
488 } 506 }
489 507
@@ -513,11 +531,30 @@ impl<'a> FindUsages<'a> {
513 FileReference { range, name: ast::NameLike::Name(name.clone()), access: None }; 531 FileReference { range, name: ast::NameLike::Name(name.clone()), access: None };
514 sink(file_id, reference) 532 sink(file_id, reference)
515 } 533 }
516 _ => false, // not a usage 534 _ => false,
517 } 535 }
518 } 536 }
519} 537}
520 538
539fn def_to_ty(sema: &Semantics<RootDatabase>, def: &Definition) -> Option<hir::Type> {
540 match def {
541 Definition::ModuleDef(def) => match def {
542 ModuleDef::Adt(adt) => Some(adt.ty(sema.db)),
543 ModuleDef::TypeAlias(it) => Some(it.ty(sema.db)),
544 ModuleDef::BuiltinType(it) => {
545 let graph = sema.db.crate_graph();
546 let krate = graph.iter().next()?;
547 let root_file = graph[krate].root_file_id;
548 let module = sema.to_module_def(root_file)?;
549 Some(it.ty(sema.db, module))
550 }
551 _ => None,
552 },
553 Definition::SelfType(it) => Some(it.self_ty(sema.db)),
554 _ => None,
555 }
556}
557
521fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> { 558fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
522 // Only Locals and Fields have accesses for now. 559 // Only Locals and Fields have accesses for now.
523 if !matches!(def, Definition::Local(_) | Definition::Field(_)) { 560 if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index 3ab347834..f1d1f9eaa 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -83,7 +83,7 @@ fn pattern_single_r(p: &mut Parser, recovery_set: TokenSet) {
83} 83}
84 84
85const PAT_RECOVERY_SET: TokenSet = 85const PAT_RECOVERY_SET: TokenSet =
86 TokenSet::new(&[T![let], T![if], T![while], T![loop], T![match], T![')'], T![,]]); 86 TokenSet::new(&[T![let], T![if], T![while], T![loop], T![match], T![')'], T![,], T![=]]);
87 87
88fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> { 88fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> {
89 let m = match p.nth(0) { 89 let m = match p.nth(0) {
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index ba263be0d..3f9b84ab9 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -1,10 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{fmt, hash::BuildHasherDefault, ops::RangeInclusive};
4 fmt,
5 hash::BuildHasherDefault,
6 ops::{self, RangeInclusive},
7};
8 4
9use indexmap::IndexMap; 5use indexmap::IndexMap;
10use itertools::Itertools; 6use itertools::Itertools;
@@ -358,107 +354,11 @@ impl fmt::Debug for SyntaxRewriter<'_> {
358} 354}
359 355
360impl SyntaxRewriter<'_> { 356impl SyntaxRewriter<'_> {
361 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
362 let what = what.clone().into();
363 let replacement = Replacement::Delete;
364 self.replacements.insert(what, replacement);
365 }
366 pub fn insert_before<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
367 &mut self,
368 before: &T,
369 what: &U,
370 ) {
371 let before = before.clone().into();
372 let pos = match before.prev_sibling_or_token() {
373 Some(sibling) => InsertPos::After(sibling),
374 None => match before.parent() {
375 Some(parent) => InsertPos::FirstChildOf(parent),
376 None => return,
377 },
378 };
379 self.insertions.entry(pos).or_insert_with(Vec::new).push(what.clone().into());
380 }
381 pub fn insert_after<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
382 &mut self,
383 after: &T,
384 what: &U,
385 ) {
386 self.insertions
387 .entry(InsertPos::After(after.clone().into()))
388 .or_insert_with(Vec::new)
389 .push(what.clone().into());
390 }
391 pub fn insert_as_first_child<T: Clone + Into<SyntaxNode>, U: Clone + Into<SyntaxElement>>(
392 &mut self,
393 parent: &T,
394 what: &U,
395 ) {
396 self.insertions
397 .entry(InsertPos::FirstChildOf(parent.clone().into()))
398 .or_insert_with(Vec::new)
399 .push(what.clone().into());
400 }
401 pub fn insert_many_before<
402 T: Clone + Into<SyntaxElement>,
403 U: IntoIterator<Item = SyntaxElement>,
404 >(
405 &mut self,
406 before: &T,
407 what: U,
408 ) {
409 let before = before.clone().into();
410 let pos = match before.prev_sibling_or_token() {
411 Some(sibling) => InsertPos::After(sibling),
412 None => match before.parent() {
413 Some(parent) => InsertPos::FirstChildOf(parent),
414 None => return,
415 },
416 };
417 self.insertions.entry(pos).or_insert_with(Vec::new).extend(what);
418 }
419 pub fn insert_many_after<
420 T: Clone + Into<SyntaxElement>,
421 U: IntoIterator<Item = SyntaxElement>,
422 >(
423 &mut self,
424 after: &T,
425 what: U,
426 ) {
427 self.insertions
428 .entry(InsertPos::After(after.clone().into()))
429 .or_insert_with(Vec::new)
430 .extend(what);
431 }
432 pub fn insert_many_as_first_children<
433 T: Clone + Into<SyntaxNode>,
434 U: IntoIterator<Item = SyntaxElement>,
435 >(
436 &mut self,
437 parent: &T,
438 what: U,
439 ) {
440 self.insertions
441 .entry(InsertPos::FirstChildOf(parent.clone().into()))
442 .or_insert_with(Vec::new)
443 .extend(what)
444 }
445 pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) { 357 pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) {
446 let what = what.clone().into(); 358 let what = what.clone().into();
447 let replacement = Replacement::Single(with.clone().into()); 359 let replacement = Replacement::Single(with.clone().into());
448 self.replacements.insert(what, replacement); 360 self.replacements.insert(what, replacement);
449 } 361 }
450 pub fn replace_with_many<T: Clone + Into<SyntaxElement>>(
451 &mut self,
452 what: &T,
453 with: Vec<SyntaxElement>,
454 ) {
455 let what = what.clone().into();
456 let replacement = Replacement::Many(with);
457 self.replacements.insert(what, replacement);
458 }
459 pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) {
460 self.replace(what.syntax(), with.syntax())
461 }
462 362
463 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { 363 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
464 let _p = profile::span("rewrite"); 364 let _p = profile::span("rewrite");
@@ -534,10 +434,6 @@ impl SyntaxRewriter<'_> {
534 if let Some(replacement) = self.replacement(&element) { 434 if let Some(replacement) = self.replacement(&element) {
535 match replacement { 435 match replacement {
536 Replacement::Single(element) => acc.push(element_to_green(element)), 436 Replacement::Single(element) => acc.push(element_to_green(element)),
537 Replacement::Many(replacements) => {
538 acc.extend(replacements.into_iter().map(element_to_green))
539 }
540 Replacement::Delete => (),
541 }; 437 };
542 } else { 438 } else {
543 match element { 439 match element {
@@ -560,25 +456,9 @@ fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row
560 } 456 }
561} 457}
562 458
563impl ops::AddAssign for SyntaxRewriter<'_> {
564 fn add_assign(&mut self, rhs: SyntaxRewriter) {
565 self.replacements.extend(rhs.replacements);
566 for (pos, insertions) in rhs.insertions.into_iter() {
567 match self.insertions.entry(pos) {
568 indexmap::map::Entry::Occupied(mut occupied) => {
569 occupied.get_mut().extend(insertions)
570 }
571 indexmap::map::Entry::Vacant(vacant) => drop(vacant.insert(insertions)),
572 }
573 }
574 }
575}
576
577#[derive(Clone, Debug)] 459#[derive(Clone, Debug)]
578enum Replacement { 460enum Replacement {
579 Delete,
580 Single(SyntaxElement), 461 Single(SyntaxElement),
581 Many(Vec<SyntaxElement>),
582} 462}
583 463
584fn with_children( 464fn with_children(
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 8c60927e4..cbc75f922 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -665,18 +665,8 @@ pub trait AstNodeEdit: AstNode + Clone + Sized {
665 665
666 #[must_use] 666 #[must_use]
667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self { 667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
668 self.replace_descendants(iter::once((old, new)))
669 }
670
671 #[must_use]
672 fn replace_descendants<D: AstNode>(
673 &self,
674 replacement_map: impl IntoIterator<Item = (D, D)>,
675 ) -> Self {
676 let mut rewriter = SyntaxRewriter::default(); 668 let mut rewriter = SyntaxRewriter::default();
677 for (from, to) in replacement_map { 669 rewriter.replace(old.syntax(), new.syntax());
678 rewriter.replace(from.syntax(), to.syntax())
679 }
680 rewriter.rewrite_ast(self) 670 rewriter.rewrite_ast(self)
681 } 671 }
682 fn indent_level(&self) -> IndentLevel { 672 fn indent_level(&self) -> IndentLevel {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index f8b508a90..5a6687397 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -275,6 +275,9 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
275 let expr = elements.into_iter().format(", "); 275 let expr = elements.into_iter().format(", ");
276 expr_from_text(&format!("({})", expr)) 276 expr_from_text(&format!("({})", expr))
277} 277}
278pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
279 expr_from_text(&format!("{} = {}", lhs, rhs))
280}
278fn expr_from_text(text: &str) -> ast::Expr { 281fn expr_from_text(text: &str) -> ast::Expr {
279 ast_from_text(&format!("const C: () = {};", text)) 282 ast_from_text(&format!("const C: () = {};", text))
280} 283}