mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 21:13:37 +00:00
Auto merge of #12539 - soruh:instanciate_empty_structs, r=Veykril
Automatically instaciate trivially instaciable structs in "Generate new" and "Fill struct fields" As proposed in #12535 this PR changes the "Generate new" and "Fill struct fields" assist/diagnostic to instanciate structs with no fields and enums with a single empty variant. For example: ```rust pub enum Bar { Bar {}, } struct Foo<T> { a: usize, bar: Bar, _phantom: std::marker::PhantomData<T>, } impl<T> Foo<T> { /* generate new */ fn random() -> Self { Self { /* Fill struct fields */ } } } ``` was previously: ```rust impl<T> Foo<T> { fn new(a: usize, bar: Bar, _phantom: std::marker::PhantomData<T>) -> Self { Self { a, bar, _phantom } } fn random() -> Self { Self { a: todo!(), bar: todo!(), _phantom: todo!(), } } } ``` and is now: ```rust impl<T> Foo<T> { fn new(a: usize) -> Self { Self { a, bar: Bar::Bar {}, _phantom: std::marker::PhantomData } } fn random() -> Self { Self { a: todo!(), bar: Bar::Bar {}, _phantom: std::marker::PhantomData, } } } ``` I'd be happy about any suggestions. ## TODO - [x] deduplicate `use_trivial_constructor` (unclear how to do as it's used in two separate crates) - [x] write tests Closes #12535
This commit is contained in:
commit
01d251789f
4 changed files with 287 additions and 4 deletions
|
@ -1,3 +1,6 @@
|
|||
use ide_db::{
|
||||
imports::import_assets::item_for_path_search, use_trivial_contructor::use_trivial_constructor,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use stdx::format_to;
|
||||
use syntax::ast::{self, AstNode, HasName, HasVisibility, StructKind};
|
||||
|
@ -38,6 +41,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
|||
// Return early if we've found an existing new fn
|
||||
let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
|
||||
|
||||
let current_module = ctx.sema.scope(strukt.syntax())?.module();
|
||||
|
||||
let target = strukt.syntax().text_range();
|
||||
acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
|
||||
let mut buf = String::with_capacity(512);
|
||||
|
@ -48,11 +53,50 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
|||
|
||||
let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
|
||||
|
||||
let trivial_constructors = field_list
|
||||
.fields()
|
||||
.map(|f| {
|
||||
let ty = ctx.sema.resolve_type(&f.ty()?)?;
|
||||
|
||||
let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
|
||||
|
||||
let type_path = current_module
|
||||
.find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
|
||||
|
||||
let expr = use_trivial_constructor(
|
||||
&ctx.sema.db,
|
||||
ide_db::helpers::mod_path_to_ast(&type_path),
|
||||
&ty,
|
||||
)?;
|
||||
|
||||
Some(format!("{}: {}", f.name()?.syntax(), expr))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let params = field_list
|
||||
.fields()
|
||||
.filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
|
||||
.enumerate()
|
||||
.filter_map(|(i, f)| {
|
||||
if trivial_constructors[i].is_none() {
|
||||
Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.format(", ");
|
||||
|
||||
let fields = field_list
|
||||
.fields()
|
||||
.enumerate()
|
||||
.filter_map(|(i, f)| {
|
||||
let contructor = trivial_constructors[i].clone();
|
||||
if contructor.is_some() {
|
||||
contructor
|
||||
} else {
|
||||
Some(f.name()?.to_string())
|
||||
}
|
||||
})
|
||||
.format(", ");
|
||||
let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
|
||||
|
||||
format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
|
||||
|
||||
|
@ -79,6 +123,97 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_new_with_zst_fields() {
|
||||
check_assist(
|
||||
generate_new,
|
||||
r#"
|
||||
struct Empty;
|
||||
|
||||
struct Foo { empty: Empty $0}
|
||||
"#,
|
||||
r#"
|
||||
struct Empty;
|
||||
|
||||
struct Foo { empty: Empty }
|
||||
|
||||
impl Foo {
|
||||
fn $0new() -> Self { Self { empty: Empty } }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
r#"
|
||||
struct Empty;
|
||||
|
||||
struct Foo { baz: String, empty: Empty $0}
|
||||
"#,
|
||||
r#"
|
||||
struct Empty;
|
||||
|
||||
struct Foo { baz: String, empty: Empty }
|
||||
|
||||
impl Foo {
|
||||
fn $0new(baz: String) -> Self { Self { baz, empty: Empty } }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
r#"
|
||||
enum Empty { Bar }
|
||||
|
||||
struct Foo { empty: Empty $0}
|
||||
"#,
|
||||
r#"
|
||||
enum Empty { Bar }
|
||||
|
||||
struct Foo { empty: Empty }
|
||||
|
||||
impl Foo {
|
||||
fn $0new() -> Self { Self { empty: Empty::Bar } }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
// make sure the assist only works on unit variants
|
||||
check_assist(
|
||||
generate_new,
|
||||
r#"
|
||||
struct Empty {}
|
||||
|
||||
struct Foo { empty: Empty $0}
|
||||
"#,
|
||||
r#"
|
||||
struct Empty {}
|
||||
|
||||
struct Foo { empty: Empty }
|
||||
|
||||
impl Foo {
|
||||
fn $0new(empty: Empty) -> Self { Self { empty } }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
r#"
|
||||
enum Empty { Bar {} }
|
||||
|
||||
struct Foo { empty: Empty $0}
|
||||
"#,
|
||||
r#"
|
||||
enum Empty { Bar {} }
|
||||
|
||||
struct Foo { empty: Empty }
|
||||
|
||||
impl Foo {
|
||||
fn $0new(empty: Empty) -> Self { Self { empty } }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_new() {
|
||||
check_assist(
|
||||
|
|
|
@ -20,6 +20,7 @@ pub mod source_change;
|
|||
pub mod symbol_index;
|
||||
pub mod traits;
|
||||
pub mod ty_filter;
|
||||
pub mod use_trivial_contructor;
|
||||
|
||||
pub mod imports {
|
||||
pub mod import_assets;
|
||||
|
|
34
crates/ide-db/src/use_trivial_contructor.rs
Normal file
34
crates/ide-db/src/use_trivial_contructor.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
//! Functionality for generating trivial contructors
|
||||
|
||||
use hir::StructKind;
|
||||
use syntax::ast;
|
||||
|
||||
/// given a type return the trivial contructor (if one exists)
|
||||
pub fn use_trivial_constructor(
|
||||
db: &crate::RootDatabase,
|
||||
path: ast::Path,
|
||||
ty: &hir::Type,
|
||||
) -> Option<ast::Expr> {
|
||||
match ty.as_adt() {
|
||||
Some(hir::Adt::Enum(x)) => {
|
||||
if let &[variant] = &*x.variants(db) {
|
||||
if variant.kind(db) == hir::StructKind::Unit {
|
||||
let path = ast::make::path_qualified(
|
||||
path,
|
||||
syntax::ast::make::path_segment(ast::make::name_ref(
|
||||
&variant.name(db).to_smol_str(),
|
||||
)),
|
||||
);
|
||||
|
||||
return Some(syntax::ast::make::expr_path(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(hir::Adt::Struct(x)) if x.kind(db) == StructKind::Unit => {
|
||||
return Some(syntax::ast::make::expr_path(path));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
|
@ -3,7 +3,10 @@ use hir::{
|
|||
db::{AstDatabase, HirDatabase},
|
||||
known, AssocItem, HirDisplay, InFile, Type,
|
||||
};
|
||||
use ide_db::{assists::Assist, famous_defs::FamousDefs, source_change::SourceChange, FxHashMap};
|
||||
use ide_db::{
|
||||
assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search,
|
||||
source_change::SourceChange, use_trivial_contructor::use_trivial_constructor, FxHashMap,
|
||||
};
|
||||
use stdx::format_to;
|
||||
use syntax::{
|
||||
algo,
|
||||
|
@ -55,6 +58,11 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
|
|||
|
||||
let root = ctx.sema.db.parse_or_expand(d.file)?;
|
||||
|
||||
let current_module = match &d.field_list_parent {
|
||||
Either::Left(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
|
||||
Either::Right(ptr) => ctx.sema.scope(ptr.to_node(&root).syntax()).map(|it| it.module()),
|
||||
};
|
||||
|
||||
let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| {
|
||||
let edit = {
|
||||
let mut builder = TextEdit::builder();
|
||||
|
@ -110,7 +118,26 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
|
|||
Some(generate_fill_expr(ty))
|
||||
}
|
||||
} else {
|
||||
Some(generate_fill_expr(ty))
|
||||
let expr = (|| -> Option<ast::Expr> {
|
||||
let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));
|
||||
|
||||
let type_path = current_module?.find_use_path(
|
||||
ctx.sema.db,
|
||||
item_for_path_search(ctx.sema.db, item_in_ns)?,
|
||||
)?;
|
||||
|
||||
use_trivial_constructor(
|
||||
&ctx.sema.db,
|
||||
ide_db::helpers::mod_path_to_ast(&type_path),
|
||||
&ty,
|
||||
)
|
||||
})();
|
||||
|
||||
if expr.is_some() {
|
||||
expr
|
||||
} else {
|
||||
Some(generate_fill_expr(ty))
|
||||
}
|
||||
};
|
||||
let field = make::record_expr_field(
|
||||
make::name_ref(&f.name(ctx.sema.db).to_smol_str()),
|
||||
|
@ -318,6 +345,92 @@ fn test_fn() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fill_struct_zst_fields() {
|
||||
check_fix(
|
||||
r#"
|
||||
struct Empty;
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct {$0};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Empty;
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct { one: 0, two: Empty };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
enum Empty { Foo };
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct {$0};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Empty { Foo };
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct { one: 0, two: Empty::Foo };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
// make sure the assist doesn't fill non Unit variants
|
||||
check_fix(
|
||||
r#"
|
||||
struct Empty {};
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct {$0};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Empty {};
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct { one: 0, two: todo!() };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
enum Empty { Foo {} };
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct {$0};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Empty { Foo {} };
|
||||
|
||||
struct TestStruct { one: i32, two: Empty }
|
||||
|
||||
fn test_fn() {
|
||||
let s = TestStruct { one: 0, two: todo!() };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fill_struct_fields_self() {
|
||||
check_fix(
|
||||
|
|
Loading…
Reference in a new issue