Repository: dtolnay/cxx (language: rust)

Issue:
Method comments in generated C++ are misplaced
Rust struct method comments are not placed before the corresponding generated C++ class methods:

`cxx = "1.0.43"`
```rust
// lib.rs

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("rust/cxx.h");
    }

    extern "Rust" {
        /// This struct comment is placed correctly.
        type SomeType;

        /// This method comment is misplaced.
        fn misplaced_comment(self: &SomeType);

        /// This free function comment is placed correctly.
        fn correctly_placed_comment();
    }
}

struct SomeType {}

impl SomeType {
    fn misplaced_comment(&self) {}
}

fn correctly_placed_comment() {}

```

this results in

```c++
// lib.rs.h

#pragma once
#include "rust/cxx.h"

struct SomeType;

#ifndef CXXBRIDGE1_STRUCT_SomeType
#define CXXBRIDGE1_STRUCT_SomeType
// This struct comment is placed correctly.
struct SomeType final : public ::rust::Opaque {
  void misplaced_comment() const noexcept;
  ~SomeType() = delete;

private:
  friend ::rust::layout;
  struct layout {
    static ::std::size_t size() noexcept;
    static ::std::size_t align() noexcept;
  };
};
#endif // CXXBRIDGE1_STRUCT_SomeType

// This method comment is misplaced.

// This free function comment is placed correctly.
void correctly_placed_comment() noexcept;
```


Discussion / hints:

Fix patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 9f9c03913..645b98874 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -9,7 +9,7 @@ use crate::syntax::set::UnorderedSet;
 use crate::syntax::symbol::Symbol;
 use crate::syntax::trivial::{self, TrivialReason};
 use crate::syntax::{
-    derive, mangle, Api, Enum, ExternFn, ExternType, Pair, Signature, Struct, Trait, Type,
+    derive, mangle, Api, Doc, Enum, ExternFn, ExternType, Pair, Signature, Struct, Trait, Type,
     TypeAlias, Types, Var,
 };
 use proc_macro2::Ident;
@@ -249,14 +249,24 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern
         writeln!(out, "{};", field.name.cxx);
     }
 
-    writeln!(out);
+    out.next_section();
 
     for method in methods {
+        if !method.doc.is_empty() {
+            out.next_section();
+        }
+        for line in method.doc.to_string().lines() {
+            writeln!(out, "  //{}", line);
+        }
         write!(out, "  ");
         let sig = &method.sig;
         let local_name = method.name.cxx.to_string();
-        write_rust_function_shim_decl(out, &local_name, sig, false);
+        let indirect_call = false;
+        write_rust_function_shim_decl(out, &local_name, sig, indirect_call);
         writeln!(out, ";");
+        if !method.doc.is_empty() {
+            out.next_section();
+        }
     }
 
     if operator_eq {
@@ -332,12 +342,22 @@ fn write_opaque_type<'a>(out: &mut OutFile<'a>, ety: &'a ExternType, methods: &[
         ety.name.cxx,
     );
 
-    for method in methods {
+    for (i, method) in methods.iter().enumerate() {
+        if i > 0 && !method.doc.is_empty() {
+            out.next_section();
+        }
+        for line in method.doc.to_string().lines() {
+            writeln!(out, "  //{}", line);
+        }
         write!(out, "  ");
         let sig = &method.sig;
         let local_name = method.name.cxx.to_string();
-        write_rust_function_shim_decl(out, &local_name, sig, false);
+        let indirect_call = false;
+        write_rust_function_shim_decl(out, &local_name, sig, indirect_call);
         writeln!(out, ";");
+        if !method.doc.is_empty() {
+            out.next_section();
+        }
     }
 
     writeln!(out, "  ~{}() = delete;", ety.name.cxx);
@@ -824,7 +844,8 @@ fn write_function_pointer_trampoline(out: &mut OutFile, efn: &ExternFn, var: &Pa
 
     out.next_section();
     let c_trampoline = mangle::c_trampoline(efn, var, out.types).to_string();
-    write_rust_function_shim_impl(out, &c_trampoline, f, &r_trampoline, indirect_call);
+    let doc = Doc::new();
+    write_rust_function_shim_impl(out, &c_trampoline, f, &doc, &r_trampoline, indirect_call);
 }
 
 fn write_rust_function_decl<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
@@ -894,9 +915,6 @@ fn write_rust_function_decl_impl(
 
 fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
     out.set_namespace(&efn.name.namespace);
-    for line in efn.doc.to_string().lines() {
-        writeln!(out, "//{}", line);
-    }
     let local_name = match &efn.sig.receiver {
         None => efn.name.cxx.to_string(),
         Some(receiver) => format!(
@@ -905,9 +923,10 @@ fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
             efn.name.cxx,
         ),
     };
+    let doc = &efn.doc;
     let invoke = mangle::extern_fn(efn, out.types);
     let indirect_call = false;
-    write_rust_function_shim_impl(out, &local_name, efn, &invoke, indirect_call);
+    write_rust_function_shim_impl(out, &local_name, efn, doc, &invoke, indirect_call);
 }
 
 fn write_rust_function_shim_decl(
@@ -947,6 +966,7 @@ fn write_rust_function_shim_impl(
     out: &mut OutFile,
     local_name: &str,
     sig: &Signature,
+    doc: &Doc,
     invoke: &Symbol,
     indirect_call: bool,
 ) {
@@ -954,6 +974,12 @@ fn write_rust_function_shim_impl(
         // We've already defined this inside the struct.
         return;
     }
+    if sig.receiver.is_none() {
+        // Member functions already documented at their declaration.
+        for line in doc.to_string().lines() {
+            writeln!(out, "//{}", line);
+        }
+    }
     write_rust_function_shim_decl(out, local_name, sig, indirect_call);
     if out.header {
         writeln!(out, ";");
diff --git a/syntax/doc.rs b/syntax/doc.rs
index 60bb4da1b..cd764facb 100644
--- a/syntax/doc.rs
+++ b/syntax/doc.rs
@@ -17,6 +17,10 @@ impl Doc {
         self.fragments.push(lit);
     }
 
+    pub fn is_empty(&self) -> bool {
+        self.fragments.is_empty()
+    }
+
     pub fn to_string(&self) -> String {
         let mut doc = String::new();
         for lit in &self.fragments {


Test patch: