Unnamed repository; edit this file 'description' to name the repository.
Improve error recovery when method-calling a field
Lukas Wirth 2025-02-12
parent 8aa4ae5 · commit c942fb6
-rw-r--r--crates/hir-ty/src/infer/expr.rs204
-rw-r--r--crates/hir-ty/src/tests.rs2
-rw-r--r--crates/hir-ty/src/tests/diagnostics.rs25
3 files changed, 152 insertions, 79 deletions
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 86e5afdb50..41d739a078 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -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,
@@ -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,
@@ -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)
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index 69ec35f406..f5a4d4ff35 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -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;
}
diff --git a/crates/hir-ty/src/tests/diagnostics.rs b/crates/hir-ty/src/tests/diagnostics.rs
index def06f2d59..d0d31bf2a5 100644
--- a/crates/hir-ty/src/tests/diagnostics.rs
+++ b/crates/hir-ty/src/tests/diagnostics.rs
@@ -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}
+}
+"#,
+ );
+}