mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 14:03:35 +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::{
|
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;
|
|
||||||
}"#,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
];
|
];
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue