-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: Add the ability to jump from into
to from
definitions
#18934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
ea87eab
c14cdcf
a0be4b1
96ed889
dc075fd
65342f4
b6d6361
c6317a3
afde04b
e8a63e4
5384dd8
913ec54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,11 @@ use crate::{ | |
navigation_target::{self, ToNav}, | ||
FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, | ||
}; | ||
use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics}; | ||
use hir::{AsAssocItem, AssocItem, FileRange, Impl, InFile, MacroFileIdExt, ModuleDef, Semantics}; | ||
use ide_db::{ | ||
base_db::{AnchoredPath, FileLoader, SourceDatabase}, | ||
defs::{Definition, IdentClass}, | ||
famous_defs::FamousDefs, | ||
helpers::pick_best_token, | ||
RootDatabase, SymbolKind, | ||
}; | ||
|
@@ -81,6 +82,10 @@ pub(crate) fn goto_definition( | |
return Some(RangeInfo::new(original_token.text_range(), navs)); | ||
} | ||
|
||
if let Some(navs) = find_from_definition(file_id, &original_token, sema) { | ||
return Some(RangeInfo::new(original_token.text_range(), navs)); | ||
} | ||
|
||
let navs = sema | ||
.descend_into_macros_no_opaque(original_token.clone()) | ||
.into_iter() | ||
|
@@ -125,6 +130,62 @@ pub(crate) fn goto_definition( | |
Some(RangeInfo::new(original_token.text_range(), navs)) | ||
} | ||
|
||
// If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr. | ||
fn find_from_definition( | ||
file_id: FileId, | ||
original_token: &SyntaxToken, | ||
sema: &Semantics<'_, RootDatabase>, | ||
Veykril marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> Option<Vec<NavigationTarget>> { | ||
let db = sema.db; | ||
let krate = sema.file_to_module_def(file_id)?.krate(); | ||
|
||
// e.g. if the method call is let b = a.into(), | ||
// - receiver_type is A (type of a) | ||
// - return_type is B (type of b) | ||
// We will find the definition of B::from(a: A). | ||
let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fwiw we should also make this work with non method call syntax, but that can wait for a follow up PR |
||
let receiver_type = sema.type_of_expr(&method_call.receiver()?)?.original(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we actually need the adjusted type for the receiver. |
||
let return_type = sema.type_of_expr(&method_call.clone().into())?.original(); | ||
|
||
let (search_method, search_trait, return_type) = match method_call.name_ref()?.text().as_str() { | ||
"into" => ("from", FamousDefs(sema, krate).core_convert_From()?, return_type), | ||
// If the mthod is try_into() or parse(), return_type is Result<T, Error>. | ||
// Get T from type arguments of Result<T, Error>. | ||
"try_into" => ( | ||
"try_from", | ||
FamousDefs(sema, krate).core_convert_TryFrom()?, | ||
return_type.type_arguments().next()?, | ||
), | ||
"parse" => ( | ||
"from_str", | ||
FamousDefs(sema, krate).core_str_FromStr()?, | ||
return_type.type_arguments().next()?, | ||
), | ||
_ => return None, | ||
}; | ||
|
||
let from_impls = Impl::all_for_type(db, return_type) | ||
.into_iter() | ||
.filter(|impl_| impl_.trait_(db).is_some_and(|trait_| trait_ == search_trait)); | ||
let from_methods = from_impls.flat_map(|impl_| impl_.items(db)).filter_map(|item| match item { | ||
AssocItem::Function(function) if function.name(db).as_str() == search_method => { | ||
Some(function) | ||
} | ||
_ => None, | ||
}); | ||
let target_method = from_methods.into_iter().find(|method| { | ||
let args = method.assoc_fn_params(db); | ||
|
||
// FIXME: This condition does not work for complicated cases such as | ||
// receiver_type: Vec<i64> | ||
// arg.ty(): T: IntoIterator<Item = i64> | ||
args.get(0).is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty())) | ||
1hakusai1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
})?; | ||
|
||
let def = Definition::from(target_method); | ||
Some(def_to_nav(db, def)) | ||
} | ||
|
||
fn try_lookup_include_path( | ||
sema: &Semantics<'_, RootDatabase>, | ||
token: ast::String, | ||
|
@@ -3022,4 +3083,27 @@ fn foo() { | |
"#, | ||
); | ||
} | ||
#[test] | ||
fn into_call_to_from_definition() { | ||
check( | ||
r#" | ||
//- minicore: from | ||
struct A; | ||
|
||
struct B; | ||
|
||
impl From<A> for B { | ||
fn from(value: A) -> Self { | ||
//^^^^ | ||
B | ||
} | ||
} | ||
|
||
fn f() { | ||
let a = A; | ||
let b: B = a.into$0(); | ||
} | ||
"#, | ||
); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.