about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir-ty/src/infer.rs1
-rw-r--r--crates/hir-ty/src/infer/expr.rs19
-rw-r--r--crates/hir/src/diagnostics.rs3
-rw-r--r--crates/hir/src/lib.rs5
-rw-r--r--crates/ide-completion/src/context/analysis.rs23
-rw-r--r--crates/ide-completion/src/render.rs6
-rw-r--r--crates/ide-completion/src/render/function.rs15
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_method.rs204
8 files changed, 247 insertions, 29 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index e295dd8d4e1..428cedbb49b 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -217,6 +217,7 @@ pub enum InferenceDiagnostic {
         name: Name,
         /// Contains the type the field resolves to
         field_with_same_name: Option<Ty>,
+        assoc_func_with_same_name: Option<AssocItemId>,
     },
     // FIXME: This should be emitted in body lowering
     BreakOutsideOfLoop {
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index a5e77a12d8c..84954ca7e90 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -1575,11 +1575,30 @@ impl InferenceContext<'_> {
                     }
                     None => None,
                 };
+
+                let assoc_func_with_same_name = method_resolution::iterate_method_candidates(
+                    &canonicalized_receiver.value,
+                    self.db,
+                    self.table.trait_env.clone(),
+                    self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
+                    VisibleFromModule::Filter(self.resolver.module()),
+                    Some(method_name),
+                    method_resolution::LookupMode::Path,
+                    |_ty, item, visible| {
+                        if visible {
+                            Some(item)
+                        } else {
+                            None
+                        }
+                    },
+                );
+
                 self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall {
                     expr: tgt_expr,
                     receiver: receiver_ty.clone(),
                     name: method_name.clone(),
                     field_with_same_name: field_with_same_name_exists,
+                    assoc_func_with_same_name,
                 });
                 (
                     receiver_ty,
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 1cb36f9b021..ba591e18921 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -8,7 +8,7 @@ pub use hir_ty::diagnostics::{CaseType, IncorrectCase};
 use base_db::CrateId;
 use cfg::{CfgExpr, CfgOptions};
 use either::Either;
-use hir_def::path::ModPath;
+use hir_def::{path::ModPath, AssocItemId};
 use hir_expand::{name::Name, HirFileId, InFile};
 use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
 
@@ -215,6 +215,7 @@ pub struct UnresolvedMethodCall {
     pub receiver: Type,
     pub name: Name,
     pub field_with_same_name: Option<Type>,
+    pub assoc_func_with_same_name: Option<AssocItemId>,
 }
 
 #[derive(Debug)]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 85762603ed1..087404ccb09 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1680,6 +1680,7 @@ impl DefWithBody {
                     receiver,
                     name,
                     field_with_same_name,
+                    assoc_func_with_same_name,
                 } => {
                     let expr = expr_syntax(*expr);
 
@@ -1691,6 +1692,7 @@ impl DefWithBody {
                             field_with_same_name: field_with_same_name
                                 .clone()
                                 .map(|ty| Type::new(db, DefWithBodyId::from(self), ty)),
+                            assoc_func_with_same_name: assoc_func_with_same_name.clone(),
                         }
                         .into(),
                     )
