Skip to content

Commit b2f822b

Browse files
authored
Merge pull request #18934 from 1hakusai1/goto_definition_from_into
feat: Add the ability to jump from `into` to `from` definitions
2 parents 8a5aa80 + 913ec54 commit b2f822b

File tree

6 files changed

+278
-2
lines changed

6 files changed

+278
-2
lines changed

crates/hir-expand/src/mod_path.rs

+3
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ macro_rules! __known_path {
398398
(core::fmt::Debug) => {};
399399
(std::fmt::format) => {};
400400
(core::ops::Try) => {};
401+
(core::convert::From) => {};
402+
(core::convert::TryFrom) => {};
403+
(core::str::FromStr) => {};
401404
($path:path) => {
402405
compile_error!("Please register your known path in the path module")
403406
};

crates/hir/src/semantics.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,10 @@ impl<'db> SemanticsImpl<'db> {
14391439
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
14401440
}
14411441

1442+
pub fn resolve_known_blanket_dual_impls(&self, call: &ast::MethodCallExpr) -> Option<Function> {
1443+
self.analyze(call.syntax())?.resolve_known_blanket_dual_impls(self.db, call)
1444+
}
1445+
14421446
fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<StructId> {
14431447
self.analyze(range_pat.syntax())?.resolve_range_pat(self.db, range_pat)
14441448
}

crates/hir/src/source_analyzer.rs

+74
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,68 @@ impl SourceAnalyzer {
322322
}
323323
}
324324

325+
// If the method is into(), try_into(), parse(), resolve it to from, try_from, from_str.
326+
pub(crate) fn resolve_known_blanket_dual_impls(
327+
&self,
328+
db: &dyn HirDatabase,
329+
call: &ast::MethodCallExpr,
330+
) -> Option<Function> {
331+
// e.g. if the method call is let b = a.into(),
332+
// - receiver_type is A (type of a)
333+
// - return_type is B (type of b)
334+
// We will find the definition of B::from(a: A).
335+
let callable = self.resolve_method_call_as_callable(db, call)?;
336+
let (_, receiver_type) = callable.receiver_param(db)?;
337+
let return_type = callable.return_type();
338+
let (search_method, substs) = match call.name_ref()?.text().as_str() {
339+
"into" => {
340+
let trait_ =
341+
self.resolver.resolve_known_trait(db.upcast(), &path![core::convert::From])?;
342+
(
343+
self.trait_fn(db, trait_, "from")?,
344+
hir_ty::TyBuilder::subst_for_def(db, trait_, None)
345+
.push(return_type.ty)
346+
.push(receiver_type.ty)
347+
.build(),
348+
)
349+
}
350+
"try_into" => {
351+
let trait_ = self
352+
.resolver
353+
.resolve_known_trait(db.upcast(), &path![core::convert::TryFrom])?;
354+
(
355+
self.trait_fn(db, trait_, "try_from")?,
356+
hir_ty::TyBuilder::subst_for_def(db, trait_, None)
357+
// If the method is try_into() or parse(), return_type is Result<T, Error>.
358+
// Get T from type arguments of Result<T, Error>.
359+
.push(return_type.type_arguments().next()?.ty)
360+
.push(receiver_type.ty)
361+
.build(),
362+
)
363+
}
364+
"parse" => {
365+
let trait_ =
366+
self.resolver.resolve_known_trait(db.upcast(), &path![core::str::FromStr])?;
367+
(
368+
self.trait_fn(db, trait_, "from_str")?,
369+
hir_ty::TyBuilder::subst_for_def(db, trait_, None)
370+
.push(return_type.type_arguments().next()?.ty)
371+
.build(),
372+
)
373+
}
374+
_ => return None,
375+
};
376+
377+
let found_method = self.resolve_impl_method_or_trait_def(db, search_method, substs);
378+
// If found_method == search_method, the method in trait itself is resolved.
379+
// It means the blanket dual impl is not found.
380+
if found_method == search_method {
381+
None
382+
} else {
383+
Some(found_method.into())
384+
}
385+
}
386+
325387
pub(crate) fn resolve_expr_as_callable(
326388
&self,
327389
db: &dyn HirDatabase,
@@ -1247,6 +1309,18 @@ impl SourceAnalyzer {
12471309
Some((trait_id, fn_id))
12481310
}
12491311

1312+
fn trait_fn(
1313+
&self,
1314+
db: &dyn HirDatabase,
1315+
trait_id: TraitId,
1316+
method_name: &str,
1317+
) -> Option<FunctionId> {
1318+
db.trait_data(trait_id).items.iter().find_map(|(item_name, item)| match item {
1319+
AssocItemId::FunctionId(t) if item_name.as_str() == method_name => Some(*t),
1320+
_ => None,
1321+
})
1322+
}
1323+
12501324
fn ty_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<&Ty> {
12511325
self.infer.as_ref()?.type_of_expr_or_pat(self.expr_id(db, expr)?)
12521326
}

crates/ide/src/goto_definition.rs

+162
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ pub(crate) fn goto_definition(
8181
return Some(RangeInfo::new(original_token.text_range(), navs));
8282
}
8383

84+
if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, &original_token) {
85+
return Some(RangeInfo::new(original_token.text_range(), navs));
86+
}
87+
8488
let navs = sema
8589
.descend_into_macros_no_opaque(original_token.clone())
8690
.into_iter()
@@ -125,6 +129,18 @@ pub(crate) fn goto_definition(
125129
Some(RangeInfo::new(original_token.text_range(), navs))
126130
}
127131

132+
// If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr.
133+
fn find_definition_for_known_blanket_dual_impls(
134+
sema: &Semantics<'_, RootDatabase>,
135+
original_token: &SyntaxToken,
136+
) -> Option<Vec<NavigationTarget>> {
137+
let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?;
138+
let target_method = sema.resolve_known_blanket_dual_impls(&method_call)?;
139+
140+
let def = Definition::from(target_method);
141+
Some(def_to_nav(sema.db, def))
142+
}
143+
128144
fn try_lookup_include_path(
129145
sema: &Semantics<'_, RootDatabase>,
130146
token: ast::String,
@@ -3022,4 +3038,150 @@ fn foo() {
30223038
"#,
30233039
);
30243040
}
3041+
#[test]
3042+
fn into_call_to_from_definition() {
3043+
check(
3044+
r#"
3045+
//- minicore: from
3046+
struct A;
3047+
3048+
struct B;
3049+
3050+
impl From<A> for B {
3051+
fn from(value: A) -> Self {
3052+
//^^^^
3053+
B
3054+
}
3055+
}
3056+
3057+
fn f() {
3058+
let a = A;
3059+
let b: B = a.into$0();
3060+
}
3061+
"#,
3062+
);
3063+
}
3064+
3065+
#[test]
3066+
fn into_call_to_from_definition_with_trait_bounds() {
3067+
check(
3068+
r#"
3069+
//- minicore: from, iterator
3070+
struct A;
3071+
3072+
impl<T> From<T> for A
3073+
where
3074+
T: IntoIterator<Item = i64>,
3075+
{
3076+
fn from(value: T) -> Self {
3077+
//^^^^
3078+
A
3079+
}
3080+
}
3081+
3082+
fn f() {
3083+
let a: A = [1, 2, 3].into$0();
3084+
}
3085+
"#,
3086+
);
3087+
}
3088+
3089+
#[test]
3090+
fn goto_into_definition_if_exists() {
3091+
check(
3092+
r#"
3093+
//- minicore: from
3094+
struct A;
3095+
3096+
struct B;
3097+
3098+
impl Into<B> for A {
3099+
fn into(self) -> B {
3100+
//^^^^
3101+
B
3102+
}
3103+
}
3104+
3105+
fn f() {
3106+
let a = A;
3107+
let b: B = a.into$0();
3108+
}
3109+
"#,
3110+
);
3111+
}
3112+
3113+
#[test]
3114+
fn try_into_call_to_try_from_definition() {
3115+
check(
3116+
r#"
3117+
//- minicore: from
3118+
struct A;
3119+
3120+
struct B;
3121+
3122+
impl TryFrom<A> for B {
3123+
type Error = String;
3124+
3125+
fn try_from(value: A) -> Result<Self, Self::Error> {
3126+
//^^^^^^^^
3127+
Ok(B)
3128+
}
3129+
}
3130+
3131+
fn f() {
3132+
let a = A;
3133+
let b: Result<B, _> = a.try_into$0();
3134+
}
3135+
"#,
3136+
);
3137+
}
3138+
3139+
#[test]
3140+
fn goto_try_into_definition_if_exists() {
3141+
check(
3142+
r#"
3143+
//- minicore: from
3144+
struct A;
3145+
3146+
struct B;
3147+
3148+
impl TryInto<B> for A {
3149+
type Error = String;
3150+
3151+
fn try_into(self) -> Result<B, Self::Error> {
3152+
//^^^^^^^^
3153+
Ok(B)
3154+
}
3155+
}
3156+
3157+
fn f() {
3158+
let a = A;
3159+
let b: Result<B, _> = a.try_into$0();
3160+
}
3161+
"#,
3162+
);
3163+
}
3164+
3165+
#[test]
3166+
fn parse_call_to_from_str_definition() {
3167+
check(
3168+
r#"
3169+
//- minicore: from, str
3170+
struct A;
3171+
3172+
impl FromStr for A {
3173+
type Error = String;
3174+
3175+
fn from_str(value: &str) -> Result<Self, Self::Error> {
3176+
//^^^^^^^^
3177+
Ok(A)
3178+
}
3179+
}
3180+
3181+
fn f() {
3182+
let a: Result<A, _> = "aaaaaa".parse$0();
3183+
}
3184+
"#,
3185+
);
3186+
}
30253187
}

