diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index afdced4215f9..a19996d80d33 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -482,7 +482,7 @@ pub trait Trait<'a, 'b, A, B, C: Default> { impl<'x, 'y, T, V, U: Default> Trait<'x, 'y, T, V, U> for () { $0fn foo(&self, _one: &'x T, _another: &'y V) -> (U, &'x i32) { let value: &'x i32 = &0; - (::default(), value) + (U::default(), value) } }"#, ); diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 21f2249a19c9..ed8f6f77a7d6 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -3,10 +3,11 @@ use std::collections::BTreeSet; use ast::make; use either::Either; use hir::{ - FileRange, PathResolution, Semantics, TypeInfo, + AsAssocItem, FileRange, HirDisplay, PathResolution, Semantics, TypeInfo, db::{ExpandDatabase, HirDatabase}, sym, }; +use ide_db::FxHashMap; use ide_db::{ EditionedFileId, RootDatabase, base_db::Crate, @@ -128,8 +129,17 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> let replaced = call_infos .into_iter() .map(|(call_info, mut_node)| { - let replacement = - inline(&ctx.sema, def_file, function, &func_body, ¶ms, &call_info); + let inferred_type_substs = + resolve_inferred_type_substs(&ctx.sema, &call_info.node, function); + let replacement = inline( + &ctx.sema, + def_file, + function, + &func_body, + ¶ms, + &call_info, + inferred_type_substs, + ); ted::replace(mut_node, replacement.syntax()); }) .count(); @@ -198,7 +208,7 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< name_ref.clone(), ctx.sema.file_to_module_def(ctx.vfs_file_id())?.krate(ctx.db()).into(), )?; - let (function, label) = match &call_info.node { + let (function, label, generic_subst) = match &call_info.node { ast::CallableExpr::Call(call) => { let path = match call.expr()? { ast::Expr::PathExpr(path) => path.path(), @@ -208,10 +218,12 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< PathResolution::Def(hir::ModuleDef::Function(f)) => f, _ => return None, }; - (function, format!("Inline `{path}`")) + (function, format!("Inline `{path}`"), None) } ast::CallableExpr::MethodCall(call) => { - (ctx.sema.resolve_method_call(call)?, format!("Inline `{name_ref}`")) + let (func_or_field, subst) = ctx.sema.resolve_method_call_fallback(call)?; + let function = func_or_field.left()?; + (function, format!("Inline `{name_ref}`"), subst) } }; @@ -233,9 +245,25 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< return None; } + let inferred_type_substs = + match (generic_subst.as_ref(), ctx.sema.scope(call_info.node.syntax())) { + (Some(subst), Some(scope)) => { + build_inferred_type_substs(ctx.db(), scope.module(), function, subst) + } + _ => FxHashMap::default(), + }; + let syntax = call_info.node.syntax().clone(); acc.add(AssistId::refactor_inline("inline_call"), label, syntax.text_range(), |builder| { - let replacement = inline(&ctx.sema, file_id, function, &fn_body, ¶ms, &call_info); + let replacement = inline( + &ctx.sema, + file_id, + function, + &fn_body, + ¶ms, + &call_info, + inferred_type_substs, + ); builder.replace_ast( match call_info.node { ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it), @@ -311,6 +339,67 @@ fn get_fn_params<'db>( Some(params) } +/// Build a map from type parameters to their concrete types based on inferred +/// generic substitutions from a method call resolution. +fn build_inferred_type_substs( + db: &dyn HirDatabase, + target_module: hir::Module, + function: hir::Function, + generic_subst: &hir::GenericSubstitution<'_>, +) -> FxHashMap { + let mut substs = FxHashMap::default(); + + // Collect the substitution types keyed by their parameter name. + let resolved_types: FxHashMap<_, _> = generic_subst.types(db).into_iter().collect(); + + // Gather type params from the function's container (trait/impl) and the function itself. + let container_def: Option = + function.as_assoc_item(db).map(|assoc| match assoc.container(db) { + hir::AssocItemContainer::Trait(t) => t.into(), + hir::AssocItemContainer::Impl(i) => i.into(), + }); + + let all_type_params = container_def + .into_iter() + .chain(std::iter::once(hir::GenericDef::from(function))) + .flat_map(|def| def.type_or_const_params(db)) + .filter_map(|param| match param.split(db) { + Either::Right(tp) => Some(tp), + Either::Left(_) => None, + }); + + for type_param in all_type_params { + let name = type_param.name(db); + if let Some(concrete_ty) = resolved_types.get(name.symbol()) + && let Ok(ty_str) = concrete_ty.display_source_code(db, target_module.into(), false) + { + substs.insert(type_param, make::ty(&ty_str)); + } + } + + substs +} + +/// Resolve inferred generic type substitutions for a call expression. +/// For method calls on trait/impl methods, this resolves the concrete types +/// that the type parameters are instantiated with at the call site. +fn resolve_inferred_type_substs( + sema: &Semantics<'_, RootDatabase>, + node: &ast::CallableExpr, + function: hir::Function, +) -> FxHashMap { + let ast::CallableExpr::MethodCall(call) = node else { + return FxHashMap::default(); + }; + let Some((_, Some(generic_subst))) = sema.resolve_method_call_fallback(call) else { + return FxHashMap::default(); + }; + let Some(target_module) = sema.scope(node.syntax()).map(|s| s.module()) else { + return FxHashMap::default(); + }; + build_inferred_type_substs(sema.db, target_module, function, &generic_subst) +} + fn inline( sema: &Semantics<'_, RootDatabase>, function_def_file_id: EditionedFileId, @@ -318,6 +407,7 @@ fn inline( fn_body: &ast::BlockExpr, params: &[(ast::Pat, Option, hir::Param<'_>)], CallInfo { node, arguments, generic_arg_list, krate }: &CallInfo, + inferred_type_substs: FxHashMap, ) -> ast::Expr { let file_id = sema.hir_file_for(fn_body.syntax()); let mut body = if let Some(macro_file) = file_id.macro_file() { @@ -538,14 +628,17 @@ fn inline( } } - if let Some(generic_arg_list) = generic_arg_list.clone() + if (generic_arg_list.is_some() || !inferred_type_substs.is_empty()) && let Some((target, source)) = &sema.scope(node.syntax()).zip(sema.scope(fn_body.syntax())) { - body.reindent_to(IndentLevel(0)); - if let Some(new_body) = ast::BlockExpr::cast( + let transform = if let Some(generic_arg_list) = generic_arg_list.clone() { PathTransform::function_call(target, source, function, generic_arg_list) - .apply(body.syntax()), - ) { + } else { + PathTransform::generic_transformation(target, source) + }; + let transform = transform.with_type_substs(inferred_type_substs); + body.reindent_to(IndentLevel(0)); + if let Some(new_body) = ast::BlockExpr::cast(transform.apply(body.syntax())) { body = new_body; } } @@ -1883,6 +1976,68 @@ macro_rules! bar { fn f() { bar!(foo$0()); } +"#, + ); + } + + #[test] + fn inline_call_into_substitutes_concrete_type() { + check_assist( + inline_call, + r#" +//- minicore: from +struct Foo(u32); +impl From for Foo { + fn from(value: u32) -> Self { + Foo(value) + } +} +fn main() { + let x: Foo = 0u32.$0into(); +} +"#, + r#" +struct Foo(u32); +impl From for Foo { + fn from(value: u32) -> Self { + Foo(value) + } +} +fn main() { + let x: Foo = Foo::from(0u32); +} +"#, + ); + } + + #[test] + fn inline_call_with_turbofish_and_inferred_trait_params() { + // Even with turbofish, trait-level type params should be resolved + // from inference since turbofish only covers function-level params. + check_assist( + inline_call, + r#" +//- minicore: from +struct Bar(u64); +impl From for Bar { + fn from(value: u64) -> Self { + Bar(value) + } +} +fn main() { + let y: Bar = 42u64.$0into(); +} +"#, + r#" +struct Bar(u64); +impl From for Bar { + fn from(value: u64) -> Self { + Bar(value) + } +} +fn main() { + let y: Bar = Bar::from(42u64); +} "#, ); } diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index 01a326a0dc63..3164261fe4da 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -58,6 +58,7 @@ pub struct PathTransform<'a> { substs: AstSubsts, target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>, + pre_resolved_type_substs: FxHashMap, } impl<'a> PathTransform<'a> { @@ -72,6 +73,7 @@ impl<'a> PathTransform<'a> { target_scope, generic_def: Some(trait_.into()), substs: get_syntactic_substs(impl_).unwrap_or_default(), + pre_resolved_type_substs: Default::default(), } } @@ -86,6 +88,7 @@ impl<'a> PathTransform<'a> { target_scope, generic_def: Some(function.into()), substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(), + pre_resolved_type_substs: Default::default(), } } @@ -100,6 +103,7 @@ impl<'a> PathTransform<'a> { target_scope, generic_def: Some(impl_.into()), substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(), + pre_resolved_type_substs: Default::default(), } } @@ -114,6 +118,7 @@ impl<'a> PathTransform<'a> { target_scope, generic_def: Some(adt.into()), substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(), + pre_resolved_type_substs: Default::default(), } } @@ -126,9 +131,20 @@ impl<'a> PathTransform<'a> { target_scope, generic_def: None, substs: AstSubsts::default(), + pre_resolved_type_substs: Default::default(), } } + /// Add pre-resolved type parameter substitutions that will be merged into + /// the substitution map built from `generic_def` and `substs`. + pub fn with_type_substs( + mut self, + substs: FxHashMap, + ) -> PathTransform<'a> { + self.pre_resolved_type_substs = substs; + self + } + #[must_use] pub fn apply(&self, syntax: &SyntaxNode) -> SyntaxNode { self.build_ctx().apply(syntax) @@ -228,6 +244,8 @@ impl<'a> PathTransform<'a> { } _ => (), // ignore mismatching params }); + // Merge in any pre-resolved type substitutions (e.g. from inferred generic args). + type_substs.extend(self.pre_resolved_type_substs.clone()); // No need to prettify lifetimes, there's nothing to prettify. let lifetime_substs: FxHashMap<_, _> = self .generic_def @@ -409,9 +427,23 @@ impl Ctx<'_> { } }); - let segment = make::path_segment_ty(subst.clone(), trait_ref); - let qualified = make::path_from_segments(std::iter::once(segment), false); - editor.replace(path.syntax(), qualified.clone_for_update().syntax()); + if trait_ref.is_none() + && let ast::Type::PathType(subst_path_ty) = subst + && let Some(subst_path) = subst_path_ty.path() + { + // When the substituted type is a simple path (e.g. `Foo`) + // and no trait qualification is needed, we can replace + // directly without wrapping in `<>`. + editor.replace( + path.syntax(), + subst_path.clone_subtree().clone_for_update().syntax(), + ); + } else { + let segment = make::path_segment_ty(subst.clone(), trait_ref); + let qualified = + make::path_from_segments(std::iter::once(segment), false); + editor.replace(path.syntax(), qualified.clone_for_update().syntax()); + } } else if let Some(path_ty) = ast::PathType::cast(parent) { let old = path_ty.syntax();