]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-diagnostics / src / handlers / unresolved_method.rs
CommitLineData
353b0b11
FG
1use hir::{db::ExpandDatabase, HirDisplay};
2use ide_db::{
3 assists::{Assist, AssistId, AssistKind},
4 base_db::FileRange,
5 label::Label,
6 source_change::SourceChange,
7};
8use syntax::{ast, AstNode, TextRange};
9use text_edit::TextEdit;
10
4b012472 11use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
353b0b11
FG
12
13// Diagnostic: unresolved-method
14//
15// This diagnostic is triggered if a method does not exist on a given type.
16pub(crate) fn unresolved_method(
17 ctx: &DiagnosticsContext<'_>,
18 d: &hir::UnresolvedMethodCall,
19) -> Diagnostic {
20 let field_suffix = if d.field_with_same_name.is_some() {
21 ", but a field with a similar name exists"
22 } else {
23 ""
24 };
4b012472 25 Diagnostic::new(
add651ee 26 DiagnosticCode::RustcHardError("E0599"),
353b0b11
FG
27 format!(
28 "no method `{}` on type `{}`{field_suffix}",
fe692bf9 29 d.name.display(ctx.sema.db),
353b0b11
FG
30 d.receiver.display(ctx.sema.db)
31 ),
4b012472
FG
32 adjusted_display_range_new(ctx, d.expr, &|expr| {
33 Some(
34 match expr {
35 ast::Expr::MethodCallExpr(it) => it.name_ref(),
36 ast::Expr::FieldExpr(it) => it.name_ref(),
37 _ => None,
38 }?
39 .syntax()
40 .text_range(),
41 )
42 }),
353b0b11
FG
43 )
44 .with_fixes(fixes(ctx, d))
45 .experimental()
46}
47
48fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> {
49 if let Some(ty) = &d.field_with_same_name {
50 field_fix(ctx, d, ty)
51 } else {
52 // FIXME: add quickfix
53 None
54 }
55}
56
57fn field_fix(
58 ctx: &DiagnosticsContext<'_>,
59 d: &hir::UnresolvedMethodCall,
60 ty: &hir::Type,
61) -> Option<Vec<Assist>> {
62 if !ty.impls_fnonce(ctx.sema.db) {
63 return None;
64 }
65 let expr_ptr = &d.expr;
fe692bf9 66 let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
353b0b11
FG
67 let expr = expr_ptr.value.to_node(&root);
68 let (file_id, range) = match expr {
69 ast::Expr::MethodCallExpr(mcall) => {
70 let FileRange { range, file_id } =
71 ctx.sema.original_range_opt(mcall.receiver()?.syntax())?;
72 let FileRange { range: range2, file_id: file_id2 } =
73 ctx.sema.original_range_opt(mcall.name_ref()?.syntax())?;
74 if file_id != file_id2 {
75 return None;
76 }
77 (file_id, TextRange::new(range.start(), range2.end()))
78 }
79 _ => return None,
80 };
81 Some(vec![Assist {
82 id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix),
83 label: Label::new("Use parentheses to call the value of the field".to_string()),
84 group: None,
85 target: range,
86 source_change: Some(SourceChange::from_iter([
87 (file_id, TextEdit::insert(range.start(), "(".to_owned())),
88 (file_id, TextEdit::insert(range.end(), ")".to_owned())),
89 ])),
90 trigger_signature_help: false,
91 }])
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::tests::{check_diagnostics, check_fix};
97
98 #[test]
99 fn smoke_test() {
100 check_diagnostics(
101 r#"
102fn main() {
103 ().foo();
4b012472
FG
104 // ^^^ error: no method `foo` on type `()`
105}
106"#,
107 );
108 }
109
110 #[test]
111 fn smoke_test_in_macro_def_site() {
112 check_diagnostics(
113 r#"
114macro_rules! m {
115 ($rcv:expr) => {
116 $rcv.foo()
117 }
118}
119fn main() {
120 m!(());
121 // ^^^^^^ error: no method `foo` on type `()`
122}
123"#,
124 );
125 }
126
127 #[test]
128 fn smoke_test_in_macro_call_site() {
129 check_diagnostics(
130 r#"
131macro_rules! m {
132 ($ident:ident) => {
133 ().$ident()
134 }
135}
136fn main() {
137 m!(foo);
138 // ^^^ error: no method `foo` on type `()`
353b0b11
FG
139}
140"#,
141 );
142 }
143
144 #[test]
145 fn field() {
146 check_diagnostics(
147 r#"
148struct Foo { bar: i32 }
149fn foo() {
150 Foo { bar: i32 }.bar();
4b012472 151 // ^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists
353b0b11
FG
152}
153"#,
154 );
155 }
156
157 #[test]
158 fn callable_field() {
159 check_fix(
160 r#"
161//- minicore: fn
162struct Foo { bar: fn() }
163fn foo() {
164 Foo { bar: foo }.b$0ar();
165}
166"#,
167 r#"
168struct Foo { bar: fn() }
169fn foo() {
170 (Foo { bar: foo }.bar)();
171}
172"#,
173 );
174 }
175}