diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs index 1fba2e2e4d348..f4564c94bb594 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs @@ -12,9 +12,6 @@ use std::sync::OnceLock; use rustc_hash::FxHashMap; -/// Ignored attribute namespaces used by tools. -pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"]; - pub struct BuiltinAttribute { pub name: &'static str, pub template: AttributeTemplate, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index a528c4cc6972d..497b69889acc6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -84,6 +84,14 @@ use crate::{ LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, }; +const PREDEFINED_TOOLS: &[SmolStr] = &[ + SmolStr::new_static("clippy"), + SmolStr::new_static("rustfmt"), + SmolStr::new_static("diagnostic"), + SmolStr::new_static("miri"), + SmolStr::new_static("rust_analyzer"), +]; + /// Contains the results of (early) name resolution. /// /// A `DefMap` stores the module tree and the definitions that are in scope in every module after @@ -160,7 +168,7 @@ impl DefMapCrateData { fn_proc_macro_mapping: FxHashMap::default(), proc_macro_loading_error: None, registered_attrs: Vec::new(), - registered_tools: Vec::new(), + registered_tools: PREDEFINED_TOOLS.into(), unstable_features: FxHashSet::default(), rustc_coherence_is_core: false, no_core: false, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs index eb7f4c05ae217..3cb0666edf9c3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs @@ -10,7 +10,7 @@ use syntax::{ast, SmolStr}; use triomphe::Arc; use crate::{ - attr::builtin::{find_builtin_attr_idx, TOOL_MODULES}, + attr::builtin::find_builtin_attr_idx, db::DefDatabase, item_scope::BuiltinShadowMode, nameres::path_resolution::ResolveMode, @@ -82,8 +82,7 @@ impl DefMap { let name = name.to_smol_str(); let pred = |n: &_| *n == name; - let registered = self.data.registered_tools.iter().map(SmolStr::as_str); - let is_tool = TOOL_MODULES.iter().copied().chain(registered).any(pred); + let is_tool = self.data.registered_tools.iter().map(SmolStr::as_str).any(pred); // FIXME: tool modules can be shadowed by actual modules if is_tool { return true; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index b68fefc5150b1..36e3a458898da 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -797,19 +797,22 @@ impl<'a> InferenceTable<'a> { }) .build(); - let projection = { - let b = TyBuilder::subst_for_def(self.db, fn_once_trait, None); - if b.remaining() != 2 { - return None; - } - let fn_once_subst = b.push(ty.clone()).push(arg_ty).build(); + let b = TyBuilder::trait_ref(self.db, fn_once_trait); + if b.remaining() != 2 { + return None; + } + let mut trait_ref = b.push(ty.clone()).push(arg_ty).build(); - TyBuilder::assoc_type_projection(self.db, output_assoc_type, Some(fn_once_subst)) - .build() + let projection = { + TyBuilder::assoc_type_projection( + self.db, + output_assoc_type, + Some(trait_ref.substitution.clone()), + ) + .build() }; let trait_env = self.trait_env.env.clone(); - let mut trait_ref = projection.trait_ref(self.db); let obligation = InEnvironment { goal: trait_ref.clone().cast(Interner), environment: trait_env.clone(), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 1727cec9893b2..26a839f0e9fae 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -570,6 +570,10 @@ impl CallableSig { } } + pub fn abi(&self) -> FnAbi { + self.abi + } + pub fn params(&self) -> &[Ty] { &self.params_and_return[0..self.params_and_return.len() - 1] } @@ -892,20 +896,16 @@ where Canonical { value, binders: chalk_ir::CanonicalVarKinds::from_iter(Interner, kinds) } } -pub fn callable_sig_from_fnonce( - mut self_ty: &Ty, - env: Arc, +pub fn callable_sig_from_fn_trait( + self_ty: &Ty, + trait_env: Arc, db: &dyn HirDatabase, -) -> Option { - if let Some((ty, _, _)) = self_ty.as_reference() { - // This will happen when it implements fn or fn mut, since we add a autoborrow adjustment - self_ty = ty; - } - let krate = env.krate; +) -> Option<(FnTrait, CallableSig)> { + let krate = trait_env.krate; let fn_once_trait = FnTrait::FnOnce.get_id(db, krate)?; let output_assoc_type = db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?; - let mut table = InferenceTable::new(db, env); + let mut table = InferenceTable::new(db, trait_env.clone()); let b = TyBuilder::trait_ref(db, fn_once_trait); if b.remaining() != 2 { return None; @@ -915,23 +915,56 @@ pub fn callable_sig_from_fnonce( // - Self: FnOnce // - >::Output == ?ret_ty let args_ty = table.new_type_var(); - let trait_ref = b.push(self_ty.clone()).push(args_ty.clone()).build(); + let mut trait_ref = b.push(self_ty.clone()).push(args_ty.clone()).build(); let projection = TyBuilder::assoc_type_projection( db, output_assoc_type, Some(trait_ref.substitution.clone()), ) .build(); - table.register_obligation(trait_ref.cast(Interner)); - let ret_ty = table.normalize_projection_ty(projection); - - let ret_ty = table.resolve_completely(ret_ty); - let args_ty = table.resolve_completely(args_ty); - let params = - args_ty.as_tuple()?.iter(Interner).map(|it| it.assert_ty_ref(Interner)).cloned().collect(); - - Some(CallableSig::from_params_and_return(params, ret_ty, false, Safety::Safe, FnAbi::RustCall)) + let block = trait_env.block; + let trait_env = trait_env.env.clone(); + let obligation = + InEnvironment { goal: trait_ref.clone().cast(Interner), environment: trait_env.clone() }; + let canonical = table.canonicalize(obligation.clone()); + if db.trait_solve(krate, block, canonical.cast(Interner)).is_some() { + table.register_obligation(obligation.goal); + let return_ty = table.normalize_projection_ty(projection); + for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] { + let fn_x_trait = fn_x.get_id(db, krate)?; + trait_ref.trait_id = to_chalk_trait_id(fn_x_trait); + let obligation: chalk_ir::InEnvironment> = InEnvironment { + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), + }; + let canonical = table.canonicalize(obligation.clone()); + if db.trait_solve(krate, block, canonical.cast(Interner)).is_some() { + let ret_ty = table.resolve_completely(return_ty); + let args_ty = table.resolve_completely(args_ty); + let params = args_ty + .as_tuple()? + .iter(Interner) + .map(|it| it.assert_ty_ref(Interner)) + .cloned() + .collect(); + + return Some(( + fn_x, + CallableSig::from_params_and_return( + params, + ret_ty, + false, + Safety::Safe, + FnAbi::RustCall, + ), + )); + } + } + unreachable!("It should at least implement FnOnce at this point"); + } else { + None + } } struct PlaceholderCollector<'db> { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs index 930bc7df5e01e..02f2cd761599a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs @@ -1,5 +1,6 @@ //! Trait solving using Chalk. +use core::fmt; use std::env::var; use chalk_ir::{fold::TypeFoldable, DebruijnIndex, GoalData}; @@ -209,7 +210,25 @@ pub enum FnTrait { Fn, } +impl fmt::Display for FnTrait { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FnTrait::FnOnce => write!(f, "FnOnce"), + FnTrait::FnMut => write!(f, "FnMut"), + FnTrait::Fn => write!(f, "Fn"), + } + } +} + impl FnTrait { + pub const fn function_name(&self) -> &'static str { + match self { + FnTrait::FnOnce => "call_once", + FnTrait::FnMut => "call_mut", + FnTrait::Fn => "call", + } + } + const fn lang_item(self) -> LangItem { match self { FnTrait::FnOnce => LangItem::FnOnce, diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 85f33a10fcb2d..82d2bbb6cf314 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -35,7 +35,7 @@ pub mod term_search; mod display; -use std::{iter, mem::discriminant, ops::ControlFlow}; +use std::{mem::discriminant, ops::ControlFlow}; use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateId, CrateOrigin, FileId}; @@ -52,7 +52,6 @@ use hir_def::{ path::ImportAlias, per_ns::PerNs, resolver::{HasResolver, Resolver}, - src::HasSource as _, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, MacroExpander, @@ -141,7 +140,7 @@ pub use { display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, layout::LayoutError, mir::{MirEvalError, MirLowerError}, - PointerCast, Safety, + FnAbi, PointerCast, Safety, }, // FIXME: Properly encapsulate mir hir_ty::{mir, Interner as ChalkTyInterner}, @@ -1965,7 +1964,7 @@ impl Function { .enumerate() .map(|(idx, ty)| { let ty = Type { env: environment.clone(), ty: ty.clone() }; - Param { func: self, ty, idx } + Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx } }) .collect() } @@ -1991,7 +1990,7 @@ impl Function { .skip(skip) .map(|(idx, ty)| { let ty = Type { env: environment.clone(), ty: ty.clone() }; - Param { func: self, ty, idx } + Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx } }) .collect() } @@ -2037,7 +2036,7 @@ impl Function { .skip(skip) .map(|(idx, ty)| { let ty = Type { env: environment.clone(), ty: ty.clone() }; - Param { func: self, ty, idx } + Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx } }) .collect() } @@ -2167,17 +2166,24 @@ impl From for Access { #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct Param { - func: Function, + func: Callee, /// The index in parameter list, including self parameter. idx: usize, ty: Type, } impl Param { - pub fn parent_fn(&self) -> Function { - self.func + pub fn parent_fn(&self) -> Option { + match self.func { + Callee::Def(CallableDefId::FunctionId(f)) => Some(f.into()), + _ => None, + } } + // pub fn parent_closure(&self) -> Option { + // self.func.as_ref().right().cloned() + // } + pub fn index(&self) -> usize { self.idx } @@ -2191,7 +2197,11 @@ impl Param { } pub fn as_local(&self, db: &dyn HirDatabase) -> Option { - let parent = DefWithBodyId::FunctionId(self.func.into()); + let parent = match self.func { + Callee::Def(CallableDefId::FunctionId(it)) => DefWithBodyId::FunctionId(it), + Callee::Closure(closure, _) => db.lookup_intern_closure(closure.into()).0, + _ => return None, + }; let body = db.body(parent); if let Some(self_param) = body.self_param.filter(|_| self.idx == 0) { Some(Local { parent, binding_id: self_param }) @@ -2205,18 +2215,45 @@ impl Param { } pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option { - self.source(db).and_then(|p| p.value.pat()) + self.source(db).and_then(|p| p.value.right()?.pat()) } - pub fn source(&self, db: &dyn HirDatabase) -> Option> { - let InFile { file_id, value } = self.func.source(db)?; - let params = value.param_list()?; - if params.self_param().is_some() { - params.params().nth(self.idx.checked_sub(params.self_param().is_some() as usize)?) - } else { - params.params().nth(self.idx) + pub fn source( + &self, + db: &dyn HirDatabase, + ) -> Option>> { + match self.func { + Callee::Def(CallableDefId::FunctionId(func)) => { + let InFile { file_id, value } = Function { id: func }.source(db)?; + let params = value.param_list()?; + if let Some(self_param) = params.self_param() { + if let Some(idx) = self.idx.checked_sub(1) { + params.params().nth(idx).map(Either::Right) + } else { + Some(Either::Left(self_param)) + } + } else { + params.params().nth(self.idx).map(Either::Right) + } + .map(|value| InFile { file_id, value }) + } + Callee::Closure(closure, _) => { + let InternedClosure(owner, expr_id) = db.lookup_intern_closure(closure.into()); + let (_, source_map) = db.body_with_source_map(owner); + let ast @ InFile { file_id, value } = source_map.expr_syntax(expr_id).ok()?; + let root = db.parse_or_expand(file_id); + match value.to_node(&root) { + ast::Expr::ClosureExpr(it) => it + .param_list()? + .params() + .nth(self.idx) + .map(Either::Right) + .map(|value| InFile { file_id: ast.file_id, value }), + _ => None, + } + } + _ => None, } - .map(|value| InFile { file_id, value }) } } @@ -3372,34 +3409,21 @@ impl BuiltinAttr { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ToolModule { - krate: Option, + krate: CrateId, idx: u32, } impl ToolModule { - // FIXME: consider crates\hir_def\src\nameres\attr_resolution.rs? pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option { - if let builtin @ Some(_) = Self::builtin(name) { - return builtin; - } + let krate = krate.id; let idx = - db.crate_def_map(krate.id).registered_tools().iter().position(|it| it == name)? as u32; - Some(ToolModule { krate: Some(krate.id), idx }) - } - - fn builtin(name: &str) -> Option { - hir_def::attr::builtin::TOOL_MODULES - .iter() - .position(|&tool| tool == name) - .map(|idx| ToolModule { krate: None, idx: idx as u32 }) + db.crate_def_map(krate).registered_tools().iter().position(|it| it == name)? as u32; + Some(ToolModule { krate, idx }) } pub fn name(&self, db: &dyn HirDatabase) -> SmolStr { // FIXME: Return a `Name` here - match self.krate { - Some(krate) => db.crate_def_map(krate).registered_tools()[self.idx as usize].clone(), - None => SmolStr::new(hir_def::attr::builtin::TOOL_MODULES[self.idx as usize]), - } + db.crate_def_map(self.krate).registered_tools()[self.idx as usize].clone() } } @@ -4292,27 +4316,37 @@ impl Type { } pub fn as_callable(&self, db: &dyn HirDatabase) -> Option { - let mut the_ty = &self.ty; let callee = match self.ty.kind(Interner) { - TyKind::Ref(_, _, ty) if ty.as_closure().is_some() => { - the_ty = ty; - Callee::Closure(ty.as_closure().unwrap()) - } - TyKind::Closure(id, _) => Callee::Closure(*id), + TyKind::Closure(id, subst) => Callee::Closure(*id, subst.clone()), TyKind::Function(_) => Callee::FnPtr, TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?), - _ => { - let sig = hir_ty::callable_sig_from_fnonce(&self.ty, self.env.clone(), db)?; + kind => { + // This will happen when it implements fn or fn mut, since we add an autoborrow adjustment + let (ty, kind) = if let TyKind::Ref(_, _, ty) = kind { + (ty, ty.kind(Interner)) + } else { + (&self.ty, kind) + }; + if let TyKind::Closure(closure, subst) = kind { + let sig = ty.callable_sig(db)?; + return Some(Callable { + ty: self.clone(), + sig, + callee: Callee::Closure(*closure, subst.clone()), + is_bound_method: false, + }); + } + let (fn_trait, sig) = hir_ty::callable_sig_from_fn_trait(ty, self.env.clone(), db)?; return Some(Callable { ty: self.clone(), sig, - callee: Callee::Other, + callee: Callee::FnImpl(fn_trait), is_bound_method: false, }); } }; - let sig = the_ty.callable_sig(db)?; + let sig = self.ty.callable_sig(db)?; Some(Callable { ty: self.clone(), sig, callee, is_bound_method: false }) } @@ -4929,37 +4963,39 @@ pub struct Callable { sig: CallableSig, callee: Callee, /// Whether this is a method that was called with method call syntax. - pub(crate) is_bound_method: bool, + is_bound_method: bool, } -#[derive(Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] enum Callee { Def(CallableDefId), - Closure(ClosureId), + Closure(ClosureId, Substitution), FnPtr, - Other, + FnImpl(FnTrait), } pub enum CallableKind { Function(Function), TupleStruct(Struct), TupleEnumVariant(Variant), - Closure, + Closure(Closure), FnPtr, - /// Some other type that implements `FnOnce`. - Other, + FnImpl(FnTrait), } impl Callable { pub fn kind(&self) -> CallableKind { - use Callee::*; match self.callee { - Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()), - Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()), - Def(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()), - Closure(_) => CallableKind::Closure, - FnPtr => CallableKind::FnPtr, - Other => CallableKind::Other, + Callee::Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()), + Callee::Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()), + Callee::Def(CallableDefId::EnumVariantId(it)) => { + CallableKind::TupleEnumVariant(it.into()) + } + Callee::Closure(id, ref subst) => { + CallableKind::Closure(Closure { id, subst: subst.clone() }) + } + Callee::FnPtr => CallableKind::FnPtr, + Callee::FnImpl(fn_) => CallableKind::FnImpl(fn_), } } pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(SelfParam, Type)> { @@ -4973,43 +5009,15 @@ impl Callable { pub fn n_params(&self) -> usize { self.sig.params().len() - if self.is_bound_method { 1 } else { 0 } } - pub fn params( - &self, - db: &dyn HirDatabase, - ) -> Vec<(Option>, Type)> { - let types = self - .sig + pub fn params(&self) -> Vec { + self.sig .params() .iter() + .enumerate() .skip(if self.is_bound_method { 1 } else { 0 }) - .map(|ty| self.ty.derived(ty.clone())); - let map_param = |it: ast::Param| it.pat().map(Either::Right); - let patterns = match self.callee { - Callee::Def(CallableDefId::FunctionId(func)) => { - let src = func.lookup(db.upcast()).source(db.upcast()); - src.value.param_list().map(|param_list| { - param_list - .self_param() - .map(|it| Some(Either::Left(it))) - .filter(|_| !self.is_bound_method) - .into_iter() - .chain(param_list.params().map(map_param)) - }) - } - Callee::Closure(closure_id) => match closure_source(db, closure_id) { - Some(src) => src.param_list().map(|param_list| { - param_list - .self_param() - .map(|it| Some(Either::Left(it))) - .filter(|_| !self.is_bound_method) - .into_iter() - .chain(param_list.params().map(map_param)) - }), - None => None, - }, - _ => None, - }; - patterns.into_iter().flatten().chain(iter::repeat(None)).zip(types).collect() + .map(|(idx, ty)| (idx, self.ty.derived(ty.clone()))) + .map(|(idx, ty)| Param { func: self.callee.clone(), idx, ty }) + .collect() } pub fn return_type(&self) -> Type { self.ty.derived(self.sig.ret().clone()) @@ -5017,17 +5025,9 @@ impl Callable { pub fn sig(&self) -> &CallableSig { &self.sig } -} -fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option { - let InternedClosure(owner, expr_id) = db.lookup_intern_closure(closure.into()); - let (_, source_map) = db.body_with_source_map(owner); - let ast = source_map.expr_syntax(expr_id).ok()?; - let root = ast.file_syntax(db.upcast()); - let expr = ast.value.to_node(&root); - match expr { - ast::Expr::ClosureExpr(it) => Some(it), - _ => None, + pub fn ty(&self) -> &Type { + &self.ty } } diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 057b03baef07a..d22958406420e 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -307,7 +307,8 @@ impl SourceAnalyzer { db: &dyn HirDatabase, call: &ast::Expr, ) -> Option { - self.type_of_expr(db, &call.clone())?.0.as_callable(db) + let (orig, adjusted) = self.type_of_expr(db, &call.clone())?; + adjusted.unwrap_or(orig).as_callable(db) } pub(crate) fn resolve_field( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs index 7f3b0d758839f..37ea5123a71e6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs @@ -114,10 +114,10 @@ pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<' let callable = ctx.sema.resolve_method_call_as_callable(&call)?; let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?; let n_params = callable.n_params() + 1; - let params = callable.params(ctx.sema.db); + let params = callable.params(); // FIXME: Check that the arg is of the form `() -> T` - if !params.first()?.1.impls_fnonce(ctx.sema.db) { + if !params.first()?.ty().impls_fnonce(ctx.sema.db) { return None; } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs index 1859825b3d65e..23a06404f3010 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs @@ -253,11 +253,8 @@ fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option pat, - _ => return None, - }; + let param = func.params().into_iter().nth(idx)?; + let pat = param.source(sema.db)?.value.right()?.pat()?; let name = var_name_from_pat(&pat)?; normalize(&name.to_string()) } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs index 5780b5a5bb93c..98d2e81754611 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs @@ -1,7 +1,7 @@ //! This module provides functionality for querying callable information about a token. use either::Either; -use hir::{Semantics, Type}; +use hir::{InFile, Semantics, Type}; use parser::T; use syntax::{ ast::{self, HasArgList, HasName}, @@ -13,7 +13,7 @@ use crate::RootDatabase; #[derive(Debug)] pub struct ActiveParameter { pub ty: Type, - pub pat: Option>, + pub src: Option>>, } impl ActiveParameter { @@ -22,18 +22,18 @@ impl ActiveParameter { let (signature, active_parameter) = callable_for_token(sema, token)?; let idx = active_parameter?; - let mut params = signature.params(sema.db); + let mut params = signature.params(); if idx >= params.len() { cov_mark::hit!(too_many_arguments); return None; } - let (pat, ty) = params.swap_remove(idx); - Some(ActiveParameter { ty, pat }) + let param = params.swap_remove(idx); + Some(ActiveParameter { ty: param.ty().clone(), src: param.source(sema.db) }) } pub fn ident(&self) -> Option { - self.pat.as_ref().and_then(|param| match param { - Either::Right(ast::Pat::IdentPat(ident)) => ident.name(), + self.src.as_ref().and_then(|param| match param.value.as_ref().right()?.pat()? { + ast::Pat::IdentPat(ident) => ident.name(), _ => None, }) } @@ -60,10 +60,7 @@ pub fn callable_for_node( token: &SyntaxToken, ) -> Option<(hir::Callable, Option)> { let callable = match calling_node { - ast::CallableExpr::Call(call) => { - let expr = call.expr()?; - sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db) - } + ast::CallableExpr::Call(call) => sema.resolve_expr_as_callable(&call.expr()?), ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call), }?; let active_param = calling_node.arg_list().map(|arg_list| { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs index 8302b015ddafd..92478ef480d6a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs @@ -41,6 +41,7 @@ pub enum FormatSpecifier { Escape, } +// FIXME: Remove this, we can use rustc_format_parse instead pub fn lex_format_specifiers( string: &ast::String, mut callback: &mut dyn FnMut(TextRange, FormatSpecifier), diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs index 458b852e2a1e7..654a1cd316475 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -109,12 +109,12 @@ pub(crate) fn outgoing_calls( let expr = call.expr()?; let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?; match callable.kind() { - hir::CallableKind::Function(it) => { - let range = expr.syntax().text_range(); - it.try_to_nav(db).zip(Some(range)) - } + hir::CallableKind::Function(it) => it.try_to_nav(db), + hir::CallableKind::TupleEnumVariant(it) => it.try_to_nav(db), + hir::CallableKind::TupleStruct(it) => it.try_to_nav(db), _ => None, } + .zip(Some(expr.syntax().text_range())) } ast::CallableExpr::MethodCall(expr) => { let range = expr.name_ref()?.syntax().text_range(); diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs index 96e845b2f3235..fb50c49a3ae88 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs @@ -24,34 +24,29 @@ pub(super) fn hints( let (callable, arg_list) = get_callable(sema, &expr)?; let hints = callable - .params(sema.db) + .params() .into_iter() .zip(arg_list.args()) - .filter_map(|((param, _ty), arg)| { + .filter_map(|(p, arg)| { // Only annotate hints for expressions that exist in the original file let range = sema.original_range_opt(arg.syntax())?; - let (param_name, name_syntax) = match param.as_ref()? { + let source = p.source(sema.db)?; + let (param_name, name_syntax) = match source.value.as_ref() { Either::Left(pat) => (pat.name()?, pat.name()), - Either::Right(pat) => match pat { + Either::Right(param) => match param.pat()? { ast::Pat::IdentPat(it) => (it.name()?, it.name()), _ => return None, }, }; + // make sure the file is cached so we can map out of macros + sema.parse_or_expand(source.file_id); Some((name_syntax, param_name, arg, range)) }) .filter(|(_, param_name, arg, _)| { !should_hide_param_name_hint(sema, &callable, ¶m_name.text(), arg) }) .map(|(param, param_name, _, FileRange { range, .. })| { - let mut linked_location = None; - if let Some(name) = param { - if let hir::CallableKind::Function(f) = callable.kind() { - // assert the file is cached so we can map out of macros - if sema.source(f).is_some() { - linked_location = sema.original_range_opt(name.syntax()); - } - } - } + let linked_location = param.and_then(|name| sema.original_range_opt(name.syntax())); let colon = if config.render_colons { ":" } else { "" }; let label = diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index b2eb5a5fff1e3..378a38892c7df 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -201,7 +201,21 @@ fn signature_help_for_call( variant.name(db).display(db) ); } - hir::CallableKind::Closure | hir::CallableKind::FnPtr | hir::CallableKind::Other => (), + hir::CallableKind::Closure(closure) => { + let fn_trait = closure.fn_trait(db); + format_to!(res.signature, "impl {fn_trait}") + } + hir::CallableKind::FnPtr => format_to!(res.signature, "fn"), + hir::CallableKind::FnImpl(fn_trait) => match callable.ty().as_adt() { + // FIXME: Render docs of the concrete trait impl function + Some(adt) => format_to!( + res.signature, + "<{} as {fn_trait}>::{}", + adt.name(db).display(db), + fn_trait.function_name() + ), + None => format_to!(res.signature, "impl {fn_trait}"), + }, } res.signature.push('('); @@ -210,12 +224,15 @@ fn signature_help_for_call( format_to!(res.signature, "{}", self_param.display(db)) } let mut buf = String::new(); - for (idx, (pat, ty)) in callable.params(db).into_iter().enumerate() { + for (idx, p) in callable.params().into_iter().enumerate() { buf.clear(); - if let Some(pat) = pat { - match pat { - Either::Left(_self) => format_to!(buf, "self: "), - Either::Right(pat) => format_to!(buf, "{}: ", pat), + if let Some(param) = p.source(sema.db) { + match param.value { + Either::Right(param) => match param.pat() { + Some(pat) => format_to!(buf, "{}: ", pat), + None => format_to!(buf, "?: "), + }, + Either::Left(_) => format_to!(buf, "self: "), } } // APITs (argument position `impl Trait`s) are inferred as {unknown} as the user is @@ -223,9 +240,9 @@ fn signature_help_for_call( // In that case, fall back to render definitions of the respective parameters. // This is overly conservative: we do not substitute known type vars // (see FIXME in tests::impl_trait) and falling back on any unknowns. - match (ty.contains_unknown(), fn_params.as_deref()) { + match (p.ty().contains_unknown(), fn_params.as_deref()) { (true, Some(fn_params)) => format_to!(buf, "{}", fn_params[idx].ty().display(db)), - _ => format_to!(buf, "{}", ty.display(db)), + _ => format_to!(buf, "{}", p.ty().display(db)), } res.push_call_param(&buf); } @@ -242,9 +259,9 @@ fn signature_help_for_call( render(func.ret_type(db)) } hir::CallableKind::Function(_) - | hir::CallableKind::Closure + | hir::CallableKind::Closure(_) | hir::CallableKind::FnPtr - | hir::CallableKind::Other => render(callable.return_type()), + | hir::CallableKind::FnImpl(_) => render(callable.return_type()), hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {} } Some(res) @@ -1345,15 +1362,43 @@ fn test() { S.foo($0); } r#" struct S; fn foo(s: S) -> i32 { 92 } +fn main() { + let _move = S; + (|s| {{_move}; foo(s)})($0) +} + "#, + expect![[r#" + impl FnOnce(s: S) -> i32 + ^^^^ + "#]], + ); + check( + r#" +struct S; +fn foo(s: S) -> i32 { 92 } fn main() { (|s| foo(s))($0) } "#, expect![[r#" - (s: S) -> i32 - ^^^^ + impl Fn(s: S) -> i32 + ^^^^ "#]], - ) + ); + check( + r#" +struct S; +fn foo(s: S) -> i32 { 92 } +fn main() { + let mut mutate = 0; + (|s| { mutate = 1; foo(s) })($0) +} + "#, + expect![[r#" + impl FnMut(s: S) -> i32 + ^^^^ + "#]], + ); } #[test] @@ -1383,12 +1428,81 @@ fn main(f: fn(i32, f64) -> char) { } "#, expect![[r#" - (i32, f64) -> char - --- ^^^ + fn(i32, f64) -> char + --- ^^^ "#]], ) } + #[test] + fn call_info_for_fn_impl() { + check( + r#" +struct S; +impl core::ops::FnOnce<(i32, f64)> for S { + type Output = char; +} +impl core::ops::FnMut<(i32, f64)> for S {} +impl core::ops::Fn<(i32, f64)> for S {} +fn main() { + S($0); +} + "#, + expect![[r#" + ::call(i32, f64) -> char + ^^^ --- + "#]], + ); + check( + r#" +struct S; +impl core::ops::FnOnce<(i32, f64)> for S { + type Output = char; +} +impl core::ops::FnMut<(i32, f64)> for S {} +impl core::ops::Fn<(i32, f64)> for S {} +fn main() { + S(1, $0); +} + "#, + expect![[r#" + ::call(i32, f64) -> char + --- ^^^ + "#]], + ); + check( + r#" +struct S; +impl core::ops::FnOnce<(i32, f64)> for S { + type Output = char; +} +impl core::ops::FnOnce<(char, char)> for S { + type Output = f64; +} +fn main() { + S($0); +} + "#, + expect![""], + ); + check( + r#" +struct S; +impl core::ops::FnOnce<(i32, f64)> for S { + type Output = char; +} +impl core::ops::FnOnce<(char, char)> for S { + type Output = f64; +} +fn main() { + // FIXME: The ide layer loses the calling info here so we get an ambiguous trait solve result + S(0i32, $0); +} + "#, + expect![""], + ); + } + #[test] fn call_info_for_unclosed_call() { check( @@ -1794,19 +1908,19 @@ fn f i32>(f: F) { } "#, expect![[r#" - (u8, u16) -> i32 - ^^ --- + impl FnOnce(u8, u16) -> i32 + ^^ --- "#]], ); check( r#" -fn f &T>(f: F) { +fn f &T>(f: F) { f($0) } "#, expect![[r#" - (&T, u16) -> &T - ^^ --- + impl FnMut(&T, u16) -> &T + ^^ --- "#]], ); } @@ -1826,7 +1940,7 @@ fn take( } "#, expect![[r#" - () -> i32 + impl Fn() -> i32 "#]], ); } diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs index 0439e509d21dd..2f387968c96a1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs @@ -9,8 +9,9 @@ pub(super) fn highlight_escape_string( string: &T, start: TextSize, ) { + let text = string.text(); string.escaped_char_ranges(&mut |piece_range, char| { - if string.text()[piece_range.start().into()..].starts_with('\\') { + if text[piece_range.start().into()..].starts_with('\\') { let highlight = match char { Ok(_) => HlTag::EscapeSequence, Err(_) => HlTag::InvalidEscapeSequence, @@ -33,17 +34,15 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: } let text = char.text(); - if !text.starts_with('\'') || !text.ends_with('\'') { + let Some(text) = text + .strip_prefix('\'') + .and_then(|it| it.strip_suffix('\'')) + .filter(|it| it.starts_with('\\')) + else { return; - } - - let text = &text[1..text.len() - 1]; - if !text.starts_with('\\') { - return; - } + }; - let range = - TextRange::new(start + TextSize::from(1), start + TextSize::from(text.len() as u32 + 1)); + let range = TextRange::at(start + TextSize::from(1), TextSize::from(text.len() as u32)); stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) } @@ -54,16 +53,14 @@ pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: } let text = byte.text(); - if !text.starts_with("b'") || !text.ends_with('\'') { + let Some(text) = text + .strip_prefix("b'") + .and_then(|it| it.strip_suffix('\'')) + .filter(|it| it.starts_with('\\')) + else { return; - } - - let text = &text[2..text.len() - 1]; - if !text.starts_with('\\') { - return; - } + }; - let range = - TextRange::new(start + TextSize::from(2), start + TextSize::from(text.len() as u32 + 2)); + let range = TextRange::at(start + TextSize::from(2), TextSize::from(text.len() as u32)); stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs index 16599881d6439..1ce548f8fc703 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs @@ -8,6 +8,7 @@ use std::{ use rustc_lexer::unescape::{ unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode, }; +use stdx::always; use crate::{ ast::{self, AstToken}, @@ -181,25 +182,25 @@ pub trait IsString: AstToken { self.quote_offsets().map(|it| it.quotes.1) } fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result)) { - let text_range_no_quotes = match self.text_range_between_quotes() { - Some(it) => it, - None => return, - }; + let Some(text_range_no_quotes) = self.text_range_between_quotes() else { return }; let start = self.syntax().text_range().start(); let text = &self.text()[text_range_no_quotes - start]; let offset = text_range_no_quotes.start() - start; unescape_unicode(text, Self::MODE, &mut |range, unescaped_char| { - let text_range = - TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap()); - cb(text_range + offset, unescaped_char); + if let Some((s, e)) = range.start.try_into().ok().zip(range.end.try_into().ok()) { + cb(TextRange::new(s, e) + offset, unescaped_char); + } }); } fn map_range_up(&self, range: TextRange) -> Option { let contents_range = self.text_range_between_quotes()?; - assert!(TextRange::up_to(contents_range.len()).contains_range(range)); - Some(range + contents_range.start()) + if always!(TextRange::up_to(contents_range.len()).contains_range(range)) { + Some(range + contents_range.start()) + } else { + None + } } }