First attempt at generic type inference for fns

This commit is contained in:
Marcus Klaas de Vries 2019-01-25 21:16:02 +01:00
parent 3f4f50baaa
commit 3bd47c0285
6 changed files with 81 additions and 21 deletions

View File

@ -388,7 +388,7 @@ pub use crate::code_model_impl::function::ScopeEntryWithSyntax;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FnSignature {
pub(crate) name: Name,
pub(crate) params: Vec<TypeRef>,
pub(crate) args: Vec<TypeRef>,
pub(crate) ret_type: TypeRef,
/// True if the first param is `self`. This is relevant to decide whether this
/// can be called as a method.
@ -400,8 +400,8 @@ impl FnSignature {
&self.name
}
pub fn params(&self) -> &[TypeRef] {
&self.params
pub fn args(&self) -> &[TypeRef] {
&self.args
}
pub fn ret_type(&self) -> &TypeRef {

View File

@ -32,7 +32,7 @@ impl FnSignature {
.name()
.map(|n| n.as_name())
.unwrap_or_else(Name::missing);
let mut params = Vec::new();
let mut args = Vec::new();
let mut has_self_param = false;
if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() {
@ -50,12 +50,12 @@ impl FnSignature {
}
}
};
params.push(self_type);
args.push(self_type);
has_self_param = true;
}
for param in param_list.params() {
let type_ref = TypeRef::from_ast_opt(param.type_ref());
params.push(type_ref);
args.push(type_ref);
}
}
let ret_type = if let Some(type_ref) = node.ret_type().and_then(|rt| rt.type_ref()) {
@ -66,7 +66,7 @@ impl FnSignature {
let sig = FnSignature {
name,
params,
args,
ret_type,
has_self_param,
};

View File

@ -49,7 +49,8 @@ impl GenericParams {
Arc::new(generics)
}
fn fill(&mut self, node: &impl TypeParamsOwner) {
// FIXME: probably shouldnt be pub(crate)
pub(crate) fn fill(&mut self, node: &impl TypeParamsOwner) {
if let Some(params) = node.type_param_list() {
self.fill_params(params)
}

View File

@ -209,6 +209,18 @@ pub enum Ty {
/// `&'a mut T` or `&'a T`.
Ref(Arc<Ty>, Mutability),
/// The anonymous type of a function declaration/definition. Each
/// function has a unique type, which is output (for a function
/// named `foo` returning an `i32`) as `fn() -> i32 {foo}`.
///
/// For example the type of `bar` here:
///
/// ```rust
/// fn foo() -> i32 { 1 }
/// let bar = foo; // bar: fn() -> i32 {foo}
/// ```
FnDef(Function, Substs),
/// A pointer to a function. Written as `fn() -> i32`.
///
/// For example the type of `bar` here:
@ -485,7 +497,7 @@ impl Ty {
}
sig_mut.output.walk_mut(f);
}
Ty::Adt { substs, .. } => {
Ty::FnDef(_, substs) | Ty::Adt { substs, .. } => {
// Without an Arc::make_mut_slice, we can't avoid the clone here:
let mut v: Vec<_> = substs.0.iter().cloned().collect();
for t in &mut v {
@ -524,6 +536,7 @@ impl Ty {
name,
substs,
},
Ty::FnDef(func, _) => Ty::FnDef(func, substs),
_ => self,
}
}
@ -579,6 +592,7 @@ impl fmt::Display for Ty {
.to_fmt(f)
}
}
Ty::FnDef(_func, _substs) => write!(f, "FNDEF-IMPLEMENT-ME"),
Ty::FnPtr(sig) => {
join(sig.input.iter())
.surround_with("fn(", ")")
@ -608,12 +622,18 @@ impl fmt::Display for Ty {
/// Compute the declared type of a function. This should not need to look at the
/// function body.
fn type_for_fn(db: &impl HirDatabase, f: Function) -> Ty {
let generics = f.generic_params(db);
let substs = make_substs(&generics);
Ty::FnDef(f.into(), substs)
}
fn get_func_sig(db: &impl HirDatabase, f: Function) -> FnSig {
let signature = f.signature(db);
let module = f.module(db);
let impl_block = f.impl_block(db);
let generics = f.generic_params(db);
let input = signature
.params()
.args()
.iter()
.map(|tr| Ty::from_hir(db, &module, impl_block.as_ref(), &generics, tr))
.collect::<Vec<_>>();
@ -624,8 +644,7 @@ fn type_for_fn(db: &impl HirDatabase, f: Function) -> Ty {
&generics,
signature.ret_type(),
);
let sig = FnSig { input, output };
Ty::FnPtr(Arc::new(sig))
FnSig { input, output }
}
fn make_substs(generics: &GenericParams) -> Substs {
@ -1142,7 +1161,13 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
let ty = self.insert_type_vars(ty.apply_substs(substs));
(ty, Some(var.into()))
}
TypableDef::Enum(_) | TypableDef::Function(_) => (Ty::Unknown, None),
TypableDef::Function(func) => {
let ty = type_for_fn(self.db, func);
let ty = self.insert_type_vars(ty.apply_substs(substs));
// FIXME: is this right?
(ty, None)
}
TypableDef::Enum(_) => (Ty::Unknown, None),
}
}
@ -1331,12 +1356,27 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
}
Expr::Call { callee, args } => {
let callee_ty = self.infer_expr(*callee, &Expectation::none());
// FIXME: so manu unnecessary clones
let (param_tys, ret_ty) = match &callee_ty {
Ty::FnPtr(sig) => (&sig.input[..], sig.output.clone()),
Ty::FnPtr(sig) => (sig.input.clone(), sig.output.clone()),
Ty::FnDef(func, substs) => {
let fn_sig = func.signature(self.db);
// TODO: get input and return types from the fn_sig.
// it contains typerefs which we can make into proper tys
let sig = get_func_sig(self.db, *func);
(
sig.input
.iter()
.map(|ty| ty.clone().subst(&substs))
.collect(),
sig.output.clone().subst(&substs),
)
}
_ => {
// not callable
// TODO report an error?
(&[][..], Ty::Unknown)
(Vec::new(), Ty::Unknown)
}
};
for (i, arg) in args.iter().enumerate() {
@ -1604,15 +1644,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
fn collect_fn_signature(&mut self, signature: &FnSignature) {
let body = Arc::clone(&self.body); // avoid borrow checker problem
for (type_ref, pat) in signature.params().iter().zip(body.params()) {
for (type_ref, pat) in signature.args().iter().zip(body.params()) {
let ty = self.make_ty(type_ref);
self.infer_pat(*pat, &ty);
}
self.return_ty = {
let ty = self.make_ty(signature.ret_type());
ty
};
self.return_ty = self.make_ty(signature.ret_type());
}
fn infer_body(&mut self) {

View File

@ -594,6 +594,28 @@ fn test() {
);
}
#[test]
fn infer_type_param() {
check_inference(
"generic_fn",
r#"
fn id<T>(x: T) -> T {
x
}
fn clone<T>(x: &T) -> T {
x
}
fn test() {
let y = 10u32;
id(y);
let x: bool = clone(z);
}
"#,
);
}
fn infer(content: &str) -> String {
let (db, _, file_id) = MockDatabase::with_single_file(content);
let source_file = db.parse(file_id);

View File

@ -240,7 +240,7 @@ impl Builder {
if ctx.use_item_syntax.is_none() && !ctx.is_call {
tested_by!(inserts_parens_for_function_calls);
let sig = function.signature(ctx.db);
if sig.params().is_empty() || sig.has_self_param() && sig.params().len() == 1 {
if sig.args().is_empty() || sig.has_self_param() && sig.args().len() == 1 {
self.insert_text = Some(format!("{}()$0", self.label));
} else {
self.insert_text = Some(format!("{}($0)", self.label));