Skip to content

Commit 12aae77

Browse files
bors[bot]matklad
andauthored
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>
2 parents 69cf9fc + b4dd475 commit 12aae77

File tree

4 files changed

+95
-39
lines changed

4 files changed

+95
-39
lines changed

crates/ra_assists/src/handlers/add_from_impl_for_enum.rs

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
use ra_ide_db::RootDatabase;
12
use ra_syntax::{
23
ast::{self, AstNode, NameOwner},
34
TextSize,
45
};
56
use stdx::format_to;
67

7-
use crate::{Assist, AssistCtx, AssistId};
8-
use ra_ide_db::RootDatabase;
8+
use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
9+
use test_utils::tested_by;
910

1011
// Assist add_from_impl_for_enum
1112
//
@@ -41,7 +42,8 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
4142
_ => return None,
4243
};
4344

44-
if already_has_from_impl(ctx.sema, &variant) {
45+
if existing_from_impl(ctx.sema, &variant).is_some() {
46+
tested_by!(test_add_from_impl_already_exists);
4547
return None;
4648
}
4749

@@ -70,41 +72,33 @@ impl From<{0}> for {1} {{
7072
)
7173
}
7274

73-
fn already_has_from_impl(
75+
fn existing_from_impl(
7476
sema: &'_ hir::Semantics<'_, RootDatabase>,
7577
variant: &ast::EnumVariant,
76-
) -> bool {
77-
let scope = sema.scope(&variant.syntax());
78+
) -> Option<()> {
79+
let variant = sema.to_def(variant)?;
80+
let enum_ = variant.parent_enum(sema.db);
81+
let krate = enum_.module(sema.db).krate();
7882

79-
let from_path = ast::make::path_from_text("From");
80-
let from_hir_path = match hir::Path::from_ast(from_path) {
81-
Some(p) => p,
82-
None => return false,
83-
};
84-
let from_trait = match scope.resolve_hir_path(&from_hir_path) {
85-
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t,
86-
_ => return false,
87-
};
83+
let from_trait = FamousDefs(sema, krate).core_convert_From()?;
8884

89-
let e: hir::Enum = match sema.to_def(&variant.parent_enum()) {
90-
Some(e) => e,
91-
None => return false,
92-
};
93-
let e_ty = e.ty(sema.db);
85+
let enum_type = enum_.ty(sema.db);
9486

95-
let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) {
96-
Some(ev) => ev,
97-
None => return false,
98-
};
99-
let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db);
87+
let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
10088

101-
e_ty.impls_trait(sema.db, from_trait, &[var_ty])
89+
if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
90+
Some(())
91+
} else {
92+
None
93+
}
10294
}
10395

10496
#[cfg(test)]
10597
mod tests {
10698
use super::*;
99+
107100
use crate::helpers::{check_assist, check_assist_not_applicable};
101+
use test_utils::covers;
108102

109103
#[test]
110104
fn test_add_from_impl_for_enum() {
@@ -136,36 +130,40 @@ mod tests {
136130
);
137131
}
138132

133+
fn check_not_applicable(ra_fixture: &str) {
134+
let fixture =
135+
format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
136+
check_assist_not_applicable(add_from_impl_for_enum, &fixture)
137+
}
138+
139139
#[test]
140140
fn test_add_from_impl_no_element() {
141-
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }");
141+
check_not_applicable("enum A { <|>One }");
142142
}
143143

144144
#[test]
145145
fn test_add_from_impl_more_than_one_element_in_tuple() {
146-
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }");
146+
check_not_applicable("enum A { <|>One(u32, String) }");
147147
}
148148

149149
#[test]
150150
fn test_add_from_impl_struct_variant() {
151-
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }");
151+
check_not_applicable("enum A { <|>One { x: u32 } }");
152152
}
153153

154154
#[test]
155155
fn test_add_from_impl_already_exists() {
156-
check_assist_not_applicable(
157-
add_from_impl_for_enum,
158-
r#"enum A { <|>One(u32), }
156+
covers!(test_add_from_impl_already_exists);
157+
check_not_applicable(
158+
r#"
159+
enum A { <|>One(u32), }
159160
160161
impl From<u32> for A {
161162
fn from(v: u32) -> Self {
162163
A::One(v)
163164
}
164165
}
165-
166-
pub trait From<T> {
167-
fn from(T) -> Self;
168-
}"#,
166+
"#,
169167
);
170168
}
171169

crates/ra_assists/src/marks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ test_utils::marks![
88
test_not_inline_mut_variable
99
test_not_applicable_if_variable_unused
1010
change_visibility_field_false_positive
11+
test_add_from_impl_already_exists
1112
];

crates/ra_assists/src/utils.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub(crate) mod insert_use;
33

44
use std::iter;
55

6-
use hir::{Adt, Semantics, Type};
6+
use hir::{Adt, Crate, Semantics, Trait, Type};
77
use ra_ide_db::RootDatabase;
88
use ra_syntax::{
99
ast::{self, make, NameOwner},
@@ -149,3 +149,61 @@ impl TryEnum {
149149
}
150150
}
151151
}
152+
153+
/// Helps with finding well-know things inside the standard library. This is
154+
/// somewhat similar to the known paths infra inside hir, but it different; We
155+
/// want to make sure that IDE specific paths don't become interesting inside
156+
/// the compiler itself as well.
157+
pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate);
158+
159+
#[allow(non_snake_case)]
160+
impl FamousDefs<'_, '_> {
161+
#[cfg(test)]
162+
pub(crate) const FIXTURE: &'static str = r#"
163+
//- /libcore.rs crate:core
164+
pub mod convert{
165+
pub trait From<T> {
166+
fn from(T) -> Self;
167+
}
168+
}
169+
170+
pub mod prelude { pub use crate::convert::From }
171+
#[prelude_import]
172+
pub use prelude::*;
173+
"#;
174+
175+
pub(crate) fn core_convert_From(&self) -> Option<Trait> {
176+
self.find_trait("core:convert:From")
177+
}
178+
179+
fn find_trait(&self, path: &str) -> Option<Trait> {
180+
let db = self.0.db;
181+
let mut path = path.split(':');
182+
let trait_ = path.next_back()?;
183+
let std_crate = path.next()?;
184+
let std_crate = self
185+
.1
186+
.dependencies(db)
187+
.into_iter()
188+
.find(|dep| &dep.name.to_string() == std_crate)?
189+
.krate;
190+
191+
let mut module = std_crate.root_module(db)?;
192+
for segment in path {
193+
module = module.children(db).find_map(|child| {
194+
let name = child.name(db)?;
195+
if &name.to_string() == segment {
196+
Some(child)
197+
} else {
198+
None
199+
}
200+
})?;
201+
}
202+
let def =
203+
module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1;
204+
match def {
205+
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
206+
_ => None,
207+
}
208+
}
209+
}

crates/ra_syntax/src/ast/make.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
2222
pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
2323
path_from_text(&format!("{}::{}", qual, segment))
2424
}
25-
26-
pub fn path_from_text(text: &str) -> ast::Path {
25+
fn path_from_text(text: &str) -> ast::Path {
2726
ast_from_text(text)
2827
}
2928

0 commit comments

Comments
 (0)