2023-03-13 15:33:52 +00:00
|
|
|
use hir::{db::ExpandDatabase, HirDisplay};
|
2023-03-03 19:41:17 +00:00
|
|
|
use ide_db::{
|
|
|
|
assists::{Assist, AssistId, AssistKind},
|
|
|
|
base_db::FileRange,
|
|
|
|
label::Label,
|
|
|
|
source_change::SourceChange,
|
|
|
|
};
|
|
|
|
use syntax::{ast, AstNode, TextRange};
|
|
|
|
use text_edit::TextEdit;
|
|
|
|
|
2023-12-08 17:46:36 +00:00
|
|
|
use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
|
2023-03-03 19:41:17 +00:00
|
|
|
|
|
|
|
// Diagnostic: unresolved-method
|
|
|
|
//
|
|
|
|
// This diagnostic is triggered if a method does not exist on a given type.
|
|
|
|
pub(crate) fn unresolved_method(
|
|
|
|
ctx: &DiagnosticsContext<'_>,
|
|
|
|
d: &hir::UnresolvedMethodCall,
|
|
|
|
) -> Diagnostic {
|
|
|
|
let field_suffix = if d.field_with_same_name.is_some() {
|
|
|
|
", but a field with a similar name exists"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
2023-12-08 17:46:36 +00:00
|
|
|
Diagnostic::new(
|
2023-06-14 22:17:22 +00:00
|
|
|
DiagnosticCode::RustcHardError("E0599"),
|
2023-03-03 19:41:17 +00:00
|
|
|
format!(
|
|
|
|
"no method `{}` on type `{}`{field_suffix}",
|
2023-05-24 16:04:29 +00:00
|
|
|
d.name.display(ctx.sema.db),
|
2023-03-03 19:41:17 +00:00
|
|
|
d.receiver.display(ctx.sema.db)
|
|
|
|
),
|
2023-12-08 17:46:36 +00:00
|
|
|
adjusted_display_range_new(ctx, d.expr, &|expr| {
|
|
|
|
Some(
|
|
|
|
match expr {
|
|
|
|
ast::Expr::MethodCallExpr(it) => it.name_ref(),
|
|
|
|
ast::Expr::FieldExpr(it) => it.name_ref(),
|
|
|
|
_ => None,
|
|
|
|
}?
|
|
|
|
.syntax()
|
|
|
|
.text_range(),
|
|
|
|
)
|
|
|
|
}),
|
2023-03-03 19:41:17 +00:00
|
|
|
)
|
|
|
|
.with_fixes(fixes(ctx, d))
|
2023-03-06 21:24:38 +00:00
|
|
|
.experimental()
|
2023-03-03 19:41:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> {
|
|
|
|
if let Some(ty) = &d.field_with_same_name {
|
|
|
|
field_fix(ctx, d, ty)
|
|
|
|
} else {
|
|
|
|
// FIXME: add quickfix
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn field_fix(
|
|
|
|
ctx: &DiagnosticsContext<'_>,
|
|
|
|
d: &hir::UnresolvedMethodCall,
|
|
|
|
ty: &hir::Type,
|
|
|
|
) -> Option<Vec<Assist>> {
|
|
|
|
if !ty.impls_fnonce(ctx.sema.db) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let expr_ptr = &d.expr;
|
2023-04-16 17:20:48 +00:00
|
|
|
let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
|
2023-03-03 19:41:17 +00:00
|
|
|
let expr = expr_ptr.value.to_node(&root);
|
|
|
|
let (file_id, range) = match expr {
|
|
|
|
ast::Expr::MethodCallExpr(mcall) => {
|
|
|
|
let FileRange { range, file_id } =
|
|
|
|
ctx.sema.original_range_opt(mcall.receiver()?.syntax())?;
|
|
|
|
let FileRange { range: range2, file_id: file_id2 } =
|
|
|
|
ctx.sema.original_range_opt(mcall.name_ref()?.syntax())?;
|
|
|
|
if file_id != file_id2 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
(file_id, TextRange::new(range.start(), range2.end()))
|
|
|
|
}
|
|
|
|
_ => return None,
|
|
|
|
};
|
|
|
|
Some(vec![Assist {
|
|
|
|
id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix),
|
|
|
|
label: Label::new("Use parentheses to call the value of the field".to_string()),
|
|
|
|
group: None,
|
|
|
|
target: range,
|
|
|
|
source_change: Some(SourceChange::from_iter([
|
|
|
|
(file_id, TextEdit::insert(range.start(), "(".to_owned())),
|
|
|
|
(file_id, TextEdit::insert(range.end(), ")".to_owned())),
|
|
|
|
])),
|
|
|
|
trigger_signature_help: false,
|
|
|
|
}])
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::tests::{check_diagnostics, check_fix};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn smoke_test() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
fn main() {
|
|
|
|
().foo();
|
2023-12-08 17:46:36 +00:00
|
|
|
// ^^^ error: no method `foo` on type `()`
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn smoke_test_in_macro_def_site() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
macro_rules! m {
|
|
|
|
($rcv:expr) => {
|
|
|
|
$rcv.foo()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn main() {
|
|
|
|
m!(());
|
|
|
|
// ^^^^^^ error: no method `foo` on type `()`
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn smoke_test_in_macro_call_site() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
macro_rules! m {
|
|
|
|
($ident:ident) => {
|
|
|
|
().$ident()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn main() {
|
|
|
|
m!(foo);
|
|
|
|
// ^^^ error: no method `foo` on type `()`
|
2023-03-03 19:41:17 +00:00
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn field() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
struct Foo { bar: i32 }
|
|
|
|
fn foo() {
|
|
|
|
Foo { bar: i32 }.bar();
|
2023-12-08 17:46:36 +00:00
|
|
|
// ^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists
|
2023-03-03 19:41:17 +00:00
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn callable_field() {
|
|
|
|
check_fix(
|
|
|
|
r#"
|
|
|
|
//- minicore: fn
|
|
|
|
struct Foo { bar: fn() }
|
|
|
|
fn foo() {
|
|
|
|
Foo { bar: foo }.b$0ar();
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
struct Foo { bar: fn() }
|
|
|
|
fn foo() {
|
|
|
|
(Foo { bar: foo }.bar)();
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|