Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-def/src/attr/builtin.rs3
-rw-r--r--crates/hir-def/src/nameres.rs10
-rw-r--r--crates/hir-def/src/nameres/attr_resolution.rs5
-rw-r--r--crates/hir-ty/src/infer/unify.rs21
-rw-r--r--crates/hir-ty/src/lib.rs73
-rw-r--r--crates/hir-ty/src/traits.rs19
-rw-r--r--crates/hir/src/lib.rs210
-rw-r--r--crates/hir/src/source_analyzer.rs3
-rw-r--r--crates/ide-assists/src/handlers/replace_method_eager_lazy.rs4
-rw-r--r--crates/ide-assists/src/utils/suggest_name.rs7
-rw-r--r--crates/ide-db/src/active_parameter.rs19
-rw-r--r--crates/ide-db/src/syntax_helpers/format_string.rs1
-rw-r--r--crates/ide/src/call_hierarchy.rs8
-rw-r--r--crates/ide/src/inlay_hints/param_name.rs21
-rw-r--r--crates/ide/src/signature_help.rs156
-rw-r--r--crates/ide/src/syntax_highlighting/escape.rs35
-rw-r--r--crates/syntax/src/ast/token_ext.rs19
17 files changed, 388 insertions, 226 deletions
diff --git a/crates/hir-def/src/attr/builtin.rs b/crates/hir-def/src/attr/builtin.rs
index 1fba2e2e4d..f4564c94bb 100644
--- a/crates/hir-def/src/attr/builtin.rs
+++ b/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/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs
index a528c4cc69..497b69889a 100644
--- a/crates/hir-def/src/nameres.rs
+++ b/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/crates/hir-def/src/nameres/attr_resolution.rs b/crates/hir-def/src/nameres/attr_resolution.rs
index eb7f4c05ae..3cb0666edf 100644
--- a/crates/hir-def/src/nameres/attr_resolution.rs
+++ b/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/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs
index b68fefc515..36e3a45889 100644
--- a/crates/hir-ty/src/infer/unify.rs
+++ b/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/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index 1727cec989..26a839f0e9 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/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<TraitEnvironment>,
+pub fn callable_sig_from_fn_trait(
+ self_ty: &Ty,
+ trait_env: Arc<TraitEnvironment>,
db: &dyn HirDatabase,
-) -> Option<CallableSig> {
- 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<?args_ty>
// - <Self as FnOnce<?args_ty>>::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<chalk_ir::Goal<Interner>> = 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/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs
index 930bc7df5e..02f2cd7615 100644
--- a/crates/hir-ty/src/traits.rs
+++ b/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/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 85f33a10fc..82d2bbb6cf 100644
--- a/crates/hir/src/lib.rs
+++ b/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<hir_ty::Mutability> 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<Function> {
+ match self.func {
+ Callee::Def(CallableDefId::FunctionId(f)) => Some(f.into()),
+ _ => None,
+ }
}
+ // pub fn parent_closure(&self) -> Option<Closure> {
+ // 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<Local> {
- 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<ast::Pat> {
- 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<InFile<ast::Param>> {
- 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<InFile<Either<ast::SelfParam, ast::Param>>> {
+ 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<CrateId>,
+ 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<Self> {
- 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<Self> {
- 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<Callable> {
- 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<Either<ast::SelfParam, ast::Pat>>, Type)> {
- let types = self
- .sig
+ pub fn params(&self) -> Vec<Param> {
+ 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<ast::ClosureExpr> {
- 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/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 057b03baef..d229584064 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -307,7 +307,8 @@ impl SourceAnalyzer {
db: &dyn HirDatabase,
call: &ast::Expr,
) -> Option<Callable> {
- 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/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs b/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs
index 7f3b0d7588..37ea5123a7 100644
--- a/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs
+++ b/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/crates/ide-assists/src/utils/suggest_name.rs b/crates/ide-assists/src/utils/suggest_name.rs
index 1859825b3d..23a06404f3 100644
--- a/crates/ide-assists/src/utils/suggest_name.rs
+++ b/crates/ide-assists/src/utils/suggest_name.rs
@@ -253,11 +253,8 @@ fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<St
};
let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
- let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
- let pat = match pat? {
- either::Either::Right(pat) => 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/crates/ide-db/src/active_parameter.rs b/crates/ide-db/src/active_parameter.rs
index 5780b5a5bb..98d2e81754 100644
--- a/crates/ide-db/src/active_parameter.rs
+++ b/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<Either<ast::SelfParam, ast::Pat>>,
+ pub src: Option<InFile<Either<ast::SelfParam, ast::Param>>>,
}
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<ast::Name> {
- 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<usize>)> {
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/crates/ide-db/src/syntax_helpers/format_string.rs b/crates/ide-db/src/syntax_helpers/format_string.rs
index 8302b015dd..92478ef480 100644
--- a/crates/ide-db/src/syntax_helpers/format_string.rs
+++ b/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/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 458b852e2a..654a1cd316 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/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/crates/ide/src/inlay_hints/param_name.rs b/crates/ide/src/inlay_hints/param_name.rs
index 96e845b2f3..fb50c49a3a 100644
--- a/crates/ide/src/inlay_hints/param_name.rs
+++ b/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, &param_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/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index b2eb5a5fff..378a38892c 100644
--- a/crates/ide/src/signature_help.rs
+++ b/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)
@@ -1346,14 +1363,42 @@ fn test() { S.foo($0); }
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,13 +1428,82 @@ 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#"
+ <S as Fn>::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#"
+ <S as Fn>::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(
r#"
@@ -1794,19 +1908,19 @@ fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
}
"#,
expect![[r#"
- (u8, u16) -> i32
- ^^ ---
+ impl FnOnce(u8, u16) -> i32
+ ^^ ---
"#]],
);
check(
r#"
-fn f<T, F: FnOnce(&T, u16) -> &T>(f: F) {
+fn f<T, F: FnMut(&T, u16) -> &T>(f: F) {
f($0)
}
"#,
expect![[r#"
- (&T, u16) -> &T
- ^^ ---
+ impl FnMut(&T, u16) -> &T
+ ^^ ---
"#]],
);
}
@@ -1826,7 +1940,7 @@ fn take<C, Error>(
}
"#,
expect![[r#"
- () -> i32
+ impl Fn() -> i32
"#]],
);
}
diff --git a/crates/ide/src/syntax_highlighting/escape.rs b/crates/ide/src/syntax_highlighting/escape.rs
index 0439e509d2..2f387968c9 100644
--- a/crates/ide/src/syntax_highlighting/escape.rs
+++ b/crates/ide/src/syntax_highlighting/escape.rs
@@ -9,8 +9,9 @@ pub(super) fn highlight_escape_string<T: IsString>(
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/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 16599881d6..1ce548f8fc 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/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<char, EscapeError>)) {
- 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<TextRange> {
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
+ }
}
}