Diagnose call expression on non-callable things

This commit is contained in:
Lukas Wirth 2023-03-03 18:04:24 +01:00
parent 3ba876a4a6
commit 3c7a0aa00e
6 changed files with 83 additions and 14 deletions

View file

@ -170,6 +170,7 @@ pub enum InferenceDiagnostic {
// FIXME: Make this proper // FIXME: Make this proper
BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool }, BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool },
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
ExpectedFunction { call_expr: ExprId, found: Ty },
} }
/// A mismatch between an expected and an inferred type. /// A mismatch between an expected and an inferred type.
@ -505,6 +506,14 @@ impl<'a> InferenceContext<'a> {
mismatch.expected = table.resolve_completely(mismatch.expected.clone()); mismatch.expected = table.resolve_completely(mismatch.expected.clone());
mismatch.actual = table.resolve_completely(mismatch.actual.clone()); mismatch.actual = table.resolve_completely(mismatch.actual.clone());
} }
for diagnostic in &mut result.diagnostics {
match diagnostic {
InferenceDiagnostic::ExpectedFunction { found, .. } => {
*found = table.resolve_completely(found.clone())
}
_ => (),
}
}
for (_, subst) in result.method_resolutions.values_mut() { for (_, subst) in result.method_resolutions.values_mut() {
*subst = table.resolve_completely(subst.clone()); *subst = table.resolve_completely(subst.clone());
} }

View file

@ -364,7 +364,13 @@ impl<'a> InferenceContext<'a> {
} }
(params, ret_ty) (params, ret_ty)
} }
None => (Vec::new(), self.err_ty()), // FIXME diagnostic None => {
self.result.diagnostics.push(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); let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
self.register_obligations_for_call(&callee_ty); self.register_obligations_for_call(&callee_ty);

View file

@ -31,6 +31,7 @@ macro_rules! diagnostics {
diagnostics![ diagnostics![
BreakOutsideOfLoop, BreakOutsideOfLoop,
ExpectedFunction,
InactiveCode, InactiveCode,
IncorrectCase, IncorrectCase,
InvalidDeriveTarget, InvalidDeriveTarget,
@ -130,6 +131,12 @@ pub struct PrivateAssocItem {
pub item: AssocItem, pub item: AssocItem,
} }
#[derive(Debug)]
pub struct ExpectedFunction {
pub call: InFile<AstPtr<ast::Expr>>,
pub found: Type,
}
#[derive(Debug)] #[derive(Debug)]
pub struct PrivateField { pub struct PrivateField {
pub expr: InFile<AstPtr<ast::Expr>>, pub expr: InFile<AstPtr<ast::Expr>>,

View file

@ -84,9 +84,9 @@ use crate::db::{DefDatabase, HirDatabase};
pub use crate::{ pub use crate::{
attrs::{HasAttrs, Namespace}, attrs::{HasAttrs, Namespace},
diagnostics::{ diagnostics::{
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase,
MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
UnresolvedProcMacro, UnresolvedProcMacro,
@ -1377,8 +1377,8 @@ impl DefWithBody {
let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
for d in &infer.diagnostics { for d in &infer.diagnostics {
match d { match d {
hir_ty::InferenceDiagnostic::NoSuchField { expr } => { &hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
let field = source_map.field_syntax(*expr); let field = source_map.field_syntax(expr);
acc.push(NoSuchField { field }.into()) acc.push(NoSuchField { field }.into())
} }
&hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop {
@ -1391,15 +1391,10 @@ impl DefWithBody {
.expect("break outside of loop in synthetic syntax"); .expect("break outside of loop in synthetic syntax");
acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into())
} }
hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
match source_map.expr_syntax(*call_expr) { match source_map.expr_syntax(call_expr) {
Ok(source_ptr) => acc.push( Ok(source_ptr) => acc.push(
MismatchedArgCount { MismatchedArgCount { call_expr: source_ptr, expected, found }.into(),
call_expr: source_ptr,
expected: *expected,
found: *found,
}
.into(),
), ),
Err(SyntheticSyntax) => (), Err(SyntheticSyntax) => (),
} }
@ -1423,6 +1418,18 @@ impl DefWithBody {
let item = item.into(); let item = item.into();
acc.push(PrivateAssocItem { expr_or_pat, item }.into()) acc.push(PrivateAssocItem { expr_or_pat, item }.into())
} }
hir_ty::InferenceDiagnostic::ExpectedFunction { call_expr, found } => {
let call_expr =
source_map.expr_syntax(*call_expr).expect("unexpected synthetic");
acc.push(
ExpectedFunction {
call: call_expr,
found: Type::new(db, DefWithBodyId::from(self), found.clone()),
}
.into(),
)
}
} }
} }
for (pat_or_expr, mismatch) in infer.type_mismatches() { for (pat_or_expr, mismatch) in infer.type_mismatches() {

View file

@ -0,0 +1,38 @@
use hir::HirDisplay;
use crate::{Diagnostic, DiagnosticsContext};
// Diagnostic: expected-function
//
// This diagnostic is triggered if a call is made on something that is not callable.
pub(crate) fn expected_function(
ctx: &DiagnosticsContext<'_>,
d: &hir::ExpectedFunction,
) -> Diagnostic {
Diagnostic::new(
"expected-function",
format!("expected function, found {}", d.found.display(ctx.sema.db)),
ctx.sema.diagnostics_display_range(d.call.clone().map(|it| it.into())).range,
)
}
#[cfg(test)]
mod tests {
use crate::tests::check_diagnostics;
#[test]
fn smoke_test() {
check_diagnostics(
r#"
fn foo() {
let x = 3;
x();
// ^^^ error: expected function, found i32
""();
// ^^^^ error: expected function, found &str
foo();
}
"#,
);
}
}

View file

@ -27,6 +27,7 @@
mod handlers { mod handlers {
pub(crate) mod break_outside_of_loop; pub(crate) mod break_outside_of_loop;
pub(crate) mod expected_function;
pub(crate) mod inactive_code; pub(crate) mod inactive_code;
pub(crate) mod incorrect_case; pub(crate) mod incorrect_case;
pub(crate) mod invalid_derive_target; pub(crate) mod invalid_derive_target;
@ -248,6 +249,7 @@ pub fn diagnostics(
#[rustfmt::skip] #[rustfmt::skip]
let d = match diag { let d = match diag {
AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d),
AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d), AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),