diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index 4a9e2256e9b0..37f5f44dfa02 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -76,6 +76,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let cfg = ctx.config.import_path_config(); + let make = SyntaxFactory::new(); + let module = ctx.sema.scope(expr.syntax())?.module(); let (mut missing_pats, is_non_exhaustive, has_hidden_variants): ( Peekable>>, @@ -93,7 +95,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .into_iter() .filter_map(|variant| { Some(( - build_pat(ctx, module, variant, cfg)?, + build_pat(ctx, &make, module, variant, cfg)?, variant.should_be_hidden(ctx.db(), module.krate()), )) }) @@ -144,10 +146,11 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let is_hidden = variants .iter() .any(|variant| variant.should_be_hidden(ctx.db(), module.krate())); - let patterns = - variants.into_iter().filter_map(|variant| build_pat(ctx, module, variant, cfg)); + let patterns = variants + .into_iter() + .filter_map(|variant| build_pat(ctx, &make, module, variant, cfg)); - (ast::Pat::from(make::tuple_pat(patterns)), is_hidden) + (ast::Pat::from(make.tuple_pat(patterns)), is_hidden) }) .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); ( @@ -176,9 +179,11 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let is_hidden = variants .iter() .any(|variant| variant.should_be_hidden(ctx.db(), module.krate())); - let patterns = - variants.into_iter().filter_map(|variant| build_pat(ctx, module, variant, cfg)); - (ast::Pat::from(make::slice_pat(patterns)), is_hidden) + let patterns = variants + .into_iter() + .filter_map(|variant| build_pat(ctx, &make, module, variant, cfg)); + + (ast::Pat::from(make.slice_pat(patterns)), is_hidden) }) .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); ( @@ -203,8 +208,6 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) "Fill match arms", ctx.sema.original_range(match_expr.syntax()).range, |builder| { - let make = SyntaxFactory::new(); - // having any hidden variants means that we need a catch-all arm needs_catch_all_arm |= has_hidden_variants; @@ -243,7 +246,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) if needs_catch_all_arm && !has_catch_all_arm { cov_mark::hit!(added_wildcard_pattern); - let arm = make.match_arm(make::wildcard_pat().into(), None, make::ext::expr_todo()); + let arm = make.match_arm(make.wildcard_pat().into(), None, make::ext::expr_todo()); arms.push(arm); } @@ -290,7 +293,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) } } - editor.add_mappings(make.finish_with_mappings()); + editor.add_mappings(make.take()); builder.add_file_edits(ctx.file_id(), editor); }, ) @@ -445,6 +448,7 @@ fn resolve_array_of_enum_def( fn build_pat( ctx: &AssistContext<'_>, + make: &SyntaxFactory, module: hir::Module, var: ExtendedVariant, cfg: ImportPathConfig, @@ -455,31 +459,32 @@ fn build_pat( let edition = module.krate().edition(db); let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition); let fields = var.fields(db); - let pat = match var.kind(db) { + let pat: ast::Pat = match var.kind(db) { hir::StructKind::Tuple => { let mut name_generator = suggest_name::NameGenerator::new(); let pats = fields.into_iter().map(|f| { let name = name_generator.for_type(&f.ty(db), db, edition); match name { - Some(name) => make::ext::simple_ident_pat(make::name(&name)).into(), - None => make::wildcard_pat().into(), + Some(name) => make::ext::simple_ident_pat(make.name(&name)).into(), + None => make.wildcard_pat().into(), } }); - make::tuple_struct_pat(path, pats).into() + make.tuple_struct_pat(path, pats).into() } hir::StructKind::Record => { - let pats = fields + let fields = fields .into_iter() - .map(|f| make::name(f.name(db).as_str())) - .map(|name| make::ext::simple_ident_pat(name).into()); - make::record_pat(path, pats).into() + .map(|f| make.name_ref(f.name(db).as_str())) + .map(|name_ref| make.record_pat_field_shorthand(name_ref)); + let fields = make.record_pat_field_list(fields, None); + make.record_pat_with_fields(path, fields).into() } - hir::StructKind::Unit => make::path_pat(path), + hir::StructKind::Unit => make.path_pat(path), }; Some(pat) } - ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), - ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))), + ExtendedVariant::True => Some(ast::Pat::from(make.literal_pat("true"))), + ExtendedVariant::False => Some(ast::Pat::from(make.literal_pat("false"))), } } diff --git a/crates/ide-assists/src/handlers/apply_demorgan.rs b/crates/ide-assists/src/handlers/apply_demorgan.rs index 491727a30a88..83c049d4613b 100644 --- a/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -3,12 +3,12 @@ use std::collections::VecDeque; use ide_db::{ assists::GroupLabel, famous_defs::FamousDefs, - source_change::SourceChangeBuilder, syntax_helpers::node_ext::{for_each_tail_expr, walk_expr}, }; use syntax::{ - ast::{self, make, AstNode, Expr::BinExpr, HasArgList}, - ted, SyntaxKind, T, + ast::{self, syntax_factory::SyntaxFactory, AstNode, Expr::BinExpr, HasArgList}, + syntax_editor::{Position, SyntaxEditor}, + SyntaxKind, SyntaxNode, T, }; use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; @@ -58,9 +58,12 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti _ => return None, }; - let demorganed = bin_expr.clone_subtree().clone_for_update(); + let make = SyntaxFactory::new(); + + let demorganed = bin_expr.clone_subtree(); + let mut editor = SyntaxEditor::new(demorganed.syntax().clone()); + editor.replace(demorganed.op_token()?, make.token(inv_token)); - ted::replace(demorganed.op_token()?, ast::make::token(inv_token)); let mut exprs = VecDeque::from([ (bin_expr.lhs()?, demorganed.lhs()?), (bin_expr.rhs()?, demorganed.rhs()?), @@ -70,35 +73,39 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti if let BinExpr(bin_expr) = &expr { if let BinExpr(cbin_expr) = &dm { if op == bin_expr.op_kind()? { - ted::replace(cbin_expr.op_token()?, ast::make::token(inv_token)); + editor.replace(cbin_expr.op_token()?, make.token(inv_token)); exprs.push_back((bin_expr.lhs()?, cbin_expr.lhs()?)); exprs.push_back((bin_expr.rhs()?, cbin_expr.rhs()?)); } else { - let mut inv = invert_boolean_expression(expr); - if inv.needs_parens_in(dm.syntax().parent()?) { - inv = ast::make::expr_paren(inv).clone_for_update(); + let mut inv = invert_boolean_expression(&make, expr); + if needs_parens_in_place_of(&inv, &dm.syntax().parent()?, &dm) { + inv = make.expr_paren(inv).into(); } - ted::replace(dm.syntax(), inv.syntax()); + editor.replace(dm.syntax(), inv.syntax()); } } else { return None; } } else { - let mut inv = invert_boolean_expression(dm.clone_subtree()).clone_for_update(); - if inv.needs_parens_in(dm.syntax().parent()?) { - inv = ast::make::expr_paren(inv).clone_for_update(); + let mut inv = invert_boolean_expression(&make, dm.clone()); + if needs_parens_in_place_of(&inv, &dm.syntax().parent()?, &dm) { + inv = make.expr_paren(inv).into(); } - ted::replace(dm.syntax(), inv.syntax()); + editor.replace(dm.syntax(), inv.syntax()); } } + editor.add_mappings(make.finish_with_mappings()); + let edit = editor.finish(); + let demorganed = ast::Expr::cast(edit.new_root().clone())?; + acc.add_group( &GroupLabel("Apply De Morgan's law".to_owned()), AssistId("apply_demorgan", AssistKind::RefactorRewrite), "Apply De Morgan's law", op_range, - |edit| { - let demorganed = ast::Expr::BinExpr(demorganed); + |builder| { + let make = SyntaxFactory::new(); let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast); let neg_expr = paren_expr .clone() @@ -107,24 +114,32 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not))) .map(ast::Expr::PrefixExpr); + let mut editor; if let Some(paren_expr) = paren_expr { if let Some(neg_expr) = neg_expr { cov_mark::hit!(demorgan_double_negation); let parent = neg_expr.syntax().parent(); + editor = builder.make_editor(neg_expr.syntax()); if parent.is_some_and(|parent| demorganed.needs_parens_in(parent)) { cov_mark::hit!(demorgan_keep_parens_for_op_precedence2); - edit.replace_ast(neg_expr, make::expr_paren(demorganed)); + editor.replace(neg_expr.syntax(), make.expr_paren(demorganed).syntax()); } else { - edit.replace_ast(neg_expr, demorganed); + editor.replace(neg_expr.syntax(), demorganed.syntax()); }; } else { cov_mark::hit!(demorgan_double_parens); - edit.replace_ast(paren_expr.into(), add_bang_paren(demorganed)); + editor = builder.make_editor(paren_expr.syntax()); + + editor.replace(paren_expr.syntax(), add_bang_paren(&make, demorganed).syntax()); } } else { - edit.replace_ast(bin_expr.into(), add_bang_paren(demorganed)); + editor = builder.make_editor(bin_expr.syntax()); + editor.replace(bin_expr.syntax(), add_bang_paren(&make, demorganed).syntax()); } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } @@ -161,7 +176,7 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_> let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?; let ast::Expr::ClosureExpr(closure_expr) = arg_expr else { return None }; - let closure_body = closure_expr.body()?; + let closure_body = closure_expr.body()?.clone_for_update(); let op_range = method_call.syntax().text_range(); let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str()); @@ -170,18 +185,19 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_> AssistId("apply_demorgan_iterator", AssistKind::RefactorRewrite), label, op_range, - |edit| { + |builder| { + let make = SyntaxFactory::new(); + let mut editor = builder.make_editor(method_call.syntax()); // replace the method name let new_name = match name.text().as_str() { - "all" => make::name_ref("any"), - "any" => make::name_ref("all"), + "all" => make.name_ref("any"), + "any" => make.name_ref("all"), _ => unreachable!(), - } - .clone_for_update(); - edit.replace_ast(name, new_name); + }; + editor.replace(name.syntax(), new_name.syntax()); // negate all tail expressions in the closure body - let tail_cb = &mut |e: &_| tail_cb_impl(edit, e); + let tail_cb = &mut |e: &_| tail_cb_impl(&mut editor, &make, e); walk_expr(&closure_body, &mut |expr| { if let ast::Expr::ReturnExpr(ret_expr) = expr { if let Some(ret_expr_arg) = &ret_expr.expr() { @@ -198,15 +214,15 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_> .and_then(ast::PrefixExpr::cast) .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not))) { - edit.delete( - prefix_expr - .op_token() - .expect("prefix expression always has an operator") - .text_range(), + editor.delete( + prefix_expr.op_token().expect("prefix expression always has an operator"), ); } else { - edit.insert(method_call.syntax().text_range().start(), "!"); + editor.insert(Position::before(method_call.syntax()), make.token(SyntaxKind::BANG)); } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } @@ -233,26 +249,50 @@ fn validate_method_call_expr( it_type.impls_trait(sema.db, iter_trait, &[]).then_some((name_ref, arg_expr)) } -fn tail_cb_impl(edit: &mut SourceChangeBuilder, e: &ast::Expr) { +fn tail_cb_impl(editor: &mut SyntaxEditor, make: &SyntaxFactory, e: &ast::Expr) { match e { ast::Expr::BreakExpr(break_expr) => { if let Some(break_expr_arg) = break_expr.expr() { - for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(edit, e)) + for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(editor, make, e)) } } ast::Expr::ReturnExpr(_) => { // all return expressions have already been handled by the walk loop } e => { - let inverted_body = invert_boolean_expression(e.clone()); - edit.replace(e.syntax().text_range(), inverted_body.syntax().text()); + let inverted_body = invert_boolean_expression(make, e.clone()); + editor.replace(e.syntax(), inverted_body.syntax()); } } } /// Add bang and parentheses to the expression. -fn add_bang_paren(expr: ast::Expr) -> ast::Expr { - make::expr_prefix(T![!], make::expr_paren(expr)).into() +fn add_bang_paren(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr { + make.expr_prefix(T![!], make.expr_paren(expr).into()).into() +} + +fn needs_parens_in_place_of( + this: &ast::Expr, + parent: &SyntaxNode, + in_place_of: &ast::Expr, +) -> bool { + assert_eq!(Some(parent), in_place_of.syntax().parent().as_ref()); + + let child_idx = parent + .children() + .enumerate() + .find_map(|(i, it)| if &it == in_place_of.syntax() { Some(i) } else { None }) + .unwrap(); + let parent = parent.clone_subtree(); + let subtree_place = parent.children().nth(child_idx).unwrap(); + + let mut editor = SyntaxEditor::new(parent); + editor.replace(subtree_place, this.syntax()); + let edit = editor.finish(); + + let replaced = edit.new_root().children().nth(child_idx).unwrap(); + let replaced = ast::Expr::cast(replaced).unwrap(); + replaced.needs_parens_in(edit.new_root().clone()) } #[cfg(test)] diff --git a/crates/ide-assists/src/handlers/convert_bool_then.rs b/crates/ide-assists/src/handlers/convert_bool_then.rs index eb784cd1226f..8d391c64ce61 100644 --- a/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -13,7 +13,7 @@ use syntax::{ }; use crate::{ - utils::{invert_boolean_expression, unwrap_trivial_block}, + utils::{invert_boolean_expression_legacy, unwrap_trivial_block}, AssistContext, AssistId, AssistKind, Assists, }; @@ -119,7 +119,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> | ast::Expr::WhileExpr(_) | ast::Expr::YieldExpr(_) ); - let cond = if invert_cond { invert_boolean_expression(cond) } else { cond }; + let cond = if invert_cond { invert_boolean_expression_legacy(cond) } else { cond }; let cond = if parenthesize { make::expr_paren(cond) } else { cond }; let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body))); let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list); diff --git a/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index e1966d476c5d..b7a77644496f 100644 --- a/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -17,7 +17,7 @@ use syntax::{ use crate::{ assist_context::{AssistContext, Assists}, - utils::invert_boolean_expression, + utils::invert_boolean_expression_legacy, AssistId, AssistKind, }; @@ -139,7 +139,7 @@ fn if_expr_to_guarded_return( let new_expr = { let then_branch = make::block_expr(once(make::expr_stmt(early_expression).into()), None); - let cond = invert_boolean_expression(cond_expr); + let cond = invert_boolean_expression_legacy(cond_expr); make::expr_if(cond, then_branch, None).indent(if_indent_level) }; new_expr.syntax().clone_for_update() diff --git a/crates/ide-assists/src/handlers/convert_while_to_loop.rs b/crates/ide-assists/src/handlers/convert_while_to_loop.rs index 0b92beefbcdf..beec64d13b68 100644 --- a/crates/ide-assists/src/handlers/convert_while_to_loop.rs +++ b/crates/ide-assists/src/handlers/convert_while_to_loop.rs @@ -13,7 +13,7 @@ use syntax::{ use crate::{ assist_context::{AssistContext, Assists}, - utils::invert_boolean_expression, + utils::invert_boolean_expression_legacy, AssistId, AssistKind, }; @@ -63,7 +63,7 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) let stmts = iter::once(make::expr_stmt(if_expr.into()).into()); make::block_expr(stmts, None) } else { - let if_cond = invert_boolean_expression(while_cond); + let if_cond = invert_boolean_expression_legacy(while_cond); let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into(); let elements = while_body.stmt_list().map_or_else( || Either::Left(iter::empty()), diff --git a/crates/ide-assists/src/handlers/invert_if.rs b/crates/ide-assists/src/handlers/invert_if.rs index 547158e29778..ac710503d8a0 100644 --- a/crates/ide-assists/src/handlers/invert_if.rs +++ b/crates/ide-assists/src/handlers/invert_if.rs @@ -6,7 +6,7 @@ use syntax::{ use crate::{ assist_context::{AssistContext, Assists}, - utils::invert_boolean_expression, + utils::invert_boolean_expression_legacy, AssistId, AssistKind, }; @@ -48,7 +48,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() }; acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { - let flip_cond = invert_boolean_expression(cond.clone()); + let flip_cond = invert_boolean_expression_legacy(cond.clone()); edit.replace_ast(cond, flip_cond); let else_node = else_block.syntax(); diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index c1332d99bff9..39686f065a9c 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -17,7 +17,9 @@ use syntax::{ self, edit::{AstNodeEdit, IndentLevel}, edit_in_place::{AttrsOwnerEdit, Indent, Removable}, - make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, + make, + syntax_factory::SyntaxFactory, + HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, }, ted, AstNode, AstToken, Direction, Edition, NodeOrToken, SourceFile, SyntaxKind::*, @@ -245,11 +247,79 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { .unwrap_or_else(|| node.text_range().start()) } -pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { - invert_special_case(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into()) +pub(crate) fn invert_boolean_expression(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr { + invert_special_case(make, &expr).unwrap_or_else(|| make.expr_prefix(T![!], expr).into()) } -fn invert_special_case(expr: &ast::Expr) -> Option { +// FIXME: Migrate usages of this function to the above function and remove this. +pub(crate) fn invert_boolean_expression_legacy(expr: ast::Expr) -> ast::Expr { + invert_special_case_legacy(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into()) +} + +fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option { + match expr { + ast::Expr::BinExpr(bin) => { + let op_kind = bin.op_kind()?; + let rev_kind = match op_kind { + ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated }) => { + ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: !negated }) + } + ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering: ast::Ordering::Less, strict }) => { + ast::BinaryOp::CmpOp(ast::CmpOp::Ord { + ordering: ast::Ordering::Greater, + strict: !strict, + }) + } + ast::BinaryOp::CmpOp(ast::CmpOp::Ord { + ordering: ast::Ordering::Greater, + strict, + }) => ast::BinaryOp::CmpOp(ast::CmpOp::Ord { + ordering: ast::Ordering::Less, + strict: !strict, + }), + // Parenthesize other expressions before prefixing `!` + _ => { + return Some( + make.expr_prefix(T![!], make.expr_paren(expr.clone()).into()).into(), + ); + } + }; + + Some(make.expr_bin(bin.lhs()?, rev_kind, bin.rhs()?).into()) + } + ast::Expr::MethodCallExpr(mce) => { + let receiver = mce.receiver()?; + let method = mce.name_ref()?; + let arg_list = mce.arg_list()?; + + let method = match method.text().as_str() { + "is_some" => "is_none", + "is_none" => "is_some", + "is_ok" => "is_err", + "is_err" => "is_ok", + _ => return None, + }; + + Some(make.expr_method_call(receiver, make.name_ref(method), arg_list).into()) + } + ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? { + ast::Expr::ParenExpr(parexpr) => { + parexpr.expr().map(|e| e.clone_subtree().clone_for_update()) + } + _ => pe.expr().map(|e| e.clone_subtree().clone_for_update()), + }, + ast::Expr::Literal(lit) => match lit.kind() { + ast::LiteralKind::Bool(b) => match b { + true => Some(ast::Expr::Literal(make.expr_literal("false"))), + false => Some(ast::Expr::Literal(make.expr_literal("true"))), + }, + _ => None, + }, + _ => None, + } +} + +fn invert_special_case_legacy(expr: &ast::Expr) -> Option { match expr { ast::Expr::BinExpr(bin) => { let bin = bin.clone_for_update(); diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 9dc2d8325304..231c21c38f85 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -784,7 +784,7 @@ pub fn record_pat_field_shorthand(name_ref: ast::NameRef) -> ast::RecordPatField ast_from_text(&format!("fn f(S {{ {name_ref} }}: ()))")) } -/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. +/// Returns a `IdentPat` if the path has just one segment, a `PathPat` otherwise. pub fn path_pat(path: ast::Path) -> ast::Pat { return from_text(&path.to_string()); fn from_text(text: &str) -> ast::Pat { diff --git a/crates/syntax/src/ast/syntax_factory.rs b/crates/syntax/src/ast/syntax_factory.rs index 73bbe49105d9..1c517ac2c77e 100644 --- a/crates/syntax/src/ast/syntax_factory.rs +++ b/crates/syntax/src/ast/syntax_factory.rs @@ -33,6 +33,11 @@ impl SyntaxFactory { self.mappings.unwrap_or_default().into_inner() } + /// Take all of the tracked syntax mappings, leaving `SyntaxMapping::default()` in its place, if any. + pub fn take(&self) -> SyntaxMapping { + self.mappings.as_ref().map(|mappings| mappings.take()).unwrap_or_default() + } + fn mappings(&self) -> Option> { self.mappings.as_ref().map(|it| it.borrow_mut()) } diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 572622db544c..19c5c64e2184 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -156,6 +156,32 @@ impl SyntaxFactory { make::literal_pat(text).clone_for_update() } + pub fn slice_pat(&self, pats: impl IntoIterator) -> ast::SlicePat { + let (pats, input) = iterator_input(pats); + let ast = make::slice_pat(pats).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input.into_iter(), ast.pats().map(|it| it.syntax().clone())); + builder.finish(&mut mapping); + } + + ast + } + + pub fn tuple_pat(&self, pats: impl IntoIterator) -> ast::TuplePat { + let (pats, input) = iterator_input(pats); + let ast = make::tuple_pat(pats).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input.into_iter(), ast.fields().map(|it| it.syntax().clone())); + builder.finish(&mut mapping); + } + + ast + } + pub fn tuple_struct_pat( &self, path: ast::Path, @@ -174,6 +200,96 @@ impl SyntaxFactory { ast } + pub fn record_pat_with_fields( + &self, + path: ast::Path, + fields: ast::RecordPatFieldList, + ) -> ast::RecordPat { + let ast = make::record_pat_with_fields(path.clone(), fields.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); + builder.map_node( + fields.syntax().clone(), + ast.record_pat_field_list().unwrap().syntax().clone(), + ); + builder.finish(&mut mapping); + } + + ast + } + + pub fn record_pat_field_list( + &self, + fields: impl IntoIterator, + rest_pat: Option, + ) -> ast::RecordPatFieldList { + let (fields, input) = iterator_input(fields); + let ast = make::record_pat_field_list(fields, rest_pat.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input.into_iter(), ast.fields().map(|it| it.syntax().clone())); + if let Some(rest_pat) = rest_pat { + builder + .map_node(rest_pat.syntax().clone(), ast.rest_pat().unwrap().syntax().clone()); + } + builder.finish(&mut mapping); + } + + ast + } + + pub fn record_pat_field(self, name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField { + let ast = make::record_pat_field(name_ref.clone(), pat.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name_ref.syntax().clone(), ast.name_ref().unwrap().syntax().clone()); + builder.map_node(pat.syntax().clone(), ast.pat().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn record_pat_field_shorthand(&self, name_ref: ast::NameRef) -> ast::RecordPatField { + let ast = make::record_pat_field_shorthand(name_ref.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name_ref.syntax().clone(), ast.pat().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn path_pat(&self, path: ast::Path) -> ast::Pat { + let ast = make::path_pat(path.clone()).clone_for_update(); + + match &ast { + ast::Pat::PathPat(ast) => { + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); + builder.finish(&mut mapping) + } + } + ast::Pat::IdentPat(ast) => { + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(path.syntax().clone(), ast.name().unwrap().syntax().clone()); + builder.finish(&mut mapping) + } + } + _ => unreachable!(), + } + + ast + } + pub fn block_expr( &self, statements: impl IntoIterator, @@ -214,6 +330,21 @@ impl SyntaxFactory { make::expr_empty_block().clone_for_update() } + pub fn expr_paren(&self, expr: ast::Expr) -> ast::ParenExpr { + // FIXME: `make::expr_paren` should return a `ParenExpr`, not just an `Expr` + let ast::Expr::ParenExpr(ast) = make::expr_paren(expr.clone()).clone_for_update() else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + pub fn expr_tuple(&self, fields: impl IntoIterator) -> ast::TupleExpr { let (fields, input) = iterator_input(fields); let ast = make::expr_tuple(fields).clone_for_update(); @@ -292,6 +423,31 @@ impl SyntaxFactory { ast } + pub fn expr_method_call( + &self, + receiver: ast::Expr, + method: ast::NameRef, + arg_list: ast::ArgList, + ) -> ast::MethodCallExpr { + // FIXME: `make::expr_method_call` should return a `MethodCallExpr`, not just an `Expr` + let ast::Expr::MethodCallExpr(ast) = + make::expr_method_call(receiver.clone(), method.clone(), arg_list.clone()) + .clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(receiver.syntax().clone(), ast.receiver().unwrap().syntax().clone()); + builder.map_node(method.syntax().clone(), ast.name_ref().unwrap().syntax().clone()); + builder.map_node(arg_list.syntax().clone(), ast.arg_list().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + pub fn arg_list(&self, args: impl IntoIterator) -> ast::ArgList { let (args, input) = iterator_input(args); let ast = make::arg_list(args).clone_for_update();