Move find_struct_impl to assist utils

This commit is contained in:
Yoshua Wuyts 2021-02-05 14:36:07 +01:00
parent 13d663dd16
commit dfd751303e
3 changed files with 85 additions and 154 deletions

View file

@ -1,10 +1,9 @@
use hir::Adt; use stdx::{format_to, to_lower_snake_case};
use stdx::format_to;
use syntax::ast::{self, AstNode, NameOwner}; use syntax::ast::{self, AstNode, NameOwner};
use syntax::{ast::VisibilityOwner, T}; use syntax::{ast::VisibilityOwner, T};
use test_utils::mark; use test_utils::mark;
use crate::{AssistContext, AssistId, AssistKind, Assists}; use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists};
// Assist: generate_enum_match_method // Assist: generate_enum_match_method
// //
@ -40,10 +39,14 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext)
return None; return None;
} }
let fn_name = to_lower_snake_case(&format!("{}", variant_name)); let fn_name = to_lower_snake_case(&variant_name.to_string());
// Return early if we've found an existing new fn // Return early if we've found an existing new fn
let impl_def = find_struct_impl(&ctx, &parent_enum, format!("is_{}", fn_name).as_str())?; let impl_def = find_struct_impl(
&ctx,
&ast::AdtDef::Enum(parent_enum.clone()),
format!("is_{}", fn_name).as_str(),
)?;
let target = variant.syntax().text_range(); let target = variant.syntax().text_range();
acc.add( acc.add(
@ -95,94 +98,14 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext)
// parameters // parameters
fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String { fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String {
let mut buf = String::with_capacity(code.len()); let mut buf = String::with_capacity(code.len());
buf.push_str("\n\nimpl"); buf.push_str("\n\nimpl ");
buf.push_str(" ");
buf.push_str(strukt.name().unwrap().text()); buf.push_str(strukt.name().unwrap().text());
format_to!(buf, " {{\n{}\n}}", code); format_to!(buf, " {{\n{}\n}}", code);
buf buf
} }
fn to_lower_snake_case(s: &str) -> String {
let mut buf = String::with_capacity(s.len());
let mut prev = false;
for c in s.chars() {
if c.is_ascii_uppercase() && prev {
buf.push('_')
}
prev = true;
buf.push(c.to_ascii_lowercase());
}
buf
}
// Uses a syntax-driven approach to find any impl blocks for the struct that
// exist within the module/file
//
// Returns `None` if we've found an existing `new` fn
//
// FIXME: change the new fn checking to a more semantic approach when that's more
// viable (e.g. we process proc macros, etc)
fn find_struct_impl(
ctx: &AssistContext,
strukt: &ast::Enum,
name: &str,
) -> Option<Option<ast::Impl>> {
let db = ctx.db();
let module = strukt.syntax().ancestors().find(|node| {
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
})?;
let struct_def = ctx.sema.to_def(strukt)?;
let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
let blk = ctx.sema.to_def(&impl_blk)?;
// FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
// (we currently use the wrong type parameter)
// also we wouldn't want to use e.g. `impl S<u32>`
let same_ty = match blk.target_ty(db).as_adt() {
Some(def) => def == Adt::Enum(struct_def),
None => false,
};
let not_trait_impl = blk.target_trait(db).is_none();
if !(same_ty && not_trait_impl) {
None
} else {
Some(impl_blk)
}
});
if let Some(ref impl_blk) = block {
if has_fn(impl_blk, name) {
mark::hit!(test_gen_enum_match_impl_already_exists);
return None;
}
}
Some(block)
}
fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool {
if let Some(il) = imp.assoc_item_list() {
for item in il.assoc_items() {
if let ast::AssocItem::Fn(f) = item {
if let Some(name) = f.name() {
if name.text().eq_ignore_ascii_case(rhs_name) {
return true;
}
}
}
}
}
false
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ide_db::helpers::FamousDefs;
use test_utils::mark; use test_utils::mark;
use crate::tests::{check_assist, check_assist_not_applicable}; use crate::tests::{check_assist, check_assist_not_applicable};
@ -190,9 +113,7 @@ mod tests {
use super::*; use super::*;
fn check_not_applicable(ra_fixture: &str) { fn check_not_applicable(ra_fixture: &str) {
let fixture = check_assist_not_applicable(generate_enum_match_method, ra_fixture)
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
check_assist_not_applicable(generate_enum_match_method, &fixture)
} }
#[test] #[test]
@ -221,7 +142,6 @@ impl Variant {
#[test] #[test]
fn test_generate_enum_match_already_implemented() { fn test_generate_enum_match_already_implemented() {
mark::check!(test_gen_enum_match_impl_already_exists);
check_not_applicable( check_not_applicable(
r#" r#"
enum Variant { enum Variant {

View file

@ -1,4 +1,3 @@
use hir::Adt;
use itertools::Itertools; use itertools::Itertools;
use stdx::format_to; use stdx::format_to;
use syntax::{ use syntax::{
@ -6,7 +5,7 @@ use syntax::{
SmolStr, T, SmolStr, T,
}; };
use crate::{AssistContext, AssistId, AssistKind, Assists}; use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists};
// Assist: generate_new // Assist: generate_new
// //
@ -38,7 +37,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
}; };
// Return early if we've found an existing new fn // Return early if we've found an existing new fn
let impl_def = find_struct_impl(&ctx, &strukt)?; let impl_def = find_struct_impl(&ctx, &ast::AdtDef::Struct(strukt.clone()), "new")?;
let target = strukt.syntax().text_range(); let target = strukt.syntax().text_range();
acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
@ -111,65 +110,6 @@ fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
buf buf
} }
// Uses a syntax-driven approach to find any impl blocks for the struct that
// exist within the module/file
//
// Returns `None` if we've found an existing `new` fn
//
// FIXME: change the new fn checking to a more semantic approach when that's more
// viable (e.g. we process proc macros, etc)
fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
let db = ctx.db();
let module = strukt.syntax().ancestors().find(|node| {
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
})?;
let struct_def = ctx.sema.to_def(strukt)?;
let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
let blk = ctx.sema.to_def(&impl_blk)?;
// FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
// (we currently use the wrong type parameter)
// also we wouldn't want to use e.g. `impl S<u32>`
let same_ty = match blk.target_ty(db).as_adt() {
Some(def) => def == Adt::Struct(struct_def),
None => false,
};
let not_trait_impl = blk.target_trait(db).is_none();
if !(same_ty && not_trait_impl) {
None
} else {
Some(impl_blk)
}
});
if let Some(ref impl_blk) = block {
if has_new_fn(impl_blk) {
return None;
}
}
Some(block)
}
fn has_new_fn(imp: &ast::Impl) -> bool {
if let Some(il) = imp.assoc_item_list() {
for item in il.assoc_items() {
if let ast::AssocItem::Fn(f) = item {
if let Some(name) = f.name() {
if name.text().eq_ignore_ascii_case("new") {
return true;
}
}
}
}
}
false
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};

View file

@ -2,7 +2,7 @@
use std::ops; use std::ops;
use hir::HasSource; use hir::{Adt, HasSource};
use ide_db::{helpers::SnippetCap, RootDatabase}; use ide_db::{helpers::SnippetCap, RootDatabase};
use itertools::Itertools; use itertools::Itertools;
use syntax::{ use syntax::{
@ -15,7 +15,10 @@ use syntax::{
SyntaxNode, TextSize, T, SyntaxNode, TextSize, T,
}; };
use crate::ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}; use crate::{
assist_context::AssistContext,
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
};
pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
extract_trivial_expression(&block) extract_trivial_expression(&block)
@ -267,3 +270,71 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
pat_head == var_head pat_head == var_head
} }
// Uses a syntax-driven approach to find any impl blocks for the struct that
// exist within the module/file
//
// Returns `None` if we've found an existing `new` fn
//
// FIXME: change the new fn checking to a more semantic approach when that's more
// viable (e.g. we process proc macros, etc)
pub(crate) fn find_struct_impl(
ctx: &AssistContext,
strukt: &ast::AdtDef,
name: &str,
) -> Option<Option<ast::Impl>> {
let db = ctx.db();
let module = strukt.syntax().ancestors().find(|node| {
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
})?;
let struct_def = match strukt {
ast::AdtDef::Enum(e) => Adt::Enum(ctx.sema.to_def(e)?),
ast::AdtDef::Struct(s) => Adt::Struct(ctx.sema.to_def(s)?),
ast::AdtDef::Union(u) => Adt::Union(ctx.sema.to_def(u)?),
};
let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
let blk = ctx.sema.to_def(&impl_blk)?;
// FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
// (we currently use the wrong type parameter)
// also we wouldn't want to use e.g. `impl S<u32>`
let same_ty = match blk.target_ty(db).as_adt() {
Some(def) => def == struct_def,
None => false,
};
let not_trait_impl = blk.target_trait(db).is_none();
if !(same_ty && not_trait_impl) {
None
} else {
Some(impl_blk)
}
});
if let Some(ref impl_blk) = block {
if has_fn(impl_blk, name) {
return None;
}
}
Some(block)
}
fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool {
if let Some(il) = imp.assoc_item_list() {
for item in il.assoc_items() {
if let ast::AssocItem::Fn(f) = item {
if let Some(name) = f.name() {
if name.text().eq_ignore_ascii_case(rhs_name) {
return true;
}
}
}
}
}
false
}