mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Merge #4208
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:
commit
12aae7771d
4 changed files with 95 additions and 39 deletions
|
@ -1,11 +1,12 @@
|
|||
use ra_ide_db::RootDatabase;
|
||||
use ra_syntax::{
|
||||
ast::{self, AstNode, NameOwner},
|
||||
TextSize,
|
||||
};
|
||||
use stdx::format_to;
|
||||
|
||||
use crate::{Assist, AssistCtx, AssistId};
|
||||
use ra_ide_db::RootDatabase;
|
||||
use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
|
||||
use test_utils::tested_by;
|
||||
|
||||
// Assist add_from_impl_for_enum
|
||||
//
|
||||
|
@ -41,7 +42,8 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
|
|||
_ => 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;
|
||||
}
|
||||
|
||||
|
@ -70,41 +72,33 @@ impl From<{0}> for {1} {{
|
|||
)
|
||||
}
|
||||
|
||||
fn already_has_from_impl(
|
||||
fn existing_from_impl(
|
||||
sema: &'_ hir::Semantics<'_, RootDatabase>,
|
||||
variant: &ast::EnumVariant,
|
||||
) -> bool {
|
||||
let scope = sema.scope(&variant.syntax());
|
||||
) -> Option<()> {
|
||||
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_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 from_trait = FamousDefs(sema, krate).core_convert_From()?;
|
||||
|
||||
let e: hir::Enum = match sema.to_def(&variant.parent_enum()) {
|
||||
Some(e) => e,
|
||||
None => return false,
|
||||
};
|
||||
let e_ty = e.ty(sema.db);
|
||||
let enum_type = enum_.ty(sema.db);
|
||||
|
||||
let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) {
|
||||
Some(ev) => ev,
|
||||
None => return false,
|
||||
};
|
||||
let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db);
|
||||
let wrapped_type = variant.fields(sema.db).get(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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::helpers::{check_assist, check_assist_not_applicable};
|
||||
use test_utils::covers;
|
||||
|
||||
#[test]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
fn test_add_from_impl_already_exists() {
|
||||
check_assist_not_applicable(
|
||||
add_from_impl_for_enum,
|
||||
r#"enum A { <|>One(u32), }
|
||||
covers!(test_add_from_impl_already_exists);
|
||||
check_not_applicable(
|
||||
r#"
|
||||
enum A { <|>One(u32), }
|
||||
|
||||
impl From<u32> for A {
|
||||
fn from(v: u32) -> Self {
|
||||
A::One(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait From<T> {
|
||||
fn from(T) -> Self;
|
||||
}"#,
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,4 +8,5 @@ test_utils::marks![
|
|||
test_not_inline_mut_variable
|
||||
test_not_applicable_if_variable_unused
|
||||
change_visibility_field_false_positive
|
||||
test_add_from_impl_already_exists
|
||||
];
|
||||
|
|
|
@ -3,7 +3,7 @@ pub(crate) mod insert_use;
|
|||
|
||||
use std::iter;
|
||||
|
||||
use hir::{Adt, Semantics, Type};
|
||||
use hir::{Adt, Crate, Semantics, Trait, Type};
|
||||
use ra_ide_db::RootDatabase;
|
||||
use ra_syntax::{
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
path_from_text(&format!("{}::{}", qual, segment))
|
||||
}
|
||||
|
||||
pub fn path_from_text(text: &str) -> ast::Path {
|
||||
fn path_from_text(text: &str) -> ast::Path {
|
||||
ast_from_text(text)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue