Merge pull request #18934 from 1hakusai1/goto_definition_from_into
feat: Add the ability to jump from `into` to `from` definitions
This commit is contained in:
commit
fe034eddc2
6 changed files with 278 additions and 2 deletions
|
@ -398,6 +398,9 @@ macro_rules! __known_path {
|
||||||
(core::fmt::Debug) => {};
|
(core::fmt::Debug) => {};
|
||||||
(std::fmt::format) => {};
|
(std::fmt::format) => {};
|
||||||
(core::ops::Try) => {};
|
(core::ops::Try) => {};
|
||||||
|
(core::convert::From) => {};
|
||||||
|
(core::convert::TryFrom) => {};
|
||||||
|
(core::str::FromStr) => {};
|
||||||
($path:path) => {
|
($path:path) => {
|
||||||
compile_error!("Please register your known path in the path module")
|
compile_error!("Please register your known path in the path module")
|
||||||
};
|
};
|
||||||
|
|
|
@ -1439,6 +1439,10 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
|
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_known_blanket_dual_impls(&self, call: &ast::MethodCallExpr) -> Option<Function> {
|
||||||
|
self.analyze(call.syntax())?.resolve_known_blanket_dual_impls(self.db, call)
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<StructId> {
|
fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<StructId> {
|
||||||
self.analyze(range_pat.syntax())?.resolve_range_pat(self.db, range_pat)
|
self.analyze(range_pat.syntax())?.resolve_range_pat(self.db, range_pat)
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,6 +322,68 @@ impl SourceAnalyzer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the method is into(), try_into(), parse(), resolve it to from, try_from, from_str.
|
||||||
|
pub(crate) fn resolve_known_blanket_dual_impls(
|
||||||
|
&self,
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
call: &ast::MethodCallExpr,
|
||||||
|
) -> Option<Function> {
|
||||||
|
// 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 callable = self.resolve_method_call_as_callable(db, call)?;
|
||||||
|
let (_, receiver_type) = callable.receiver_param(db)?;
|
||||||
|
let return_type = callable.return_type();
|
||||||
|
let (search_method, substs) = match call.name_ref()?.text().as_str() {
|
||||||
|
"into" => {
|
||||||
|
let trait_ =
|
||||||
|
self.resolver.resolve_known_trait(db.upcast(), &path![core::convert::From])?;
|
||||||
|
(
|
||||||
|
self.trait_fn(db, trait_, "from")?,
|
||||||
|
hir_ty::TyBuilder::subst_for_def(db, trait_, None)
|
||||||
|
.push(return_type.ty)
|
||||||
|
.push(receiver_type.ty)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"try_into" => {
|
||||||
|
let trait_ = self
|
||||||
|
.resolver
|
||||||
|
.resolve_known_trait(db.upcast(), &path![core::convert::TryFrom])?;
|
||||||
|
(
|
||||||
|
self.trait_fn(db, trait_, "try_from")?,
|
||||||
|
hir_ty::TyBuilder::subst_for_def(db, trait_, None)
|
||||||
|
// If the method is try_into() or parse(), return_type is Result<T, Error>.
|
||||||
|
// Get T from type arguments of Result<T, Error>.
|
||||||
|
.push(return_type.type_arguments().next()?.ty)
|
||||||
|
.push(receiver_type.ty)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"parse" => {
|
||||||
|
let trait_ =
|
||||||
|
self.resolver.resolve_known_trait(db.upcast(), &path![core::str::FromStr])?;
|
||||||
|
(
|
||||||
|
self.trait_fn(db, trait_, "from_str")?,
|
||||||
|
hir_ty::TyBuilder::subst_for_def(db, trait_, None)
|
||||||
|
.push(return_type.type_arguments().next()?.ty)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let found_method = self.resolve_impl_method_or_trait_def(db, search_method, substs);
|
||||||
|
// If found_method == search_method, the method in trait itself is resolved.
|
||||||
|
// It means the blanket dual impl is not found.
|
||||||
|
if found_method == search_method {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(found_method.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_expr_as_callable(
|
pub(crate) fn resolve_expr_as_callable(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
|
@ -1247,6 +1309,18 @@ impl SourceAnalyzer {
|
||||||
Some((trait_id, fn_id))
|
Some((trait_id, fn_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn trait_fn(
|
||||||
|
&self,
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
trait_id: TraitId,
|
||||||
|
method_name: &str,
|
||||||
|
) -> Option<FunctionId> {
|
||||||
|
db.trait_data(trait_id).items.iter().find_map(|(item_name, item)| match item {
|
||||||
|
AssocItemId::FunctionId(t) if item_name.as_str() == method_name => Some(*t),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn ty_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<&Ty> {
|
fn ty_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<&Ty> {
|
||||||
self.infer.as_ref()?.type_of_expr_or_pat(self.expr_id(db, expr)?)
|
self.infer.as_ref()?.type_of_expr_or_pat(self.expr_id(db, expr)?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,10 @@ pub(crate) fn goto_definition(
|
||||||
return Some(RangeInfo::new(original_token.text_range(), navs));
|
return Some(RangeInfo::new(original_token.text_range(), navs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, &original_token) {
|
||||||
|
return Some(RangeInfo::new(original_token.text_range(), navs));
|
||||||
|
}
|
||||||
|
|
||||||
let navs = sema
|
let navs = sema
|
||||||
.descend_into_macros_no_opaque(original_token.clone())
|
.descend_into_macros_no_opaque(original_token.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -125,6 +129,18 @@ pub(crate) fn goto_definition(
|
||||||
Some(RangeInfo::new(original_token.text_range(), navs))
|
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_definition_for_known_blanket_dual_impls(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
original_token: &SyntaxToken,
|
||||||
|
) -> Option<Vec<NavigationTarget>> {
|
||||||
|
let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?;
|
||||||
|
let target_method = sema.resolve_known_blanket_dual_impls(&method_call)?;
|
||||||
|
|
||||||
|
let def = Definition::from(target_method);
|
||||||
|
Some(def_to_nav(sema.db, def))
|
||||||
|
}
|
||||||
|
|
||||||
fn try_lookup_include_path(
|
fn try_lookup_include_path(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
token: ast::String,
|
token: ast::String,
|
||||||
|
@ -3022,4 +3038,150 @@ 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();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_call_to_from_definition_with_trait_bounds() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: from, iterator
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
impl<T> From<T> for A
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = i64>,
|
||||||
|
{
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
//^^^^
|
||||||
|
A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f() {
|
||||||
|
let a: A = [1, 2, 3].into$0();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_into_definition_if_exists() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: from
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
struct B;
|
||||||
|
|
||||||
|
impl Into<B> for A {
|
||||||
|
fn into(self) -> B {
|
||||||
|
//^^^^
|
||||||
|
B
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f() {
|
||||||
|
let a = A;
|
||||||
|
let b: B = a.into$0();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_into_call_to_try_from_definition() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: from
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
struct B;
|
||||||
|
|
||||||
|
impl TryFrom<A> for B {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: A) -> Result<Self, Self::Error> {
|
||||||
|
//^^^^^^^^
|
||||||
|
Ok(B)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f() {
|
||||||
|
let a = A;
|
||||||
|
let b: Result<B, _> = a.try_into$0();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_try_into_definition_if_exists() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: from
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
struct B;
|
||||||
|
|
||||||
|
impl TryInto<B> for A {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<B, Self::Error> {
|
||||||
|
//^^^^^^^^
|
||||||
|
Ok(B)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f() {
|
||||||
|
let a = A;
|
||||||
|
let b: Result<B, _> = a.try_into$0();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_call_to_from_str_definition() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: from, str
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
impl FromStr for A {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
//^^^^^^^^
|
||||||
|
Ok(A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f() {
|
||||||
|
let a: Result<A, _> = "aaaaaa".parse$0();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,7 @@ define_symbols! {
|
||||||
const_param_ty,
|
const_param_ty,
|
||||||
Context,
|
Context,
|
||||||
Continue,
|
Continue,
|
||||||
|
convert,
|
||||||
copy,
|
copy,
|
||||||
Copy,
|
Copy,
|
||||||
core_panic,
|
core_panic,
|
||||||
|
@ -239,6 +240,8 @@ define_symbols! {
|
||||||
format_unsafe_arg,
|
format_unsafe_arg,
|
||||||
format,
|
format,
|
||||||
freeze,
|
freeze,
|
||||||
|
From,
|
||||||
|
FromStr,
|
||||||
from_output,
|
from_output,
|
||||||
from_residual,
|
from_residual,
|
||||||
from_usize,
|
from_usize,
|
||||||
|
@ -457,6 +460,7 @@ define_symbols! {
|
||||||
transmute_trait,
|
transmute_trait,
|
||||||
transparent,
|
transparent,
|
||||||
Try,
|
Try,
|
||||||
|
TryFrom,
|
||||||
tuple_trait,
|
tuple_trait,
|
||||||
u128,
|
u128,
|
||||||
u16,
|
u16,
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
//! error: fmt
|
//! error: fmt
|
||||||
//! fmt: option, result, transmute, coerce_unsized, copy, clone, derive
|
//! fmt: option, result, transmute, coerce_unsized, copy, clone, derive
|
||||||
//! fn: tuple
|
//! fn: tuple
|
||||||
//! from: sized
|
//! from: sized, result
|
||||||
//! future: pin
|
//! future: pin
|
||||||
//! coroutine: pin
|
//! coroutine: pin
|
||||||
//! dispatch_from_dyn: unsize, pin
|
//! dispatch_from_dyn: unsize, pin
|
||||||
|
@ -332,6 +332,25 @@ pub mod convert {
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TryFrom<T>: Sized {
|
||||||
|
type Error;
|
||||||
|
fn try_from(value: T) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
pub trait TryInto<T>: Sized {
|
||||||
|
type Error;
|
||||||
|
fn try_into(self) -> Result<T, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> TryInto<U> for T
|
||||||
|
where
|
||||||
|
U: TryFrom<T>,
|
||||||
|
{
|
||||||
|
type Error = U::Error;
|
||||||
|
fn try_into(self) -> Result<U, U::Error> {
|
||||||
|
U::try_from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
// endregion:from
|
// endregion:from
|
||||||
|
|
||||||
// region:as_ref
|
// region:as_ref
|
||||||
|
@ -1555,6 +1574,15 @@ pub mod str {
|
||||||
pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str {
|
pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
pub trait FromStr: Sized {
|
||||||
|
type Err;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err>;
|
||||||
|
}
|
||||||
|
impl str {
|
||||||
|
pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
|
||||||
|
FromStr::from_str(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// endregion:str
|
// endregion:str
|
||||||
|
|
||||||
|
@ -1814,7 +1842,7 @@ pub mod prelude {
|
||||||
cmp::{Eq, PartialEq}, // :eq
|
cmp::{Eq, PartialEq}, // :eq
|
||||||
cmp::{Ord, PartialOrd}, // :ord
|
cmp::{Ord, PartialOrd}, // :ord
|
||||||
convert::AsRef, // :as_ref
|
convert::AsRef, // :as_ref
|
||||||
convert::{From, Into}, // :from
|
convert::{From, Into, TryFrom, TryInto}, // :from
|
||||||
default::Default, // :default
|
default::Default, // :default
|
||||||
iter::{IntoIterator, Iterator}, // :iterator
|
iter::{IntoIterator, Iterator}, // :iterator
|
||||||
macros::builtin::{derive, derive_const}, // :derive
|
macros::builtin::{derive, derive_const}, // :derive
|
||||||
|
@ -1829,6 +1857,7 @@ pub mod prelude {
|
||||||
option::Option::{self, None, Some}, // :option
|
option::Option::{self, None, Some}, // :option
|
||||||
panic, // :panic
|
panic, // :panic
|
||||||
result::Result::{self, Err, Ok}, // :result
|
result::Result::{self, Err, Ok}, // :result
|
||||||
|
str::FromStr, // :str
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue