Use Semantics::resolve_method_call_as_callable to find implementation

This commit is contained in:
1hakusai1 2025-01-20 21:17:48 +09:00
parent e8a63e43b1
commit 5384dd8947
6 changed files with 89 additions and 59 deletions

View File

@ -398,6 +398,9 @@ macro_rules! __known_path {
(core::fmt::Debug) => {};
(std::fmt::format) => {};
(core::ops::Try) => {};
(core::convert::From) => {};
(core::convert::TryFrom) => {};
(core::str::FromStr) => {};
($path:path) => {
compile_error!("Please register your known path in the path module")
};

View File

@ -1446,6 +1446,10 @@ impl<'db> SemanticsImpl<'db> {
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> {
self.analyze(range_pat.syntax())?.resolve_range_pat(self.db, range_pat)
}

View File

@ -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(
&self,
db: &dyn HirDatabase,
@ -1247,6 +1309,18 @@ impl SourceAnalyzer {
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> {
self.infer.as_ref()?.type_of_expr_or_pat(self.expr_id(db, expr)?)
}

View File

@ -50,14 +50,6 @@ impl FamousDefs<'_, '_> {
self.find_trait("core:convert:From")
}
pub fn core_convert_TryFrom(&self) -> Option<Trait> {
self.find_trait("core:convert:TryFrom")
}
pub fn core_str_FromStr(&self) -> Option<Trait> {
self.find_trait("core:str:FromStr")
}
pub fn core_convert_Into(&self) -> Option<Trait> {
self.find_trait("core:convert:Into")
}

View File

@ -5,11 +5,10 @@ use crate::{
navigation_target::{self, ToNav},
FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult,
};
use hir::{AsAssocItem, AssocItem, FileRange, Impl, InFile, MacroFileIdExt, ModuleDef, Semantics};
use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileLoader, SourceDatabase},
defs::{Definition, IdentClass},
famous_defs::FamousDefs,
helpers::pick_best_token,
RootDatabase, SymbolKind,
};
@ -82,8 +81,7 @@ pub(crate) fn goto_definition(
return Some(RangeInfo::new(original_token.text_range(), navs));
}
if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, file_id, &original_token)
{
if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, &original_token) {
return Some(RangeInfo::new(original_token.text_range(), navs));
}
@ -134,58 +132,13 @@ pub(crate) fn goto_definition(
// 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>,
file_id: FileId,
original_token: &SyntaxToken,
) -> 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()?)?;
let callable = sema.resolve_method_call_as_callable(&method_call)?;
let (_, receiver_type) = callable.receiver_param(db)?;
let return_type = callable.return_type();
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 method 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.first().is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty()))
})?;
let target_method = sema.resolve_known_blanket_dual_impls(&method_call)?;
let def = Definition::from(target_method);
Some(def_to_nav(db, def))
Some(def_to_nav(sema.db, def))
}
fn try_lookup_include_path(

View File

@ -174,6 +174,7 @@ define_symbols! {
const_param_ty,
Context,
Continue,
convert,
copy,
Copy,
core_panic,
@ -239,6 +240,8 @@ define_symbols! {
format_unsafe_arg,
format,
freeze,
From,
FromStr,
from_output,
from_residual,
from_usize,
@ -457,6 +460,7 @@ define_symbols! {
transmute_trait,
transparent,
Try,
TryFrom,
tuple_trait,
u128,
u16,