4208: More principled approach for finding From trait r=matklad a=matklad



bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-04-29 13:03:40 +00:00 committed by GitHub
commit 12aae7771d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 39 deletions

View file

@ -1,11 +1,12 @@
use ra_ide_db::RootDatabase;
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode, NameOwner}, ast::{self, AstNode, NameOwner},
TextSize, TextSize,
}; };
use stdx::format_to; use stdx::format_to;
use crate::{Assist, AssistCtx, AssistId}; use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
use ra_ide_db::RootDatabase; use test_utils::tested_by;
// Assist add_from_impl_for_enum // Assist add_from_impl_for_enum
// //
@ -41,7 +42,8 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
_ => return None, _ => return None,
}; };
if already_has_from_impl(ctx.sema, &variant) { if existing_from_impl(ctx.sema, &variant).is_some() {
tested_by!(test_add_from_impl_already_exists);
return None; return None;
} }
@ -70,41 +72,33 @@ impl From<{0}> for {1} {{
) )
} }
fn already_has_from_impl( fn existing_from_impl(
sema: &'_ hir::Semantics<'_, RootDatabase>, sema: &'_ hir::Semantics<'_, RootDatabase>,
variant: &ast::EnumVariant, variant: &ast::EnumVariant,
) -> bool { ) -> Option<()> {
let scope = sema.scope(&variant.syntax()); let variant = sema.to_def(variant)?;
let enum_ = variant.parent_enum(sema.db);
let krate = enum_.module(sema.db).krate();
let from_path = ast::make::path_from_text("From"); let from_trait = FamousDefs(sema, krate).core_convert_From()?;
let from_hir_path = match hir::Path::from_ast(from_path) {
Some(p) => p,
None => return false,
};
let from_trait = match scope.resolve_hir_path(&from_hir_path) {
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t,
_ => return false,
};
let e: hir::Enum = match sema.to_def(&variant.parent_enum()) { let enum_type = enum_.ty(sema.db);
Some(e) => e,
None => return false,
};
let e_ty = e.ty(sema.db);
let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) { let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
Some(ev) => ev,
None => return false,
};
let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db);
e_ty.impls_trait(sema.db, from_trait, &[var_ty]) if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
Some(())
} else {
None
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::helpers::{check_assist, check_assist_not_applicable}; use crate::helpers::{check_assist, check_assist_not_applicable};
use test_utils::covers;
#[test] #[test]
fn test_add_from_impl_for_enum() { fn test_add_from_impl_for_enum() {
@ -136,36 +130,40 @@ mod tests {
); );
} }
fn check_not_applicable(ra_fixture: &str) {
let fixture =
format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
check_assist_not_applicable(add_from_impl_for_enum, &fixture)
}
#[test] #[test]
fn test_add_from_impl_no_element() { fn test_add_from_impl_no_element() {
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }"); check_not_applicable("enum A { <|>One }");
} }
#[test] #[test]
fn test_add_from_impl_more_than_one_element_in_tuple() { fn test_add_from_impl_more_than_one_element_in_tuple() {
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }"); check_not_applicable("enum A { <|>One(u32, String) }");
} }
#[test] #[test]
fn test_add_from_impl_struct_variant() { fn test_add_from_impl_struct_variant() {
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }"); check_not_applicable("enum A { <|>One { x: u32 } }");
} }
#[test] #[test]
fn test_add_from_impl_already_exists() { fn test_add_from_impl_already_exists() {
check_assist_not_applicable( covers!(test_add_from_impl_already_exists);
add_from_impl_for_enum, check_not_applicable(
r#"enum A { <|>One(u32), } r#"
enum A { <|>One(u32), }
impl From<u32> for A { impl From<u32> for A {
fn from(v: u32) -> Self { fn from(v: u32) -> Self {
A::One(v) A::One(v)
} }
} }
"#,
pub trait From<T> {
fn from(T) -> Self;
}"#,
); );
} }

View file

@ -8,4 +8,5 @@ test_utils::marks![
test_not_inline_mut_variable test_not_inline_mut_variable
test_not_applicable_if_variable_unused test_not_applicable_if_variable_unused
change_visibility_field_false_positive change_visibility_field_false_positive
test_add_from_impl_already_exists
]; ];

View file

@ -3,7 +3,7 @@ pub(crate) mod insert_use;
use std::iter; use std::iter;
use hir::{Adt, Semantics, Type}; use hir::{Adt, Crate, Semantics, Trait, Type};
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{ use ra_syntax::{
ast::{self, make, NameOwner}, ast::{self, make, NameOwner},
@ -149,3 +149,61 @@ impl TryEnum {
} }
} }
} }
/// Helps with finding well-know things inside the standard library. This is
/// somewhat similar to the known paths infra inside hir, but it different; We
/// want to make sure that IDE specific paths don't become interesting inside
/// the compiler itself as well.
pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate);
#[allow(non_snake_case)]
impl FamousDefs<'_, '_> {
#[cfg(test)]
pub(crate) const FIXTURE: &'static str = r#"
//- /libcore.rs crate:core
pub mod convert{
pub trait From<T> {
fn from(T) -> Self;
}
}
pub mod prelude { pub use crate::convert::From }
#[prelude_import]
pub use prelude::*;
"#;
pub(crate) fn core_convert_From(&self) -> Option<Trait> {
self.find_trait("core:convert:From")
}
fn find_trait(&self, path: &str) -> Option<Trait> {
let db = self.0.db;
let mut path = path.split(':');
let trait_ = path.next_back()?;
let std_crate = path.next()?;
let std_crate = self
.1
.dependencies(db)
.into_iter()
.find(|dep| &dep.name.to_string() == std_crate)?
.krate;
let mut module = std_crate.root_module(db)?;
for segment in path {
module = module.children(db).find_map(|child| {
let name = child.name(db)?;
if &name.to_string() == segment {
Some(child)
} else {
None
}
})?;
}
let def =
module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1;
match def {
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
_ => None,
}
}
}

View file

@ -22,8 +22,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
path_from_text(&format!("{}::{}", qual, segment)) path_from_text(&format!("{}::{}", qual, segment))
} }
fn path_from_text(text: &str) -> ast::Path {
pub fn path_from_text(text: &str) -> ast::Path {
ast_from_text(text) ast_from_text(text)
} }