From 43c5e95af9ce0c4315ec44c00eeabe2de0a5c826 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 2 Mar 2025 09:09:12 +0100 Subject: Normalize some assist names --- .../src/handlers/add_explicit_enum_discriminant.rs | 209 +++ .../ide-assists/src/handlers/bool_to_enum.rs | 1930 -------------------- .../src/handlers/convert_bool_to_enum.rs | 1930 ++++++++++++++++++++ .../src/handlers/expand_rest_pattern.rs | 363 ++++ .../src/handlers/explicit_enum_discriminant.rs | 206 --- .../src/handlers/fill_record_pattern_fields.rs | 357 ---- .../src/handlers/introduce_named_generic.rs | 187 -- .../src/handlers/introduce_named_type_parameter.rs | 189 ++ .../rust-analyzer/crates/ide-assists/src/lib.rs | 60 +- .../crates/ide-assists/src/tests/generated.rs | 148 +- .../docs/book/src/assists_generated.md | 178 +- 11 files changed, 2884 insertions(+), 2873 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_enum_discriminant.rs delete mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs delete mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs delete mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs delete mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_type_parameter.rs (limited to 'src/tools') diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_enum_discriminant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_enum_discriminant.rs new file mode 100644 index 00000000000..1a5de9cb071 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_enum_discriminant.rs @@ -0,0 +1,209 @@ +use hir::Semantics; +use ide_db::{ + assists::{AssistId, AssistKind}, + source_change::SourceChangeBuilder, + RootDatabase, +}; +use syntax::{ast, AstNode}; + +use crate::{AssistContext, Assists}; + +// Assist: add_explicit_enum_discriminant +// +// Adds explicit discriminant to all enum variants. +// +// ``` +// enum TheEnum$0 { +// Foo, +// Bar, +// Baz = 42, +// Quux, +// } +// ``` +// -> +// ``` +// enum TheEnum { +// Foo = 0, +// Bar = 1, +// Baz = 42, +// Quux = 43, +// } +// ``` +pub(crate) fn add_explicit_enum_discriminant( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let enum_node = ctx.find_node_at_offset::()?; + let enum_def = ctx.sema.to_def(&enum_node)?; + + let is_data_carrying = enum_def.is_data_carrying(ctx.db()); + let has_primitive_repr = enum_def.repr(ctx.db()).and_then(|repr| repr.int).is_some(); + + // Data carrying enums without a primitive repr have no stable discriminants. + if is_data_carrying && !has_primitive_repr { + return None; + } + + let variant_list = enum_node.variant_list()?; + + // Don't offer the assist if the enum has no variants or if all variants already have an + // explicit discriminant. + if variant_list.variants().all(|variant_node| variant_node.expr().is_some()) { + return None; + } + + acc.add( + AssistId("add_explicit_enum_discriminant", AssistKind::RefactorRewrite), + "Add explicit enum discriminants", + enum_node.syntax().text_range(), + |builder| { + for variant_node in variant_list.variants() { + add_variant_discriminant(&ctx.sema, builder, &variant_node); + } + }, + ); + + Some(()) +} + +fn add_variant_discriminant( + sema: &Semantics<'_, RootDatabase>, + builder: &mut SourceChangeBuilder, + variant_node: &ast::Variant, +) { + if variant_node.expr().is_some() { + return; + } + + let Some(variant_def) = sema.to_def(variant_node) else { + return; + }; + let Ok(discriminant) = variant_def.eval(sema.db) else { + return; + }; + + let variant_range = variant_node.syntax().text_range(); + + builder.insert(variant_range.end(), format!(" = {discriminant}")); +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::add_explicit_enum_discriminant; + + #[test] + fn non_primitive_repr_non_data_bearing_add_discriminant() { + check_assist( + add_explicit_enum_discriminant, + r#" +enum TheEnum$0 { + Foo, + Bar, + Baz = 42, + Quux, + FooBar = -5, + FooBaz, +} +"#, + r#" +enum TheEnum { + Foo = 0, + Bar = 1, + Baz = 42, + Quux = 43, + FooBar = -5, + FooBaz = -4, +} +"#, + ); + } + + #[test] + fn primitive_repr_data_bearing_add_discriminant() { + check_assist( + add_explicit_enum_discriminant, + r#" +#[repr(u8)] +$0enum TheEnum { + Foo { x: u32 }, + Bar, + Baz(String), + Quux, +} +"#, + r#" +#[repr(u8)] +enum TheEnum { + Foo { x: u32 } = 0, + Bar = 1, + Baz(String) = 2, + Quux = 3, +} +"#, + ); + } + + #[test] + fn non_primitive_repr_data_bearing_not_applicable() { + check_assist_not_applicable( + add_explicit_enum_discriminant, + r#" +enum TheEnum$0 { + Foo, + Bar(u16), + Baz, +} +"#, + ); + } + + #[test] + fn primitive_repr_non_data_bearing_add_discriminant() { + check_assist( + add_explicit_enum_discriminant, + r#" +#[repr(i64)] +enum TheEnum { + Foo = 1 << 63, + Bar, + Baz$0 = 0x7fff_ffff_ffff_fffe, + Quux, +} +"#, + r#" +#[repr(i64)] +enum TheEnum { + Foo = 1 << 63, + Bar = -9223372036854775807, + Baz = 0x7fff_ffff_ffff_fffe, + Quux = 9223372036854775807, +} +"#, + ); + } + + #[test] + fn discriminants_already_explicit_not_applicable() { + check_assist_not_applicable( + add_explicit_enum_discriminant, + r#" +enum TheEnum$0 { + Foo = 0, + Bar = 4, +} +"#, + ); + } + + #[test] + fn empty_enum_not_applicable() { + check_assist_not_applicable( + add_explicit_enum_discriminant, + r#" +enum TheEnum$0 {} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs deleted file mode 100644 index cbd39796241..00000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bool_to_enum.rs +++ /dev/null @@ -1,1930 +0,0 @@ -use either::Either; -use hir::ModuleDef; -use ide_db::text_edit::TextRange; -use ide_db::{ - assists::{AssistId, AssistKind}, - defs::Definition, - helpers::mod_path_to_ast, - imports::insert_use::{insert_use, ImportScope}, - search::{FileReference, UsageSearchResult}, - source_change::SourceChangeBuilder, - FxHashSet, -}; -use itertools::Itertools; -use syntax::{ - ast::{ - self, - edit::IndentLevel, - edit_in_place::{AttrsOwnerEdit, Indent}, - make, HasName, - }, - AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, -}; - -use crate::{ - assist_context::{AssistContext, Assists}, - utils, -}; - -// Assist: bool_to_enum -// -// This converts boolean local variables, fields, constants, and statics into a new -// enum with two variants `Bool::True` and `Bool::False`, as well as replacing -// all assignments with the variants and replacing all usages with `== Bool::True` or -// `== Bool::False`. -// -// ``` -// fn main() { -// let $0bool = true; -// -// if bool { -// println!("foo"); -// } -// } -// ``` -// -> -// ``` -// #[derive(PartialEq, Eq)] -// enum Bool { True, False } -// -// fn main() { -// let bool = Bool::True; -// -// if bool == Bool::True { -// println!("foo"); -// } -// } -// ``` -pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let BoolNodeData { target_node, name, ty_annotation, initializer, definition } = - find_bool_node(ctx)?; - let target_module = ctx.sema.scope(&target_node)?.module().nearest_non_block_module(ctx.db()); - - let target = name.syntax().text_range(); - acc.add( - AssistId("bool_to_enum", AssistKind::RefactorRewrite), - "Convert boolean to enum", - target, - |edit| { - if let Some(ty) = &ty_annotation { - cov_mark::hit!(replaces_ty_annotation); - edit.replace(ty.syntax().text_range(), "Bool"); - } - - if let Some(initializer) = initializer { - replace_bool_expr(edit, initializer); - } - - let usages = definition.usages(&ctx.sema).all(); - add_enum_def(edit, ctx, &usages, target_node, &target_module); - let mut delayed_mutations = Vec::new(); - replace_usages(edit, ctx, usages, definition, &target_module, &mut delayed_mutations); - for (scope, path) in delayed_mutations { - insert_use(&scope, path, &ctx.config.insert_use); - } - }, - ) -} - -struct BoolNodeData { - target_node: SyntaxNode, - name: ast::Name, - ty_annotation: Option, - initializer: Option, - definition: Definition, -} - -/// Attempts to find an appropriate node to apply the action to. -fn find_bool_node(ctx: &AssistContext<'_>) -> Option { - let name = ctx.find_node_at_offset::()?; - - if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { - let def = ctx.sema.to_def(&ident_pat)?; - if !def.ty(ctx.db()).is_bool() { - cov_mark::hit!(not_applicable_non_bool_local); - return None; - } - - let local_definition = Definition::Local(def); - match ident_pat.syntax().parent().and_then(Either::::cast)? { - Either::Left(param) => Some(BoolNodeData { - target_node: param.syntax().clone(), - name, - ty_annotation: param.ty(), - initializer: None, - definition: local_definition, - }), - Either::Right(let_stmt) => Some(BoolNodeData { - target_node: let_stmt.syntax().clone(), - name, - ty_annotation: let_stmt.ty(), - initializer: let_stmt.initializer(), - definition: local_definition, - }), - } - } else if let Some(const_) = name.syntax().parent().and_then(ast::Const::cast) { - let def = ctx.sema.to_def(&const_)?; - if !def.ty(ctx.db()).is_bool() { - cov_mark::hit!(not_applicable_non_bool_const); - return None; - } - - Some(BoolNodeData { - target_node: const_.syntax().clone(), - name, - ty_annotation: const_.ty(), - initializer: const_.body(), - definition: Definition::Const(def), - }) - } else if let Some(static_) = name.syntax().parent().and_then(ast::Static::cast) { - let def = ctx.sema.to_def(&static_)?; - if !def.ty(ctx.db()).is_bool() { - cov_mark::hit!(not_applicable_non_bool_static); - return None; - } - - Some(BoolNodeData { - target_node: static_.syntax().clone(), - name, - ty_annotation: static_.ty(), - initializer: static_.body(), - definition: Definition::Static(def), - }) - } else { - let field = name.syntax().parent().and_then(ast::RecordField::cast)?; - if field.name()? != name { - return None; - } - - let adt = field.syntax().ancestors().find_map(ast::Adt::cast)?; - let def = ctx.sema.to_def(&field)?; - if !def.ty(ctx.db()).is_bool() { - cov_mark::hit!(not_applicable_non_bool_field); - return None; - } - Some(BoolNodeData { - target_node: adt.syntax().clone(), - name, - ty_annotation: field.ty(), - initializer: None, - definition: Definition::Field(def), - }) - } -} - -fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr) { - let expr_range = expr.syntax().text_range(); - let enum_expr = bool_expr_to_enum_expr(expr); - edit.replace(expr_range, enum_expr.syntax().text()) -} - -/// Converts an expression of type `bool` to one of the new enum type. -fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { - let true_expr = make::expr_path(make::path_from_text("Bool::True")); - let false_expr = make::expr_path(make::path_from_text("Bool::False")); - - if let ast::Expr::Literal(literal) = &expr { - match literal.kind() { - ast::LiteralKind::Bool(true) => true_expr, - ast::LiteralKind::Bool(false) => false_expr, - _ => expr, - } - } else { - make::expr_if( - expr, - make::tail_only_block_expr(true_expr), - Some(ast::ElseBranch::Block(make::tail_only_block_expr(false_expr))), - ) - .into() - } -} - -/// Replaces all usages of the target identifier, both when read and written to. -fn replace_usages( - edit: &mut SourceChangeBuilder, - ctx: &AssistContext<'_>, - usages: UsageSearchResult, - target_definition: Definition, - target_module: &hir::Module, - delayed_mutations: &mut Vec<(ImportScope, ast::Path)>, -) { - for (file_id, references) in usages { - edit.edit_file(file_id.file_id()); - - let refs_with_imports = augment_references_with_imports(ctx, references, target_module); - - refs_with_imports.into_iter().rev().for_each( - |FileReferenceWithImport { range, name, import_data }| { - // replace the usages in patterns and expressions - if let Some(ident_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { - cov_mark::hit!(replaces_record_pat_shorthand); - - let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); - if let Some(def) = definition { - replace_usages( - edit, - ctx, - def.usages(&ctx.sema).all(), - target_definition, - target_module, - delayed_mutations, - ) - } - } else if let Some(initializer) = find_assignment_usage(&name) { - cov_mark::hit!(replaces_assignment); - - replace_bool_expr(edit, initializer); - } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&name) { - cov_mark::hit!(replaces_negation); - - edit.replace( - prefix_expr.syntax().text_range(), - format!("{inner_expr} == Bool::False"), - ); - } else if let Some((record_field, initializer)) = name - .as_name_ref() - .and_then(ast::RecordExprField::for_field_name) - .and_then(|record_field| ctx.sema.resolve_record_field(&record_field)) - .and_then(|(got_field, _, _)| { - find_record_expr_usage(&name, got_field, target_definition) - }) - { - cov_mark::hit!(replaces_record_expr); - - let enum_expr = bool_expr_to_enum_expr(initializer); - utils::replace_record_field_expr(ctx, edit, record_field, enum_expr); - } else if let Some(pat) = find_record_pat_field_usage(&name) { - match pat { - ast::Pat::IdentPat(ident_pat) => { - cov_mark::hit!(replaces_record_pat); - - let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); - if let Some(def) = definition { - replace_usages( - edit, - ctx, - def.usages(&ctx.sema).all(), - target_definition, - target_module, - delayed_mutations, - ) - } - } - ast::Pat::LiteralPat(literal_pat) => { - cov_mark::hit!(replaces_literal_pat); - - if let Some(expr) = literal_pat.literal().and_then(|literal| { - literal.syntax().ancestors().find_map(ast::Expr::cast) - }) { - replace_bool_expr(edit, expr); - } - } - _ => (), - } - } else if let Some((ty_annotation, initializer)) = find_assoc_const_usage(&name) { - edit.replace(ty_annotation.syntax().text_range(), "Bool"); - replace_bool_expr(edit, initializer); - } else if let Some(receiver) = find_method_call_expr_usage(&name) { - edit.replace( - receiver.syntax().text_range(), - format!("({receiver} == Bool::True)"), - ); - } else if name.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { - // for any other usage in an expression, replace it with a check that it is the true variant - if let Some((record_field, expr)) = - name.as_name_ref().and_then(ast::RecordExprField::for_field_name).and_then( - |record_field| record_field.expr().map(|expr| (record_field, expr)), - ) - { - utils::replace_record_field_expr( - ctx, - edit, - record_field, - make::expr_bin_op( - expr, - ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }), - make::expr_path(make::path_from_text("Bool::True")), - ), - ); - } else { - edit.replace(range, format!("{} == Bool::True", name.text())); - } - } - - // add imports across modules where needed - if let Some((import_scope, path)) = import_data { - let scope = match import_scope { - ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), - }; - delayed_mutations.push((scope, path)); - } - }, - ) - } -} - -struct FileReferenceWithImport { - range: TextRange, - name: ast::NameLike, - import_data: Option<(ImportScope, ast::Path)>, -} - -fn augment_references_with_imports( - ctx: &AssistContext<'_>, - references: Vec, - target_module: &hir::Module, -) -> Vec { - let mut visited_modules = FxHashSet::default(); - - let cfg = ctx.config.import_path_config(); - - let edition = target_module.krate().edition(ctx.db()); - references - .into_iter() - .filter_map(|FileReference { range, name, .. }| { - let name = name.into_name_like()?; - ctx.sema.scope(name.syntax()).map(|scope| (range, name, scope.module())) - }) - .map(|(range, name, ref_module)| { - // if the referenced module is not the same as the target one and has not been seen before, add an import - let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module - && !visited_modules.contains(&ref_module) - { - visited_modules.insert(ref_module); - - let import_scope = ImportScope::find_insert_use_container(name.syntax(), &ctx.sema); - let path = ref_module - .find_use_path( - ctx.sema.db, - ModuleDef::Module(*target_module), - ctx.config.insert_use.prefix_kind, - cfg, - ) - .map(|mod_path| { - make::path_concat( - mod_path_to_ast(&mod_path, edition), - make::path_from_text("Bool"), - ) - }); - - import_scope.zip(path) - } else { - None - }; - - FileReferenceWithImport { range, name, import_data } - }) - .collect() -} - -fn find_assignment_usage(name: &ast::NameLike) -> Option { - let bin_expr = name.syntax().ancestors().find_map(ast::BinExpr::cast)?; - - if !bin_expr.lhs()?.syntax().descendants().contains(name.syntax()) { - cov_mark::hit!(dont_assign_incorrect_ref); - return None; - } - - if let Some(ast::BinaryOp::Assignment { op: None }) = bin_expr.op_kind() { - bin_expr.rhs() - } else { - None - } -} - -fn find_negated_usage(name: &ast::NameLike) -> Option<(ast::PrefixExpr, ast::Expr)> { - let prefix_expr = name.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; - - if !matches!(prefix_expr.expr()?, ast::Expr::PathExpr(_) | ast::Expr::FieldExpr(_)) { - cov_mark::hit!(dont_overwrite_expression_inside_negation); - return None; - } - - if let Some(ast::UnaryOp::Not) = prefix_expr.op_kind() { - let inner_expr = prefix_expr.expr()?; - Some((prefix_expr, inner_expr)) - } else { - None - } -} - -fn find_record_expr_usage( - name: &ast::NameLike, - got_field: hir::Field, - target_definition: Definition, -) -> Option<(ast::RecordExprField, ast::Expr)> { - let name_ref = name.as_name_ref()?; - let record_field = ast::RecordExprField::for_field_name(name_ref)?; - let initializer = record_field.expr()?; - - match target_definition { - Definition::Field(expected_field) if got_field == expected_field => { - Some((record_field, initializer)) - } - _ => None, - } -} - -fn find_record_pat_field_usage(name: &ast::NameLike) -> Option { - let record_pat_field = name.syntax().parent().and_then(ast::RecordPatField::cast)?; - let pat = record_pat_field.pat()?; - - match pat { - ast::Pat::IdentPat(_) | ast::Pat::LiteralPat(_) | ast::Pat::WildcardPat(_) => Some(pat), - _ => None, - } -} - -fn find_assoc_const_usage(name: &ast::NameLike) -> Option<(ast::Type, ast::Expr)> { - let const_ = name.syntax().parent().and_then(ast::Const::cast)?; - const_.syntax().parent().and_then(ast::AssocItemList::cast)?; - - Some((const_.ty()?, const_.body()?)) -} - -fn find_method_call_expr_usage(name: &ast::NameLike) -> Option { - let method_call = name.syntax().ancestors().find_map(ast::MethodCallExpr::cast)?; - let receiver = method_call.receiver()?; - - if !receiver.syntax().descendants().contains(name.syntax()) { - return None; - } - - Some(receiver) -} - -/// Adds the definition of the new enum before the target node. -fn add_enum_def( - edit: &mut SourceChangeBuilder, - ctx: &AssistContext<'_>, - usages: &UsageSearchResult, - target_node: SyntaxNode, - target_module: &hir::Module, -) -> Option<()> { - let insert_before = node_to_insert_before(target_node); - - if ctx - .sema - .scope(&insert_before)? - .module() - .scope(ctx.db(), Some(*target_module)) - .iter() - .any(|(name, _)| name.as_str() == "Bool") - { - return None; - } - - let make_enum_pub = usages - .iter() - .flat_map(|(_, refs)| refs) - .filter_map(|FileReference { name, .. }| { - let name = name.clone().into_name_like()?; - ctx.sema.scope(name.syntax()).map(|scope| scope.module()) - }) - .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module); - let enum_def = make_bool_enum(make_enum_pub); - - let indent = IndentLevel::from_node(&insert_before); - enum_def.reindent_to(indent); - - edit.insert( - insert_before.text_range().start(), - format!("{}\n\n{indent}", enum_def.syntax().text()), - ); - - Some(()) -} - -/// Finds where to put the new enum definition. -/// Tries to find the ast node at the nearest module or at top-level, otherwise just -/// returns the input node. -fn node_to_insert_before(target_node: SyntaxNode) -> SyntaxNode { - target_node - .ancestors() - .take_while(|it| !matches!(it.kind(), SyntaxKind::MODULE | SyntaxKind::SOURCE_FILE)) - .filter(|it| ast::Item::can_cast(it.kind())) - .last() - .unwrap_or(target_node) -} - -fn make_bool_enum(make_pub: bool) -> ast::Enum { - let enum_def = make::enum_( - if make_pub { Some(make::visibility_pub()) } else { None }, - make::name("Bool"), - None, - None, - make::variant_list(vec![ - make::variant(None, make::name("True"), None, None), - make::variant(None, make::name("False"), None, None), - ]), - ) - .clone_for_update(); - - let derive_eq = make::attr_outer(make::meta_token_tree( - make::ext::ident_path("derive"), - make::token_tree( - T!['('], - vec![ - NodeOrToken::Token(make::tokens::ident("PartialEq")), - NodeOrToken::Token(make::token(T![,])), - NodeOrToken::Token(make::tokens::single_space()), - NodeOrToken::Token(make::tokens::ident("Eq")), - ], - ), - )) - .clone_for_update(); - enum_def.add_attr(derive_eq); - - enum_def -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::tests::{check_assist, check_assist_not_applicable}; - - #[test] - fn parameter_with_first_param_usage() { - check_assist( - bool_to_enum, - r#" -fn function($0foo: bool, bar: bool) { - if foo { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn function(foo: Bool, bar: bool) { - if foo == Bool::True { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn no_duplicate_enums() { - check_assist( - bool_to_enum, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn function(foo: bool, $0bar: bool) { - if bar { - println!("bar"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn function(foo: bool, bar: Bool) { - if bar == Bool::True { - println!("bar"); - } -} -"#, - ) - } - - #[test] - fn parameter_with_last_param_usage() { - check_assist( - bool_to_enum, - r#" -fn function(foo: bool, $0bar: bool) { - if bar { - println!("bar"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn function(foo: bool, bar: Bool) { - if bar == Bool::True { - println!("bar"); - } -} -"#, - ) - } - - #[test] - fn parameter_with_middle_param_usage() { - check_assist( - bool_to_enum, - r#" -fn function(foo: bool, $0bar: bool, baz: bool) { - if bar { - println!("bar"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn function(foo: bool, bar: Bool, baz: bool) { - if bar == Bool::True { - println!("bar"); - } -} -"#, - ) - } - - #[test] - fn parameter_with_closure_usage() { - check_assist( - bool_to_enum, - r#" -fn main() { - let foo = |$0bar: bool| bar; -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = |bar: Bool| bar == Bool::True; -} -"#, - ) - } - - #[test] - fn local_variable_with_usage() { - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo = true; - - if foo { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = Bool::True; - - if foo == Bool::True { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn local_variable_with_usage_negated() { - cov_mark::check!(replaces_negation); - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo = true; - - if !foo { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = Bool::True; - - if foo == Bool::False { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn local_variable_with_type_annotation() { - cov_mark::check!(replaces_ty_annotation); - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo: bool = false; -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo: Bool = Bool::False; -} -"#, - ) - } - - #[test] - fn local_variable_with_non_literal_initializer() { - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo = 1 == 2; -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = if 1 == 2 { Bool::True } else { Bool::False }; -} -"#, - ) - } - - #[test] - fn local_variable_binexpr_usage() { - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo = false; - let bar = true; - - if !foo && bar { - println!("foobar"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = Bool::False; - let bar = true; - - if foo == Bool::False && bar { - println!("foobar"); - } -} -"#, - ) - } - - #[test] - fn local_variable_unop_usage() { - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo = true; - - if *&foo { - println!("foobar"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = Bool::True; - - if *&foo == Bool::True { - println!("foobar"); - } -} -"#, - ) - } - - #[test] - fn local_variable_assigned_later() { - cov_mark::check!(replaces_assignment); - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo: bool; - foo = true; -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo: Bool; - foo = Bool::True; -} -"#, - ) - } - - #[test] - fn local_variable_does_not_apply_recursively() { - check_assist( - bool_to_enum, - r#" -fn main() { - let $0foo = true; - let bar = !foo; - - if bar { - println!("bar"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = Bool::True; - let bar = foo == Bool::False; - - if bar { - println!("bar"); - } -} -"#, - ) - } - - #[test] - fn local_variable_nested_in_negation() { - cov_mark::check!(dont_overwrite_expression_inside_negation); - check_assist( - bool_to_enum, - r#" -fn main() { - if !"foo".chars().any(|c| { - let $0foo = true; - foo - }) { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - if !"foo".chars().any(|c| { - let foo = Bool::True; - foo == Bool::True - }) { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn local_variable_non_bool() { - cov_mark::check!(not_applicable_non_bool_local); - check_assist_not_applicable( - bool_to_enum, - r#" -fn main() { - let $0foo = 1; -} -"#, - ) - } - - #[test] - fn local_variable_cursor_not_on_ident() { - check_assist_not_applicable( - bool_to_enum, - r#" -fn main() { - let foo = $0true; -} -"#, - ) - } - - #[test] - fn local_variable_non_ident_pat() { - check_assist_not_applicable( - bool_to_enum, - r#" -fn main() { - let ($0foo, bar) = (true, false); -} -"#, - ) - } - - #[test] - fn local_var_init_struct_usage() { - check_assist( - bool_to_enum, - r#" -struct Foo { - foo: bool, -} - -fn main() { - let $0foo = true; - let s = Foo { foo }; -} -"#, - r#" -struct Foo { - foo: bool, -} - -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let foo = Bool::True; - let s = Foo { foo: foo == Bool::True }; -} -"#, - ) - } - - #[test] - fn local_var_init_struct_usage_in_macro() { - check_assist( - bool_to_enum, - r#" -struct Struct { - boolean: bool, -} - -macro_rules! identity { - ($body:expr) => { - $body - } -} - -fn new() -> Struct { - let $0boolean = true; - identity![Struct { boolean }] -} -"#, - r#" -struct Struct { - boolean: bool, -} - -macro_rules! identity { - ($body:expr) => { - $body - } -} - -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn new() -> Struct { - let boolean = Bool::True; - identity![Struct { boolean: boolean == Bool::True }] -} -"#, - ) - } - - #[test] - fn field_struct_basic() { - cov_mark::check!(replaces_record_expr); - check_assist( - bool_to_enum, - r#" -struct Foo { - $0bar: bool, - baz: bool, -} - -fn main() { - let foo = Foo { bar: true, baz: false }; - - if foo.bar { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Foo { - bar: Bool, - baz: bool, -} - -fn main() { - let foo = Foo { bar: Bool::True, baz: false }; - - if foo.bar == Bool::True { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn field_enum_basic() { - cov_mark::check!(replaces_record_pat); - check_assist( - bool_to_enum, - r#" -enum Foo { - Foo, - Bar { $0bar: bool }, -} - -fn main() { - let foo = Foo::Bar { bar: true }; - - if let Foo::Bar { bar: baz } = foo { - if baz { - println!("foo"); - } - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -enum Foo { - Foo, - Bar { bar: Bool }, -} - -fn main() { - let foo = Foo::Bar { bar: Bool::True }; - - if let Foo::Bar { bar: baz } = foo { - if baz == Bool::True { - println!("foo"); - } - } -} -"#, - ) - } - - #[test] - fn field_enum_cross_file() { - // FIXME: The import is missing - check_assist( - bool_to_enum, - r#" -//- /foo.rs -pub enum Foo { - Foo, - Bar { $0bar: bool }, -} - -fn foo() { - let foo = Foo::Bar { bar: true }; -} - -//- /main.rs -use foo::Foo; - -mod foo; - -fn main() { - let foo = Foo::Bar { bar: false }; -} -"#, - r#" -//- /foo.rs -#[derive(PartialEq, Eq)] -pub enum Bool { True, False } - -pub enum Foo { - Foo, - Bar { bar: Bool }, -} - -fn foo() { - let foo = Foo::Bar { bar: Bool::True }; -} - -//- /main.rs -use foo::Foo; - -mod foo; - -fn main() { - let foo = Foo::Bar { bar: Bool::False }; -} -"#, - ) - } - - #[test] - fn field_enum_shorthand() { - cov_mark::check!(replaces_record_pat_shorthand); - check_assist( - bool_to_enum, - r#" -enum Foo { - Foo, - Bar { $0bar: bool }, -} - -fn main() { - let foo = Foo::Bar { bar: true }; - - match foo { - Foo::Bar { bar } => { - if bar { - println!("foo"); - } - } - _ => (), - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -enum Foo { - Foo, - Bar { bar: Bool }, -} - -fn main() { - let foo = Foo::Bar { bar: Bool::True }; - - match foo { - Foo::Bar { bar } => { - if bar == Bool::True { - println!("foo"); - } - } - _ => (), - } -} -"#, - ) - } - - #[test] - fn field_enum_replaces_literal_patterns() { - cov_mark::check!(replaces_literal_pat); - check_assist( - bool_to_enum, - r#" -enum Foo { - Foo, - Bar { $0bar: bool }, -} - -fn main() { - let foo = Foo::Bar { bar: true }; - - if let Foo::Bar { bar: true } = foo { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -enum Foo { - Foo, - Bar { bar: Bool }, -} - -fn main() { - let foo = Foo::Bar { bar: Bool::True }; - - if let Foo::Bar { bar: Bool::True } = foo { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn field_enum_keeps_wildcard_patterns() { - check_assist( - bool_to_enum, - r#" -enum Foo { - Foo, - Bar { $0bar: bool }, -} - -fn main() { - let foo = Foo::Bar { bar: true }; - - if let Foo::Bar { bar: _ } = foo { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -enum Foo { - Foo, - Bar { bar: Bool }, -} - -fn main() { - let foo = Foo::Bar { bar: Bool::True }; - - if let Foo::Bar { bar: _ } = foo { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn field_union_basic() { - check_assist( - bool_to_enum, - r#" -union Foo { - $0foo: bool, - bar: usize, -} - -fn main() { - let foo = Foo { foo: true }; - - if unsafe { foo.foo } { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -union Foo { - foo: Bool, - bar: usize, -} - -fn main() { - let foo = Foo { foo: Bool::True }; - - if unsafe { foo.foo == Bool::True } { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn field_negated() { - check_assist( - bool_to_enum, - r#" -struct Foo { - $0bar: bool, -} - -fn main() { - let foo = Foo { bar: false }; - - if !foo.bar { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Foo { - bar: Bool, -} - -fn main() { - let foo = Foo { bar: Bool::False }; - - if foo.bar == Bool::False { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn field_in_mod_properly_indented() { - check_assist( - bool_to_enum, - r#" -mod foo { - struct Bar { - $0baz: bool, - } - - impl Bar { - fn new(baz: bool) -> Self { - Self { baz } - } - } -} -"#, - r#" -mod foo { - #[derive(PartialEq, Eq)] - enum Bool { True, False } - - struct Bar { - baz: Bool, - } - - impl Bar { - fn new(baz: bool) -> Self { - Self { baz: if baz { Bool::True } else { Bool::False } } - } - } -} -"#, - ) - } - - #[test] - fn field_multiple_initializations() { - check_assist( - bool_to_enum, - r#" -struct Foo { - $0bar: bool, - baz: bool, -} - -fn main() { - let foo1 = Foo { bar: true, baz: false }; - let foo2 = Foo { bar: false, baz: false }; - - if foo1.bar && foo2.bar { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Foo { - bar: Bool, - baz: bool, -} - -fn main() { - let foo1 = Foo { bar: Bool::True, baz: false }; - let foo2 = Foo { bar: Bool::False, baz: false }; - - if foo1.bar == Bool::True && foo2.bar == Bool::True { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn field_assigned_to_another() { - cov_mark::check!(dont_assign_incorrect_ref); - check_assist( - bool_to_enum, - r#" -struct Foo { - $0foo: bool, -} - -struct Bar { - bar: bool, -} - -fn main() { - let foo = Foo { foo: true }; - let mut bar = Bar { bar: true }; - - bar.bar = foo.foo; -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Foo { - foo: Bool, -} - -struct Bar { - bar: bool, -} - -fn main() { - let foo = Foo { foo: Bool::True }; - let mut bar = Bar { bar: true }; - - bar.bar = foo.foo == Bool::True; -} -"#, - ) - } - - #[test] - fn field_initialized_with_other() { - check_assist( - bool_to_enum, - r#" -struct Foo { - $0foo: bool, -} - -struct Bar { - bar: bool, -} - -fn main() { - let foo = Foo { foo: true }; - let bar = Bar { bar: foo.foo }; -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Foo { - foo: Bool, -} - -struct Bar { - bar: bool, -} - -fn main() { - let foo = Foo { foo: Bool::True }; - let bar = Bar { bar: foo.foo == Bool::True }; -} -"#, - ) - } - - #[test] - fn field_method_chain_usage() { - check_assist( - bool_to_enum, - r#" -struct Foo { - $0bool: bool, -} - -fn main() { - let foo = Foo { bool: true }; - - foo.bool.then(|| 2); -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Foo { - bool: Bool, -} - -fn main() { - let foo = Foo { bool: Bool::True }; - - (foo.bool == Bool::True).then(|| 2); -} -"#, - ) - } - - #[test] - fn field_in_macro() { - check_assist( - bool_to_enum, - r#" -struct Struct { - $0boolean: bool, -} - -fn boolean(x: Struct) { - let Struct { boolean } = x; -} - -macro_rules! identity { ($body:expr) => { $body } } - -fn new() -> Struct { - identity!(Struct { boolean: true }) -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -struct Struct { - boolean: Bool, -} - -fn boolean(x: Struct) { - let Struct { boolean } = x; -} - -macro_rules! identity { ($body:expr) => { $body } } - -fn new() -> Struct { - identity!(Struct { boolean: Bool::True }) -} -"#, - ) - } - - #[test] - fn field_non_bool() { - cov_mark::check!(not_applicable_non_bool_field); - check_assist_not_applicable( - bool_to_enum, - r#" -struct Foo { - $0bar: usize, -} - -fn main() { - let foo = Foo { bar: 1 }; -} -"#, - ) - } - - #[test] - fn const_basic() { - check_assist( - bool_to_enum, - r#" -const $0FOO: bool = false; - -fn main() { - if FOO { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -const FOO: Bool = Bool::False; - -fn main() { - if FOO == Bool::True { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn const_in_module() { - check_assist( - bool_to_enum, - r#" -fn main() { - if foo::FOO { - println!("foo"); - } -} - -mod foo { - pub const $0FOO: bool = true; -} -"#, - r#" -use foo::Bool; - -fn main() { - if foo::FOO == Bool::True { - println!("foo"); - } -} - -mod foo { - #[derive(PartialEq, Eq)] - pub enum Bool { True, False } - - pub const FOO: Bool = Bool::True; -} -"#, - ) - } - - #[test] - fn const_in_module_with_import() { - check_assist( - bool_to_enum, - r#" -fn main() { - use foo::FOO; - - if FOO { - println!("foo"); - } -} - -mod foo { - pub const $0FOO: bool = true; -} -"#, - r#" -use foo::Bool; - -fn main() { - use foo::FOO; - - if FOO == Bool::True { - println!("foo"); - } -} - -mod foo { - #[derive(PartialEq, Eq)] - pub enum Bool { True, False } - - pub const FOO: Bool = Bool::True; -} -"#, - ) - } - - #[test] - fn const_cross_file() { - check_assist( - bool_to_enum, - r#" -//- /main.rs -mod foo; - -fn main() { - if foo::FOO { - println!("foo"); - } -} - -//- /foo.rs -pub const $0FOO: bool = true; -"#, - r#" -//- /main.rs -use foo::Bool; - -mod foo; - -fn main() { - if foo::FOO == Bool::True { - println!("foo"); - } -} - -//- /foo.rs -#[derive(PartialEq, Eq)] -pub enum Bool { True, False } - -pub const FOO: Bool = Bool::True; -"#, - ) - } - - #[test] - fn const_cross_file_and_module() { - check_assist( - bool_to_enum, - r#" -//- /main.rs -mod foo; - -fn main() { - use foo::bar; - - if bar::BAR { - println!("foo"); - } -} - -//- /foo.rs -pub mod bar { - pub const $0BAR: bool = false; -} -"#, - r#" -//- /main.rs -use foo::bar::Bool; - -mod foo; - -fn main() { - use foo::bar; - - if bar::BAR == Bool::True { - println!("foo"); - } -} - -//- /foo.rs -pub mod bar { - #[derive(PartialEq, Eq)] - pub enum Bool { True, False } - - pub const BAR: Bool = Bool::False; -} -"#, - ) - } - - #[test] - fn const_in_impl_cross_file() { - check_assist( - bool_to_enum, - r#" -//- /main.rs -mod foo; - -struct Foo; - -impl Foo { - pub const $0BOOL: bool = true; -} - -//- /foo.rs -use crate::Foo; - -fn foo() -> bool { - Foo::BOOL -} -"#, - r#" -//- /main.rs -mod foo; - -struct Foo; - -#[derive(PartialEq, Eq)] -pub enum Bool { True, False } - -impl Foo { - pub const BOOL: Bool = Bool::True; -} - -//- /foo.rs -use crate::{Bool, Foo}; - -fn foo() -> bool { - Foo::BOOL == Bool::True -} -"#, - ) - } - - #[test] - fn const_in_trait() { - check_assist( - bool_to_enum, - r#" -trait Foo { - const $0BOOL: bool; -} - -impl Foo for usize { - const BOOL: bool = true; -} - -fn main() { - if ::BOOL { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -trait Foo { - const BOOL: Bool; -} - -impl Foo for usize { - const BOOL: Bool = Bool::True; -} - -fn main() { - if ::BOOL == Bool::True { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn const_non_bool() { - cov_mark::check!(not_applicable_non_bool_const); - check_assist_not_applicable( - bool_to_enum, - r#" -const $0FOO: &str = "foo"; - -fn main() { - println!("{FOO}"); -} -"#, - ) - } - - #[test] - fn static_basic() { - check_assist( - bool_to_enum, - r#" -static mut $0BOOL: bool = true; - -fn main() { - unsafe { BOOL = false }; - if unsafe { BOOL } { - println!("foo"); - } -} -"#, - r#" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -static mut BOOL: Bool = Bool::True; - -fn main() { - unsafe { BOOL = Bool::False }; - if unsafe { BOOL == Bool::True } { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn static_non_bool() { - cov_mark::check!(not_applicable_non_bool_static); - check_assist_not_applicable( - bool_to_enum, - r#" -static mut $0FOO: usize = 0; - -fn main() { - if unsafe { FOO } == 0 { - println!("foo"); - } -} -"#, - ) - } - - #[test] - fn not_applicable_to_other_names() { - check_assist_not_applicable(bool_to_enum, "fn $0main() {}") - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs new file mode 100644 index 00000000000..7716e99e604 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs @@ -0,0 +1,1930 @@ +use either::Either; +use hir::ModuleDef; +use ide_db::text_edit::TextRange; +use ide_db::{ + assists::{AssistId, AssistKind}, + defs::Definition, + helpers::mod_path_to_ast, + imports::insert_use::{insert_use, ImportScope}, + search::{FileReference, UsageSearchResult}, + source_change::SourceChangeBuilder, + FxHashSet, +}; +use itertools::Itertools; +use syntax::{ + ast::{ + self, + edit::IndentLevel, + edit_in_place::{AttrsOwnerEdit, Indent}, + make, HasName, + }, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, +}; + +use crate::{ + assist_context::{AssistContext, Assists}, + utils, +}; + +// Assist: convert_bool_to_enum +// +// This converts boolean local variables, fields, constants, and statics into a new +// enum with two variants `Bool::True` and `Bool::False`, as well as replacing +// all assignments with the variants and replacing all usages with `== Bool::True` or +// `== Bool::False`. +// +// ``` +// fn main() { +// let $0bool = true; +// +// if bool { +// println!("foo"); +// } +// } +// ``` +// -> +// ``` +// #[derive(PartialEq, Eq)] +// enum Bool { True, False } +// +// fn main() { +// let bool = Bool::True; +// +// if bool == Bool::True { +// println!("foo"); +// } +// } +// ``` +pub(crate) fn convert_bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let BoolNodeData { target_node, name, ty_annotation, initializer, definition } = + find_bool_node(ctx)?; + let target_module = ctx.sema.scope(&target_node)?.module().nearest_non_block_module(ctx.db()); + + let target = name.syntax().text_range(); + acc.add( + AssistId("convert_bool_to_enum", AssistKind::RefactorRewrite), + "Convert boolean to enum", + target, + |edit| { + if let Some(ty) = &ty_annotation { + cov_mark::hit!(replaces_ty_annotation); + edit.replace(ty.syntax().text_range(), "Bool"); + } + + if let Some(initializer) = initializer { + replace_bool_expr(edit, initializer); + } + + let usages = definition.usages(&ctx.sema).all(); + add_enum_def(edit, ctx, &usages, target_node, &target_module); + let mut delayed_mutations = Vec::new(); + replace_usages(edit, ctx, usages, definition, &target_module, &mut delayed_mutations); + for (scope, path) in delayed_mutations { + insert_use(&scope, path, &ctx.config.insert_use); + } + }, + ) +} + +struct BoolNodeData { + target_node: SyntaxNode, + name: ast::Name, + ty_annotation: Option, + initializer: Option, + definition: Definition, +} + +/// Attempts to find an appropriate node to apply the action to. +fn find_bool_node(ctx: &AssistContext<'_>) -> Option { + let name = ctx.find_node_at_offset::()?; + + if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { + let def = ctx.sema.to_def(&ident_pat)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_local); + return None; + } + + let local_definition = Definition::Local(def); + match ident_pat.syntax().parent().and_then(Either::::cast)? { + Either::Left(param) => Some(BoolNodeData { + target_node: param.syntax().clone(), + name, + ty_annotation: param.ty(), + initializer: None, + definition: local_definition, + }), + Either::Right(let_stmt) => Some(BoolNodeData { + target_node: let_stmt.syntax().clone(), + name, + ty_annotation: let_stmt.ty(), + initializer: let_stmt.initializer(), + definition: local_definition, + }), + } + } else if let Some(const_) = name.syntax().parent().and_then(ast::Const::cast) { + let def = ctx.sema.to_def(&const_)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_const); + return None; + } + + Some(BoolNodeData { + target_node: const_.syntax().clone(), + name, + ty_annotation: const_.ty(), + initializer: const_.body(), + definition: Definition::Const(def), + }) + } else if let Some(static_) = name.syntax().parent().and_then(ast::Static::cast) { + let def = ctx.sema.to_def(&static_)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_static); + return None; + } + + Some(BoolNodeData { + target_node: static_.syntax().clone(), + name, + ty_annotation: static_.ty(), + initializer: static_.body(), + definition: Definition::Static(def), + }) + } else { + let field = name.syntax().parent().and_then(ast::RecordField::cast)?; + if field.name()? != name { + return None; + } + + let adt = field.syntax().ancestors().find_map(ast::Adt::cast)?; + let def = ctx.sema.to_def(&field)?; + if !def.ty(ctx.db()).is_bool() { + cov_mark::hit!(not_applicable_non_bool_field); + return None; + } + Some(BoolNodeData { + target_node: adt.syntax().clone(), + name, + ty_annotation: field.ty(), + initializer: None, + definition: Definition::Field(def), + }) + } +} + +fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr) { + let expr_range = expr.syntax().text_range(); + let enum_expr = bool_expr_to_enum_expr(expr); + edit.replace(expr_range, enum_expr.syntax().text()) +} + +/// Converts an expression of type `bool` to one of the new enum type. +fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { + let true_expr = make::expr_path(make::path_from_text("Bool::True")); + let false_expr = make::expr_path(make::path_from_text("Bool::False")); + + if let ast::Expr::Literal(literal) = &expr { + match literal.kind() { + ast::LiteralKind::Bool(true) => true_expr, + ast::LiteralKind::Bool(false) => false_expr, + _ => expr, + } + } else { + make::expr_if( + expr, + make::tail_only_block_expr(true_expr), + Some(ast::ElseBranch::Block(make::tail_only_block_expr(false_expr))), + ) + .into() + } +} + +/// Replaces all usages of the target identifier, both when read and written to. +fn replace_usages( + edit: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + usages: UsageSearchResult, + target_definition: Definition, + target_module: &hir::Module, + delayed_mutations: &mut Vec<(ImportScope, ast::Path)>, +) { + for (file_id, references) in usages { + edit.edit_file(file_id.file_id()); + + let refs_with_imports = augment_references_with_imports(ctx, references, target_module); + + refs_with_imports.into_iter().rev().for_each( + |FileReferenceWithImport { range, name, import_data }| { + // replace the usages in patterns and expressions + if let Some(ident_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { + cov_mark::hit!(replaces_record_pat_shorthand); + + let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); + if let Some(def) = definition { + replace_usages( + edit, + ctx, + def.usages(&ctx.sema).all(), + target_definition, + target_module, + delayed_mutations, + ) + } + } else if let Some(initializer) = find_assignment_usage(&name) { + cov_mark::hit!(replaces_assignment); + + replace_bool_expr(edit, initializer); + } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&name) { + cov_mark::hit!(replaces_negation); + + edit.replace( + prefix_expr.syntax().text_range(), + format!("{inner_expr} == Bool::False"), + ); + } else if let Some((record_field, initializer)) = name + .as_name_ref() + .and_then(ast::RecordExprField::for_field_name) + .and_then(|record_field| ctx.sema.resolve_record_field(&record_field)) + .and_then(|(got_field, _, _)| { + find_record_expr_usage(&name, got_field, target_definition) + }) + { + cov_mark::hit!(replaces_record_expr); + + let enum_expr = bool_expr_to_enum_expr(initializer); + utils::replace_record_field_expr(ctx, edit, record_field, enum_expr); + } else if let Some(pat) = find_record_pat_field_usage(&name) { + match pat { + ast::Pat::IdentPat(ident_pat) => { + cov_mark::hit!(replaces_record_pat); + + let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); + if let Some(def) = definition { + replace_usages( + edit, + ctx, + def.usages(&ctx.sema).all(), + target_definition, + target_module, + delayed_mutations, + ) + } + } + ast::Pat::LiteralPat(literal_pat) => { + cov_mark::hit!(replaces_literal_pat); + + if let Some(expr) = literal_pat.literal().and_then(|literal| { + literal.syntax().ancestors().find_map(ast::Expr::cast) + }) { + replace_bool_expr(edit, expr); + } + } + _ => (), + } + } else if let Some((ty_annotation, initializer)) = find_assoc_const_usage(&name) { + edit.replace(ty_annotation.syntax().text_range(), "Bool"); + replace_bool_expr(edit, initializer); + } else if let Some(receiver) = find_method_call_expr_usage(&name) { + edit.replace( + receiver.syntax().text_range(), + format!("({receiver} == Bool::True)"), + ); + } else if name.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { + // for any other usage in an expression, replace it with a check that it is the true variant + if let Some((record_field, expr)) = + name.as_name_ref().and_then(ast::RecordExprField::for_field_name).and_then( + |record_field| record_field.expr().map(|expr| (record_field, expr)), + ) + { + utils::replace_record_field_expr( + ctx, + edit, + record_field, + make::expr_bin_op( + expr, + ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }), + make::expr_path(make::path_from_text("Bool::True")), + ), + ); + } else { + edit.replace(range, format!("{} == Bool::True", name.text())); + } + } + + // add imports across modules where needed + if let Some((import_scope, path)) = import_data { + let scope = match import_scope { + ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), + ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), + ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), + }; + delayed_mutations.push((scope, path)); + } + }, + ) + } +} + +struct FileReferenceWithImport { + range: TextRange, + name: ast::NameLike, + import_data: Option<(ImportScope, ast::Path)>, +} + +fn augment_references_with_imports( + ctx: &AssistContext<'_>, + references: Vec, + target_module: &hir::Module, +) -> Vec { + let mut visited_modules = FxHashSet::default(); + + let cfg = ctx.config.import_path_config(); + + let edition = target_module.krate().edition(ctx.db()); + references + .into_iter() + .filter_map(|FileReference { range, name, .. }| { + let name = name.into_name_like()?; + ctx.sema.scope(name.syntax()).map(|scope| (range, name, scope.module())) + }) + .map(|(range, name, ref_module)| { + // if the referenced module is not the same as the target one and has not been seen before, add an import + let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module + && !visited_modules.contains(&ref_module) + { + visited_modules.insert(ref_module); + + let import_scope = ImportScope::find_insert_use_container(name.syntax(), &ctx.sema); + let path = ref_module + .find_use_path( + ctx.sema.db, + ModuleDef::Module(*target_module), + ctx.config.insert_use.prefix_kind, + cfg, + ) + .map(|mod_path| { + make::path_concat( + mod_path_to_ast(&mod_path, edition), + make::path_from_text("Bool"), + ) + }); + + import_scope.zip(path) + } else { + None + }; + + FileReferenceWithImport { range, name, import_data } + }) + .collect() +} + +fn find_assignment_usage(name: &ast::NameLike) -> Option { + let bin_expr = name.syntax().ancestors().find_map(ast::BinExpr::cast)?; + + if !bin_expr.lhs()?.syntax().descendants().contains(name.syntax()) { + cov_mark::hit!(dont_assign_incorrect_ref); + return None; + } + + if let Some(ast::BinaryOp::Assignment { op: None }) = bin_expr.op_kind() { + bin_expr.rhs() + } else { + None + } +} + +fn find_negated_usage(name: &ast::NameLike) -> Option<(ast::PrefixExpr, ast::Expr)> { + let prefix_expr = name.syntax().ancestors().find_map(ast::PrefixExpr::cast)?; + + if !matches!(prefix_expr.expr()?, ast::Expr::PathExpr(_) | ast::Expr::FieldExpr(_)) { + cov_mark::hit!(dont_overwrite_expression_inside_negation); + return None; + } + + if let Some(ast::UnaryOp::Not) = prefix_expr.op_kind() { + let inner_expr = prefix_expr.expr()?; + Some((prefix_expr, inner_expr)) + } else { + None + } +} + +fn find_record_expr_usage( + name: &ast::NameLike, + got_field: hir::Field, + target_definition: Definition, +) -> Option<(ast::RecordExprField, ast::Expr)> { + let name_ref = name.as_name_ref()?; + let record_field = ast::RecordExprField::for_field_name(name_ref)?; + let initializer = record_field.expr()?; + + match target_definition { + Definition::Field(expected_field) if got_field == expected_field => { + Some((record_field, initializer)) + } + _ => None, + } +} + +fn find_record_pat_field_usage(name: &ast::NameLike) -> Option { + let record_pat_field = name.syntax().parent().and_then(ast::RecordPatField::cast)?; + let pat = record_pat_field.pat()?; + + match pat { + ast::Pat::IdentPat(_) | ast::Pat::LiteralPat(_) | ast::Pat::WildcardPat(_) => Some(pat), + _ => None, + } +} + +fn find_assoc_const_usage(name: &ast::NameLike) -> Option<(ast::Type, ast::Expr)> { + let const_ = name.syntax().parent().and_then(ast::Const::cast)?; + const_.syntax().parent().and_then(ast::AssocItemList::cast)?; + + Some((const_.ty()?, const_.body()?)) +} + +fn find_method_call_expr_usage(name: &ast::NameLike) -> Option { + let method_call = name.syntax().ancestors().find_map(ast::MethodCallExpr::cast)?; + let receiver = method_call.receiver()?; + + if !receiver.syntax().descendants().contains(name.syntax()) { + return None; + } + + Some(receiver) +} + +/// Adds the definition of the new enum before the target node. +fn add_enum_def( + edit: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, + usages: &UsageSearchResult, + target_node: SyntaxNode, + target_module: &hir::Module, +) -> Option<()> { + let insert_before = node_to_insert_before(target_node); + + if ctx + .sema + .scope(&insert_before)? + .module() + .scope(ctx.db(), Some(*target_module)) + .iter() + .any(|(name, _)| name.as_str() == "Bool") + { + return None; + } + + let make_enum_pub = usages + .iter() + .flat_map(|(_, refs)| refs) + .filter_map(|FileReference { name, .. }| { + let name = name.clone().into_name_like()?; + ctx.sema.scope(name.syntax()).map(|scope| scope.module()) + }) + .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module); + let enum_def = make_bool_enum(make_enum_pub); + + let indent = IndentLevel::from_node(&insert_before); + enum_def.reindent_to(indent); + + edit.insert( + insert_before.text_range().start(), + format!("{}\n\n{indent}", enum_def.syntax().text()), + ); + + Some(()) +} + +/// Finds where to put the new enum definition. +/// Tries to find the ast node at the nearest module or at top-level, otherwise just +/// returns the input node. +fn node_to_insert_before(target_node: SyntaxNode) -> SyntaxNode { + target_node + .ancestors() + .take_while(|it| !matches!(it.kind(), SyntaxKind::MODULE | SyntaxKind::SOURCE_FILE)) + .filter(|it| ast::Item::can_cast(it.kind())) + .last() + .unwrap_or(target_node) +} + +fn make_bool_enum(make_pub: bool) -> ast::Enum { + let enum_def = make::enum_( + if make_pub { Some(make::visibility_pub()) } else { None }, + make::name("Bool"), + None, + None, + make::variant_list(vec![ + make::variant(None, make::name("True"), None, None), + make::variant(None, make::name("False"), None, None), + ]), + ) + .clone_for_update(); + + let derive_eq = make::attr_outer(make::meta_token_tree( + make::ext::ident_path("derive"), + make::token_tree( + T!['('], + vec![ + NodeOrToken::Token(make::tokens::ident("PartialEq")), + NodeOrToken::Token(make::token(T![,])), + NodeOrToken::Token(make::tokens::single_space()), + NodeOrToken::Token(make::tokens::ident("Eq")), + ], + ), + )) + .clone_for_update(); + enum_def.add_attr(derive_eq); + + enum_def +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn parameter_with_first_param_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn function($0foo: bool, bar: bool) { + if foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn function(foo: Bool, bar: bool) { + if foo == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn no_duplicate_enums() { + check_assist( + convert_bool_to_enum, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn function(foo: bool, $0bar: bool) { + if bar { + println!("bar"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn function(foo: bool, bar: Bool) { + if bar == Bool::True { + println!("bar"); + } +} +"#, + ) + } + + #[test] + fn parameter_with_last_param_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn function(foo: bool, $0bar: bool) { + if bar { + println!("bar"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn function(foo: bool, bar: Bool) { + if bar == Bool::True { + println!("bar"); + } +} +"#, + ) + } + + #[test] + fn parameter_with_middle_param_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn function(foo: bool, $0bar: bool, baz: bool) { + if bar { + println!("bar"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn function(foo: bool, bar: Bool, baz: bool) { + if bar == Bool::True { + println!("bar"); + } +} +"#, + ) + } + + #[test] + fn parameter_with_closure_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let foo = |$0bar: bool| bar; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = |bar: Bool| bar == Bool::True; +} +"#, + ) + } + + #[test] + fn local_variable_with_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = true; + + if foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::True; + + if foo == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn local_variable_with_usage_negated() { + cov_mark::check!(replaces_negation); + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = true; + + if !foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::True; + + if foo == Bool::False { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn local_variable_with_type_annotation() { + cov_mark::check!(replaces_ty_annotation); + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo: bool = false; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo: Bool = Bool::False; +} +"#, + ) + } + + #[test] + fn local_variable_with_non_literal_initializer() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = 1 == 2; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = if 1 == 2 { Bool::True } else { Bool::False }; +} +"#, + ) + } + + #[test] + fn local_variable_binexpr_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = false; + let bar = true; + + if !foo && bar { + println!("foobar"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::False; + let bar = true; + + if foo == Bool::False && bar { + println!("foobar"); + } +} +"#, + ) + } + + #[test] + fn local_variable_unop_usage() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = true; + + if *&foo { + println!("foobar"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::True; + + if *&foo == Bool::True { + println!("foobar"); + } +} +"#, + ) + } + + #[test] + fn local_variable_assigned_later() { + cov_mark::check!(replaces_assignment); + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo: bool; + foo = true; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo: Bool; + foo = Bool::True; +} +"#, + ) + } + + #[test] + fn local_variable_does_not_apply_recursively() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = true; + let bar = !foo; + + if bar { + println!("bar"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::True; + let bar = foo == Bool::False; + + if bar { + println!("bar"); + } +} +"#, + ) + } + + #[test] + fn local_variable_nested_in_negation() { + cov_mark::check!(dont_overwrite_expression_inside_negation); + check_assist( + convert_bool_to_enum, + r#" +fn main() { + if !"foo".chars().any(|c| { + let $0foo = true; + foo + }) { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + if !"foo".chars().any(|c| { + let foo = Bool::True; + foo == Bool::True + }) { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn local_variable_non_bool() { + cov_mark::check!(not_applicable_non_bool_local); + check_assist_not_applicable( + convert_bool_to_enum, + r#" +fn main() { + let $0foo = 1; +} +"#, + ) + } + + #[test] + fn local_variable_cursor_not_on_ident() { + check_assist_not_applicable( + convert_bool_to_enum, + r#" +fn main() { + let foo = $0true; +} +"#, + ) + } + + #[test] + fn local_variable_non_ident_pat() { + check_assist_not_applicable( + convert_bool_to_enum, + r#" +fn main() { + let ($0foo, bar) = (true, false); +} +"#, + ) + } + + #[test] + fn local_var_init_struct_usage() { + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + foo: bool, +} + +fn main() { + let $0foo = true; + let s = Foo { foo }; +} +"#, + r#" +struct Foo { + foo: bool, +} + +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::True; + let s = Foo { foo: foo == Bool::True }; +} +"#, + ) + } + + #[test] + fn local_var_init_struct_usage_in_macro() { + check_assist( + convert_bool_to_enum, + r#" +struct Struct { + boolean: bool, +} + +macro_rules! identity { + ($body:expr) => { + $body + } +} + +fn new() -> Struct { + let $0boolean = true; + identity![Struct { boolean }] +} +"#, + r#" +struct Struct { + boolean: bool, +} + +macro_rules! identity { + ($body:expr) => { + $body + } +} + +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn new() -> Struct { + let boolean = Bool::True; + identity![Struct { boolean: boolean == Bool::True }] +} +"#, + ) + } + + #[test] + fn field_struct_basic() { + cov_mark::check!(replaces_record_expr); + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + $0bar: bool, + baz: bool, +} + +fn main() { + let foo = Foo { bar: true, baz: false }; + + if foo.bar { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + bar: Bool, + baz: bool, +} + +fn main() { + let foo = Foo { bar: Bool::True, baz: false }; + + if foo.bar == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_enum_basic() { + cov_mark::check!(replaces_record_pat); + check_assist( + convert_bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + if let Foo::Bar { bar: baz } = foo { + if baz { + println!("foo"); + } + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + if let Foo::Bar { bar: baz } = foo { + if baz == Bool::True { + println!("foo"); + } + } +} +"#, + ) + } + + #[test] + fn field_enum_cross_file() { + // FIXME: The import is missing + check_assist( + convert_bool_to_enum, + r#" +//- /foo.rs +pub enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn foo() { + let foo = Foo::Bar { bar: true }; +} + +//- /main.rs +use foo::Foo; + +mod foo; + +fn main() { + let foo = Foo::Bar { bar: false }; +} +"#, + r#" +//- /foo.rs +#[derive(PartialEq, Eq)] +pub enum Bool { True, False } + +pub enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn foo() { + let foo = Foo::Bar { bar: Bool::True }; +} + +//- /main.rs +use foo::Foo; + +mod foo; + +fn main() { + let foo = Foo::Bar { bar: Bool::False }; +} +"#, + ) + } + + #[test] + fn field_enum_shorthand() { + cov_mark::check!(replaces_record_pat_shorthand); + check_assist( + convert_bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + match foo { + Foo::Bar { bar } => { + if bar { + println!("foo"); + } + } + _ => (), + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + match foo { + Foo::Bar { bar } => { + if bar == Bool::True { + println!("foo"); + } + } + _ => (), + } +} +"#, + ) + } + + #[test] + fn field_enum_replaces_literal_patterns() { + cov_mark::check!(replaces_literal_pat); + check_assist( + convert_bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + if let Foo::Bar { bar: true } = foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + if let Foo::Bar { bar: Bool::True } = foo { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_enum_keeps_wildcard_patterns() { + check_assist( + convert_bool_to_enum, + r#" +enum Foo { + Foo, + Bar { $0bar: bool }, +} + +fn main() { + let foo = Foo::Bar { bar: true }; + + if let Foo::Bar { bar: _ } = foo { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +enum Foo { + Foo, + Bar { bar: Bool }, +} + +fn main() { + let foo = Foo::Bar { bar: Bool::True }; + + if let Foo::Bar { bar: _ } = foo { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_union_basic() { + check_assist( + convert_bool_to_enum, + r#" +union Foo { + $0foo: bool, + bar: usize, +} + +fn main() { + let foo = Foo { foo: true }; + + if unsafe { foo.foo } { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +union Foo { + foo: Bool, + bar: usize, +} + +fn main() { + let foo = Foo { foo: Bool::True }; + + if unsafe { foo.foo == Bool::True } { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_negated() { + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + $0bar: bool, +} + +fn main() { + let foo = Foo { bar: false }; + + if !foo.bar { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + bar: Bool, +} + +fn main() { + let foo = Foo { bar: Bool::False }; + + if foo.bar == Bool::False { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_in_mod_properly_indented() { + check_assist( + convert_bool_to_enum, + r#" +mod foo { + struct Bar { + $0baz: bool, + } + + impl Bar { + fn new(baz: bool) -> Self { + Self { baz } + } + } +} +"#, + r#" +mod foo { + #[derive(PartialEq, Eq)] + enum Bool { True, False } + + struct Bar { + baz: Bool, + } + + impl Bar { + fn new(baz: bool) -> Self { + Self { baz: if baz { Bool::True } else { Bool::False } } + } + } +} +"#, + ) + } + + #[test] + fn field_multiple_initializations() { + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + $0bar: bool, + baz: bool, +} + +fn main() { + let foo1 = Foo { bar: true, baz: false }; + let foo2 = Foo { bar: false, baz: false }; + + if foo1.bar && foo2.bar { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + bar: Bool, + baz: bool, +} + +fn main() { + let foo1 = Foo { bar: Bool::True, baz: false }; + let foo2 = Foo { bar: Bool::False, baz: false }; + + if foo1.bar == Bool::True && foo2.bar == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn field_assigned_to_another() { + cov_mark::check!(dont_assign_incorrect_ref); + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + $0foo: bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: true }; + let mut bar = Bar { bar: true }; + + bar.bar = foo.foo; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + foo: Bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: Bool::True }; + let mut bar = Bar { bar: true }; + + bar.bar = foo.foo == Bool::True; +} +"#, + ) + } + + #[test] + fn field_initialized_with_other() { + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + $0foo: bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: true }; + let bar = Bar { bar: foo.foo }; +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + foo: Bool, +} + +struct Bar { + bar: bool, +} + +fn main() { + let foo = Foo { foo: Bool::True }; + let bar = Bar { bar: foo.foo == Bool::True }; +} +"#, + ) + } + + #[test] + fn field_method_chain_usage() { + check_assist( + convert_bool_to_enum, + r#" +struct Foo { + $0bool: bool, +} + +fn main() { + let foo = Foo { bool: true }; + + foo.bool.then(|| 2); +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Foo { + bool: Bool, +} + +fn main() { + let foo = Foo { bool: Bool::True }; + + (foo.bool == Bool::True).then(|| 2); +} +"#, + ) + } + + #[test] + fn field_in_macro() { + check_assist( + convert_bool_to_enum, + r#" +struct Struct { + $0boolean: bool, +} + +fn boolean(x: Struct) { + let Struct { boolean } = x; +} + +macro_rules! identity { ($body:expr) => { $body } } + +fn new() -> Struct { + identity!(Struct { boolean: true }) +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Struct { + boolean: Bool, +} + +fn boolean(x: Struct) { + let Struct { boolean } = x; +} + +macro_rules! identity { ($body:expr) => { $body } } + +fn new() -> Struct { + identity!(Struct { boolean: Bool::True }) +} +"#, + ) + } + + #[test] + fn field_non_bool() { + cov_mark::check!(not_applicable_non_bool_field); + check_assist_not_applicable( + convert_bool_to_enum, + r#" +struct Foo { + $0bar: usize, +} + +fn main() { + let foo = Foo { bar: 1 }; +} +"#, + ) + } + + #[test] + fn const_basic() { + check_assist( + convert_bool_to_enum, + r#" +const $0FOO: bool = false; + +fn main() { + if FOO { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +const FOO: Bool = Bool::False; + +fn main() { + if FOO == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn const_in_module() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + if foo::FOO { + println!("foo"); + } +} + +mod foo { + pub const $0FOO: bool = true; +} +"#, + r#" +use foo::Bool; + +fn main() { + if foo::FOO == Bool::True { + println!("foo"); + } +} + +mod foo { + #[derive(PartialEq, Eq)] + pub enum Bool { True, False } + + pub const FOO: Bool = Bool::True; +} +"#, + ) + } + + #[test] + fn const_in_module_with_import() { + check_assist( + convert_bool_to_enum, + r#" +fn main() { + use foo::FOO; + + if FOO { + println!("foo"); + } +} + +mod foo { + pub const $0FOO: bool = true; +} +"#, + r#" +use foo::Bool; + +fn main() { + use foo::FOO; + + if FOO == Bool::True { + println!("foo"); + } +} + +mod foo { + #[derive(PartialEq, Eq)] + pub enum Bool { True, False } + + pub const FOO: Bool = Bool::True; +} +"#, + ) + } + + #[test] + fn const_cross_file() { + check_assist( + convert_bool_to_enum, + r#" +//- /main.rs +mod foo; + +fn main() { + if foo::FOO { + println!("foo"); + } +} + +//- /foo.rs +pub const $0FOO: bool = true; +"#, + r#" +//- /main.rs +use foo::Bool; + +mod foo; + +fn main() { + if foo::FOO == Bool::True { + println!("foo"); + } +} + +//- /foo.rs +#[derive(PartialEq, Eq)] +pub enum Bool { True, False } + +pub const FOO: Bool = Bool::True; +"#, + ) + } + + #[test] + fn const_cross_file_and_module() { + check_assist( + convert_bool_to_enum, + r#" +//- /main.rs +mod foo; + +fn main() { + use foo::bar; + + if bar::BAR { + println!("foo"); + } +} + +//- /foo.rs +pub mod bar { + pub const $0BAR: bool = false; +} +"#, + r#" +//- /main.rs +use foo::bar::Bool; + +mod foo; + +fn main() { + use foo::bar; + + if bar::BAR == Bool::True { + println!("foo"); + } +} + +//- /foo.rs +pub mod bar { + #[derive(PartialEq, Eq)] + pub enum Bool { True, False } + + pub const BAR: Bool = Bool::False; +} +"#, + ) + } + + #[test] + fn const_in_impl_cross_file() { + check_assist( + convert_bool_to_enum, + r#" +//- /main.rs +mod foo; + +struct Foo; + +impl Foo { + pub const $0BOOL: bool = true; +} + +//- /foo.rs +use crate::Foo; + +fn foo() -> bool { + Foo::BOOL +} +"#, + r#" +//- /main.rs +mod foo; + +struct Foo; + +#[derive(PartialEq, Eq)] +pub enum Bool { True, False } + +impl Foo { + pub const BOOL: Bool = Bool::True; +} + +//- /foo.rs +use crate::{Bool, Foo}; + +fn foo() -> bool { + Foo::BOOL == Bool::True +} +"#, + ) + } + + #[test] + fn const_in_trait() { + check_assist( + convert_bool_to_enum, + r#" +trait Foo { + const $0BOOL: bool; +} + +impl Foo for usize { + const BOOL: bool = true; +} + +fn main() { + if ::BOOL { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +trait Foo { + const BOOL: Bool; +} + +impl Foo for usize { + const BOOL: Bool = Bool::True; +} + +fn main() { + if ::BOOL == Bool::True { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn const_non_bool() { + cov_mark::check!(not_applicable_non_bool_const); + check_assist_not_applicable( + convert_bool_to_enum, + r#" +const $0FOO: &str = "foo"; + +fn main() { + println!("{FOO}"); +} +"#, + ) + } + + #[test] + fn static_basic() { + check_assist( + convert_bool_to_enum, + r#" +static mut $0BOOL: bool = true; + +fn main() { + unsafe { BOOL = false }; + if unsafe { BOOL } { + println!("foo"); + } +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +static mut BOOL: Bool = Bool::True; + +fn main() { + unsafe { BOOL = Bool::False }; + if unsafe { BOOL == Bool::True } { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn static_non_bool() { + cov_mark::check!(not_applicable_non_bool_static); + check_assist_not_applicable( + convert_bool_to_enum, + r#" +static mut $0FOO: usize = 0; + +fn main() { + if unsafe { FOO } == 0 { + println!("foo"); + } +} +"#, + ) + } + + #[test] + fn not_applicable_to_other_names() { + check_assist_not_applicable(convert_bool_to_enum, "fn $0main() {}") + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs new file mode 100644 index 00000000000..315d7e727b4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs @@ -0,0 +1,363 @@ +use syntax::{ + ast::{self, make}, + match_ast, AstNode, ToSmolStr, +}; + +use crate::{AssistContext, AssistId, Assists}; + +// Assist: expand_rest_pattern +// +// Fills fields by replacing rest pattern in record patterns. +// +// ``` +// struct Bar { y: Y, z: Z } +// +// fn foo(bar: Bar) { +// let Bar { ..$0 } = bar; +// } +// ``` +// -> +// ``` +// struct Bar { y: Y, z: Z } +// +// fn foo(bar: Bar) { +// let Bar { y, z } = bar; +// } +// ``` +pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let rest_pat = ctx.find_node_at_offset::()?; + let parent = rest_pat.syntax().parent()?; + let record_pat = match_ast! { + match parent { + ast::RecordPatFieldList(it) => ast::RecordPat::cast(it.syntax().parent()?)?, + // ast::TupleStructPat(it) => (), + // ast::TuplePat(it) => (), + // ast::SlicePat(it) => (), + _ => return None, + } + }; + + let ellipsis = record_pat.record_pat_field_list().and_then(|r| r.rest_pat())?; + let target_range = ellipsis.syntax().text_range(); + + let missing_fields = ctx.sema.record_pattern_missing_fields(&record_pat); + + if missing_fields.is_empty() { + cov_mark::hit!(no_missing_fields); + return None; + } + + let old_field_list = record_pat.record_pat_field_list()?; + let new_field_list = + make::record_pat_field_list(old_field_list.fields(), None).clone_for_update(); + for (f, _) in missing_fields.iter() { + let edition = ctx.sema.scope(record_pat.syntax())?.krate().edition(ctx.db()); + let field = make::record_pat_field_shorthand(make::name_ref( + &f.name(ctx.sema.db).display_no_db(edition).to_smolstr(), + )); + new_field_list.add_field(field.clone_for_update()); + } + + let old_range = ctx.sema.original_range_opt(old_field_list.syntax())?; + if old_range.file_id != ctx.file_id() { + return None; + } + + acc.add( + AssistId("expand_rest_pattern", crate::AssistKind::RefactorRewrite), + "Fill structure fields", + target_range, + move |builder| builder.replace_ast(old_field_list, new_field_list), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn fill_fields_enum_with_only_ellipsis() { + check_assist( + expand_rest_pattern, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{ ..$0 } => true, + }; +} +"#, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{ y, z } => true, + }; +} +"#, + ) + } + + #[test] + fn fill_fields_enum_with_fields() { + check_assist( + expand_rest_pattern, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{ y, ..$0 } => true, + }; +} +"#, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{ y, z } => true, + }; +} +"#, + ) + } + + #[test] + fn fill_fields_struct_with_only_ellipsis() { + check_assist( + expand_rest_pattern, + r#" +struct Bar { + y: Y, + z: Z, +} + +fn foo(bar: Bar) { + let Bar { ..$0 } = bar; +} +"#, + r#" +struct Bar { + y: Y, + z: Z, +} + +fn foo(bar: Bar) { + let Bar { y, z } = bar; +} +"#, + ) + } + + #[test] + fn fill_fields_struct_with_fields() { + check_assist( + expand_rest_pattern, + r#" +struct Bar { + y: Y, + z: Z, +} + +fn foo(bar: Bar) { + let Bar { y, ..$0 } = bar; +} +"#, + r#" +struct Bar { + y: Y, + z: Z, +} + +fn foo(bar: Bar) { + let Bar { y, z } = bar; +} +"#, + ) + } + + #[test] + fn fill_fields_struct_generated_by_macro() { + check_assist( + expand_rest_pattern, + r#" +macro_rules! position { + ($t: ty) => { + struct Pos {x: $t, y: $t} + }; +} + +position!(usize); + +fn macro_call(pos: Pos) { + let Pos { ..$0 } = pos; +} +"#, + r#" +macro_rules! position { + ($t: ty) => { + struct Pos {x: $t, y: $t} + }; +} + +position!(usize); + +fn macro_call(pos: Pos) { + let Pos { x, y } = pos; +} +"#, + ); + } + + #[test] + fn fill_fields_enum_generated_by_macro() { + check_assist( + expand_rest_pattern, + r#" +macro_rules! enum_gen { + ($t: ty) => { + enum Foo { + A($t), + B{x: $t, y: $t}, + } + }; +} + +enum_gen!(usize); + +fn macro_call(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{ ..$0 } => true, + } +} +"#, + r#" +macro_rules! enum_gen { + ($t: ty) => { + enum Foo { + A($t), + B{x: $t, y: $t}, + } + }; +} + +enum_gen!(usize); + +fn macro_call(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{ x, y } => true, + } +} +"#, + ); + } + + #[test] + fn not_applicable_when_not_in_ellipsis() { + check_assist_not_applicable( + expand_rest_pattern, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{..}$0 => true, + }; +} +"#, + ); + check_assist_not_applicable( + expand_rest_pattern, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B$0{..} => true, + }; +} +"#, + ); + check_assist_not_applicable( + expand_rest_pattern, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::$0B{..} => true, + }; +} +"#, + ); + } + + #[test] + fn not_applicable_when_no_missing_fields() { + // This is still possible even though it's meaningless + cov_mark::check!(no_missing_fields); + check_assist_not_applicable( + expand_rest_pattern, + r#" +enum Foo { + A(X), + B{y: Y, z: Z} +} + +fn bar(foo: Foo) { + match foo { + Foo::A(_) => false, + Foo::B{y, z, ..$0} => true, + }; +} +"#, + ); + check_assist_not_applicable( + expand_rest_pattern, + r#" +struct Bar { + y: Y, + z: Z, +} + +fn foo(bar: Bar) { + let Bar { y, z, ..$0 } = bar; +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs deleted file mode 100644 index fafc3448a87..00000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs +++ /dev/null @@ -1,206 +0,0 @@ -use hir::Semantics; -use ide_db::{ - assists::{AssistId, AssistKind}, - source_change::SourceChangeBuilder, - RootDatabase, -}; -use syntax::{ast, AstNode}; - -use crate::{AssistContext, Assists}; - -// Assist: explicit_enum_discriminant -// -// Adds explicit discriminant to all enum variants. -// -// ``` -// enum TheEnum$0 { -// Foo, -// Bar, -// Baz = 42, -// Quux, -// } -// ``` -// -> -// ``` -// enum TheEnum { -// Foo = 0, -// Bar = 1, -// Baz = 42, -// Quux = 43, -// } -// ``` -pub(crate) fn explicit_enum_discriminant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let enum_node = ctx.find_node_at_offset::()?; - let enum_def = ctx.sema.to_def(&enum_node)?; - - let is_data_carrying = enum_def.is_data_carrying(ctx.db()); - let has_primitive_repr = enum_def.repr(ctx.db()).and_then(|repr| repr.int).is_some(); - - // Data carrying enums without a primitive repr have no stable discriminants. - if is_data_carrying && !has_primitive_repr { - return None; - } - - let variant_list = enum_node.variant_list()?; - - // Don't offer the assist if the enum has no variants or if all variants already have an - // explicit discriminant. - if variant_list.variants().all(|variant_node| variant_node.expr().is_some()) { - return None; - } - - acc.add( - AssistId("explicit_enum_discriminant", AssistKind::RefactorRewrite), - "Add explicit enum discriminants", - enum_node.syntax().text_range(), - |builder| { - for variant_node in variant_list.variants() { - add_variant_discriminant(&ctx.sema, builder, &variant_node); - } - }, - ); - - Some(()) -} - -fn add_variant_discriminant( - sema: &Semantics<'_, RootDatabase>, - builder: &mut SourceChangeBuilder, - variant_node: &ast::Variant, -) { - if variant_node.expr().is_some() { - return; - } - - let Some(variant_def) = sema.to_def(variant_node) else { - return; - }; - let Ok(discriminant) = variant_def.eval(sema.db) else { - return; - }; - - let variant_range = variant_node.syntax().text_range(); - - builder.insert(variant_range.end(), format!(" = {discriminant}")); -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; - - use super::explicit_enum_discriminant; - - #[test] - fn non_primitive_repr_non_data_bearing_add_discriminant() { - check_assist( - explicit_enum_discriminant, - r#" -enum TheEnum$0 { - Foo, - Bar, - Baz = 42, - Quux, - FooBar = -5, - FooBaz, -} -"#, - r#" -enum TheEnum { - Foo = 0, - Bar = 1, - Baz = 42, - Quux = 43, - FooBar = -5, - FooBaz = -4, -} -"#, - ); - } - - #[test] - fn primitive_repr_data_bearing_add_discriminant() { - check_assist( - explicit_enum_discriminant, - r#" -#[repr(u8)] -$0enum TheEnum { - Foo { x: u32 }, - Bar, - Baz(String), - Quux, -} -"#, - r#" -#[repr(u8)] -enum TheEnum { - Foo { x: u32 } = 0, - Bar = 1, - Baz(String) = 2, - Quux = 3, -} -"#, - ); - } - - #[test] - fn non_primitive_repr_data_bearing_not_applicable() { - check_assist_not_applicable( - explicit_enum_discriminant, - r#" -enum TheEnum$0 { - Foo, - Bar(u16), - Baz, -} -"#, - ); - } - - #[test] - fn primitive_repr_non_data_bearing_add_discriminant() { - check_assist( - explicit_enum_discriminant, - r#" -#[repr(i64)] -enum TheEnum { - Foo = 1 << 63, - Bar, - Baz$0 = 0x7fff_ffff_ffff_fffe, - Quux, -} -"#, - r#" -#[repr(i64)] -enum TheEnum { - Foo = 1 << 63, - Bar = -9223372036854775807, - Baz = 0x7fff_ffff_ffff_fffe, - Quux = 9223372036854775807, -} -"#, - ); - } - - #[test] - fn discriminants_already_explicit_not_applicable() { - check_assist_not_applicable( - explicit_enum_discriminant, - r#" -enum TheEnum$0 { - Foo = 0, - Bar = 4, -} -"#, - ); - } - - #[test] - fn empty_enum_not_applicable() { - check_assist_not_applicable( - explicit_enum_discriminant, - r#" -enum TheEnum$0 {} -"#, - ); - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs deleted file mode 100644 index ee321864805..00000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs +++ /dev/null @@ -1,357 +0,0 @@ -use syntax::{ - ast::{self, make}, - AstNode, ToSmolStr, -}; - -use crate::{AssistContext, AssistId, Assists}; - -// Assist: fill_record_pattern_fields -// -// Fills fields by replacing rest pattern in record patterns. -// -// ``` -// struct Bar { y: Y, z: Z } -// -// fn foo(bar: Bar) { -// let Bar { ..$0 } = bar; -// } -// ``` -// -> -// ``` -// struct Bar { y: Y, z: Z } -// -// fn foo(bar: Bar) { -// let Bar { y, z } = bar; -// } -// ``` -pub(crate) fn fill_record_pattern_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let record_pat = ctx.find_node_at_offset::()?; - - let ellipsis = record_pat.record_pat_field_list().and_then(|r| r.rest_pat())?; - if !ellipsis.syntax().text_range().contains_inclusive(ctx.offset()) { - return None; - } - - let target_range = ellipsis.syntax().text_range(); - - let missing_fields = ctx.sema.record_pattern_missing_fields(&record_pat); - - if missing_fields.is_empty() { - cov_mark::hit!(no_missing_fields); - return None; - } - - let old_field_list = record_pat.record_pat_field_list()?; - let new_field_list = - make::record_pat_field_list(old_field_list.fields(), None).clone_for_update(); - for (f, _) in missing_fields.iter() { - let edition = ctx.sema.scope(record_pat.syntax())?.krate().edition(ctx.db()); - let field = make::record_pat_field_shorthand(make::name_ref( - &f.name(ctx.sema.db).display_no_db(edition).to_smolstr(), - )); - new_field_list.add_field(field.clone_for_update()); - } - - let old_range = ctx.sema.original_range_opt(old_field_list.syntax())?; - if old_range.file_id != ctx.file_id() { - return None; - } - - acc.add( - AssistId("fill_record_pattern_fields", crate::AssistKind::RefactorRewrite), - "Fill structure fields", - target_range, - move |builder| builder.replace_ast(old_field_list, new_field_list), - ) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{check_assist, check_assist_not_applicable}; - - #[test] - fn fill_fields_enum_with_only_ellipsis() { - check_assist( - fill_record_pattern_fields, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{ ..$0 } => true, - }; -} -"#, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{ y, z } => true, - }; -} -"#, - ) - } - - #[test] - fn fill_fields_enum_with_fields() { - check_assist( - fill_record_pattern_fields, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{ y, ..$0 } => true, - }; -} -"#, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{ y, z } => true, - }; -} -"#, - ) - } - - #[test] - fn fill_fields_struct_with_only_ellipsis() { - check_assist( - fill_record_pattern_fields, - r#" -struct Bar { - y: Y, - z: Z, -} - -fn foo(bar: Bar) { - let Bar { ..$0 } = bar; -} -"#, - r#" -struct Bar { - y: Y, - z: Z, -} - -fn foo(bar: Bar) { - let Bar { y, z } = bar; -} -"#, - ) - } - - #[test] - fn fill_fields_struct_with_fields() { - check_assist( - fill_record_pattern_fields, - r#" -struct Bar { - y: Y, - z: Z, -} - -fn foo(bar: Bar) { - let Bar { y, ..$0 } = bar; -} -"#, - r#" -struct Bar { - y: Y, - z: Z, -} - -fn foo(bar: Bar) { - let Bar { y, z } = bar; -} -"#, - ) - } - - #[test] - fn fill_fields_struct_generated_by_macro() { - check_assist( - fill_record_pattern_fields, - r#" -macro_rules! position { - ($t: ty) => { - struct Pos {x: $t, y: $t} - }; -} - -position!(usize); - -fn macro_call(pos: Pos) { - let Pos { ..$0 } = pos; -} -"#, - r#" -macro_rules! position { - ($t: ty) => { - struct Pos {x: $t, y: $t} - }; -} - -position!(usize); - -fn macro_call(pos: Pos) { - let Pos { x, y } = pos; -} -"#, - ); - } - - #[test] - fn fill_fields_enum_generated_by_macro() { - check_assist( - fill_record_pattern_fields, - r#" -macro_rules! enum_gen { - ($t: ty) => { - enum Foo { - A($t), - B{x: $t, y: $t}, - } - }; -} - -enum_gen!(usize); - -fn macro_call(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{ ..$0 } => true, - } -} -"#, - r#" -macro_rules! enum_gen { - ($t: ty) => { - enum Foo { - A($t), - B{x: $t, y: $t}, - } - }; -} - -enum_gen!(usize); - -fn macro_call(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{ x, y } => true, - } -} -"#, - ); - } - - #[test] - fn not_applicable_when_not_in_ellipsis() { - check_assist_not_applicable( - fill_record_pattern_fields, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{..}$0 => true, - }; -} -"#, - ); - check_assist_not_applicable( - fill_record_pattern_fields, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B$0{..} => true, - }; -} -"#, - ); - check_assist_not_applicable( - fill_record_pattern_fields, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::$0B{..} => true, - }; -} -"#, - ); - } - - #[test] - fn not_applicable_when_no_missing_fields() { - // This is still possible even though it's meaningless - cov_mark::check!(no_missing_fields); - check_assist_not_applicable( - fill_record_pattern_fields, - r#" -enum Foo { - A(X), - B{y: Y, z: Z} -} - -fn bar(foo: Foo) { - match foo { - Foo::A(_) => false, - Foo::B{y, z, ..$0} => true, - }; -} -"#, - ); - check_assist_not_applicable( - fill_record_pattern_fields, - r#" -struct Bar { - y: Y, - z: Z, -} - -fn foo(bar: Bar) { - let Bar { y, z, ..$0 } = bar; -} -"#, - ); - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs deleted file mode 100644 index 8c276415bb1..00000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ /dev/null @@ -1,187 +0,0 @@ -use ide_db::syntax_helpers::suggest_name; -use itertools::Itertools; -use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName}; - -use crate::{AssistContext, AssistId, AssistKind, Assists}; - -// Assist: introduce_named_generic -// -// Replaces `impl Trait` function argument with the named generic. -// -// ``` -// fn foo(bar: $0impl Bar) {} -// ``` -// -> -// ``` -// fn foo<$0B: Bar>(bar: B) {} -// ``` -pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let impl_trait_type = ctx.find_node_at_offset::()?; - let param = impl_trait_type.syntax().ancestors().find_map(ast::Param::cast)?; - let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?; - - let type_bound_list = impl_trait_type.type_bound_list()?; - - let make = SyntaxFactory::new(); - let target = fn_.syntax().text_range(); - acc.add( - AssistId("introduce_named_generic", AssistKind::RefactorRewrite), - "Replace impl trait with generic", - target, - |builder| { - let mut editor = builder.make_editor(fn_.syntax()); - - let existing_names = match fn_.generic_param_list() { - Some(generic_param_list) => generic_param_list - .generic_params() - .flat_map(|param| match param { - ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), - p => Some(p.to_string()), - }) - .collect_vec(), - None => Vec::new(), - }; - let type_param_name = suggest_name::NameGenerator::new_with_names( - existing_names.iter().map(|s| s.as_str()), - ) - .for_impl_trait_as_generic(&impl_trait_type); - - let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list)); - let new_ty = make.ty(&type_param_name); - - editor.replace(impl_trait_type.syntax(), new_ty.syntax()); - editor.add_generic_param(&fn_, type_param.clone().into()); - - if let Some(cap) = ctx.config.snippet_cap { - editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap)); - } - - editor.add_mappings(make.finish_with_mappings()); - builder.add_file_edits(ctx.file_id(), editor); - }, - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::tests::check_assist; - - #[test] - fn introduce_named_generic_params() { - check_assist( - introduce_named_generic, - r#"fn foo(bar: $0impl Bar) {}"#, - r#"fn foo(bar: B) {}"#, - ); - } - - #[test] - fn replace_impl_trait_without_generic_params() { - check_assist( - introduce_named_generic, - r#"fn foo(bar: $0impl Bar) {}"#, - r#"fn foo<$0B: Bar>(bar: B) {}"#, - ); - } - - #[test] - fn replace_two_impl_trait_with_generic_params() { - check_assist( - introduce_named_generic, - r#"fn foo(foo: impl Foo, bar: $0impl Bar) {}"#, - r#"fn foo(foo: impl Foo, bar: B) {}"#, - ); - } - - #[test] - fn replace_impl_trait_with_empty_generic_params() { - check_assist( - introduce_named_generic, - r#"fn foo<>(bar: $0impl Bar) {}"#, - r#"fn foo<$0B: Bar>(bar: B) {}"#, - ); - } - - #[test] - fn replace_impl_trait_with_empty_multiline_generic_params() { - check_assist( - introduce_named_generic, - r#" -fn foo< ->(bar: $0impl Bar) {} -"#, - r#" -fn foo<$0B: Bar ->(bar: B) {} -"#, - ); - } - - #[test] - fn replace_impl_trait_with_exist_generic_letter() { - check_assist( - introduce_named_generic, - r#"fn foo(bar: $0impl Bar) {}"#, - r#"fn foo(bar: B1) {}"#, - ); - } - - #[test] - fn replace_impl_trait_with_more_exist_generic_letter() { - check_assist( - introduce_named_generic, - r#"fn foo(bar: $0impl Bar) {}"#, - r#"fn foo(bar: B4) {}"#, - ); - } - - #[test] - fn replace_impl_trait_with_multiline_generic_params() { - check_assist( - introduce_named_generic, - r#" -fn foo< - G: Foo, - F, - H, ->(bar: $0impl Bar) {} -"#, - r#" -fn foo< - G: Foo, - F, - H, $0B: Bar, ->(bar: B) {} -"#, - ); - } - - #[test] - fn replace_impl_trait_multiple() { - check_assist( - introduce_named_generic, - r#"fn foo(bar: $0impl Foo + Bar) {}"#, - r#"fn foo<$0F: Foo + Bar>(bar: F) {}"#, - ); - } - - #[test] - fn replace_impl_with_mut() { - check_assist( - introduce_named_generic, - r#"fn f(iter: &mut $0impl Iterator) {}"#, - r#"fn f<$0I: Iterator>(iter: &mut I) {}"#, - ); - } - - #[test] - fn replace_impl_inside() { - check_assist( - introduce_named_generic, - r#"fn f(x: &mut Vec<$0impl Iterator>) {}"#, - r#"fn f<$0I: Iterator>(x: &mut Vec) {}"#, - ); - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_type_parameter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_type_parameter.rs new file mode 100644 index 00000000000..994e4a0edda --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_type_parameter.rs @@ -0,0 +1,189 @@ +use ide_db::syntax_helpers::suggest_name; +use itertools::Itertools; +use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: introduce_named_type_parameter +// +// Replaces `impl Trait` function argument with the named generic. +// +// ``` +// fn foo(bar: $0impl Bar) {} +// ``` +// -> +// ``` +// fn foo<$0B: Bar>(bar: B) {} +// ``` +pub(crate) fn introduce_named_type_parameter( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let impl_trait_type = ctx.find_node_at_offset::()?; + let param = impl_trait_type.syntax().ancestors().find_map(ast::Param::cast)?; + let fn_ = param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?; + let type_bound_list = impl_trait_type.type_bound_list()?; + + let make = SyntaxFactory::new(); + let target = fn_.syntax().text_range(); + acc.add( + AssistId("introduce_named_type_parameter", AssistKind::RefactorRewrite), + "Replace impl trait with type parameter", + target, + |builder| { + let mut editor = builder.make_editor(fn_.syntax()); + + let existing_names = match fn_.generic_param_list() { + Some(generic_param_list) => generic_param_list + .generic_params() + .flat_map(|param| match param { + ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), + p => Some(p.to_string()), + }) + .collect_vec(), + None => Vec::new(), + }; + let type_param_name = suggest_name::NameGenerator::new_with_names( + existing_names.iter().map(|s| s.as_str()), + ) + .for_impl_trait_as_generic(&impl_trait_type); + + let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list)); + let new_ty = make.ty(&type_param_name); + + editor.replace(impl_trait_type.syntax(), new_ty.syntax()); + editor.add_generic_param(&fn_, type_param.clone().into()); + + if let Some(cap) = ctx.config.snippet_cap { + editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap)); + } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); + }, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::tests::check_assist; + + #[test] + fn introduce_named_generic_params() { + check_assist( + introduce_named_type_parameter, + r#"fn foo(bar: $0impl Bar) {}"#, + r#"fn foo(bar: B) {}"#, + ); + } + + #[test] + fn replace_impl_trait_without_generic_params() { + check_assist( + introduce_named_type_parameter, + r#"fn foo(bar: $0impl Bar) {}"#, + r#"fn foo<$0B: Bar>(bar: B) {}"#, + ); + } + + #[test] + fn replace_two_impl_trait_with_generic_params() { + check_assist( + introduce_named_type_parameter, + r#"fn foo(foo: impl Foo, bar: $0impl Bar) {}"#, + r#"fn foo(foo: impl Foo, bar: B) {}"#, + ); + } + + #[test] + fn replace_impl_trait_with_empty_generic_params() { + check_assist( + introduce_named_type_parameter, + r#"fn foo<>(bar: $0impl Bar) {}"#, + r#"fn foo<$0B: Bar>(bar: B) {}"#, + ); + } + + #[test] + fn replace_impl_trait_with_empty_multiline_generic_params() { + check_assist( + introduce_named_type_parameter, + r#" +fn foo< +>(bar: $0impl Bar) {} +"#, + r#" +fn foo<$0B: Bar +>(bar: B) {} +"#, + ); + } + + #[test] + fn replace_impl_trait_with_exist_generic_letter() { + check_assist( + introduce_named_type_parameter, + r#"fn foo(bar: $0impl Bar) {}"#, + r#"fn foo(bar: B1) {}"#, + ); + } + + #[test] + fn replace_impl_trait_with_more_exist_generic_letter() { + check_assist( + introduce_named_type_parameter, + r#"fn foo(bar: $0impl Bar) {}"#, + r#"fn foo(bar: B4) {}"#, + ); + } + + #[test] + fn replace_impl_trait_with_multiline_generic_params() { + check_assist( + introduce_named_type_parameter, + r#" +fn foo< + G: Foo, + F, + H, +>(bar: $0impl Bar) {} +"#, + r#" +fn foo< + G: Foo, + F, + H, $0B: Bar, +>(bar: B) {} +"#, + ); + } + + #[test] + fn replace_impl_trait_multiple() { + check_assist( + introduce_named_type_parameter, + r#"fn foo(bar: $0impl Foo + Bar) {}"#, + r#"fn foo<$0F: Foo + Bar>(bar: F) {}"#, + ); + } + + #[test] + fn replace_impl_with_mut() { + check_assist( + introduce_named_type_parameter, + r#"fn f(iter: &mut $0impl Iterator) {}"#, + r#"fn f<$0I: Iterator>(iter: &mut I) {}"#, + ); + } + + #[test] + fn replace_impl_inside() { + check_assist( + introduce_named_type_parameter, + r#"fn f(x: &mut Vec<$0impl Iterator>) {}"#, + r#"fn f<$0I: Iterator>(x: &mut Vec) {}"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 179742f91b4..11d48430a0a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -105,6 +105,7 @@ mod handlers { pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>; mod add_braces; + mod add_explicit_enum_discriminant; mod add_explicit_type; mod add_label_to_loop; mod add_lifetime_to_type; @@ -115,9 +116,9 @@ mod handlers { mod apply_demorgan; mod auto_import; mod bind_unused_param; - mod bool_to_enum; mod change_visibility; mod convert_bool_then; + mod convert_bool_to_enum; mod convert_closure_to_fn; mod convert_comment_block; mod convert_comment_from_or_to_doc; @@ -138,14 +139,13 @@ mod handlers { mod destructure_tuple_binding; mod desugar_doc_comment; mod expand_glob_import; - mod explicit_enum_discriminant; + mod expand_rest_pattern; mod extract_expressions_from_format_string; mod extract_function; mod extract_module; mod extract_struct_from_enum_variant; mod extract_type_alias; mod extract_variable; - mod fill_record_pattern_fields; mod fix_visibility; mod flip_binexpr; mod flip_comma; @@ -176,8 +176,8 @@ mod handlers { mod inline_macro; mod inline_type_alias; mod into_to_qualified_from; - mod introduce_named_generic; mod introduce_named_lifetime; + mod introduce_named_type_parameter; mod invert_if; mod merge_imports; mod merge_match_arms; @@ -233,49 +233,47 @@ mod handlers { &[ // These are alphabetic for the foolish consistency add_braces::add_braces, + add_explicit_enum_discriminant::add_explicit_enum_discriminant, add_explicit_type::add_explicit_type, add_label_to_loop::add_label_to_loop, - add_missing_match_arms::add_missing_match_arms, add_lifetime_to_type::add_lifetime_to_type, + add_missing_match_arms::add_missing_match_arms, add_return_type::add_return_type, add_turbo_fish::add_turbo_fish, - apply_demorgan::apply_demorgan, apply_demorgan::apply_demorgan_iterator, + apply_demorgan::apply_demorgan, auto_import::auto_import, bind_unused_param::bind_unused_param, - bool_to_enum::bool_to_enum, change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, - toggle_async_sugar::desugar_async_into_impl_future, - toggle_async_sugar::sugar_impl_future_into_async, + convert_bool_to_enum::convert_bool_to_enum, + convert_closure_to_fn::convert_closure_to_fn, convert_comment_block::convert_comment_block, convert_comment_from_or_to_doc::convert_comment_from_or_to_doc, - convert_closure_to_fn::convert_closure_to_fn, convert_from_to_tryfrom::convert_from_to_tryfrom, convert_integer_literal::convert_integer_literal, convert_into_to_from::convert_into_to_from, - convert_iter_for_each_to_for::convert_iter_for_each_to_for, convert_iter_for_each_to_for::convert_for_loop_with_for_each, + convert_iter_for_each_to_for::convert_iter_for_each_to_for, convert_let_else_to_match::convert_let_else_to_match, convert_match_to_let_else::convert_match_to_let_else, - convert_tuple_return_type_to_struct::convert_tuple_return_type_to_struct, convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct, convert_nested_function_to_closure::convert_nested_function_to_closure, convert_to_guarded_return::convert_to_guarded_return, + convert_tuple_return_type_to_struct::convert_tuple_return_type_to_struct, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro, convert_while_to_loop::convert_while_to_loop, - desugar_doc_comment::desugar_doc_comment, - destructure_tuple_binding::destructure_tuple_binding, destructure_struct_binding::destructure_struct_binding, + destructure_tuple_binding::destructure_tuple_binding, + desugar_doc_comment::desugar_doc_comment, expand_glob_import::expand_glob_import, expand_glob_import::expand_glob_reexport, - explicit_enum_discriminant::explicit_enum_discriminant, + expand_rest_pattern::expand_rest_pattern, extract_expressions_from_format_string::extract_expressions_from_format_string, extract_struct_from_enum_variant::extract_struct_from_enum_variant, extract_type_alias::extract_type_alias, - fill_record_pattern_fields::fill_record_pattern_fields, fix_visibility::fix_visibility, flip_binexpr::flip_binexpr, flip_comma::flip_comma, @@ -285,8 +283,8 @@ mod handlers { generate_default_from_new::generate_default_from_new, generate_delegate_trait::generate_delegate_trait, generate_derive::generate_derive, - generate_documentation_template::generate_documentation_template, generate_documentation_template::generate_doc_example, + generate_documentation_template::generate_documentation_template, generate_enum_is_method::generate_enum_is_method, generate_enum_projection_method::generate_enum_as_method, generate_enum_projection_method::generate_enum_try_into_method, @@ -296,8 +294,8 @@ mod handlers { generate_function::generate_function, generate_impl::generate_impl, generate_impl::generate_trait_impl, - generate_mut_trait_impl::generate_mut_trait_impl, generate_is_empty_from_len::generate_is_empty_from_len, + generate_mut_trait_impl::generate_mut_trait_impl, generate_new::generate_new, generate_trait_from_impl::generate_trait_from_impl, inline_call::inline_call, @@ -305,39 +303,41 @@ mod handlers { inline_const_as_literal::inline_const_as_literal, inline_local_variable::inline_local_variable, inline_macro::inline_macro, - inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias_uses, + inline_type_alias::inline_type_alias, into_to_qualified_from::into_to_qualified_from, - introduce_named_generic::introduce_named_generic, introduce_named_lifetime::introduce_named_lifetime, + introduce_named_type_parameter::introduce_named_type_parameter, invert_if::invert_if, merge_imports::merge_imports, merge_match_arms::merge_match_arms, merge_nested_if::merge_nested_if, move_bounds::move_bounds_to_where_clause, move_const_to_impl::move_const_to_impl, + move_from_mod_rs::move_from_mod_rs, move_guard::move_arm_cond_to_match_guard, move_guard::move_guard_to_arm_body, move_module_to_file::move_module_to_file, move_to_mod_rs::move_to_mod_rs, - move_from_mod_rs::move_from_mod_rs, normalize_import::normalize_import, number_representation::reformat_number_literal, - pull_assignment_up::pull_assignment_up, promote_local_to_const::promote_local_to_const, - qualify_path::qualify_path, + pull_assignment_up::pull_assignment_up, qualify_method_call::qualify_method_call, + qualify_path::qualify_path, raw_string::add_hash, raw_string::make_usual_string, raw_string::remove_hash, remove_dbg::remove_dbg, remove_mut::remove_mut, + remove_parentheses::remove_parentheses, remove_unused_imports::remove_unused_imports, remove_unused_param::remove_unused_param, - remove_parentheses::remove_parentheses, reorder_fields::reorder_fields, reorder_impl_items::reorder_impl_items, - replace_try_expr_with_match::replace_try_expr_with_match, + replace_arith_op::replace_arith_with_checked, + replace_arith_op::replace_arith_with_saturating, + replace_arith_op::replace_arith_with_wrapping, replace_derive_with_manual_impl::replace_derive_with_manual_impl, replace_if_let_with_match::replace_if_let_with_match, replace_if_let_with_match::replace_match_with_if_let, @@ -346,23 +346,23 @@ mod handlers { replace_method_eager_lazy::replace_with_eager_method, replace_method_eager_lazy::replace_with_lazy_method, replace_named_generic_with_impl::replace_named_generic_with_impl, - replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_qualified_name_with_use::replace_qualified_name_with_use, - replace_arith_op::replace_arith_with_wrapping, - replace_arith_op::replace_arith_with_checked, - replace_arith_op::replace_arith_with_saturating, + replace_try_expr_with_match::replace_try_expr_with_match, + replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, sort_items::sort_items, split_import::split_import, term_search::term_search, + toggle_async_sugar::desugar_async_into_impl_future, + toggle_async_sugar::sugar_impl_future_into_async, toggle_ignore::toggle_ignore, toggle_macro_delimiter::toggle_macro_delimiter, unmerge_match_arm::unmerge_match_arm, unmerge_use::unmerge_use, unnecessary_async::unnecessary_async, + unqualify_method_call::unqualify_method_call, unwrap_block::unwrap_block, unwrap_return_type::unwrap_return_type, unwrap_tuple::unwrap_tuple, - unqualify_method_call::unqualify_method_call, wrap_return_type::wrap_return_type, wrap_unwrap_cfg_attr::wrap_unwrap_cfg_attr, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 74ae126adae..dd994ef4a64 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -27,6 +27,29 @@ fn foo(n: i32) -> i32 { ) } +#[test] +fn doctest_add_explicit_enum_discriminant() { + check_doc_test( + "add_explicit_enum_discriminant", + r#####" +enum TheEnum$0 { + Foo, + Bar, + Baz = 42, + Quux, +} +"#####, + r#####" +enum TheEnum { + Foo = 0, + Bar = 1, + Baz = 42, + Quux = 43, +} +"#####, + ) +} + #[test] fn doctest_add_explicit_type() { check_doc_test( @@ -304,34 +327,6 @@ fn some_function(x: i32) { ) } -#[test] -fn doctest_bool_to_enum() { - check_doc_test( - "bool_to_enum", - r#####" -fn main() { - let $0bool = true; - - if bool { - println!("foo"); - } -} -"#####, - r#####" -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let bool = Bool::True; - - if bool == Bool::True { - println!("foo"); - } -} -"#####, - ) -} - #[test] fn doctest_change_visibility() { check_doc_test( @@ -382,6 +377,34 @@ fn main() { ) } +#[test] +fn doctest_convert_bool_to_enum() { + check_doc_test( + "convert_bool_to_enum", + r#####" +fn main() { + let $0bool = true; + + if bool { + println!("foo"); + } +} +"#####, + r#####" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let bool = Bool::True; + + if bool == Bool::True { + println!("foo"); + } +} +"#####, + ) +} + #[test] fn doctest_convert_closure_to_fn() { check_doc_test( @@ -933,23 +956,21 @@ pub use foo::{Bar, Baz}; } #[test] -fn doctest_explicit_enum_discriminant() { +fn doctest_expand_rest_pattern() { check_doc_test( - "explicit_enum_discriminant", + "expand_rest_pattern", r#####" -enum TheEnum$0 { - Foo, - Bar, - Baz = 42, - Quux, +struct Bar { y: Y, z: Z } + +fn foo(bar: Bar) { + let Bar { ..$0 } = bar; } "#####, r#####" -enum TheEnum { - Foo = 0, - Bar = 1, - Baz = 42, - Quux = 43, +struct Bar { y: Y, z: Z } + +fn foo(bar: Bar) { + let Bar { y, z } = bar; } "#####, ) @@ -1117,27 +1138,6 @@ fn main() { ) } -#[test] -fn doctest_fill_record_pattern_fields() { - check_doc_test( - "fill_record_pattern_fields", - r#####" -struct Bar { y: Y, z: Z } - -fn foo(bar: Bar) { - let Bar { ..$0 } = bar; -} -"#####, - r#####" -struct Bar { y: Y, z: Z } - -fn foo(bar: Bar) { - let Bar { y, z } = bar; -} -"#####, - ) -} - #[test] fn doctest_fix_visibility() { check_doc_test( @@ -2176,19 +2176,6 @@ fn main() -> () { ) } -#[test] -fn doctest_introduce_named_generic() { - check_doc_test( - "introduce_named_generic", - r#####" -fn foo(bar: $0impl Bar) {} -"#####, - r#####" -fn foo<$0B: Bar>(bar: B) {} -"#####, - ) -} - #[test] fn doctest_introduce_named_lifetime() { check_doc_test( @@ -2214,6 +2201,19 @@ impl<'a> Cursor<'a> { ) } +#[test] +fn doctest_introduce_named_type_parameter() { + check_doc_test( + "introduce_named_type_parameter", + r#####" +fn foo(bar: $0impl Bar) {} +"#####, + r#####" +fn foo<$0B: Bar>(bar: B) {} +"#####, + ) +} + #[test] fn doctest_invert_if() { check_doc_test( diff --git a/src/tools/rust-analyzer/docs/book/src/assists_generated.md b/src/tools/rust-analyzer/docs/book/src/assists_generated.md index 2d233ca62ad..743e5c5e10d 100644 --- a/src/tools/rust-analyzer/docs/book/src/assists_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/assists_generated.md @@ -28,6 +28,32 @@ fn foo(n: i32) -> i32 { ``` +### `add_explicit_enum_discriminant` +**Source:** [add_explicit_enum_discriminant.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/add_explicit_enum_discriminant.rs#L11) + +Adds explicit discriminant to all enum variants. + +#### Before +```rust +enum TheEnum┃ { + Foo, + Bar, + Baz = 42, + Quux, +} +``` + +#### After +```rust +enum TheEnum { + Foo = 0, + Bar = 1, + Baz = 42, + Quux = 43, +} +``` + + ### `add_explicit_type` **Source:** [add_explicit_type.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/add_explicit_type.rs#L7) @@ -257,7 +283,7 @@ fn main() { ### `apply_demorgan` -**Source:** [apply_demorgan.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/apply_demorgan.rs#L16) +**Source:** [apply_demorgan.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/apply_demorgan.rs#L23) Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). This transforms expressions of the form `!l || !r` into `!(l && r)`. @@ -280,7 +306,7 @@ fn main() { ### `apply_demorgan_iterator` -**Source:** [apply_demorgan.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/apply_demorgan.rs#L147) +**Source:** [apply_demorgan.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/apply_demorgan.rs#L154) Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) to `Iterator::all` and `Iterator::any`. @@ -350,40 +376,6 @@ fn some_function(x: i32) { ``` -### `bool_to_enum` -**Source:** [bool_to_enum.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/bool_to_enum.rs#L29) - -This converts boolean local variables, fields, constants, and statics into a new -enum with two variants `Bool::True` and `Bool::False`, as well as replacing -all assignments with the variants and replacing all usages with `== Bool::True` or -`== Bool::False`. - -#### Before -```rust -fn main() { - let ┃bool = true; - - if bool { - println!("foo"); - } -} -``` - -#### After -```rust -#[derive(PartialEq, Eq)] -enum Bool { True, False } - -fn main() { - let bool = Bool::True; - - if bool == Bool::True { - println!("foo"); - } -} -``` - - ### `change_visibility` **Source:** [change_visibility.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/change_visibility.rs#L13) @@ -442,6 +434,40 @@ fn main() { ``` +### `convert_bool_to_enum` +**Source:** [convert_bool_to_enum.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/convert_bool_to_enum.rs#L29) + +This converts boolean local variables, fields, constants, and statics into a new +enum with two variants `Bool::True` and `Bool::False`, as well as replacing +all assignments with the variants and replacing all usages with `== Bool::True` or +`== Bool::False`. + +#### Before +```rust +fn main() { + let ┃bool = true; + + if bool { + println!("foo"); + } +} +``` + +#### After +```rust +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let bool = Bool::True; + + if bool == Bool::True { + println!("foo"); + } +} +``` + + ### `convert_closure_to_fn` **Source:** [convert_closure_to_fn.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/convert_closure_to_fn.rs#L25) @@ -1043,28 +1069,26 @@ pub use foo::{Bar, Baz}; ``` -### `explicit_enum_discriminant` -**Source:** [explicit_enum_discriminant.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/explicit_enum_discriminant.rs#L11) +### `expand_rest_pattern` +**Source:** [expand_rest_pattern.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/expand_rest_pattern.rs#L8) -Adds explicit discriminant to all enum variants. +Fills fields by replacing rest pattern in record patterns. #### Before ```rust -enum TheEnum┃ { - Foo, - Bar, - Baz = 42, - Quux, +struct Bar { y: Y, z: Z } + +fn foo(bar: Bar) { + let Bar { ..┃ } = bar; } ``` #### After ```rust -enum TheEnum { - Foo = 0, - Bar = 1, - Baz = 42, - Quux = 43, +struct Bar { y: Y, z: Z } + +fn foo(bar: Bar) { + let Bar { y, z } = bar; } ``` @@ -1255,30 +1279,6 @@ fn main() { ``` -### `fill_record_pattern_fields` -**Source:** [fill_record_pattern_fields.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs#L8) - -Fills fields by replacing rest pattern in record patterns. - -#### Before -```rust -struct Bar { y: Y, z: Z } - -fn foo(bar: Bar) { - let Bar { ..┃ } = bar; -} -``` - -#### After -```rust -struct Bar { y: Y, z: Z } - -fn foo(bar: Bar) { - let Bar { y, z } = bar; -} -``` - - ### `fix_visibility` **Source:** [fix_visibility.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/fix_visibility.rs#L14) @@ -2403,22 +2403,6 @@ fn main() -> () { ``` -### `introduce_named_generic` -**Source:** [introduce_named_generic.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/introduce_named_generic.rs#L7) - -Replaces `impl Trait` function argument with the named generic. - -#### Before -```rust -fn foo(bar: ┃impl Bar) {} -``` - -#### After -```rust -fn foo<┃B: Bar>(bar: B) {} -``` - - ### `introduce_named_lifetime` **Source:** [introduce_named_lifetime.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/introduce_named_lifetime.rs#L13) @@ -2447,6 +2431,22 @@ impl<'a> Cursor<'a> { ``` +### `introduce_named_type_parameter` +**Source:** [introduce_named_type_parameter.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/introduce_named_type_parameter.rs#L7) + +Replaces `impl Trait` function argument with the named generic. + +#### Before +```rust +fn foo(bar: ┃impl Bar) {} +``` + +#### After +```rust +fn foo<┃B: Bar>(bar: B) {} +``` + + ### `invert_if` **Source:** [invert_if.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/invert_if.rs#L13) -- cgit 1.4.1-3-g733a5