Skip to content

Commit 9792803

Browse files
committed
Add the ability to jump from into to from definitions
1 parent 0f900e2 commit 9792803

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ impl FamousDefs<'_, '_> {
5050
self.find_trait("core:convert:From")
5151
}
5252

53+
pub fn core_convert_TryFrom(&self) -> Option<Trait> {
54+
self.find_trait("core:convert:TryFrom")
55+
}
56+
57+
pub fn core_str_FromStr(&self) -> Option<Trait> {
58+
self.find_trait("core:str:FromStr")
59+
}
60+
5361
pub fn core_convert_Into(&self) -> Option<Trait> {
5462
self.find_trait("core:convert:Into")
5563
}

src/tools/rust-analyzer/crates/ide/src/goto_definition.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use crate::{
55
navigation_target::{self, ToNav},
66
FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult,
77
};
8-
use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics};
8+
use hir::{AsAssocItem, AssocItem, FileRange, Impl, InFile, MacroFileIdExt, ModuleDef, Semantics};
99
use ide_db::{
1010
base_db::{AnchoredPath, FileLoader, SourceDatabase},
1111
defs::{Definition, IdentClass},
12+
famous_defs::FamousDefs,
1213
helpers::pick_best_token,
1314
RootDatabase, SymbolKind,
1415
};
@@ -81,6 +82,10 @@ pub(crate) fn goto_definition(
8182
return Some(RangeInfo::new(original_token.text_range(), navs));
8283
}
8384

85+
if let Some(navs) = find_from_definition(file_id, &original_token, sema) {
86+
return Some(RangeInfo::new(original_token.text_range(), navs));
87+
}
88+
8489
let navs = sema
8590
.descend_into_macros_no_opaque(original_token.clone())
8691
.into_iter()
@@ -125,6 +130,62 @@ pub(crate) fn goto_definition(
125130
Some(RangeInfo::new(original_token.text_range(), navs))
126131
}
127132

133+
// If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr.
134+
fn find_from_definition(
135+
file_id: FileId,
136+
original_token: &SyntaxToken,
137+
sema: &Semantics<'_, RootDatabase>,
138+
) -> Option<Vec<NavigationTarget>> {
139+
let db = sema.db;
140+
let krate = sema.file_to_module_def(file_id)?.krate();
141+
142+
// e.g. if the method call is let b = a.into(),
143+
// - receiver_type is A (type of a)
144+
// - return_type is B (type of b)
145+
// We will find the definition of B::from(a: A).
146+
let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?;
147+
let receiver_type = sema.type_of_expr(&method_call.receiver()?)?.original();
148+
let return_type = sema.type_of_expr(&method_call.clone().into())?.original();
149+
150+
let (search_method, search_trait, return_type) = match method_call.name_ref()?.text().as_str() {
151+
"into" => ("from", FamousDefs(sema, krate).core_convert_From()?, return_type),
152+
// If the mthod is try_into() or parse(), return_type is Result<T, Error>.
153+
// Get T from type arguments of Result<T, Error>.
154+
"try_into" => (
155+
"try_from",
156+
FamousDefs(sema, krate).core_convert_TryFrom()?,
157+
return_type.type_arguments().next()?,
158+
),
159+
"parse" => (
160+
"from_str",
161+
FamousDefs(sema, krate).core_str_FromStr()?,
162+
return_type.type_arguments().next()?,
163+
),
164+
_ => return None,
165+
};
166+
167+
let from_impls = Impl::all_for_type(db, return_type)
168+
.into_iter()
169+
.filter(|impl_| impl_.trait_(db).is_some_and(|trait_| trait_ == search_trait));
170+
let from_methods = from_impls.flat_map(|impl_| impl_.items(db)).filter_map(|item| match item {
171+
AssocItem::Function(function) if function.name(db).as_str() == search_method => {
172+
Some(function)
173+
}
174+
_ => None,
175+
});
176+
let target_method = from_methods.into_iter().find(|method| {
177+
let args = method.assoc_fn_params(db);
178+
179+
// FIXME: This condition does not work for complicated cases such as
180+
// receiver_type: Vec<i64>
181+
// arg.ty(): T: IntoIterator<Item = i64>
182+
args.get(0).is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty()))
183+
})?;
184+
185+
let def = Definition::from(target_method);
186+
Some(def_to_nav(db, def))
187+
}
188+
128189
fn try_lookup_include_path(
129190
sema: &Semantics<'_, RootDatabase>,
130191
token: ast::String,
@@ -3022,4 +3083,27 @@ fn foo() {
30223083
"#,
30233084
);
30243085
}
3086+
#[test]
3087+
fn into_call_to_from_definition() {
3088+
check(
3089+
r#"
3090+
//- minicore: from
3091+
struct A;
3092+
3093+
struct B;
3094+
3095+
impl From<A> for B {
3096+
fn from(value: A) -> Self {
3097+
//^^^^
3098+
B
3099+
}
3100+
}
3101+
3102+
fn f() {
3103+
let a = A;
3104+
let b: B = a.into$0();
3105+
}
3106+
"#,
3107+
);
3108+
}
30253109
}

0 commit comments

Comments
 (0)