Repository: dtolnay/cxx (language: rust)

Issue:
Allow mutable return reference on signatures with any lifetime in argument
As of #608 now that extern types defined in the bridge can be generic over lifetimes, it no longer makes sense to expect specifically a reference in one of the function arguments of a function that returns a mutable reference. The following two signatures would be perfectly valid:

```rust
#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type Thing<'a>;
    }

    unsafe extern "C++" {
        fn f<'a>(t: Box<Thing<'a>>) -> &'a mut i32;
        fn g(t: Box<Thing>) -> &mut i32;  // same thing, with lifetime elision
    }
}

pub struct Thing<'a>(&'a mut i32);
```

Current behavior:

```console
error[cxxbridge]: &mut return type is not allowed unless there is a &mut argument
  ┌─ src/main.rs:8:9
  │
8 │         fn f<'a>(t: Box<Thing<'a>>) -> &'a mut i32;
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ &mut return type is not allowed unless there is a &mut argument

error[cxxbridge]: &mut return type is not allowed unless there is a &mut argument
  ┌─ src/main.rs:9:9
  │
9 │         fn g(t: Box<Thing>) -> &mut i32;
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ &mut return type is not allowed unless there is a &mut argument
```


Discussion / hints:

Fix patch:
diff --git a/syntax/check.rs b/syntax/check.rs
index 807847c02..591e53da9 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -1,5 +1,6 @@
 use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::report::Errors;
+use crate::syntax::visit::{self, Visit};
 use crate::syntax::{
     error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, NamedType, Ptr,
     Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
@@ -518,20 +519,45 @@ fn check_mut_return_restriction(cx: &mut Check, efn: &ExternFn) {
         _ => return,
     }
 
-    if let Some(r) = &efn.receiver {
-        if r.mutable {
+    if let Some(receiver) = &efn.receiver {
+        if receiver.mutable {
+            return;
+        }
+        let resolve = cx.types.resolve(&receiver.ty);
+        if !resolve.generics.lifetimes.is_empty() {
             return;
         }
     }
 
-    for arg in &efn.args {
-        if let Type::Ref(ty) = &arg.ty {
-            if ty.mutable {
-                return;
-            }
+    struct FindLifetimeMut<'a> {
+        cx: &'a Check<'a>,
+        found: bool,
+    }
+
+    impl<'t, 'a> Visit<'t> for FindLifetimeMut<'a> {
+        fn visit_type(&mut self, ty: &'t Type) {
+            self.found |= match ty {
+                Type::Ref(ty) => ty.mutable,
+                Type::Ident(ident) => {
+                    let resolve = self.cx.types.resolve(ident);
+                    !resolve.generics.lifetimes.is_empty()
+                }
+                _ => false,
+            };
+            visit::visit_type(self, ty);
         }
     }
 
+    let mut visitor = FindLifetimeMut { cx, found: false };
+
+    for arg in &efn.args {
+        visitor.visit_type(&arg.ty);
+    }
+
+    if visitor.found {
+        return;
+    }
+
     cx.error(
         efn,
         "&mut return type is not allowed unless there is a &mut argument",


Test patch:
diff --git a/tests/ui/mut_return.rs b/tests/ui/mut_return.rs
index 74a5c6960..aef3680b9 100644
--- a/tests/ui/mut_return.rs
+++ b/tests/ui/mut_return.rs
@@ -1,10 +1,16 @@
 #[cxx::bridge]
 mod ffi {
+    extern "Rust" {
+        type Mut<'a>;
+    }
+
     unsafe extern "C++" {
         type Thing;
 
         fn f(t: &Thing) -> Pin<&mut CxxString>;
         unsafe fn g(t: &Thing) -> Pin<&mut CxxString>;
+        fn h(t: Box<Mut>) -> Pin<&mut CxxString>;
+        fn i<'a>(t: Box<Mut<'a>>) -> Pin<&'a mut CxxString>;
     }
 }
 
diff --git a/tests/ui/mut_return.stderr b/tests/ui/mut_return.stderr
index d6ddb88b8..5845eb8ae 100644
--- a/tests/ui/mut_return.stderr
+++ b/tests/ui/mut_return.stderr
@@ -1,5 +1,5 @@
 error: &mut return type is not allowed unless there is a &mut argument
- --> $DIR/mut_return.rs:6:9
-  |
-6 |         fn f(t: &Thing) -> Pin<&mut CxxString>;
-  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  --> $DIR/mut_return.rs:10:9
+   |
+10 |         fn f(t: &Thing) -> Pin<&mut CxxString>;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^