Show fn traits in signature info for trait implementors

This commit is contained in:
Lukas Wirth 2024-05-18 16:22:59 +02:00
parent 0de904d539
commit 7bd343e085
8 changed files with 195 additions and 67 deletions

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 fn_once_subst = b.push(ty.clone()).push(arg_ty).build(); 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)) let projection = {
TyBuilder::assoc_type_projection(
self.db,
output_assoc_type,
Some(trait_ref.substitution.clone()),
)
.build() .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 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 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

@ -221,6 +221,14 @@ impl fmt::Display for FnTrait {
} }
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

@ -140,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},
@ -2227,7 +2227,7 @@ impl Param {
let InFile { file_id, value } = Function { id: func }.source(db)?; let InFile { file_id, value } = Function { id: func }.source(db)?;
let params = value.param_list()?; let params = value.param_list()?;
if let Some(self_param) = params.self_param() { if let Some(self_param) = params.self_param() {
if let Some(idx) = self.idx.checked_sub(1 as usize) { if let Some(idx) = self.idx.checked_sub(1) {
params.params().nth(idx).map(Either::Right) params.params().nth(idx).map(Either::Right)
} else { } else {
Some(Either::Left(self_param)) Some(Either::Left(self_param))
@ -4321,9 +4321,13 @@ impl Type {
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 => { kind => {
// This branch shouldn't be necessary? // This will happen when it implements fn or fn mut, since we add an autoborrow adjustment
if let TyKind::Ref(_, _, ty) = kind { let (ty, kind) = if let TyKind::Ref(_, _, ty) = kind {
if let TyKind::Closure(closure, subst) = ty.kind(Interner) { (ty, ty.kind(Interner))
} else {
(&self.ty, kind)
};
if let TyKind::Closure(closure, subst) = kind {
let sig = ty.callable_sig(db)?; let sig = ty.callable_sig(db)?;
return Some(Callable { return Some(Callable {
ty: self.clone(), ty: self.clone(),
@ -4332,12 +4336,11 @@ impl Type {
is_bound_method: false, is_bound_method: false,
}); });
} }
} let (fn_trait, sig) = hir_ty::callable_sig_from_fn_trait(ty, self.env.clone(), db)?;
let sig = hir_ty::callable_sig_from_fnonce(&self.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,
}); });
} }
@ -4968,7 +4971,7 @@ enum Callee {
Def(CallableDefId), Def(CallableDefId),
Closure(ClosureId, Substitution), Closure(ClosureId, Substitution),
FnPtr, FnPtr,
Other, FnImpl(FnTrait),
} }
pub enum CallableKind { pub enum CallableKind {
@ -4977,8 +4980,7 @@ pub enum CallableKind {
TupleEnumVariant(Variant), TupleEnumVariant(Variant),
Closure(Closure), Closure(Closure),
FnPtr, FnPtr,
/// Some other type that implements `FnOnce`. FnImpl(FnTrait),
Other,
} }
impl Callable { impl Callable {
@ -4993,7 +4995,7 @@ impl Callable {
CallableKind::Closure(Closure { id, subst: subst.clone() }) CallableKind::Closure(Closure { id, subst: subst.clone() })
} }
Callee::FnPtr => CallableKind::FnPtr, Callee::FnPtr => CallableKind::FnPtr,
Callee::Other => CallableKind::Other, 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)> {
@ -5023,6 +5025,10 @@ impl Callable {
pub fn sig(&self) -> &CallableSig { pub fn sig(&self) -> &CallableSig {
&self.sig &self.sig
} }
pub fn ty(&self) -> &Type {
&self.ty
}
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]

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

@ -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

@ -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

@ -202,9 +202,20 @@ fn signature_help_for_call(
); );
} }
hir::CallableKind::Closure(closure) => { hir::CallableKind::Closure(closure) => {
format_to!(res.signature, "impl {}", closure.fn_trait(db)); let fn_trait = closure.fn_trait(db);
format_to!(res.signature, "impl {fn_trait}")
} }
hir::CallableKind::FnPtr | hir::CallableKind::Other => (), 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('(');
@ -250,7 +261,7 @@ fn signature_help_for_call(
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)
@ -1417,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(
@ -1828,18 +1908,18 @@ 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
^^ --- ^^ ---
"#]], "#]],
); );
@ -1860,7 +1940,7 @@ fn take<C, Error>(
} }
"#, "#,
expect![[r#" expect![[r#"
() -> i32 impl Fn() -> i32
"#]], "#]],
); );
} }