Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 92 additions & 28 deletions crates/assists/src/handlers/extract_struct_from_enum_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
use ide_db::{defs::Definition, search::Reference, RootDatabase};
use rustc_hash::{FxHashMap, FxHashSet};
use syntax::{
algo::find_node_at_offset,
algo::SyntaxRewriter,
ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
SourceFile, SyntaxElement,
algo::{find_node_at_offset, SyntaxRewriter},
ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
SourceFile, SyntaxElement, SyntaxNode, T,
};

use crate::{
Expand Down Expand Up @@ -130,17 +129,21 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &En
fn insert_import(
ctx: &AssistContext,
rewriter: &mut SyntaxRewriter,
path: &ast::PathExpr,
scope_node: &SyntaxNode,
module: &Module,
enum_module_def: &ModuleDef,
variant_hir_name: &Name,
) -> Option<()> {
let db = ctx.db();
let mod_path = module.find_use_path(db, enum_module_def.clone());
let mod_path = module.find_use_path_prefixed(
db,
enum_module_def.clone(),
ctx.config.insert_use.prefix_kind,
);
if let Some(mut mod_path) = mod_path {
mod_path.segments.pop();
mod_path.segments.push(variant_hir_name.clone());
let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
let scope = ImportScope::find_insert_use_container(scope_node, ctx)?;

*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
}
Expand Down Expand Up @@ -204,27 +207,31 @@ fn update_reference(
variant_hir_name: &Name,
visited_modules_set: &mut FxHashSet<Module>,
) -> Option<()> {
let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
source_file.syntax(),
reference.file_range.range.start(),
)?;
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
let list = call.arg_list()?;
let segment = path_expr.path()?.segment()?;
let module = ctx.sema.scope(&path_expr.syntax()).module()?;
let offset = reference.file_range.range.start();
let (segment, expr) = if let Some(path_expr) =
find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
{
// tuple variant
(path_expr.path()?.segment()?, path_expr.syntax().parent()?.clone())
} else if let Some(record_expr) =
find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
{
// record variant
(record_expr.path()?.segment()?, record_expr.syntax().clone())
} else {
return None;
};
Comment on lines +211 to +223
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this'll work correctly for ast::RecordExpr nested into an ast::CallExpr for a different tuple struct?

Copy link
Member Author

@Veykril Veykril Nov 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean something like this?

enum A { <|>One { a: u32, b: u32 } }

struct B(A);

fn foo() {
    let _ = B(A::One { a: 1, b: 2 });
}

This seems to work properly as I get

struct One{ pub a: u32, pub b: u32 }

enum A { One(One) }

struct B(A);

fn foo() {
    let _ = B(A::One(One { a: 1, b: 2 }));
}

which seems correct to me. Though I would've expected this to fail as well now that you say it.
Will definitely add this as a test if anything.

Edit: Ah the ast::PathExpr in the ast::CallExpr is a neighbor of the arg_list node containing the record which is why this still works, since find_node_at_offset considers ancestors only so this shouldn't be a problem.


let module = ctx.sema.scope(&expr).module()?;
if !visited_modules_set.contains(&module) {
if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
.is_some()
if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
{
visited_modules_set.insert(module);
}
}

let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
rewriter.insert_after(&lparen, segment.syntax());
rewriter.insert_after(&lparen, &lparen);
rewriter.insert_before(&rparen, &rparen);
rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
rewriter.insert_after(segment.syntax(), segment.syntax());
rewriter.insert_after(&expr, &make::token(T![')']));
Some(())
}

Expand Down Expand Up @@ -320,7 +327,7 @@ fn another_fn() {
r#"use my_mod::my_other_mod::MyField;

mod my_mod {
use my_other_mod::MyField;
use self::my_other_mod::MyField;

fn another_fn() {
let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
Expand All @@ -345,6 +352,33 @@ fn another_fn() {
);
}

#[test]
fn extract_record_fix_references() {
check_assist(
extract_struct_from_enum_variant,
r#"
enum E {
<|>V { i: i32, j: i32 }
}

fn f() {
let e = E::V { i: 9, j: 2 };
}
"#,
r#"
struct V{ pub i: i32, pub j: i32 }

enum E {
V(V)
}

fn f() {
let e = E::V(V { i: 9, j: 2 });
}
"#,
)
}

#[test]
fn test_several_files() {
check_assist(
Expand Down Expand Up @@ -372,9 +406,7 @@ enum E {
mod foo;

//- /foo.rs
use V;

use crate::E;
use crate::{E, V};
fn f() {
let e = E::V(V(9, 2));
}
Expand All @@ -384,7 +416,6 @@ fn f() {

#[test]
fn test_several_files_record() {
// FIXME: this should fix the usage as well!
check_assist(
extract_struct_from_enum_variant,
r#"
Expand All @@ -401,17 +432,50 @@ fn f() {
}
"#,
r#"
//- /main.rs
struct V{ pub i: i32, pub j: i32 }

enum E {
V(V)
}
mod foo;

//- /foo.rs
use crate::{E, V};
fn f() {
let e = E::V(V { i: 9, j: 2 });
}
"#,
)
}

#[test]
fn test_extract_struct_record_nested_call_exp() {
check_assist(
extract_struct_from_enum_variant,
r#"
enum A { <|>One { a: u32, b: u32 } }

struct B(A);

fn foo() {
let _ = B(A::One { a: 1, b: 2 });
}
"#,
r#"
struct One{ pub a: u32, pub b: u32 }

enum A { One(One) }

struct B(A);

fn foo() {
let _ = B(A::One(One { a: 1, b: 2 }));
}
"#,
);
}

fn check_not_applicable(ra_fixture: &str) {
let fixture =
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
Expand Down