From 3c7a0aa00ee4d574af36a1ab982a63488acb211a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Mar 2023 18:04:24 +0100 Subject: [PATCH] Diagnose call expression on non-callable things --- crates/hir-ty/src/infer.rs | 9 +++++ crates/hir-ty/src/infer/expr.rs | 8 +++- crates/hir/src/diagnostics.rs | 7 ++++ crates/hir/src/lib.rs | 33 +++++++++------- .../src/handlers/expected_function.rs | 38 +++++++++++++++++++ crates/ide-diagnostics/src/lib.rs | 2 + 6 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/expected_function.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 336de142821..0c7529cffee 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -170,6 +170,7 @@ pub enum InferenceDiagnostic { // FIXME: Make this proper BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, + ExpectedFunction { call_expr: ExprId, found: Ty }, } /// 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.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() { *subst = table.resolve_completely(subst.clone()); } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index e64b020c7fb..9d75b67bc78 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -364,7 +364,13 @@ impl<'a> InferenceContext<'a> { } (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); self.register_obligations_for_call(&callee_ty); diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index bb7468d4660..3a6f26a4ea0 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -31,6 +31,7 @@ macro_rules! diagnostics { diagnostics![ BreakOutsideOfLoop, + ExpectedFunction, InactiveCode, IncorrectCase, InvalidDeriveTarget, @@ -130,6 +131,12 @@ pub struct PrivateAssocItem { pub item: AssocItem, } +#[derive(Debug)] +pub struct ExpectedFunction { + pub call: InFile>, + pub found: Type, +} + #[derive(Debug)] pub struct PrivateField { pub expr: InFile>, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index bfc0d58cc78..08ccf38b654 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -84,9 +84,9 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, - MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, - MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, + AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase, + InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, + MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, @@ -1377,8 +1377,8 @@ impl DefWithBody { let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); for d in &infer.diagnostics { match d { - hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(*expr); + &hir_ty::InferenceDiagnostic::NoSuchField { expr } => { + let field = source_map.field_syntax(expr); acc.push(NoSuchField { field }.into()) } &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { @@ -1391,15 +1391,10 @@ impl DefWithBody { .expect("break outside of loop in synthetic syntax"); acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) } - hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - match source_map.expr_syntax(*call_expr) { + &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { + match source_map.expr_syntax(call_expr) { Ok(source_ptr) => acc.push( - MismatchedArgCount { - call_expr: source_ptr, - expected: *expected, - found: *found, - } - .into(), + MismatchedArgCount { call_expr: source_ptr, expected, found }.into(), ), Err(SyntheticSyntax) => (), } @@ -1423,6 +1418,18 @@ impl DefWithBody { let item = 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() { diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs new file mode 100644 index 00000000000..23bc778da29 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/expected_function.rs @@ -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(); +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 64ba08ac883..b878119fee1 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -27,6 +27,7 @@ mod handlers { pub(crate) mod break_outside_of_loop; + pub(crate) mod expected_function; pub(crate) mod inactive_code; pub(crate) mod incorrect_case; pub(crate) mod invalid_derive_target; @@ -248,6 +249,7 @@ pub fn diagnostics( #[rustfmt::skip] let d = match diag { 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::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d),