crates/intern/src/symbol/symbols.rs

+4
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ define_symbols! {
174174
const_param_ty,
175175
Context,
176176
Continue,
177+
convert,
177178
copy,
178179
Copy,
179180
core_panic,
@@ -239,6 +240,8 @@ define_symbols! {
239240
format_unsafe_arg,
240241
format,
241242
freeze,
243+
From,
244+
FromStr,
242245
from_output,
243246
from_residual,
244247
from_usize,
@@ -457,6 +460,7 @@ define_symbols! {
457460
transmute_trait,
458461
transparent,
459462
Try,
463+
TryFrom,
460464
tuple_trait,
461465
u128,
462466
u16,

crates/test-utils/src/minicore.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
//! error: fmt
3333
//! fmt: option, result, transmute, coerce_unsized, copy, clone, derive
3434
//! fn: tuple
35-
//! from: sized
35+
//! from: sized, result
3636
//! future: pin
3737
//! coroutine: pin
3838
//! dispatch_from_dyn: unsize, pin
@@ -332,6 +332,25 @@ pub mod convert {
332332
t
333333
}
334334
}
335+
336+
pub trait TryFrom<T>: Sized {
337+
type Error;
338+
fn try_from(value: T) -> Result<Self, Self::Error>;
339+
}
340+
pub trait TryInto<T>: Sized {
341+
type Error;
342+
fn try_into(self) -> Result<T, Self::Error>;
343+
}
344+
345+
impl<T, U> TryInto<U> for T
346+
where
347+
U: TryFrom<T>,
348+
{
349+
type Error = U::Error;
350+
fn try_into(self) -> Result<U, U::Error> {
351+
U::try_from(self)
352+
}
353+
}
335354
// endregion:from
336355

337356
// region:as_ref
@@ -1555,6 +1574,15 @@ pub mod str {
15551574
pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str {
15561575
""
15571576
}
1577+
pub trait FromStr: Sized {
1578+
type Err;
1579+
fn from_str(s: &str) -> Result<Self, Self::Err>;
1580+
}
1581+
impl str {
1582+
pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
1583+
FromStr::from_str(self)
1584+
}
1585+
}
15581586
}
15591587
// endregion:str
15601588

@@ -1814,7 +1842,7 @@ pub mod prelude {
18141842
cmp::{Eq, PartialEq}, // :eq
18151843
cmp::{Ord, PartialOrd}, // :ord
18161844
convert::AsRef, // :as_ref
1817-
convert::{From, Into}, // :from
1845+
convert::{From, Into, TryFrom, TryInto}, // :from
18181846
default::Default, // :default
18191847
iter::{IntoIterator, Iterator}, // :iterator
18201848
macros::builtin::{derive, derive_const}, // :derive
@@ -1829,6 +1857,7 @@ pub mod prelude {
18291857
option::Option::{self, None, Some}, // :option
18301858
panic, // :panic
18311859
result::Result::{self, Err, Ok}, // :result
1860+
str::FromStr, // :str
18321861
};
18331862
}
18341863

0 commit comments

Comments
 (0)