From 03ea40369f258f3807f94ce814dd14536f5d14ab Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 20 Jan 2025 16:23:19 +0100 Subject: [PATCH 1/2] Move dual blanket impl logic from source analyzer to goto_def --- crates/hir-def/src/lib.rs | 1 + crates/hir-expand/src/name.rs | 12 +++++ crates/hir/src/lib.rs | 9 ++++ crates/hir/src/semantics.rs | 16 ++++++- crates/hir/src/source_analyzer.rs | 74 ----------------------------- crates/ide-db/src/famous_defs.rs | 12 +++++ crates/ide/src/goto_definition.rs | 65 ++++++++++++++----------- crates/intern/src/symbol/symbols.rs | 6 +++ crates/test-utils/src/minicore.rs | 10 ---- 9 files changed, 92 insertions(+), 113 deletions(-) diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 84c105a0a3..da9ffae8aa 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -910,6 +910,7 @@ pub enum AssocItemId { ConstId(ConstId), TypeAliasId(TypeAliasId), } + // FIXME: not every function, ... is actually an assoc item. maybe we should make // sure that you can only turn actual assoc items into AssocItemIds. This would // require not implementing From, and instead having some checked way of diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index cc53d2e34a..9cfa3b4b32 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -51,12 +51,24 @@ impl PartialEq for Name { } } +impl PartialEq<&Symbol> for Name { + fn eq(&self, &sym: &&Symbol) -> bool { + self.symbol == *sym + } +} + impl PartialEq for Symbol { fn eq(&self, name: &Name) -> bool { *self == name.symbol } } +impl PartialEq for &Symbol { + fn eq(&self, name: &Name) -> bool { + **self == name.symbol + } +} + /// Wrapper of `Name` to print the name without "r#" even when it is a raw identifier. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct UnescapedName<'a>(&'a Name); diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index db3121d3cd..220baeb152 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2756,6 +2756,15 @@ impl Trait { traits.iter().map(|tr| Trait::from(*tr)).collect() } + pub fn function(self, db: &dyn HirDatabase, name: impl PartialEq) -> Option { + db.trait_data(self.id).items.iter().find(|(n, _)| name == *n).and_then( + |&(_, it)| match it { + AssocItemId::FunctionId(id) => Some(Function { id }), + _ => None, + }, + ) + } + pub fn items(self, db: &dyn HirDatabase) -> Vec { db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect() } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 523bc6f10a..0b76f2e112 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1439,8 +1439,20 @@ 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 { - self.analyze(call.syntax())?.resolve_known_blanket_dual_impls(self.db, call) + /// Env is used to derive the trait environment + // FIXME: better api for the trait environment + pub fn resolve_impl_method( + &self, + env: Type, + trait_: Trait, + func: Function, + subst: impl IntoIterator, + ) -> Option { + let mut substs = hir_ty::TyBuilder::subst_for_def(self.db, TraitId::from(trait_), None); + for s in subst { + substs = substs.push(s.ty); + } + Some(self.db.lookup_impl_method(env.env, func.into(), substs.build()).0.into()) } fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option { diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 6b78d7a363..b699ccde41 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -322,68 +322,6 @@ 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 { - // 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. - // Get T from type arguments of Result. - .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, @@ -1309,18 +1247,6 @@ impl SourceAnalyzer { Some((trait_id, fn_id)) } - fn trait_fn( - &self, - db: &dyn HirDatabase, - trait_id: TraitId, - method_name: &str, - ) -> Option { - 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)?) } diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index 9e3506d6f5..1dc61e3f0a 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -46,6 +46,10 @@ impl FamousDefs<'_, '_> { self.find_trait("core:cmp:Ord") } + pub fn core_convert_FromStr(&self) -> Option { + self.find_trait("core:str:FromStr") + } + pub fn core_convert_From(&self) -> Option { self.find_trait("core:convert:From") } @@ -54,6 +58,14 @@ impl FamousDefs<'_, '_> { self.find_trait("core:convert:Into") } + pub fn core_convert_TryFrom(&self) -> Option { + self.find_trait("core:convert:TryFrom") + } + + pub fn core_convert_TryInto(&self) -> Option { + self.find_trait("core:convert:TryInto") + } + pub fn core_convert_Index(&self) -> Option { self.find_trait("core:ops:Index") } diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index f804cc3677..2c6c6e6e6c 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -5,10 +5,14 @@ use crate::{ navigation_target::{self, ToNav}, FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, }; -use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics}; +use hir::{ + sym, AsAssocItem, AssocItem, CallableKind, FileRange, HasCrate, InFile, MacroFileIdExt, + ModuleDef, Semantics, +}; use ide_db::{ base_db::{AnchoredPath, FileLoader, SourceDatabase}, defs::{Definition, IdentClass}, + famous_defs::FamousDefs, helpers::pick_best_token, RootDatabase, SymbolKind, }; @@ -129,15 +133,45 @@ 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. +// If the token is into(), try_into(), search the definition of From, TryFrom. fn find_definition_for_known_blanket_dual_impls( sema: &Semantics<'_, RootDatabase>, original_token: &SyntaxToken, ) -> Option> { let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?; - let target_method = sema.resolve_known_blanket_dual_impls(&method_call)?; + let callable = sema.resolve_method_call_as_callable(&method_call)?; + let CallableKind::Function(f) = callable.kind() else { return None }; + let t = f.as_assoc_item(sema.db)?.container_trait(sema.db)?; - let def = Definition::from(target_method); + let return_type = callable.return_type(); + let fd = FamousDefs(sema, return_type.krate(sema.db)); + let fn_name = f.name(sema.db); + let f = if fn_name == sym::into && fd.core_convert_Into() == Some(t) { + let dual = fd.core_convert_From()?; + let dual_f = dual.function(sema.db, &sym::from)?; + sema.resolve_impl_method( + return_type.clone(), + dual, + dual_f, + [return_type, callable.receiver_param(sema.db)?.1], + )? + } else if fn_name == sym::try_into && fd.core_convert_TryInto() == Some(t) { + let dual = fd.core_convert_TryFrom()?; + let dual_f = dual.function(sema.db, &sym::try_from)?; + sema.resolve_impl_method( + return_type.clone(), + dual, + dual_f, + // Extract the `T` from `Result` + [return_type.type_arguments().next()?, callable.receiver_param(sema.db)?.1], + )? + } else { + return None; + }; + // Assert that we got a trait impl function, if we are back in a trait definition we didn't + // succeed + let _t = f.as_assoc_item(sema.db)?.implemented_trait(sema.db)?; + let def = Definition::from(f); Some(def_to_nav(sema.db, def)) } @@ -3157,29 +3191,6 @@ impl TryInto for A { fn f() { let a = A; let b: Result = 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 { - //^^^^^^^^ - Ok(A) - } -} - -fn f() { - let a: Result = "aaaaaa".parse$0(); } "#, ); diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index b3b46421b5..e7c3668c0f 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -240,6 +240,7 @@ define_symbols! { format_unsafe_arg, format, freeze, + from, From, FromStr, from_output, @@ -273,6 +274,8 @@ define_symbols! { index_mut, index, Index, + into, + Into, into_future, into_iter, IntoFuture, @@ -361,6 +364,7 @@ define_symbols! { panic_nounwind, panic, Param, + parse, partial_ord, PartialEq, PartialOrd, @@ -459,8 +463,10 @@ define_symbols! { transmute_opts, transmute_trait, transparent, + try_into, Try, TryFrom, + try_from, tuple_trait, u128, u16, diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index fd06736a25..4a44eaf5d1 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1574,15 +1574,6 @@ pub mod str { pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str { "" } - pub trait FromStr: Sized { - type Err; - fn from_str(s: &str) -> Result; - } - impl str { - pub fn parse(&self) -> Result { - FromStr::from_str(self) - } - } } // endregion:str @@ -1857,7 +1848,6 @@ pub mod prelude { option::Option::{self, None, Some}, // :option panic, // :panic result::Result::{self, Err, Ok}, // :result - str::FromStr, // :str }; } From 0fdab1be36adad8b94407e3a0afeb052d2943768 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 20 Jan 2025 16:32:17 +0100 Subject: [PATCH 2/2] Bring back goto def redirect for parse -> FromStr --- crates/hir/src/lib.rs | 4 +++ crates/hir/src/semantics.rs | 2 +- crates/ide/src/goto_definition.rs | 46 +++++++++++++++++++++++++++-- crates/intern/src/symbol/symbols.rs | 1 + crates/test-utils/src/minicore.rs | 10 +++++++ 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 220baeb152..4938478bb1 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -4682,6 +4682,10 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Bool)) } + pub fn is_str(&self) -> bool { + matches!(self.ty.kind(Interner), TyKind::Str) + } + pub fn is_never(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::Never) } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 0b76f2e112..708db2c08d 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1441,7 +1441,7 @@ impl<'db> SemanticsImpl<'db> { /// Env is used to derive the trait environment // FIXME: better api for the trait environment - pub fn resolve_impl_method( + pub fn resolve_trait_impl_method( &self, env: Type, trait_: Trait, diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 2c6c6e6e6c..99d0d6af71 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -141,15 +141,35 @@ fn find_definition_for_known_blanket_dual_impls( let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?; let callable = sema.resolve_method_call_as_callable(&method_call)?; let CallableKind::Function(f) = callable.kind() else { return None }; - let t = f.as_assoc_item(sema.db)?.container_trait(sema.db)?; + let assoc = f.as_assoc_item(sema.db)?; let return_type = callable.return_type(); let fd = FamousDefs(sema, return_type.krate(sema.db)); + + let t = match assoc.container(sema.db) { + hir::AssocItemContainer::Trait(t) => t, + hir::AssocItemContainer::Impl(impl_) + if impl_.self_ty(sema.db).is_str() && f.name(sema.db) == sym::parse => + { + let t = fd.core_convert_FromStr()?; + let t_f = t.function(sema.db, &sym::from_str)?; + return sema + .resolve_trait_impl_method( + return_type.clone(), + t, + t_f, + [return_type.type_arguments().next()?], + ) + .map(|f| def_to_nav(sema.db, f.into())); + } + hir::AssocItemContainer::Impl(_) => return None, + }; + let fn_name = f.name(sema.db); let f = if fn_name == sym::into && fd.core_convert_Into() == Some(t) { let dual = fd.core_convert_From()?; let dual_f = dual.function(sema.db, &sym::from)?; - sema.resolve_impl_method( + sema.resolve_trait_impl_method( return_type.clone(), dual, dual_f, @@ -158,7 +178,7 @@ fn find_definition_for_known_blanket_dual_impls( } else if fn_name == sym::try_into && fd.core_convert_TryInto() == Some(t) { let dual = fd.core_convert_TryFrom()?; let dual_f = dual.function(sema.db, &sym::try_from)?; - sema.resolve_impl_method( + sema.resolve_trait_impl_method( return_type.clone(), dual, dual_f, @@ -3191,6 +3211,26 @@ impl TryInto for A { fn f() { let a = A; let b: Result = 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 { + //^^^^^^^^ + Ok(A) + } +} +fn f() { + let a: Result = "aaaaaa".parse$0(); } "#, ); diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index e7c3668c0f..59c0c9dca1 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -243,6 +243,7 @@ define_symbols! { from, From, FromStr, + from_str, from_output, from_residual, from_usize, diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 4a44eaf5d1..fd06736a25 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1574,6 +1574,15 @@ pub mod str { pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str { "" } + pub trait FromStr: Sized { + type Err; + fn from_str(s: &str) -> Result; + } + impl str { + pub fn parse(&self) -> Result { + FromStr::from_str(self) + } + } } // endregion:str @@ -1848,6 +1857,7 @@ pub mod prelude { option::Option::{self, None, Some}, // :option panic, // :panic result::Result::{self, Err, Ok}, // :result + str::FromStr, // :str }; }