From f098a2b31bffafdbd513f32e8c45d62e709173ea Mon Sep 17 00:00:00 2001 From: Domantas Jadenkus Date: Mon, 15 Feb 2021 23:25:33 +0200 Subject: [PATCH] move into_ and as_ generation to a separate file --- .../handlers/generate_enum_match_method.rs | 406 +----------------- .../generate_enum_projection_method.rs | 307 +++++++++++++ crates/ide_assists/src/lib.rs | 5 +- crates/ide_assists/src/utils.rs | 24 +- 4 files changed, 340 insertions(+), 402 deletions(-) create mode 100644 crates/ide_assists/src/handlers/generate_enum_projection_method.rs diff --git a/crates/ide_assists/src/handlers/generate_enum_match_method.rs b/crates/ide_assists/src/handlers/generate_enum_match_method.rs index 670c8220025..7e181a4801e 100644 --- a/crates/ide_assists/src/handlers/generate_enum_match_method.rs +++ b/crates/ide_assists/src/handlers/generate_enum_match_method.rs @@ -1,11 +1,9 @@ -use itertools::Itertools; use stdx::to_lower_snake_case; use syntax::ast::VisibilityOwner; use syntax::ast::{self, AstNode, NameOwner}; use crate::{ - assist_context::AssistBuilder, - utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, + utils::{add_method_to_adt, find_struct_impl}, AssistContext, AssistId, AssistKind, Assists, }; @@ -39,7 +37,11 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> let variant = ctx.find_node_at_offset::()?; let variant_name = variant.name()?; let parent_enum = ast::Adt::Enum(variant.parent_enum()); - let variant_kind = variant_kind(&variant); + let pattern_suffix = match variant.kind() { + ast::StructKind::Record(_) => " { .. }", + ast::StructKind::Tuple(_) => "(..)", + ast::StructKind::Unit => "", + }; let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text())); @@ -59,12 +61,7 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> {}fn {}(&self) -> bool {{ matches!(self, Self::{}{}) }}", - enum_lowercase_name, - variant_name, - vis, - fn_name, - variant_name, - variant_kind.pattern_suffix(), + enum_lowercase_name, variant_name, vis, fn_name, variant_name, pattern_suffix, ); add_method_to_adt(builder, &parent_enum, impl_def, &method); @@ -72,237 +69,6 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> ) } -// Assist: generate_enum_into_method -// -// Generate an `into_` method for an enum variant. -// -// ``` -// enum Value { -// Number(i32), -// Text(String)$0, -// } -// ``` -// -> -// ``` -// enum Value { -// Number(i32), -// Text(String), -// } -// -// impl Value { -// fn into_text(self) -> Option { -// if let Self::Text(v) = self { -// Some(v) -// } else { -// None -// } -// } -// } -// ``` -pub(crate) fn generate_enum_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let variant = ctx.find_node_at_offset::()?; - let variant_name = variant.name()?; - let parent_enum = ast::Adt::Enum(variant.parent_enum()); - let variant_kind = variant_kind(&variant); - - let fn_name = format!("into_{}", &to_lower_snake_case(variant_name.text())); - - // Return early if we've found an existing new fn - let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; - - let field_type = variant_kind.single_field_type()?; - let (pattern_suffix, bound_name) = variant_kind.binding_pattern()?; - - let target = variant.syntax().text_range(); - acc.add( - AssistId("generate_enum_into_method", AssistKind::Generate), - "Generate an `into_` method for an enum variant", - target, - |builder| { - let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); - let method = format!( - " {}fn {}(self) -> Option<{}> {{ - if let Self::{}{} = self {{ - Some({}) - }} else {{ - None - }} - }}", - vis, - fn_name, - field_type.syntax(), - variant_name, - pattern_suffix, - bound_name, - ); - - add_method_to_adt(builder, &parent_enum, impl_def, &method); - }, - ) -} - -// Assist: generate_enum_as_method -// -// Generate an `as_` method for an enum variant. -// -// ``` -// enum Value { -// Number(i32), -// Text(String)$0, -// } -// ``` -// -> -// ``` -// enum Value { -// Number(i32), -// Text(String), -// } -// -// impl Value { -// fn as_text(&self) -> Option<&String> { -// if let Self::Text(v) = self { -// Some(v) -// } else { -// None -// } -// } -// } -// ``` -pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let variant = ctx.find_node_at_offset::()?; - let variant_name = variant.name()?; - let parent_enum = ast::Adt::Enum(variant.parent_enum()); - let variant_kind = variant_kind(&variant); - - let fn_name = format!("as_{}", &to_lower_snake_case(variant_name.text())); - - // Return early if we've found an existing new fn - let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; - - let field_type = variant_kind.single_field_type()?; - let (pattern_suffix, bound_name) = variant_kind.binding_pattern()?; - - let target = variant.syntax().text_range(); - acc.add( - AssistId("generate_enum_as_method", AssistKind::Generate), - "Generate an `as_` method for an enum variant", - target, - |builder| { - let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); - let method = format!( - " {}fn {}(&self) -> Option<&{}> {{ - if let Self::{}{} = self {{ - Some({}) - }} else {{ - None - }} - }}", - vis, - fn_name, - field_type.syntax(), - variant_name, - pattern_suffix, - bound_name, - ); - - add_method_to_adt(builder, &parent_enum, impl_def, &method); - }, - ) -} - -fn add_method_to_adt( - builder: &mut AssistBuilder, - adt: &ast::Adt, - impl_def: Option, - method: &str, -) { - let mut buf = String::with_capacity(method.len() + 2); - if impl_def.is_some() { - buf.push('\n'); - } - buf.push_str(method); - - let start_offset = impl_def - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) - .unwrap_or_else(|| { - buf = generate_impl_text(&adt, &buf); - adt.syntax().text_range().end() - }); - - builder.insert(start_offset, buf); -} - -enum VariantKind { - Unit, - /// Tuple with a single field - NewtypeTuple { - ty: Option, - }, - /// Tuple with 0 or more than 2 fields - Tuple, - /// Record with a single field - NewtypeRecord { - field_name: Option, - field_type: Option, - }, - /// Record with 0 or more than 2 fields - Record, -} - -impl VariantKind { - fn pattern_suffix(&self) -> &'static str { - match self { - VariantKind::Unit => "", - VariantKind::NewtypeTuple { .. } | VariantKind::Tuple => "(..)", - VariantKind::NewtypeRecord { .. } | VariantKind::Record => " { .. }", - } - } - - fn binding_pattern(&self) -> Option<(String, String)> { - match self { - VariantKind::Unit - | VariantKind::Tuple - | VariantKind::Record - | VariantKind::NewtypeRecord { field_name: None, .. } => None, - VariantKind::NewtypeTuple { .. } => Some(("(v)".to_owned(), "v".to_owned())), - VariantKind::NewtypeRecord { field_name: Some(name), .. } => { - Some((format!(" {{ {} }}", name.syntax()), name.syntax().to_string())) - } - } - } - - fn single_field_type(&self) -> Option<&ast::Type> { - match self { - VariantKind::Unit | VariantKind::Tuple | VariantKind::Record => None, - VariantKind::NewtypeTuple { ty } => ty.as_ref(), - VariantKind::NewtypeRecord { field_type, .. } => field_type.as_ref(), - } - } -} - -fn variant_kind(variant: &ast::Variant) -> VariantKind { - match variant.kind() { - ast::StructKind::Record(record) => { - if let Some((single_field,)) = record.fields().collect_tuple() { - let field_name = single_field.name(); - let field_type = single_field.ty(); - VariantKind::NewtypeRecord { field_name, field_type } - } else { - VariantKind::Record - } - } - ast::StructKind::Tuple(tuple) => { - if let Some((single_field,)) = tuple.fields().collect_tuple() { - let ty = single_field.ty(); - VariantKind::NewtypeTuple { ty } - } else { - VariantKind::Tuple - } - } - ast::StructKind::Unit => VariantKind::Unit, - } -} - #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -478,164 +244,6 @@ impl Variant { fn is_major(&self) -> bool { matches!(self, Self::Major) } -}"#, - ); - } - - #[test] - fn test_generate_enum_into_tuple_variant() { - check_assist( - generate_enum_into_method, - r#" -enum Value { - Number(i32), - Text(String)$0, -}"#, - r#"enum Value { - Number(i32), - Text(String), -} - -impl Value { - fn into_text(self) -> Option { - if let Self::Text(v) = self { - Some(v) - } else { - None - } - } -}"#, - ); - } - - #[test] - fn test_generate_enum_into_already_implemented() { - check_assist_not_applicable( - generate_enum_into_method, - r#"enum Value { - Number(i32), - Text(String)$0, -} - -impl Value { - fn into_text(self) -> Option { - if let Self::Text(v) = self { - Some(v) - } else { - None - } - } -}"#, - ); - } - - #[test] - fn test_generate_enum_into_unit_variant() { - check_assist_not_applicable( - generate_enum_into_method, - r#"enum Value { - Number(i32), - Text(String), - Unit$0, -}"#, - ); - } - - #[test] - fn test_generate_enum_into_record_with_multiple_fields() { - check_assist_not_applicable( - generate_enum_into_method, - r#"enum Value { - Number(i32), - Text(String), - Both { first: i32, second: String }$0, -}"#, - ); - } - - #[test] - fn test_generate_enum_into_tuple_with_multiple_fields() { - check_assist_not_applicable( - generate_enum_into_method, - r#"enum Value { - Number(i32), - Text(String, String)$0, -}"#, - ); - } - - #[test] - fn test_generate_enum_into_record_variant() { - check_assist( - generate_enum_into_method, - r#"enum Value { - Number(i32), - Text { text: String }$0, -}"#, - r#"enum Value { - Number(i32), - Text { text: String }, -} - -impl Value { - fn into_text(self) -> Option { - if let Self::Text { text } = self { - Some(text) - } else { - None - } - } -}"#, - ); - } - - #[test] - fn test_generate_enum_as_tuple_variant() { - check_assist( - generate_enum_as_method, - r#" -enum Value { - Number(i32), - Text(String)$0, -}"#, - r#"enum Value { - Number(i32), - Text(String), -} - -impl Value { - fn as_text(&self) -> Option<&String> { - if let Self::Text(v) = self { - Some(v) - } else { - None - } - } -}"#, - ); - } - - #[test] - fn test_generate_enum_as_record_variant() { - check_assist( - generate_enum_as_method, - r#"enum Value { - Number(i32), - Text { text: String }$0, -}"#, - r#"enum Value { - Number(i32), - Text { text: String }, -} - -impl Value { - fn as_text(&self) -> Option<&String> { - if let Self::Text { text } = self { - Some(text) - } else { - None - } - } }"#, ); } diff --git a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs new file mode 100644 index 00000000000..71447f3104d --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs @@ -0,0 +1,307 @@ +use itertools::Itertools; +use stdx::to_lower_snake_case; +use syntax::ast::VisibilityOwner; +use syntax::ast::{self, AstNode, NameOwner}; + +use crate::{ + utils::{add_method_to_adt, find_struct_impl}, + AssistContext, AssistId, AssistKind, Assists, +}; + +// Assist: generate_enum_into_method +// +// Generate an `into_` method for an enum variant. +// +// ``` +// enum Value { +// Number(i32), +// Text(String)$0, +// } +// ``` +// -> +// ``` +// enum Value { +// Number(i32), +// Text(String), +// } +// +// impl Value { +// fn into_text(self) -> Option { +// if let Self::Text(v) = self { +// Some(v) +// } else { +// None +// } +// } +// } +// ``` +pub(crate) fn generate_enum_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + generate_enum_projection_method( + acc, + ctx, + "generate_enum_into_method", + "Generate an `into_` method for an enum variant", + "into", + "", + ) +} + +// Assist: generate_enum_as_method +// +// Generate an `as_` method for an enum variant. +// +// ``` +// enum Value { +// Number(i32), +// Text(String)$0, +// } +// ``` +// -> +// ``` +// enum Value { +// Number(i32), +// Text(String), +// } +// +// impl Value { +// fn as_text(&self) -> Option<&String> { +// if let Self::Text(v) = self { +// Some(v) +// } else { +// None +// } +// } +// } +// ``` +pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + generate_enum_projection_method( + acc, + ctx, + "generate_enum_as_method", + "Generate an `as_` method for an enum variant", + "as", + "&", + ) +} + +pub(crate) fn generate_enum_projection_method( + acc: &mut Assists, + ctx: &AssistContext, + assist_id: &'static str, + assist_description: &str, + fn_name_prefix: &str, + ref_prefix: &str, +) -> Option<()> { + let variant = ctx.find_node_at_offset::()?; + let variant_name = variant.name()?; + let parent_enum = ast::Adt::Enum(variant.parent_enum()); + + let (pattern_suffix, field_type, bound_name) = match variant.kind() { + ast::StructKind::Record(record) => { + let (field,) = record.fields().collect_tuple()?; + let name = field.name()?.to_string(); + let ty = field.ty()?; + let pattern_suffix = format!(" {{ {} }}", name); + (pattern_suffix, ty, name) + } + ast::StructKind::Tuple(tuple) => { + let (field,) = tuple.fields().collect_tuple()?; + let ty = field.ty()?; + ("(v)".to_owned(), ty, "v".to_owned()) + } + ast::StructKind::Unit => return None, + }; + + let fn_name = format!("{}_{}", fn_name_prefix, &to_lower_snake_case(variant_name.text())); + + // Return early if we've found an existing new fn + let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; + + let target = variant.syntax().text_range(); + acc.add(AssistId(assist_id, AssistKind::Generate), assist_description, target, |builder| { + let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); + let method = format!( + " {0}fn {1}({2}self) -> Option<{2}{3}> {{ + if let Self::{4}{5} = self {{ + Some({6}) + }} else {{ + None + }} + }}", + vis, + fn_name, + ref_prefix, + field_type.syntax(), + variant_name, + pattern_suffix, + bound_name, + ); + + add_method_to_adt(builder, &parent_enum, impl_def, &method); + }) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_generate_enum_into_tuple_variant() { + check_assist( + generate_enum_into_method, + r#" +enum Value { + Number(i32), + Text(String)$0, +}"#, + r#"enum Value { + Number(i32), + Text(String), +} + +impl Value { + fn into_text(self) -> Option { + if let Self::Text(v) = self { + Some(v) + } else { + None + } + } +}"#, + ); + } + + #[test] + fn test_generate_enum_into_already_implemented() { + check_assist_not_applicable( + generate_enum_into_method, + r#"enum Value { + Number(i32), + Text(String)$0, +} + +impl Value { + fn into_text(self) -> Option { + if let Self::Text(v) = self { + Some(v) + } else { + None + } + } +}"#, + ); + } + + #[test] + fn test_generate_enum_into_unit_variant() { + check_assist_not_applicable( + generate_enum_into_method, + r#"enum Value { + Number(i32), + Text(String), + Unit$0, +}"#, + ); + } + + #[test] + fn test_generate_enum_into_record_with_multiple_fields() { + check_assist_not_applicable( + generate_enum_into_method, + r#"enum Value { + Number(i32), + Text(String), + Both { first: i32, second: String }$0, +}"#, + ); + } + + #[test] + fn test_generate_enum_into_tuple_with_multiple_fields() { + check_assist_not_applicable( + generate_enum_into_method, + r#"enum Value { + Number(i32), + Text(String, String)$0, +}"#, + ); + } + + #[test] + fn test_generate_enum_into_record_variant() { + check_assist( + generate_enum_into_method, + r#"enum Value { + Number(i32), + Text { text: String }$0, +}"#, + r#"enum Value { + Number(i32), + Text { text: String }, +} + +impl Value { + fn into_text(self) -> Option { + if let Self::Text { text } = self { + Some(text) + } else { + None + } + } +}"#, + ); + } + + #[test] + fn test_generate_enum_as_tuple_variant() { + check_assist( + generate_enum_as_method, + r#" +enum Value { + Number(i32), + Text(String)$0, +}"#, + r#"enum Value { + Number(i32), + Text(String), +} + +impl Value { + fn as_text(&self) -> Option<&String> { + if let Self::Text(v) = self { + Some(v) + } else { + None + } + } +}"#, + ); + } + + #[test] + fn test_generate_enum_as_record_variant() { + check_assist( + generate_enum_as_method, + r#"enum Value { + Number(i32), + Text { text: String }$0, +}"#, + r#"enum Value { + Number(i32), + Text { text: String }, +} + +impl Value { + fn as_text(&self) -> Option<&String> { + if let Self::Text { text } = self { + Some(text) + } else { + None + } + } +}"#, + ); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 84ac928e975..4a7cd58486a 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -129,6 +129,7 @@ mod handlers { mod generate_default_from_enum_variant; mod generate_derive; mod generate_enum_match_method; + mod generate_enum_projection_method; mod generate_from_impl_for_enum; mod generate_function; mod generate_getter; @@ -190,8 +191,8 @@ mod handlers { generate_default_from_enum_variant::generate_default_from_enum_variant, generate_derive::generate_derive, generate_enum_match_method::generate_enum_is_method, - generate_enum_match_method::generate_enum_into_method, - generate_enum_match_method::generate_enum_as_method, + generate_enum_projection_method::generate_enum_into_method, + generate_enum_projection_method::generate_enum_as_method, generate_from_impl_for_enum::generate_from_impl_for_enum, generate_function::generate_function, generate_getter::generate_getter, diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 276792bc1a2..880ab6fe3eb 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs @@ -21,7 +21,7 @@ use syntax::{ }; use crate::{ - assist_context::AssistContext, + assist_context::{AssistBuilder, AssistContext}, ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, }; @@ -464,3 +464,25 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str buf } + +pub(crate) fn add_method_to_adt( + builder: &mut AssistBuilder, + adt: &ast::Adt, + impl_def: Option, + method: &str, +) { + let mut buf = String::with_capacity(method.len() + 2); + if impl_def.is_some() { + buf.push('\n'); + } + buf.push_str(method); + + let start_offset = impl_def + .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) + .unwrap_or_else(|| { + buf = generate_impl_text(&adt, &buf); + adt.syntax().text_range().end() + }); + + builder.insert(start_offset, buf); +}