Auto merge of #17268 - Veykril:signatures, r=Veykril

feat: More callable info

With this PR we retain more info about callables other than functions, allowing for closure parameter type inlay hints to be linkable as well as better signature help around closures and `Fn*` implementors.
This commit is contained in:
bors 2024-05-22 12:48:17 +00:00
commit daf66ad8eb
17 changed files with 387 additions and 225 deletions

View file

@ -12,9 +12,6 @@ use std::sync::OnceLock;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
/// Ignored attribute namespaces used by tools.
pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"];
pub struct BuiltinAttribute { pub struct BuiltinAttribute {
pub name: &'static str, pub name: &'static str,
pub template: AttributeTemplate, pub template: AttributeTemplate,

View file

@ -84,6 +84,14 @@ use crate::{
LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, 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. /// Contains the results of (early) name resolution.
/// ///
/// A `DefMap` stores the module tree and the definitions that are in scope in every module after /// 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(), fn_proc_macro_mapping: FxHashMap::default(),
proc_macro_loading_error: None, proc_macro_loading_error: None,
registered_attrs: Vec::new(), registered_attrs: Vec::new(),
registered_tools: Vec::new(), registered_tools: PREDEFINED_TOOLS.into(),
unstable_features: FxHashSet::default(), unstable_features: FxHashSet::default(),
rustc_coherence_is_core: false, rustc_coherence_is_core: false,
no_core: false, no_core: false,

View file

@ -10,7 +10,7 @@ use syntax::{ast, SmolStr};
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
attr::builtin::{find_builtin_attr_idx, TOOL_MODULES}, attr::builtin::find_builtin_attr_idx,
db::DefDatabase, db::DefDatabase,
item_scope::BuiltinShadowMode, item_scope::BuiltinShadowMode,
nameres::path_resolution::ResolveMode, nameres::path_resolution::ResolveMode,
@ -82,8 +82,7 @@ impl DefMap {
let name = name.to_smol_str(); let name = name.to_smol_str();
let pred = |n: &_| *n == name; let pred = |n: &_| *n == name;
let registered = self.data.registered_tools.iter().map(SmolStr::as_str); let is_tool = self.data.registered_tools.iter().map(SmolStr::as_str).any(pred);
let is_tool = TOOL_MODULES.iter().copied().chain(registered).any(pred);
// FIXME: tool modules can be shadowed by actual modules // FIXME: tool modules can be shadowed by actual modules
if is_tool { if is_tool {
return true; return true;

View file

@ -797,19 +797,22 @@ impl<'a> InferenceTable<'a> {
}) })
.build(); .build();
let projection = { let b = TyBuilder::trait_ref(self.db, fn_once_trait);
let b = TyBuilder::subst_for_def(self.db, fn_once_trait, None); if b.remaining() != 2 {
if b.remaining() != 2 { return None;
return None; }
} let mut trait_ref = b.push(ty.clone()).push(arg_ty).build();
let fn_once_subst = b.push(ty.clone()).push(arg_ty).build();
TyBuilder::assoc_type_projection(self.db, output_assoc_type, Some(fn_once_subst)) let projection = {
.build() TyBuilder::assoc_type_projection(
self.db,
output_assoc_type,
Some(trait_ref.substitution.clone()),
)
.build()
}; };
let trait_env = self.trait_env.env.clone(); let trait_env = self.trait_env.env.clone();
let mut trait_ref = projection.trait_ref(self.db);
let obligation = InEnvironment { let obligation = InEnvironment {
goal: trait_ref.clone().cast(Interner), goal: trait_ref.clone().cast(Interner),
environment: trait_env.clone(), environment: trait_env.clone(),

View file

@ -570,6 +570,10 @@ impl CallableSig {
} }
} }
pub fn abi(&self) -> FnAbi {
self.abi
}
pub fn params(&self) -> &[Ty] { pub fn params(&self) -> &[Ty] {
&self.params_and_return[0..self.params_and_return.len() - 1] &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) } Canonical { value, binders: chalk_ir::CanonicalVarKinds::from_iter(Interner, kinds) }
} }
pub fn callable_sig_from_fnonce( pub fn callable_sig_from_fn_trait(
mut self_ty: &Ty, self_ty: &Ty,
env: Arc<TraitEnvironment>, trait_env: Arc<TraitEnvironment>,
db: &dyn HirDatabase, db: &dyn HirDatabase,
) -> Option<CallableSig> { ) -> Option<(FnTrait, CallableSig)> {
if let Some((ty, _, _)) = self_ty.as_reference() { let krate = trait_env.krate;
// This will happen when it implements fn or fn mut, since we add a autoborrow adjustment
self_ty = ty;
}
let krate = env.krate;
let fn_once_trait = FnTrait::FnOnce.get_id(db, 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 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); let b = TyBuilder::trait_ref(db, fn_once_trait);
if b.remaining() != 2 { if b.remaining() != 2 {
return None; return None;
@ -915,23 +915,56 @@ pub fn callable_sig_from_fnonce(
// - Self: FnOnce<?args_ty> // - Self: FnOnce<?args_ty>
// - <Self as FnOnce<?args_ty>>::Output == ?ret_ty // - <Self as FnOnce<?args_ty>>::Output == ?ret_ty
let args_ty = table.new_type_var(); 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( let projection = TyBuilder::assoc_type_projection(
db, db,
output_assoc_type, output_assoc_type,
Some(trait_ref.substitution.clone()), Some(trait_ref.substitution.clone()),
) )
.build(); .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 block = trait_env.block;
let args_ty = table.resolve_completely(args_ty); 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();
let params = return Some((
args_ty.as_tuple()?.iter(Interner).map(|it| it.assert_ty_ref(Interner)).cloned().collect(); fn_x,
CallableSig::from_params_and_return(
Some(CallableSig::from_params_and_return(params, ret_ty, false, Safety::Safe, FnAbi::RustCall)) params,
ret_ty,
false,
Safety::Safe,
FnAbi::RustCall,
),
));
}
}
unreachable!("It should at least implement FnOnce at this point");
} else {
None
}
} }
struct PlaceholderCollector<'db> { struct PlaceholderCollector<'db> {

View file

@ -1,5 +1,6 @@
//! Trait solving using Chalk. //! Trait solving using Chalk.
use core::fmt;
use std::env::var; use std::env::var;
use chalk_ir::{fold::TypeFoldable, DebruijnIndex, GoalData}; use chalk_ir::{fold::TypeFoldable, DebruijnIndex, GoalData};
@ -209,7 +210,25 @@ pub enum FnTrait {
Fn, 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 { 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 { const fn lang_item(self) -> LangItem {
match self { match self {
FnTrait::FnOnce => LangItem::FnOnce, FnTrait::FnOnce => LangItem::FnOnce,

View file

@ -35,7 +35,7 @@ pub mod term_search;
mod display; mod display;
use std::{iter, mem::discriminant, ops::ControlFlow}; use std::{mem::discriminant, ops::ControlFlow};
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, CrateOrigin, FileId}; use base_db::{CrateDisplayName, CrateId, CrateOrigin, FileId};
@ -52,7 +52,6 @@ use hir_def::{
path::ImportAlias, path::ImportAlias,
per_ns::PerNs, per_ns::PerNs,
resolver::{HasResolver, Resolver}, resolver::{HasResolver, Resolver},
src::HasSource as _,
AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId,
EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule,
ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, MacroExpander, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, MacroExpander,
@ -141,7 +140,7 @@ pub use {
display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
layout::LayoutError, layout::LayoutError,
mir::{MirEvalError, MirLowerError}, mir::{MirEvalError, MirLowerError},
PointerCast, Safety, FnAbi, PointerCast, Safety,
}, },
// FIXME: Properly encapsulate mir // FIXME: Properly encapsulate mir
hir_ty::{mir, Interner as ChalkTyInterner}, hir_ty::{mir, Interner as ChalkTyInterner},
@ -1965,7 +1964,7 @@ impl Function {
.enumerate() .enumerate()
.map(|(idx, ty)| { .map(|(idx, ty)| {
let ty = Type { env: environment.clone(), ty: ty.clone() }; 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() .collect()
} }
@ -1991,7 +1990,7 @@ impl Function {
.skip(skip) .skip(skip)
.map(|(idx, ty)| { .map(|(idx, ty)| {
let ty = Type { env: environment.clone(), ty: ty.clone() }; 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() .collect()
} }
@ -2037,7 +2036,7 @@ impl Function {
.skip(skip) .skip(skip)
.map(|(idx, ty)| { .map(|(idx, ty)| {
let ty = Type { env: environment.clone(), ty: ty.clone() }; 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() .collect()
} }
@ -2167,17 +2166,24 @@ impl From<hir_ty::Mutability> for Access {
#[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Param { pub struct Param {
func: Function, func: Callee,
/// The index in parameter list, including self parameter. /// The index in parameter list, including self parameter.
idx: usize, idx: usize,
ty: Type, ty: Type,
} }
impl Param { impl Param {
pub fn parent_fn(&self) -> Function { pub fn parent_fn(&self) -> Option<Function> {
self.func 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 { pub fn index(&self) -> usize {
self.idx self.idx
} }
@ -2191,7 +2197,11 @@ impl Param {
} }
pub fn as_local(&self, db: &dyn HirDatabase) -> Option<Local> { 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); let body = db.body(parent);
if let Some(self_param) = body.self_param.filter(|_| self.idx == 0) { if let Some(self_param) = body.self_param.filter(|_| self.idx == 0) {
Some(Local { parent, binding_id: self_param }) Some(Local { parent, binding_id: self_param })
@ -2205,18 +2215,45 @@ impl Param {
} }
pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> { 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>> { pub fn source(
let InFile { file_id, value } = self.func.source(db)?; &self,
let params = value.param_list()?; db: &dyn HirDatabase,
if params.self_param().is_some() { ) -> Option<InFile<Either<ast::SelfParam, ast::Param>>> {
params.params().nth(self.idx.checked_sub(params.self_param().is_some() as usize)?) match self.func {
} else { Callee::Def(CallableDefId::FunctionId(func)) => {
params.params().nth(self.idx) 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ToolModule { pub struct ToolModule {
krate: Option<CrateId>, krate: CrateId,
idx: u32, idx: u32,
} }
impl ToolModule { 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> { pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option<Self> {
if let builtin @ Some(_) = Self::builtin(name) { let krate = krate.id;
return builtin;
}
let idx = let idx =
db.crate_def_map(krate.id).registered_tools().iter().position(|it| it == name)? as u32; db.crate_def_map(krate).registered_tools().iter().position(|it| it == name)? as u32;
Some(ToolModule { krate: Some(krate.id), idx }) Some(ToolModule { krate, 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 })
} }
pub fn name(&self, db: &dyn HirDatabase) -> SmolStr { pub fn name(&self, db: &dyn HirDatabase) -> SmolStr {
// FIXME: Return a `Name` here // FIXME: Return a `Name` here
match self.krate { db.crate_def_map(self.krate).registered_tools()[self.idx as usize].clone()
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]),
}
} }
} }
@ -4292,27 +4316,37 @@ impl Type {
} }
pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> { pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> {
let mut the_ty = &self.ty;
let callee = match self.ty.kind(Interner) { let callee = match self.ty.kind(Interner) {
TyKind::Ref(_, _, ty) if ty.as_closure().is_some() => { TyKind::Closure(id, subst) => Callee::Closure(*id, subst.clone()),
the_ty = ty;
Callee::Closure(ty.as_closure().unwrap())
}
TyKind::Closure(id, _) => Callee::Closure(*id),
TyKind::Function(_) => Callee::FnPtr, TyKind::Function(_) => Callee::FnPtr,
TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?), TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?),
_ => { kind => {
let sig = hir_ty::callable_sig_from_fnonce(&self.ty, self.env.clone(), db)?; // 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 { return Some(Callable {
ty: self.clone(), ty: self.clone(),
sig, sig,
callee: Callee::Other, callee: Callee::FnImpl(fn_trait),
is_bound_method: false, 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 }) Some(Callable { ty: self.clone(), sig, callee, is_bound_method: false })
} }
@ -4929,37 +4963,39 @@ pub struct Callable {
sig: CallableSig, sig: CallableSig,
callee: Callee, callee: Callee,
/// Whether this is a method that was called with method call syntax. /// 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 { enum Callee {
Def(CallableDefId), Def(CallableDefId),
Closure(ClosureId), Closure(ClosureId, Substitution),
FnPtr, FnPtr,
Other, FnImpl(FnTrait),
} }
pub enum CallableKind { pub enum CallableKind {
Function(Function), Function(Function),
TupleStruct(Struct), TupleStruct(Struct),
TupleEnumVariant(Variant), TupleEnumVariant(Variant),
Closure, Closure(Closure),
FnPtr, FnPtr,
/// Some other type that implements `FnOnce`. FnImpl(FnTrait),
Other,
} }
impl Callable { impl Callable {
pub fn kind(&self) -> CallableKind { pub fn kind(&self) -> CallableKind {
use Callee::*;
match self.callee { match self.callee {
Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()), Callee::Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()),
Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()), Callee::Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()),
Def(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()), Callee::Def(CallableDefId::EnumVariantId(it)) => {
Closure(_) => CallableKind::Closure, CallableKind::TupleEnumVariant(it.into())
FnPtr => CallableKind::FnPtr, }
Other => CallableKind::Other, 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)> { pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(SelfParam, Type)> {
@ -4973,43 +5009,15 @@ impl Callable {
pub fn n_params(&self) -> usize { pub fn n_params(&self) -> usize {
self.sig.params().len() - if self.is_bound_method { 1 } else { 0 } self.sig.params().len() - if self.is_bound_method { 1 } else { 0 }
} }
pub fn params( pub fn params(&self) -> Vec<Param> {
&self, self.sig
db: &dyn HirDatabase,
) -> Vec<(Option<Either<ast::SelfParam, ast::Pat>>, Type)> {
let types = self
.sig
.params() .params()
.iter() .iter()
.enumerate()
.skip(if self.is_bound_method { 1 } else { 0 }) .skip(if self.is_bound_method { 1 } else { 0 })
.map(|ty| self.ty.derived(ty.clone())); .map(|(idx, ty)| (idx, self.ty.derived(ty.clone())))
let map_param = |it: ast::Param| it.pat().map(Either::Right); .map(|(idx, ty)| Param { func: self.callee.clone(), idx, ty })
let patterns = match self.callee { .collect()
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()
} }
pub fn return_type(&self) -> Type { pub fn return_type(&self) -> Type {
self.ty.derived(self.sig.ret().clone()) self.ty.derived(self.sig.ret().clone())
@ -5017,17 +5025,9 @@ impl Callable {
pub fn sig(&self) -> &CallableSig { pub fn sig(&self) -> &CallableSig {
&self.sig &self.sig
} }
}
fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option<ast::ClosureExpr> { pub fn ty(&self) -> &Type {
let InternedClosure(owner, expr_id) = db.lookup_intern_closure(closure.into()); &self.ty
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,
} }
} }

View file

@ -307,7 +307,8 @@ impl SourceAnalyzer {
db: &dyn HirDatabase, db: &dyn HirDatabase,
call: &ast::Expr, call: &ast::Expr,
) -> Option<Callable> { ) -> 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( pub(crate) fn resolve_field(

View file

@ -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 callable = ctx.sema.resolve_method_call_as_callable(&call)?;
let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?; let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?;
let n_params = callable.n_params() + 1; 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` // 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; return None;
} }

View file

@ -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 (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
let (pat, _) = func.params(sema.db).into_iter().nth(idx)?; let param = func.params().into_iter().nth(idx)?;
let pat = match pat? { let pat = param.source(sema.db)?.value.right()?.pat()?;
either::Either::Right(pat) => pat,
_ => return None,
};
let name = var_name_from_pat(&pat)?; let name = var_name_from_pat(&pat)?;
normalize(&name.to_string()) normalize(&name.to_string())
} }

View file

@ -1,7 +1,7 @@
//! This module provides functionality for querying callable information about a token. //! This module provides functionality for querying callable information about a token.
use either::Either; use either::Either;
use hir::{Semantics, Type}; use hir::{InFile, Semantics, Type};
use parser::T; use parser::T;
use syntax::{ use syntax::{
ast::{self, HasArgList, HasName}, ast::{self, HasArgList, HasName},
@ -13,7 +13,7 @@ use crate::RootDatabase;
#[derive(Debug)] #[derive(Debug)]
pub struct ActiveParameter { pub struct ActiveParameter {
pub ty: Type, pub ty: Type,
pub pat: Option<Either<ast::SelfParam, ast::Pat>>, pub src: Option<InFile<Either<ast::SelfParam, ast::Param>>>,
} }
impl ActiveParameter { impl ActiveParameter {
@ -22,18 +22,18 @@ impl ActiveParameter {
let (signature, active_parameter) = callable_for_token(sema, token)?; let (signature, active_parameter) = callable_for_token(sema, token)?;
let idx = active_parameter?; let idx = active_parameter?;
let mut params = signature.params(sema.db); let mut params = signature.params();
if idx >= params.len() { if idx >= params.len() {
cov_mark::hit!(too_many_arguments); cov_mark::hit!(too_many_arguments);
return None; return None;
} }
let (pat, ty) = params.swap_remove(idx); let param = params.swap_remove(idx);
Some(ActiveParameter { ty, pat }) Some(ActiveParameter { ty: param.ty().clone(), src: param.source(sema.db) })
} }
pub fn ident(&self) -> Option<ast::Name> { pub fn ident(&self) -> Option<ast::Name> {
self.pat.as_ref().and_then(|param| match param { self.src.as_ref().and_then(|param| match param.value.as_ref().right()?.pat()? {
Either::Right(ast::Pat::IdentPat(ident)) => ident.name(), ast::Pat::IdentPat(ident) => ident.name(),
_ => None, _ => None,
}) })
} }
@ -60,10 +60,7 @@ pub fn callable_for_node(
token: &SyntaxToken, token: &SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> { ) -> Option<(hir::Callable, Option<usize>)> {
let callable = match calling_node { let callable = match calling_node {
ast::CallableExpr::Call(call) => { ast::CallableExpr::Call(call) => sema.resolve_expr_as_callable(&call.expr()?),
let expr = call.expr()?;
sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db)
}
ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call), ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
}?; }?;
let active_param = calling_node.arg_list().map(|arg_list| { let active_param = calling_node.arg_list().map(|arg_list| {

View file

@ -41,6 +41,7 @@ pub enum FormatSpecifier {
Escape, Escape,
} }
// FIXME: Remove this, we can use rustc_format_parse instead
pub fn lex_format_specifiers( pub fn lex_format_specifiers(
string: &ast::String, string: &ast::String,
mut callback: &mut dyn FnMut(TextRange, FormatSpecifier), mut callback: &mut dyn FnMut(TextRange, FormatSpecifier),

View file

@ -109,12 +109,12 @@ pub(crate) fn outgoing_calls(
let expr = call.expr()?; let expr = call.expr()?;
let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?; let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
match callable.kind() { match callable.kind() {
hir::CallableKind::Function(it) => { hir::CallableKind::Function(it) => it.try_to_nav(db),
let range = expr.syntax().text_range(); hir::CallableKind::TupleEnumVariant(it) => it.try_to_nav(db),
it.try_to_nav(db).zip(Some(range)) hir::CallableKind::TupleStruct(it) => it.try_to_nav(db),
}
_ => None, _ => None,
} }
.zip(Some(expr.syntax().text_range()))
} }
ast::CallableExpr::MethodCall(expr) => { ast::CallableExpr::MethodCall(expr) => {
let range = expr.name_ref()?.syntax().text_range(); let range = expr.name_ref()?.syntax().text_range();

View file

@ -24,34 +24,29 @@ pub(super) fn hints(
let (callable, arg_list) = get_callable(sema, &expr)?; let (callable, arg_list) = get_callable(sema, &expr)?;
let hints = callable let hints = callable
.params(sema.db) .params()
.into_iter() .into_iter()
.zip(arg_list.args()) .zip(arg_list.args())
.filter_map(|((param, _ty), arg)| { .filter_map(|(p, arg)| {
// Only annotate hints for expressions that exist in the original file // Only annotate hints for expressions that exist in the original file
let range = sema.original_range_opt(arg.syntax())?; 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::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()), ast::Pat::IdentPat(it) => (it.name()?, it.name()),
_ => return None, _ => 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)) Some((name_syntax, param_name, arg, range))
}) })
.filter(|(_, param_name, arg, _)| { .filter(|(_, param_name, arg, _)| {
!should_hide_param_name_hint(sema, &callable, &param_name.text(), arg) !should_hide_param_name_hint(sema, &callable, &param_name.text(), arg)
}) })
.map(|(param, param_name, _, FileRange { range, .. })| { .map(|(param, param_name, _, FileRange { range, .. })| {
let mut linked_location = None; let linked_location = param.and_then(|name| sema.original_range_opt(name.syntax()));
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 colon = if config.render_colons { ":" } else { "" }; let colon = if config.render_colons { ":" } else { "" };
let label = let label =

View file

@ -201,7 +201,21 @@ fn signature_help_for_call(
variant.name(db).display(db) 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('('); res.signature.push('(');
@ -210,12 +224,15 @@ fn signature_help_for_call(
format_to!(res.signature, "{}", self_param.display(db)) format_to!(res.signature, "{}", self_param.display(db))
} }
let mut buf = String::new(); 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(); buf.clear();
if let Some(pat) = pat { if let Some(param) = p.source(sema.db) {
match pat { match param.value {
Either::Left(_self) => format_to!(buf, "self: "), Either::Right(param) => match param.pat() {
Either::Right(pat) => format_to!(buf, "{}: ", 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 // 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. // In that case, fall back to render definitions of the respective parameters.
// This is overly conservative: we do not substitute known type vars // This is overly conservative: we do not substitute known type vars
// (see FIXME in tests::impl_trait) and falling back on any unknowns. // (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)), (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); res.push_call_param(&buf);
} }
@ -242,9 +259,9 @@ fn signature_help_for_call(
render(func.ret_type(db)) render(func.ret_type(db))
} }
hir::CallableKind::Function(_) hir::CallableKind::Function(_)
| hir::CallableKind::Closure | hir::CallableKind::Closure(_)
| hir::CallableKind::FnPtr | hir::CallableKind::FnPtr
| hir::CallableKind::Other => render(callable.return_type()), | hir::CallableKind::FnImpl(_) => render(callable.return_type()),
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {} hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
} }
Some(res) Some(res)
@ -1345,15 +1362,43 @@ fn test() { S.foo($0); }
r#" r#"
struct S; struct S;
fn foo(s: S) -> i32 { 92 } 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() { fn main() {
(|s| foo(s))($0) (|s| foo(s))($0)
} }
"#, "#,
expect![[r#" 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] #[test]
@ -1383,12 +1428,81 @@ fn main(f: fn(i32, f64) -> char) {
} }
"#, "#,
expect![[r#" 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] #[test]
fn call_info_for_unclosed_call() { fn call_info_for_unclosed_call() {
check( check(
@ -1794,19 +1908,19 @@ fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
} }
"#, "#,
expect![[r#" expect![[r#"
(u8, u16) -> i32 impl FnOnce(u8, u16) -> i32
^^ --- ^^ ---
"#]], "#]],
); );
check( check(
r#" r#"
fn f<T, F: FnOnce(&T, u16) -> &T>(f: F) { fn f<T, F: FnMut(&T, u16) -> &T>(f: F) {
f($0) f($0)
} }
"#, "#,
expect![[r#" expect![[r#"
(&T, u16) -> &T impl FnMut(&T, u16) -> &T
^^ --- ^^ ---
"#]], "#]],
); );
} }
@ -1826,7 +1940,7 @@ fn take<C, Error>(
} }
"#, "#,
expect![[r#" expect![[r#"
() -> i32 impl Fn() -> i32
"#]], "#]],
); );
} }

View file

@ -9,8 +9,9 @@ pub(super) fn highlight_escape_string<T: IsString>(
string: &T, string: &T,
start: TextSize, start: TextSize,
) { ) {
let text = string.text();
string.escaped_char_ranges(&mut |piece_range, char| { 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 { let highlight = match char {
Ok(_) => HlTag::EscapeSequence, Ok(_) => HlTag::EscapeSequence,
Err(_) => HlTag::InvalidEscapeSequence, Err(_) => HlTag::InvalidEscapeSequence,
@ -33,17 +34,15 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start:
} }
let text = char.text(); 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; return;
} };
let text = &text[1..text.len() - 1]; let range = TextRange::at(start + TextSize::from(1), TextSize::from(text.len() as u32));
if !text.starts_with('\\') {
return;
}
let range =
TextRange::new(start + TextSize::from(1), start + TextSize::from(text.len() as u32 + 1));
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) 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(); 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; return;
} };
let text = &text[2..text.len() - 1]; let range = TextRange::at(start + TextSize::from(2), TextSize::from(text.len() as u32));
if !text.starts_with('\\') {
return;
}
let range =
TextRange::new(start + TextSize::from(2), start + TextSize::from(text.len() as u32 + 2));
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None }) stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
} }

View file

@ -8,6 +8,7 @@ use std::{
use rustc_lexer::unescape::{ use rustc_lexer::unescape::{
unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode, unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode,
}; };
use stdx::always;
use crate::{ use crate::{
ast::{self, AstToken}, ast::{self, AstToken},
@ -181,25 +182,25 @@ pub trait IsString: AstToken {
self.quote_offsets().map(|it| it.quotes.1) self.quote_offsets().map(|it| it.quotes.1)
} }
fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) { fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) {
let text_range_no_quotes = match self.text_range_between_quotes() { let Some(text_range_no_quotes) = self.text_range_between_quotes() else { return };
Some(it) => it,
None => return,
};
let start = self.syntax().text_range().start(); let start = self.syntax().text_range().start();
let text = &self.text()[text_range_no_quotes - start]; let text = &self.text()[text_range_no_quotes - start];
let offset = text_range_no_quotes.start() - start; let offset = text_range_no_quotes.start() - start;
unescape_unicode(text, Self::MODE, &mut |range, unescaped_char| { unescape_unicode(text, Self::MODE, &mut |range, unescaped_char| {
let text_range = if let Some((s, e)) = range.start.try_into().ok().zip(range.end.try_into().ok()) {
TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap()); cb(TextRange::new(s, e) + offset, unescaped_char);
cb(text_range + offset, unescaped_char); }
}); });
} }
fn map_range_up(&self, range: TextRange) -> Option<TextRange> { fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
let contents_range = self.text_range_between_quotes()?; let contents_range = self.text_range_between_quotes()?;
assert!(TextRange::up_to(contents_range.len()).contains_range(range)); if always!(TextRange::up_to(contents_range.len()).contains_range(range)) {
Some(range + contents_range.start()) Some(range + contents_range.start())
} else {
None
}
} }
} }