@@ -4657,6 +4659,9 @@ impl Callable {
     pub fn return_type(&self) -> Type {
         self.ty.derived(self.sig.ret().clone())
     }
+    pub fn sig(&self) -> &CallableSig {
+        &self.sig
+    }
 }
 
 fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option<ast::ClosureExpr> {
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 2d62c45174f..7da66483657 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -361,7 +361,12 @@ fn expected_type_and_name(
                     let ty = it.pat()
                         .and_then(|pat| sema.type_of_pat(&pat))
                         .or_else(|| it.initializer().and_then(|it| sema.type_of_expr(&it)))
-                        .map(TypeInfo::original);
+                        .map(TypeInfo::original)
+                        .filter(|ty| {
+                            // don't infer the let type if the expr is a function,
+                            // preventing parenthesis from vanishing
+                            it.ty().is_some() || !ty.is_fn()
+                        });
                     let name = match it.pat() {
                         Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
                         Some(_) | None => None,
@@ -415,20 +420,16 @@ fn expected_type_and_name(
                     })().unwrap_or((None, None))
                 },
                 ast::RecordExprField(it) => {
+                    let field_ty = sema.resolve_record_field(&it).map(|(_, _, ty)| ty);
+                    let field_name = it.field_name().map(NameOrNameRef::NameRef);
                     if let Some(expr) = it.expr() {
                         cov_mark::hit!(expected_type_struct_field_with_leading_char);
-                        (
-                            sema.type_of_expr(&expr).map(TypeInfo::original),
-                            it.field_name().map(NameOrNameRef::NameRef),
-                        )
+                        let ty = field_ty
+                            .or_else(|| sema.type_of_expr(&expr).map(TypeInfo::original));
+                        (ty, field_name)
                     } else {
                         cov_mark::hit!(expected_type_struct_field_followed_by_comma);
-                        let ty = sema.resolve_record_field(&it)
-                            .map(|(_, _, ty)| ty);
-                        (
-                            ty,
-                            it.field_name().map(NameOrNameRef::NameRef),
-                        )
+                        (field_ty, field_name)
                     }
                 },
                 // match foo { $0 }
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 2ea3f74d18b..581d557e831 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -837,11 +837,11 @@ fn main() {
 }
 "#,
             expect![[r#"
-                fn main []
-                fn test []
+                fn main() []
+                fn test(…) []
                 md dep []
                 fn function (use dep::test_mod_a::function) [requires_import]
-                fn function (use dep::test_mod_b::function) [requires_import]
+                fn function(…) (use dep::test_mod_b::function) [requires_import]
             "#]],
         );
     }
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index d23ed71fdcc..b306bede653 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -305,12 +305,15 @@ fn params(
         return None;
     }
 
-    // Don't add parentheses if the expected type is some function reference.
-    if let Some(ty) = &ctx.expected_type {
-        // FIXME: check signature matches?
-        if ty.is_fn() {
-            cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
-            return None;
+    // Don't add parentheses if the expected type is a function reference with the same signature.
+    if let Some(expected) = ctx.expected_type.as_ref().filter(|e| e.is_fn()) {
+        if let Some(expected) = expected.as_callable(ctx.db) {
+            if let Some(completed) = func.ty(ctx.db).as_callable(ctx.db) {
+                if expected.sig() == completed.sig() {
+                    cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
+                    return None;
+                }
+            }
         }
     }
 
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index 464b0a710ea..60a45a05a4a 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -1,11 +1,14 @@
-use hir::{db::ExpandDatabase, HirDisplay};
+use hir::{db::ExpandDatabase, AssocItem, HirDisplay, InFile};
 use ide_db::{
     assists::{Assist, AssistId, AssistKind},
     base_db::FileRange,
     label::Label,
     source_change::SourceChange,
 };
-use syntax::{ast, AstNode, TextRange};
+use syntax::{
+    ast::{self, make, HasArgList},
+    AstNode, SmolStr, TextRange,
+};
 use text_edit::TextEdit;
 
 use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
@@ -17,15 +20,17 @@ pub(crate) fn unresolved_method(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::UnresolvedMethodCall,
 ) -> Diagnostic {
-    let field_suffix = if d.field_with_same_name.is_some() {
+    let suffix = if d.field_with_same_name.is_some() {
         ", but a field with a similar name exists"
+    } else if d.assoc_func_with_same_name.is_some() {
+        ", but an associated function with a similar name exists"
     } else {
         ""
     };
     Diagnostic::new(
         DiagnosticCode::RustcHardError("E0599"),
         format!(
-            "no method `{}` on type `{}`{field_suffix}",
+            "no method `{}` on type `{}`{suffix}",
             d.name.display(ctx.sema.db),
             d.receiver.display(ctx.sema.db)
         ),
@@ -46,11 +51,27 @@ pub(crate) fn unresolved_method(
 }
 
 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> {
-    if let Some(ty) = &d.field_with_same_name {
+    let field_fix = if let Some(ty) = &d.field_with_same_name {
         field_fix(ctx, d, ty)
     } else {
         // FIXME: add quickfix
         None
+    };
+
+    let assoc_func_fix = assoc_func_fix(ctx, d);
+
+    let mut fixes = vec![];
+    if let Some(field_fix) = field_fix {
+        fixes.push(field_fix);
+    }
+    if let Some(assoc_func_fix) = assoc_func_fix {
+        fixes.push(assoc_func_fix);
+    }
+
+    if fixes.is_empty() {
+        None
+    } else {
+        Some(fixes)
     }
 }
 
@@ -58,7 +79,7 @@ fn field_fix(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::UnresolvedMethodCall,
     ty: &hir::Type,
-) -> Option<Vec<Assist>> {
+) -> Option<Assist> {
     if !ty.impls_fnonce(ctx.sema.db) {
         return None;
     }
@@ -78,7 +99,7 @@ fn field_fix(
         }
         _ => return None,
     };
-    Some(vec![Assist {
+    Some(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,
@@ -88,7 +109,95 @@ fn field_fix(
             (file_id, TextEdit::insert(range.end(), ")".to_owned())),
         ])),
         trigger_signature_help: false,
-    }])
+    })
+}
+
+fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Assist> {
+    if let Some(assoc_item_id) = d.assoc_func_with_same_name {
+        let db = ctx.sema.db;
+
+        let expr_ptr = &d.expr;
+        let root = db.parse_or_expand(expr_ptr.file_id);
+        let expr: ast::Expr = expr_ptr.value.to_node(&root);
+
+        let call = ast::MethodCallExpr::cast(expr.syntax().clone())?;
+        let range = InFile::new(expr_ptr.file_id, call.syntax().text_range())
+            .original_node_file_range_rooted(db)
+            .range;
+
+        let receiver = call.receiver()?;
+        let receiver_type = &ctx.sema.type_of_expr(&receiver)?.original;
+
+        let need_to_take_receiver_as_first_arg = match hir::AssocItem::from(assoc_item_id) {
+            AssocItem::Function(f) => {
+                let assoc_fn_params = f.assoc_fn_params(db);
+                if assoc_fn_params.is_empty() {
+                    false
+                } else {
+                    assoc_fn_params
+                        .first()
+                        .map(|first_arg| {
+                            // For generic type, say `Box`, take `Box::into_raw(b: Self)` as example,
+                            // type of `b` is `Self`, which is `Box<T, A>`, containing unspecified generics.
+                            // However, type of `receiver` is specified, it could be `Box<i32, Global>` or something like that,
+                            // so `first_arg.ty() == receiver_type` evaluate to `false` here.
+                            // Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard,
+                            // apply `.as_adt()` over `Box<T, A>` or `Box<i32, Global>` gets `Box`, so we get `true` here.
+
+                            // FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver`
+                            first_arg.ty() == receiver_type
+                                || first_arg.ty().as_adt() == receiver_type.as_adt()
+                        })
+                        .unwrap_or(false)
+                }
+            }
+            _ => false,
+        };
+
+        let mut receiver_type_adt_name = receiver_type.as_adt()?.name(db).to_smol_str().to_string();
+
+        let generic_parameters: Vec<SmolStr> = receiver_type.generic_parameters(db).collect();
+        // if receiver should be pass as first arg in the assoc func,
+        // we could omit generic parameters cause compiler can deduce it automatically
+        if !need_to_take_receiver_as_first_arg && !generic_parameters.is_empty() {
+            let generic_parameters = generic_parameters.join(", ").to_string();
+            receiver_type_adt_name =
+                format!("{}::<{}>", receiver_type_adt_name, generic_parameters);
+        }
+
+        let method_name = call.name_ref()?;
+        let assoc_func_call = format!("{}::{}()", receiver_type_adt_name, method_name);
+
+        let assoc_func_call = make::expr_path(make::path_from_text(&assoc_func_call));
+
+        let args: Vec<_> = if need_to_take_receiver_as_first_arg {
+            std::iter::once(receiver).chain(call.arg_list()?.args()).collect()
+        } else {
+            call.arg_list()?.args().collect()
+        };
+        let args = make::arg_list(args);
+
+        let assoc_func_call_expr_string = make::expr_call(assoc_func_call, args).to_string();
+
+        let file_id = ctx.sema.original_range_opt(call.receiver()?.syntax())?.file_id;
+
+        Some(Assist {
+            id: AssistId("method_call_to_assoc_func_call_fix", AssistKind::QuickFix),
+            label: Label::new(format!(
+                "Use associated func call instead: `{}`",
+                assoc_func_call_expr_string
+            )),
+            group: None,
+            target: range,
+            source_change: Some(SourceChange::from_text_edit(
+                file_id,
+                TextEdit::replace(range, assoc_func_call_expr_string),
+            )),
+            trigger_signature_help: false,
+        })
+    } else {
+        None
+    }
 }
 
 #[cfg(test)]
@@ -96,6 +205,85 @@ mod tests {
     use crate::tests::{check_diagnostics, check_fix};
 
     #[test]
+    fn test_assoc_func_fix() {
+        check_fix(
+            r#"
+struct A {}
+
+impl A {
+    fn hello() {}
+}
+fn main() {
+    let a = A{};
+    a.hello$0();
+}
+"#,
+            r#"
+struct A {}
+
+impl A {
+    fn hello() {}
+}
+fn main() {
+    let a = A{};
+    A::hello();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_assoc_func_diagnostic() {
+        check_diagnostics(
+            r#"
+struct A {}
+impl A {
+    fn hello() {}
+}
+fn main() {
+    let a = A{};
+    a.hello();
+   // ^^^^^ 💡 error: no method `hello` on type `A`, but an associated function with a similar name exists
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_assoc_func_fix_with_generic() {
+        check_fix(
+            r#"
+struct A<T, U> {
+    a: T,
+    b: U
+}
+
+impl<T, U> A<T, U> {
+    fn foo() {}
+}
+fn main() {
+    let a = A {a: 0, b: ""};
+    a.foo()$0;
+}
+"#,
+            r#"
+struct A<T, U> {
+    a: T,
+    b: U
+}
+
+impl<T, U> A<T, U> {
+    fn foo() {}
+}
+fn main() {
+    let a = A {a: 0, b: ""};
+    A::<i32, &str>::foo();
+}
+"#,
+        );
+    }
+
+    #[test]
     fn smoke_test() {
         check_diagnostics(
             r#"