Skip to content

Commit

Permalink
Improve error recovery when method-calling a field
Browse files Browse the repository at this point in the history
  • Loading branch information
Veykril committed Feb 12, 2025
1 parent 8aa4ae5 commit c942fb6
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 79 deletions.
204 changes: 126 additions & 78 deletions crates/hir-ty/src/infer/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,78 +489,7 @@ impl InferenceContext<'_> {

ty
}
Expr::Call { callee, args, .. } => {
let callee_ty = self.infer_expr(*callee, &Expectation::none(), ExprIsRead::Yes);
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false, true);
let (res, derefed_callee) = loop {
let Some((callee_deref_ty, _)) = derefs.next() else {
break (None, callee_ty.clone());
};
if let Some(res) = derefs.table.callable_sig(&callee_deref_ty, args.len()) {
break (Some(res), callee_deref_ty);
}
};
// if the function is unresolved, we use is_varargs=true to
// suppress the arg count diagnostic here
let is_varargs =
derefed_callee.callable_sig(self.db).is_some_and(|sig| sig.is_varargs)
|| res.is_none();
let (param_tys, ret_ty) = match res {
Some((func, params, ret_ty)) => {
let mut adjustments = auto_deref_adjust_steps(&derefs);
if let TyKind::Closure(c, _) =
self.table.resolve_completely(callee_ty.clone()).kind(Interner)
{
if let Some(par) = self.current_closure {
self.closure_dependencies.entry(par).or_default().push(*c);
}
self.deferred_closures.entry(*c).or_default().push((
derefed_callee.clone(),
callee_ty.clone(),
params.clone(),
tgt_expr,
));
}
if let Some(fn_x) = func {
self.write_fn_trait_method_resolution(
fn_x,
&derefed_callee,
&mut adjustments,
&callee_ty,
&params,
tgt_expr,
);
}
self.write_expr_adj(*callee, adjustments);
(params, ret_ty)
}
None => {
self.push_diagnostic(InferenceDiagnostic::ExpectedFunction {
call_expr: tgt_expr,
found: callee_ty.clone(),
});
(Vec::new(), self.err_ty())
}
};
let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
self.register_obligations_for_call(&callee_ty);

let expected_inputs = self.expected_inputs_for_expected_output(
expected,
ret_ty.clone(),
param_tys.clone(),
);

self.check_call_arguments(
tgt_expr,
args,
&expected_inputs,
&param_tys,
&indices_to_skip,
is_varargs,
);
self.normalize_associated_types_in(ret_ty)
}
Expr::Call { callee, args, .. } => self.infer_call(tgt_expr, *callee, args, expected),
Expr::MethodCall { receiver, args, method_name, generic_args } => self
.infer_method_call(
tgt_expr,
Expand Down Expand Up @@ -1872,6 +1801,107 @@ impl InferenceContext<'_> {
}
}

fn infer_call(
&mut self,
tgt_expr: ExprId,
callee: ExprId,
args: &[ExprId],
expected: &Expectation,
) -> Ty {
let callee_ty = self.infer_expr(callee, &Expectation::none(), ExprIsRead::Yes);
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false, true);
let (res, derefed_callee) = loop {
let Some((callee_deref_ty, _)) = derefs.next() else {
break (None, callee_ty.clone());
};
if let Some(res) = derefs.table.callable_sig(&callee_deref_ty, args.len()) {
break (Some(res), callee_deref_ty);
}
};
// if the function is unresolved, we use is_varargs=true to
// suppress the arg count diagnostic here
let is_varargs =
derefed_callee.callable_sig(self.db).is_some_and(|sig| sig.is_varargs) || res.is_none();
let (param_tys, ret_ty) = match res {
Some((func, params, ret_ty)) => {
let mut adjustments = auto_deref_adjust_steps(&derefs);
if let TyKind::Closure(c, _) =
self.table.resolve_completely(callee_ty.clone()).kind(Interner)
{
if let Some(par) = self.current_closure {
self.closure_dependencies.entry(par).or_default().push(*c);
}
self.deferred_closures.entry(*c).or_default().push((
derefed_callee.clone(),
callee_ty.clone(),
params.clone(),
tgt_expr,
));
}
if let Some(fn_x) = func {
self.write_fn_trait_method_resolution(
fn_x,
&derefed_callee,
&mut adjustments,
&callee_ty,
&params,
tgt_expr,
);
}
self.write_expr_adj(callee, adjustments);
(params, ret_ty)
}
None => {
self.push_diagnostic(InferenceDiagnostic::ExpectedFunction {
call_expr: tgt_expr,
found: callee_ty.clone(),
});
(Vec::new(), self.err_ty())
}
};
let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
self.check_call(
tgt_expr,
args,
callee_ty,
&param_tys,
ret_ty,
&indices_to_skip,
is_varargs,
expected,
)
}

fn check_call(
&mut self,
tgt_expr: ExprId,
args: &[ExprId],
callee_ty: Ty,
param_tys: &[Ty],
ret_ty: Ty,
indices_to_skip: &[u32],
is_varargs: bool,
expected: &Expectation,
) -> Ty {
self.register_obligations_for_call(&callee_ty);

let expected_inputs = self.expected_inputs_for_expected_output(
expected,
ret_ty.clone(),
param_tys.to_owned(),
);

self.check_call_arguments(
tgt_expr,
args,
&expected_inputs,
param_tys,
indices_to_skip,
is_varargs,
);
self.normalize_associated_types_in(ret_ty)
}

fn infer_method_call(
&mut self,
tgt_expr: ExprId,
Expand Down Expand Up @@ -1939,14 +1969,32 @@ impl InferenceContext<'_> {
expr: tgt_expr,
receiver: receiver_ty.clone(),
name: method_name.clone(),
field_with_same_name: field_with_same_name_exists,
field_with_same_name: field_with_same_name_exists.clone(),
assoc_func_with_same_name,
});
(
receiver_ty,
Binders::empty(Interner, self.err_ty()),
Substitution::empty(Interner),
)

return match field_with_same_name_exists {
Some(field_ty) => match field_ty.callable_sig(self.db) {
Some(sig) => self.check_call(
tgt_expr,
args,
field_ty,
sig.params(),
sig.ret().clone(),
&[],
true,
expected,
),
None => {
self.check_call_arguments(tgt_expr, args, &[], &[], &[], true);
field_ty
}
},
None => {
self.check_call_arguments(tgt_expr, args, &[], &[], &[], true);
self.err_ty()
}
};
}
};
self.check_method_call(tgt_expr, args, method_ty, substs, receiver_ty, expected)
Expand Down
2 changes: 1 addition & 1 deletion crates/hir-ty/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ fn check_impl(
expected.trim_start_matches("adjustments:").trim().to_owned(),
);
} else {
panic!("unexpected annotation: {expected}");
panic!("unexpected annotation: {expected} @ {range:?}");
}
had_annotations = true;
}
Expand Down
25 changes: 25 additions & 0 deletions crates/hir-ty/src/tests/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,28 @@ fn consume() -> Option<()> {
"#,
);
}

#[test]
fn method_call_on_field() {
check(
r#"
struct S {
field: fn() -> u32,
field2: u32
}
fn main() {
let s = S { field: || 0, field2: 0 };
s.field(0);
// ^ type: i32
// ^^^^^^^^^^ type: u32
s.field2(0);
// ^ type: i32
// ^^^^^^^^^^^ type: u32
s.not_a_field(0);
// ^ type: i32
// ^^^^^^^^^^^^^^^^ type: {unknown}
}
"#,
);
}

0 comments on commit c942fb6

Please sign in to comment.