Repository: rust-analyzer/rust-analyzer
Pull request #8987 (2021-05-25 12:30:45)

Fix lowering of FnOnce() without return type
This should result in an implicit `-> ()`, not leaving out the binding.


Patch:
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 5d5dd9c8f8..54ede73939 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -205,15 +205,14 @@ fn lower_generic_args_from_fn_path(
 ) -> Option<GenericArgs> {
     let mut args = Vec::new();
     let mut bindings = Vec::new();
-    if let Some(params) = params {
-        let mut param_types = Vec::new();
-        for param in params.params() {
-            let type_ref = TypeRef::from_ast_opt(&ctx, param.ty());
-            param_types.push(type_ref);
-        }
-        let arg = GenericArg::Type(TypeRef::Tuple(param_types));
-        args.push(arg);
+    let params = params?;
+    let mut param_types = Vec::new();
+    for param in params.params() {
+        let type_ref = TypeRef::from_ast_opt(&ctx, param.ty());
+        param_types.push(type_ref);
     }
+    let arg = GenericArg::Type(TypeRef::Tuple(param_types));
+    args.push(arg);
     if let Some(ret_type) = ret_type {
         let type_ref = TypeRef::from_ast_opt(&ctx, ret_type.ty());
         bindings.push(AssociatedTypeBinding {
@@ -221,10 +220,14 @@ fn lower_generic_args_from_fn_path(
             type_ref: Some(type_ref),
             bounds: Vec::new(),
         });
-    }
-    if args.is_empty() && bindings.is_empty() {
-        None
     } else {
-        Some(GenericArgs { args, has_self_type: false, bindings })
+        // -> ()
+        let type_ref = TypeRef::Tuple(Vec::new());
+        bindings.push(AssociatedTypeBinding {
+            name: name![Output],
+            type_ref: Some(type_ref),
+            bounds: Vec::new(),
+        });
     }
+    Some(GenericArgs { args, has_self_type: false, bindings })
 }
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 637bbc634d..44f843bf38 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -778,8 +778,10 @@ fn write_bounds_like_dyn_trait(
             }
             WhereClause::AliasEq(alias_eq) if is_fn_trait => {
                 is_fn_trait = false;
-                write!(f, " -> ")?;
-                alias_eq.ty.hir_fmt(f)?;
+                if !alias_eq.ty.is_unit() {
+                    write!(f, " -> ")?;
+                    alias_eq.ty.hir_fmt(f)?;
+                }
             }
             WhereClause::AliasEq(AliasEq { ty, alias }) => {
                 // in types in actual Rust, these will always come


Test patch:
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index a5a2df54cb..eea98fd4ff 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -3041,7 +3041,7 @@ fn infer_fn_trait_arg() {
 
 #[test]
 fn infer_box_fn_arg() {
-    // The type mismatch is a bug
+    // The type mismatch is because we don't define Unsize and CoerceUnsized
     check_infer_with_mismatches(
         r#"
 //- /lib.rs deps:std
@@ -3101,7 +3101,7 @@ fn foo() {
             555..557 'ps': {unknown}
             559..561 '{}': ()
             568..569 'f': Box<dyn FnOnce(&Option<i32>)>
-            568..573 'f(&s)': FnOnce::Output<dyn FnOnce(&Option<i32>), (&Option<i32>,)>
+            568..573 'f(&s)': ()
             570..572 '&s': &Option<i32>
             571..572 's': Option<i32>
             549..562: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|{unknown}| -> ()>
@@ -3571,3 +3571,25 @@ fn main() {
         "#]],
     )
 }
+
+#[test]
+fn fn_returning_unit() {
+    check_infer_with_mismatches(
+        r#"
+#[lang = "fn_once"]
+trait FnOnce<Args> {
+    type Output;
+}
+
+fn test<F: FnOnce()>(f: F) {
+    let _: () = f();
+}"#,
+        expect![[r#"
+            82..83 'f': F
+            88..112 '{     ...f(); }': ()
+            98..99 '_': ()
+            106..107 'f': F
+            106..109 'f()': ()
+        "#]],
+    );
+}