buffi/
lib.rs

1// Copyright (C) 2024 by GiGa infosystems
2
3//! This code is used to generate the C++ side API bindings for a Rust API
4//! based on the rustdoc json output
5//!
6//! It generates the following files based on your code:
7//!
8//! * api_functions.hpp (C-API with byte buffers)
9//! * types.hpp (includes all the types, name is dependent on the namespace)
10//! * testclient.hpp (C++ functions belonging to a struct such as `testclient`)
11//! * free_standing_functions.hpp (C++ functions not from an "impl" block)
12//!
13//! And these files to handle serde and bincode:
14//!
15//! * binary.hpp and bincode.hpp (for Bincode)
16//! * serde.hpp (for Serde)
17//!
18#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
19
20use serde::{Deserialize, Serialize};
21use serde_generate::SourceInstaller;
22use std::borrow::Cow;
23use std::cell::RefCell;
24use std::collections::BTreeMap;
25use std::collections::HashMap;
26use std::ffi::OsStr;
27use std::fmt::Write as _;
28use std::fs;
29use std::fs::File;
30use std::io::BufWriter;
31use std::io::Write as _;
32use std::path::PathBuf;
33use std::path::{Component, Path};
34use std::process::{Output, Stdio};
35
36const FUNCTION_PREFIX: &str = "buffi";
37
38#[derive(Debug, serde::Deserialize)]
39struct WorkspaceMetadata {
40    target_directory: String,
41}
42
43/// A Config object that provides information for the generation of C/C++ code
44#[derive(Serialize, Deserialize, Debug)]
45pub struct Config {
46    /// The namespace that should be used in the C++ code (required)
47    pub namespace: String,
48    /// The name of the API library that is built (important for Rustdoc, required)
49    pub api_lib_name: String,
50    /// The name of your API crate (important for Rustdoc, required)
51    pub parent_crate: String,
52    /// All crates that include the types you use in your API, needs to at least include your API crate (required)
53    pub rustdoc_crates: Vec<String>,
54    /// In case the file names of the generated C/C++ should have a prefix, put this here
55    pub file_prefix: Option<String>,
56    /// Copyright header to be included in every C/C++ file
57    pub copyright_header: Option<String>,
58    /// Generated-by header to be included in every C/C++ file
59    pub generated_by_header: Option<String>,
60    /// In case you need to set any feature flags for build process of Rustdoc, add them here
61    pub crate_feature_flags: Option<Vec<String>>,
62    /// Add some additional rustdoc flags here, can be useful for debugging
63    pub rustdoc_flags: Option<Vec<String>>,
64}
65
66impl Config {
67    /// Create a new config object by only setting required fields
68    pub fn new(
69        namespace: String,
70        api_lib_name: String,
71        parent_crate: String,
72        rustdoc_crates: Vec<String>,
73    ) -> Self {
74        Self {
75            namespace,
76            api_lib_name,
77            parent_crate,
78            rustdoc_crates,
79            file_prefix: None,
80            copyright_header: None,
81            generated_by_header: None,
82            crate_feature_flags: None,
83            rustdoc_flags: None,
84        }
85    }
86
87    /// Add some additional flags that should be passed when creating the rustdocs
88    pub fn extend_rustdoc_flags(&mut self, flags: Vec<String>) {
89        if let Some(rustdoc_flags) = self.rustdoc_flags.as_mut() {
90            rustdoc_flags.extend(flags);
91        } else {
92            self.rustdoc_flags = Some(flags);
93        }
94    }
95}
96
97struct ItemResolver {
98    base_path: String,
99    doc_types: rustdoc_types::Crate,
100    other_crates: RefCell<HashMap<String, rustdoc_types::Crate>>,
101}
102
103impl ItemResolver {
104    fn new(json_path: String, api_lib_name: &str) -> Self {
105        let content = std::fs::read_to_string(json_path.clone() + api_lib_name + ".json").unwrap();
106        let doc_types = serde_json::from_str(&content).unwrap();
107        Self {
108            base_path: json_path,
109            doc_types,
110            other_crates: RefCell::new(HashMap::new()),
111        }
112    }
113
114    // this function expects a fully qualified path.
115    fn resolve_by_path(
116        &self,
117        path: &str,
118        parent_crate: &str,
119        requested_item: rustdoc_types::ItemKind,
120    ) -> rustdoc_types::Path {
121        let mut parts = path.split("::").collect::<Vec<_>>();
122        if parts[0] == "crate" {
123            parts[0] = parent_crate;
124        }
125        let id = {
126            let mut other_crates = self.other_crates.borrow_mut();
127            let map = if parts[0] == parent_crate {
128                &self.doc_types
129            } else {
130                other_crates.entry(parts[0].to_owned()).or_insert_with(|| {
131                    self.load_extern_crate_doc(parts[0], &format!("(needed for {path:?})"))
132                })
133            };
134            let (id, summary) = map
135                .paths
136                .iter()
137                .find(|(_, i)| i.path == parts)
138                .expect("It's there");
139            if summary.kind == requested_item {
140                *id
141            } else {
142                panic!(
143                    "Incompatible type: Expected {requested_item:?}, Got {:?}",
144                    summary.kind
145                );
146            }
147        };
148        rustdoc_types::Path {
149            name: parts[parts.len() - 1].to_owned(),
150            id,
151            args: None,
152        }
153    }
154
155    fn resolve_index(
156        &self,
157        t: Option<&rustdoc_types::Path>,
158        id: &rustdoc_types::Id,
159        parent_crate: &str,
160    ) -> rustdoc_types::Item {
161        let mut other_crates = self.other_crates.borrow_mut();
162
163        let candidates = std::iter::once(&self.doc_types)
164            .chain(other_crates.values())
165            .filter_map(|c| c.index.get(id))
166            .filter(|i| extract_crate_from_span(i) == Some(parent_crate.into()))
167            .collect::<Vec<_>>();
168        match &candidates as &[&rustdoc_types::Item] {
169            [i] => return rustdoc_types::Item::clone(i),
170            [] => {
171                // handled by the code below
172            }
173            items => {
174                // we might get several candidates. In that case check that:
175                //
176                // * There is a candidate coming from this crate (indicated by the parent_crate)
177                //   argument
178                let matches_parent_crate = items
179                    .iter()
180                    .position(|i| extract_crate_from_span(i) == Some(parent_crate.into()));
181                match matches_parent_crate {
182                    Some(t) => {
183                        return rustdoc_types::Item::clone(items[t]);
184                    }
185                    _ => {
186                        panic!("Cannot decide what's the correct candidate")
187                    }
188                }
189            }
190        }
191
192        // expect possibly multiple matching entries?
193        let mut matched_ids = Vec::with_capacity(1);
194        if let Some(item) = self.doc_types.paths.get(id) {
195            let input_name = t.and_then(|t| t.name.split("::").last());
196            if input_name == item.path.last().map(|x| x.as_str()) {
197                matched_ids.push(item.clone());
198            }
199        }
200        for c in other_crates.values() {
201            if let Some(s) = c.paths.get(id) {
202                let input_name = t.and_then(|t| t.name.split("::").last());
203                if input_name == s.path.last().map(|x| x.as_str()) {
204                    matched_ids.push(s.clone());
205                }
206            }
207        }
208
209        // use the first matching entry
210        for crate_id in matched_ids {
211            // we need to resolve other crates by name
212            // not by crate-id as these id's are not stable across
213            // different crates
214            let crate_name = crate_id.path.first().unwrap().clone();
215            let other_index = other_crates.entry(crate_name.clone()).or_insert_with(|| {
216                self.load_extern_crate_doc(&crate_name, &format!("(needed for {t:?})"))
217            });
218
219            // This is just guessing the right item at this point
220            // This likely needs improvements
221            // TODO: Fix this as soon as the generated rustdoc contains the right information
222            // (Check on compiler updates)
223            let name = crate_id.path.last().unwrap();
224            let item = other_index.index.values().find(|i| {
225                i.name.as_ref() == Some(name)
226                    && matches!(
227                        (&i.inner, &crate_id.kind),
228                        (
229                            rustdoc_types::ItemEnum::Struct(_),
230                            rustdoc_types::ItemKind::Struct
231                        ) | (
232                            rustdoc_types::ItemEnum::Enum(_),
233                            rustdoc_types::ItemKind::Enum
234                        )
235                    )
236            });
237            if let Some(item) = item {
238                return item.clone();
239            }
240        }
241        panic!(
242            "Unknown id: {:?}, crate: {:?} (full type:{:?})",
243            id, parent_crate, t
244        );
245    }
246
247    fn load_extern_crate_doc(
248        &self,
249        crate_name: &str,
250        additional_message: &str,
251    ) -> rustdoc_types::Crate {
252        let content = std::fs::read_to_string(self.base_path.clone() + crate_name + ".json")
253            .unwrap_or_else(|_| {
254                panic!(
255                    "Failed to find docs for `{}` {}",
256                    &crate_name, additional_message
257                );
258            });
259        serde_json::from_str(&content).unwrap()
260    }
261}
262
263enum TypeCache {
264    NeedToPopulate,
265    Cached(
266        Vec<(
267            serde_reflection::Format,
268            Option<serde_reflection::ContainerFormat>,
269        )>,
270    ),
271}
272
273pub fn generate_bindings(out_dir: &Path, config: Config) {
274    if !out_dir.exists() {
275        panic!("Out directory does not exist");
276    }
277
278    let (target_directory, handle) = generate_docs(
279        &config.api_lib_name,
280        &config.rustdoc_crates,
281        config.crate_feature_flags.as_ref().unwrap_or(&Vec::new()),
282        config.rustdoc_flags.as_ref().unwrap_or(&Vec::new()),
283    );
284
285    let mut failed = false;
286    if let Ok(handle) = handle {
287        if handle.status.success() {
288            let resolver = ItemResolver::new(target_directory + "/doc/", &config.api_lib_name);
289            let mut type_map = HashMap::new();
290            let out_dir = out_dir.display().to_string();
291            generate_type_definitions(&resolver, &out_dir, &mut type_map, &config);
292            generate_function_definitions(
293                resolver,
294                &out_dir,
295                &mut type_map,
296                FUNCTION_PREFIX,
297                &config,
298            );
299        } else {
300            failed = true;
301        }
302    } else {
303        failed = true;
304    }
305
306    if !failed {
307        println!("Finished, wrote bindings to `{}`", out_dir.display());
308    }
309
310    if failed {
311        eprintln!("Failed to generate bindings");
312        std::process::exit(1);
313    }
314}
315
316pub fn generate_docs(
317    api_lib_name: &String,
318    rustdoc_crates: &[String],
319    crate_flags: &[String],
320    rustdoc_flags: &[String],
321) -> (String, Result<Output, std::io::Error>) {
322    print!("Gather workspace metadata:");
323    std::io::stdout().flush().expect("Flushing does not fail");
324    let metadata = std::process::Command::new("cargo")
325        .arg("metadata")
326        .arg("--format-version=1")
327        .stderr(Stdio::inherit())
328        .output()
329        .expect("Failed to get workspace metadata");
330    println!(" OK");
331
332    let WorkspaceMetadata { target_directory } = serde_json::from_slice(&metadata.stdout).unwrap();
333    // remove all old json doc files (if any exist), important in case the configuration has changed
334    let doc_directory = target_directory.to_owned() + "/doc";
335    if matches!(fs::exists(&doc_directory), Ok(true)) {
336        for entry in fs::read_dir(doc_directory).unwrap() {
337            let file_path = entry.unwrap().path();
338            if file_path.extension().and_then(|s| s.to_str()) == Some("json") {
339                fs::remove_file(file_path).unwrap();
340            }
341        }
342    }
343
344    if rustdoc_crates.is_empty() {
345        eprintln!("Need at least one input crate to create bindings!");
346        std::process::exit(1);
347    }
348
349    // only build documentation for our own crates for now
350    let mut args = vec!["--no-deps"];
351    let crate_args: Vec<_> = rustdoc_crates
352        .iter()
353        .flat_map(|crate_name| vec!["-p", crate_name])
354        .collect();
355    let crate_flag_args: Vec<_> = crate_flags
356        .iter()
357        .flat_map(|crate_and_flag| vec!["-F", crate_and_flag])
358        .collect();
359    args.extend(crate_args);
360    args.extend(crate_flag_args);
361    args.extend(rustdoc_flags.iter().map(|s| s as &str));
362
363    let bootstrap_crates = vec![api_lib_name].into_iter().chain(rustdoc_crates).fold(
364        String::new(),
365        |cumulated, crate_name| {
366            let crate_name = crate_name.replace("-", "_");
367            cumulated + &format!(",{crate_name}")
368        },
369    );
370    // this works because `rustdoc_crates` has at least one entry
371    let bootstrap_crates = &bootstrap_crates[1..bootstrap_crates.len()];
372
373    println!("Compile rustdocs:");
374    let mut rustdoc_command = std::process::Command::new("cargo");
375
376    rustdoc_command
377        .arg("doc")
378        .args(args)
379        .env("RUSTC_BOOTSTRAP", bootstrap_crates)
380        .env("RUSTDOCFLAGS", "-Z unstable-options --output-format json ")
381        .env("CARGO_TARGET_DIR", &target_directory)
382        .stderr(Stdio::inherit())
383        .stdout(Stdio::inherit());
384
385    let handle = rustdoc_command.output();
386    (target_directory, handle)
387}
388
389fn generate_function_definitions(
390    res: ItemResolver,
391    out_dir: &str,
392    type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
393    function_prefix: &str,
394    config: &Config,
395) {
396    let namespace = &config.namespace;
397    let file_prefix = config.file_prefix.as_ref().unwrap_or(&config.api_lib_name);
398
399    let out_dir = PathBuf::from(out_dir);
400    let mut extern_c_functions = res
401        .doc_types
402        .index
403        .values()
404        .filter_map(|item| {
405            if let rustdoc_types::ItemEnum::Function(ref func) = item.inner {
406                if matches!(func.header.abi, rustdoc_types::Abi::C { .. }) {
407                    let s = generate_extern_c_function_def(item.name.as_deref().unwrap(), func);
408                    Some(s)
409                } else {
410                    None
411                }
412            } else {
413                None
414            }
415        })
416        .collect::<Vec<_>>();
417    // ensure that we always emit these functions in the same order
418    extern_c_functions.sort();
419
420    let mut free_standing_functions = res
421        .doc_types
422        .index
423        .values()
424        .filter(is_free_standing_impl)
425        .collect::<Vec<_>>();
426
427    free_standing_functions.sort_by_key(|f| f.name.as_ref());
428
429    let mut relevant_impls = res
430        .doc_types
431        .index
432        .values()
433        .filter(is_relevant_impl)
434        .flat_map(|item| {
435            if let rustdoc_types::ItemEnum::Impl(ref impl_) = item.inner {
436                impl_
437                    .items
438                    .iter()
439                    .map(|id| res.resolve_index(None, id, &config.parent_crate))
440                    .filter(|item| matches!(item.inner, rustdoc_types::ItemEnum::Function(_)))
441                    .map(move |i| (&impl_.for_, i))
442            } else {
443                unreachable!()
444            }
445        })
446        .fold(HashMap::<_, Vec<_>>::new(), |mut acc, (t, i)| {
447            acc.entry(t).or_default().push(i);
448            acc
449        })
450        .into_iter()
451        .map(|(n, mut items)| {
452            items.sort_by_key(|i| i.name.clone());
453            (n, items)
454        })
455        .collect::<Vec<_>>();
456
457    // ensure that we always order the type definitions in the same way
458    relevant_impls.sort_by_key(|(t, _)| {
459        if let rustdoc_types::Type::ResolvedPath(p) = t {
460            get_name_without_path(&p.name)
461        } else {
462            unreachable!()
463        }
464    });
465    let extern_c_header = out_dir.join(format!("{file_prefix}_api_functions.hpp"));
466    let mut extern_c_header = BufWriter::new(File::create(extern_c_header).unwrap());
467    write_function_header(&mut extern_c_header, config);
468    writeln!(extern_c_header, "#include <cstdint>").unwrap();
469    writeln!(extern_c_header).unwrap();
470    for (t, _) in relevant_impls.iter() {
471        if let rustdoc_types::Type::ResolvedPath(p) = t {
472            let name = get_name_without_path(&p.name);
473            writeln!(extern_c_header, "struct {};\n", name).unwrap();
474        } else {
475            unreachable!()
476        }
477    }
478    for function in extern_c_functions {
479        writeln!(extern_c_header, "{function}").unwrap();
480    }
481    extern_c_header.flush().unwrap();
482
483    for (t, impls) in relevant_impls {
484        if let rustdoc_types::Type::ResolvedPath(p) = t {
485            let name = get_name_without_path(&p.name);
486            let type_header =
487                out_dir.join(format!("{file_prefix}_{}.hpp", name.to_ascii_lowercase()));
488            let mut writer = BufWriter::new(File::create(type_header).unwrap());
489            write_function_header(&mut writer, config);
490            writeln!(writer, "#include \"{file_prefix}_api_functions.hpp\"\n").unwrap();
491            writeln!(writer, "#include \"{namespace}.hpp\"\n").unwrap();
492
493            writeln!(writer).unwrap();
494            writeln!(writer, "namespace {namespace} {{").unwrap();
495            writeln!(writer).unwrap();
496            writeln!(writer, "class {name}Holder {{").unwrap();
497            writeln!(writer, "    {name}* inner;").unwrap();
498            writeln!(writer, "public:").unwrap();
499            writeln!(writer, "    {name}Holder({name}* ptr) {{").unwrap();
500            writeln!(writer, "        this->inner = ptr;").unwrap();
501            writeln!(writer, "    }}\n").unwrap();
502            for impl_ in impls {
503                if let rustdoc_types::ItemEnum::Function(ref m) = impl_.inner {
504                    generate_function_def(
505                        m,
506                        &res,
507                        &impl_,
508                        &mut writer,
509                        type_map,
510                        function_prefix,
511                        config,
512                        Some(t),
513                    );
514                }
515            }
516            writeln!(writer, "}};\n").unwrap();
517            writeln!(writer, "}}  // end of namespace {namespace}").unwrap();
518            writer.flush().unwrap();
519        }
520    }
521
522    let free_standing_function_header =
523        out_dir.join(format!("{file_prefix}_free_standing_functions.hpp"));
524    let mut free_standing_function_header =
525        BufWriter::new(File::create(free_standing_function_header).unwrap());
526
527    write_function_header(&mut free_standing_function_header, config);
528    writeln!(
529        free_standing_function_header,
530        "#include \"{file_prefix}_api_functions.hpp\"\n"
531    )
532    .unwrap();
533    writeln!(
534        free_standing_function_header,
535        "#include \"{namespace}.hpp\"\n"
536    )
537    .unwrap();
538
539    writeln!(free_standing_function_header).unwrap();
540    writeln!(free_standing_function_header, "namespace {namespace} {{").unwrap();
541    writeln!(free_standing_function_header).unwrap();
542
543    for item in &free_standing_functions {
544        if let rustdoc_types::ItemEnum::Function(ref f) = item.inner {
545            generate_function_def(
546                f,
547                &res,
548                item,
549                &mut free_standing_function_header,
550                type_map,
551                function_prefix,
552                config,
553                None,
554            );
555            writeln!(free_standing_function_header).unwrap();
556        }
557    }
558
559    writeln!(
560        free_standing_function_header,
561        "}}  // end of namespace {namespace}"
562    )
563    .unwrap();
564    free_standing_function_header.flush().unwrap();
565}
566
567fn write_function_header(out_functions: &mut BufWriter<File>, config: &Config) {
568    if let Some(copyright_header) = &config.copyright_header {
569        writeln!(out_functions, "// {copyright_header}").unwrap();
570    }
571    if let Some(generated_by) = &config.generated_by_header {
572        writeln!(out_functions, "// {generated_by}").unwrap();
573    }
574    if config.copyright_header.is_some() || config.generated_by_header.is_some() {
575        writeln!(out_functions).unwrap();
576    }
577    writeln!(out_functions, "#pragma once\n").unwrap();
578    writeln!(out_functions, "#include <cstddef>").unwrap();
579    writeln!(out_functions, "#include <limits>").unwrap();
580}
581
582#[allow(clippy::too_many_arguments)]
583fn generate_function_def(
584    m: &rustdoc_types::Function,
585    res: &ItemResolver,
586    item: &rustdoc_types::Item,
587    out_functions: &mut BufWriter<File>,
588    type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
589    prefix: &str,
590    config: &Config,
591    impl_type: Option<&rustdoc_types::Type>,
592) {
593    let output_type = if let Some(ref tpe) = m.sig.output {
594        let tpe = to_serde_reflect_type(
595            tpe,
596            res,
597            &mut None,
598            Vec::new(),
599            &config.parent_crate,
600            &config.namespace,
601            type_map,
602        );
603        to_cpp_type_name(&tpe.last().unwrap().0)
604    } else {
605        unimplemented!()
606    };
607    let inputs = m
608        .sig
609        .inputs
610        .iter()
611        .map(|(name, tpe)| {
612            if name == "self" {
613                let impl_type_path = impl_type
614                    .map(|tpe| {
615                        let rustdoc_types::Type::ResolvedPath(path) = tpe else {
616                            panic!("Impl type must be a resolved path");
617                        };
618                        path
619                    })
620                    .expect("we have an impl type for impl functions");
621                return (name, get_name_without_path(&impl_type_path.name).to_owned());
622            }
623            let reflect_type = to_serde_reflect_type(
624                tpe,
625                res,
626                &mut None,
627                Vec::new(),
628                &config.parent_crate,
629                &config.namespace,
630                type_map,
631            );
632            let type_string = reflect_type
633                .last()
634                .map(|(f, _)| to_cpp_type_name(f))
635                .unwrap_or_else(|| panic!("Unknown type: {:?}", tpe));
636            (name, type_string)
637        })
638        .collect::<Vec<_>>();
639    let return_output_type = match m.sig.output {
640        Some(rustdoc_types::Type::ResolvedPath(ref p))
641            if get_name_without_path(&p.name) == "Result" =>
642        {
643            if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref()
644            {
645                if let rustdoc_types::GenericArg::Type(tpe) = &args[0] {
646                    let tpe = to_serde_reflect_type(
647                        tpe,
648                        res,
649                        &mut None,
650                        Vec::new(),
651                        &config.parent_crate,
652                        &config.namespace,
653                        type_map,
654                    );
655                    Cow::Owned(to_cpp_type_name(&tpe.last().unwrap().0))
656                } else {
657                    unreachable!()
658                }
659            } else {
660                unreachable!()
661            }
662        }
663        Some(rustdoc_types::Type::ResolvedPath(ref p))
664            if get_name_without_path(&p.name) == "String" =>
665        {
666            Cow::Owned(to_cpp_type_name(&serde_reflection::Format::Str))
667        }
668        _ => Cow::Borrowed(&output_type as &str),
669    };
670    if let Some(ref docs) = item.docs {
671        for line in docs.lines() {
672            writeln!(out_functions, "    // {line}").unwrap()
673        }
674    }
675    write!(
676        out_functions,
677        "    inline {return_output_type} {}(",
678        item.name.as_ref().unwrap()
679    )
680    .unwrap();
681    for (idx, (name, tpe)) in inputs.iter().filter(|(n, _)| *n != "self").enumerate() {
682        if idx != 0 {
683            write!(out_functions, ", ").unwrap();
684        }
685        write!(out_functions, "const {tpe}& {name}").unwrap();
686    }
687    writeln!(out_functions, ") {{").unwrap();
688    for (name, tpe) in &inputs {
689        if *name == "self" {
690            continue;
691        }
692        writeln!(
693            out_functions,
694            "        auto serializer_{name} = serde::BincodeSerializer();"
695        )
696        .unwrap();
697        writeln!(
698            out_functions,
699            "        serde::Serializable<{tpe}>::serialize({name}, serializer_{name});"
700        )
701        .unwrap();
702        writeln!(
703            out_functions,
704            "        std::vector<uint8_t> {name}_serialized = std::move(serializer_{name}).bytes();"
705        )
706        .unwrap();
707    }
708    writeln!(out_functions, "        uint8_t* out_ptr = nullptr;").unwrap();
709    writeln!(out_functions).unwrap();
710    write!(
711        out_functions,
712        "        size_t res_size = {}_{}(",
713        prefix,
714        item.name.as_deref().unwrap(),
715    )
716    .unwrap();
717    for (name, _) in inputs.iter() {
718        if *name == "self" {
719            write!(out_functions, "this->inner, ").unwrap();
720        } else {
721            write!(
722                out_functions,
723                "{name}_serialized.data(), {name}_serialized.size(), "
724            )
725            .unwrap();
726        }
727    }
728    writeln!(out_functions, "&out_ptr);").unwrap();
729    writeln!(out_functions).unwrap();
730    writeln!(
731        out_functions,
732        "        std::vector<uint8_t> serialized_result(out_ptr, out_ptr + res_size);"
733    )
734    .unwrap();
735    writeln!(
736        out_functions,
737        "        {output_type} out = {output_type}::bincodeDeserialize(serialized_result);"
738    )
739    .unwrap();
740    writeln!(
741        out_functions,
742        "        {}_free_byte_buffer(out_ptr, res_size);",
743        prefix
744    )
745    .unwrap();
746    writeln!(out_functions).unwrap();
747    if matches!(m.sig.output, Some(rustdoc_types::Type::ResolvedPath(ref p)) if get_name_without_path(&p.name) == "Result")
748    {
749        writeln!(
750            out_functions,
751            "        if (out.value.index() == 0) {{ // Ok"
752        )
753        .unwrap();
754        if return_output_type == "void" {
755            writeln!(out_functions, "            return;").unwrap();
756        } else {
757            writeln!(
758                out_functions,
759                "            auto ok = std::get<0>(out.value);"
760            )
761            .unwrap();
762            writeln!(out_functions, "            return std::get<0>(ok.value);").unwrap();
763        }
764        writeln!(out_functions, "        }} else {{ // Err").unwrap();
765        writeln!(
766            out_functions,
767            "            auto err = std::get<1>(out.value);"
768        )
769        .unwrap();
770        writeln!(
771            out_functions,
772            "            auto error = std::get<0>(err.value);"
773        )
774        .unwrap();
775        writeln!(out_functions, "            throw error;").unwrap();
776        writeln!(out_functions, "        }}").unwrap();
777    } else {
778        writeln!(out_functions, "        return out;").unwrap();
779    }
780    writeln!(out_functions, "    }}\n").unwrap();
781}
782
783fn generate_type_definitions(
784    res: &ItemResolver,
785    out_types: &str,
786    type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
787    config: &Config,
788) {
789    let comments = serde_generate::DocComments::new();
790    let mut comments = Some(comments);
791    let mut types_for_impls = res
792        .doc_types
793        .index
794        .values()
795        .filter(|i| is_relevant_impl(i) || is_free_standing_impl(i))
796        .flat_map(|item| {
797            if let rustdoc_types::ItemEnum::Impl(ref impl_) = item.inner {
798                impl_
799                    .items
800                    .iter()
801                    .map(|id| res.resolve_index(None, id, &config.parent_crate))
802                    .filter(|item| matches!(item.inner, rustdoc_types::ItemEnum::Function(_)))
803                    .collect()
804            } else if let rustdoc_types::ItemEnum::Function(ref _f) = item.inner {
805                vec![item.clone()]
806            } else {
807                unreachable!()
808            }
809        })
810        .flat_map(|m| {
811            if let rustdoc_types::ItemEnum::Function(ref m) = m.inner {
812                m.sig
813                    .inputs
814                    .iter()
815                    .map(|(_, t)| t.clone())
816                    .chain(
817                        m.sig
818                            .output
819                            .as_ref()
820                            .map(|e| vec![e.clone()])
821                            .unwrap_or_default(),
822                    )
823                    .collect::<Vec<_>>()
824            } else {
825                unreachable!()
826            }
827        })
828        .collect::<Vec<_>>();
829    types_for_impls.dedup();
830    let registry = types_for_impls
831        .into_iter()
832        .map(|t| {
833            to_serde_reflect_type(
834                &t,
835                res,
836                &mut comments,
837                Vec::new(),
838                &config.parent_crate,
839                &config.namespace,
840                type_map,
841            )
842        })
843        .flat_map(|types| {
844            types.into_iter().filter_map(|(format, container)| {
845                let container = container?;
846                if let serde_reflection::Format::TypeName(n) = format {
847                    Some((n, container))
848                } else {
849                    None
850                }
851            })
852        })
853        .collect::<serde_reflection::Registry>();
854
855    let config = serde_generate::CodeGeneratorConfig::new(config.namespace.to_owned())
856        .with_comments(comments.unwrap())
857        .with_encodings([serde_generate::Encoding::Bincode]);
858    let installer = serde_generate::cpp::Installer::new(PathBuf::from(out_types));
859    installer.install_module(&config, &registry).unwrap();
860    installer.install_serde_runtime().unwrap();
861    installer.install_bincode_runtime().unwrap();
862}
863
864fn to_cpp_type_name(f: &serde_reflection::Format) -> String {
865    match f {
866        serde_reflection::Format::Variable(_) => unimplemented!(),
867        serde_reflection::Format::TypeName(_) => to_type_name(f).into_owned(),
868        serde_reflection::Format::Unit => unimplemented!(),
869        serde_reflection::Format::Bool => String::from("bool"),
870        serde_reflection::Format::I8 => String::from("int8_t"),
871        serde_reflection::Format::I16 => String::from("int16_t"),
872        serde_reflection::Format::I32 => String::from("int32_t"),
873        serde_reflection::Format::I64 => String::from("int64_t"),
874        serde_reflection::Format::I128 => unimplemented!(),
875        serde_reflection::Format::U8 => String::from("uint8_t"),
876        serde_reflection::Format::U16 => String::from("uint16_t"),
877        serde_reflection::Format::U32 => String::from("uint32_t"),
878        serde_reflection::Format::U64 => String::from("uint64_t"),
879        serde_reflection::Format::U128 => unimplemented!(),
880        serde_reflection::Format::F32 => String::from("float"),
881        serde_reflection::Format::F64 => String::from("double"),
882        serde_reflection::Format::Char => unimplemented!(),
883        serde_reflection::Format::Str => String::from("std::string"),
884        serde_reflection::Format::Bytes => unimplemented!(),
885        serde_reflection::Format::Option(t) => {
886            format!("std::optional<{}>", to_cpp_type_name(t))
887        }
888        serde_reflection::Format::Seq(p) => {
889            format!("std::vector<{}>", to_cpp_type_name(p))
890        }
891        serde_reflection::Format::Map { .. } => unimplemented!(),
892        serde_reflection::Format::Tuple(d) if d.is_empty() => String::from("void"),
893        serde_reflection::Format::Tuple(_) => unimplemented!(),
894        serde_reflection::Format::TupleArray { .. } => unimplemented!(),
895    }
896}
897
898fn to_type_name(f: &serde_reflection::Format) -> Cow<str> {
899    match f {
900        serde_reflection::Format::Variable(_) => unimplemented!(),
901        serde_reflection::Format::TypeName(n) => Cow::Borrowed(n),
902        serde_reflection::Format::Unit => unimplemented!(),
903        serde_reflection::Format::Bool => Cow::Borrowed("bool"),
904        serde_reflection::Format::I8 => Cow::Borrowed("i8"),
905        serde_reflection::Format::I16 => Cow::Borrowed("i16"),
906        serde_reflection::Format::I32 => Cow::Borrowed("i32"),
907        serde_reflection::Format::I64 => Cow::Borrowed("i64"),
908        serde_reflection::Format::I128 => unimplemented!(),
909        serde_reflection::Format::U8 => Cow::Borrowed("u8"),
910        serde_reflection::Format::U16 => Cow::Borrowed("u16"),
911        serde_reflection::Format::U32 => Cow::Borrowed("u32"),
912        serde_reflection::Format::U64 => Cow::Borrowed("u64"),
913        serde_reflection::Format::U128 => unimplemented!(),
914        serde_reflection::Format::F32 => Cow::Borrowed("f32"),
915        serde_reflection::Format::F64 => Cow::Borrowed("f64"),
916        serde_reflection::Format::Char => unimplemented!(),
917        serde_reflection::Format::Str => Cow::Borrowed("String"),
918        serde_reflection::Format::Bytes => unimplemented!(),
919        serde_reflection::Format::Option(t) => Cow::Owned(format!("Option_{}", to_type_name(t))),
920        serde_reflection::Format::Seq(t) => Cow::Owned(format!("Vec_{}", to_type_name(t))),
921        serde_reflection::Format::Map { .. } => unimplemented!(),
922        serde_reflection::Format::Tuple(d) if d.is_empty() => Cow::Borrowed("void"),
923        serde_reflection::Format::Tuple(d) => {
924            dbg!(d);
925            unimplemented!()
926        }
927        serde_reflection::Format::TupleArray { .. } => unimplemented!(),
928    }
929}
930
931fn to_serde_reflect_type(
932    t: &rustdoc_types::Type,
933    crate_map: &ItemResolver,
934    comment_map: &mut Option<serde_generate::DocComments>,
935    parent_args: Vec<rustdoc_types::GenericArg>,
936    parent_crate: &str,
937    namespace: &str,
938    type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
939) -> Vec<(
940    serde_reflection::Format,
941    Option<serde_reflection::ContainerFormat>,
942)> {
943    use serde_reflection::{ContainerFormat, Format};
944
945    /// This is here for DRY (used by primitives and arrays.)
946    fn reflect_primitive(p: &rustdoc_types::Type) -> Vec<(Format, Option<ContainerFormat>)> {
947        let rustdoc_types::Type::Primitive(p) = p else {
948            unreachable!("Primitive!")
949        };
950        match p.as_ref() {
951            "i64" => {
952                vec![(Format::I64, None)]
953            }
954            "i32" => {
955                vec![(Format::I32, None)]
956            }
957            "i16" => {
958                vec![(Format::I16, None)]
959            }
960            "i8" => {
961                vec![(Format::I8, None)]
962            }
963            "bool" => {
964                vec![(Format::Bool, None)]
965            }
966            "f64" => {
967                vec![(Format::F64, None)]
968            }
969            "f32" => {
970                vec![(Format::F32, None)]
971            }
972            "u8" => {
973                vec![(Format::U8, None)]
974            }
975            "u16" => {
976                vec![(Format::U16, None)]
977            }
978            "u32" => {
979                vec![(Format::U32, None)]
980            }
981            "u64" => {
982                vec![(Format::U64, None)]
983            }
984            "usize" if size_of::<usize>() == 8 => {
985                // TODO: This, properly.
986                vec![(Format::U64, None)]
987            }
988            "usize" if size_of::<usize>() == 4 => {
989                // TODO: This, properly.
990                vec![(Format::U32, None)]
991            }
992            "usize" => {
993                panic!("Invalid size of usize.");
994            }
995            _ => {
996                dbg!(p);
997                unimplemented!()
998            }
999        }
1000    }
1001
1002    let recursive_type = match type_map.get(t) {
1003        Some(TypeCache::Cached(t)) => return t.clone(),
1004        Some(TypeCache::NeedToPopulate) => true,
1005        None => {
1006            type_map.insert(t.clone(), TypeCache::NeedToPopulate);
1007            false
1008        }
1009    };
1010
1011    let r = match t {
1012        rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.name) == "Result" => {
1013            let mut out = Vec::new();
1014            let (ok, error) = if let Some(rustdoc_types::GenericArgs::AngleBracketed {
1015                args, ..
1016            }) = p.args.as_deref()
1017            {
1018                let ok = &args[0];
1019                let ok = if let rustdoc_types::GenericArg::Type(tpe) = ok {
1020                    to_serde_reflect_type(
1021                        tpe,
1022                        crate_map,
1023                        comment_map,
1024                        Vec::new(),
1025                        parent_crate,
1026                        namespace,
1027                        type_map,
1028                    )
1029                } else {
1030                    unreachable!()
1031                };
1032                let err = if let Some((id, _)) =
1033                    crate_map.doc_types.index.iter().find(|(_, item)| {
1034                        item.name.as_deref().map(get_name_without_path) == Some("SerializableError")
1035                    }) {
1036                    let t = rustdoc_types::Type::ResolvedPath(rustdoc_types::Path {
1037                        name: "SerializableError".into(),
1038                        id: *id,
1039                        args: None,
1040                    });
1041                    to_serde_reflect_type(
1042                        &t,
1043                        crate_map,
1044                        comment_map,
1045                        Vec::new(),
1046                        parent_crate,
1047                        namespace,
1048                        type_map,
1049                    )
1050                } else {
1051                    unreachable!(
1052                        "Could not find docs for `SerializableError`! Maybe the `errors` module or the type itself is still private?"
1053                    )
1054                };
1055                (ok, err)
1056            } else {
1057                unreachable!()
1058            };
1059            let mut result_enum = BTreeMap::new();
1060            result_enum.insert(
1061                0,
1062                serde_reflection::Named {
1063                    name: "Ok".into(),
1064                    value: serde_reflection::VariantFormat::Tuple(vec![
1065                        ok.last().unwrap().0.clone(),
1066                    ]),
1067                },
1068            );
1069            result_enum.insert(
1070                1,
1071                serde_reflection::Named {
1072                    name: "Err".into(),
1073                    value: serde_reflection::VariantFormat::Tuple(vec![
1074                        error.last().unwrap().0.clone(),
1075                    ]),
1076                },
1077            );
1078            let ok_name = to_type_name(&ok.last().unwrap().0);
1079            let err_name = to_type_name(&error.last().unwrap().0);
1080            let name = format!("Result_{ok_name}_{err_name}");
1081            out.extend(ok);
1082            out.extend(error);
1083            out.push((
1084                Format::TypeName(name),
1085                Some(ContainerFormat::Enum(result_enum)),
1086            ));
1087
1088            out
1089        }
1090        rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.name) == "String" => {
1091            vec![(Format::Str, None)]
1092        }
1093        rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.name) == "Vec" => {
1094            if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref()
1095            {
1096                if let rustdoc_types::GenericArg::Type(tpe) = &args[0] {
1097                    let mut inner = to_serde_reflect_type(
1098                        tpe,
1099                        crate_map,
1100                        comment_map,
1101                        Vec::new(),
1102                        parent_crate,
1103                        namespace,
1104                        type_map,
1105                    );
1106                    let last = inner.last().unwrap().0.clone();
1107                    inner.push((Format::Seq(Box::new(last)), None));
1108                    inner
1109                } else {
1110                    unreachable!()
1111                }
1112            } else {
1113                unreachable!()
1114            }
1115        }
1116        rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.name) == "Option" => {
1117            if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref()
1118            {
1119                if let rustdoc_types::GenericArg::Type(tpe) = &args[0] {
1120                    let mut inner = to_serde_reflect_type(
1121                        tpe,
1122                        crate_map,
1123                        comment_map,
1124                        Vec::new(),
1125                        parent_crate,
1126                        namespace,
1127                        type_map,
1128                    );
1129                    let last = inner.last().unwrap().0.clone();
1130                    inner.push((Format::Option(Box::new(last)), None));
1131                    inner
1132                } else {
1133                    unreachable!()
1134                }
1135            } else {
1136                unreachable!()
1137            }
1138        }
1139        rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.name) == "Box" => {
1140            let t = match p.args.as_deref() {
1141                Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. })
1142                    if args.len() == 1 =>
1143                {
1144                    if let Some(rustdoc_types::GenericArg::Type(t)) = args.first() {
1145                        t
1146                    } else {
1147                        unreachable!()
1148                    }
1149                }
1150                Some(_) | None => unreachable!(),
1151            };
1152            if recursive_type {
1153                let name = match t {
1154                    rustdoc_types::Type::ResolvedPath(p) => get_name_without_path(&p.name),
1155                    _ => unreachable!(),
1156                };
1157                // we need an explicit early return here as we **don't** want to
1158                // update the type map with the preliminary result
1159                return vec![(Format::TypeName(name.to_owned()), None)];
1160            } else {
1161                to_serde_reflect_type(
1162                    t,
1163                    crate_map,
1164                    comment_map,
1165                    parent_args,
1166                    parent_crate,
1167                    namespace,
1168                    type_map,
1169                )
1170            }
1171        }
1172        rustdoc_types::Type::ResolvedPath(p) => {
1173            let t = crate_map.resolve_index(Some(p), &p.id, parent_crate);
1174            let parent_crate = extract_crate_from_span(&t).expect("parent crate is set");
1175            if let Some(comment_map) = comment_map {
1176                if let Some(ref doc) = t.docs {
1177                    comment_map.insert(vec![namespace.to_owned(), p.name.clone()], doc.clone());
1178                }
1179            }
1180            if let rustdoc_types::ItemEnum::Struct(rustdoc_types::Struct {
1181                kind: rustdoc_types::StructKind::Plain { ref fields, .. },
1182                ..
1183            }) = t.inner
1184            {
1185                return generate_exported_struct(
1186                    fields,
1187                    crate_map,
1188                    comment_map,
1189                    p,
1190                    parent_args,
1191                    &parent_crate,
1192                    namespace,
1193                    type_map,
1194                    recursive_type,
1195                );
1196            }
1197            if let rustdoc_types::ItemEnum::Struct(rustdoc_types::Struct {
1198                kind: rustdoc_types::StructKind::Unit {},
1199                ..
1200            }) = t.inner
1201            {
1202                return generate_exported_struct(
1203                    &[],
1204                    crate_map,
1205                    comment_map,
1206                    p,
1207                    parent_args,
1208                    &parent_crate,
1209                    namespace,
1210                    type_map,
1211                    recursive_type,
1212                );
1213            }
1214            if let rustdoc_types::ItemEnum::Enum(ref e) = t.inner {
1215                return generate_exported_enum(
1216                    e,
1217                    crate_map,
1218                    comment_map,
1219                    p,
1220                    &parent_crate,
1221                    namespace,
1222                    type_map,
1223                    recursive_type,
1224                );
1225            }
1226            if let rustdoc_types::ItemEnum::TypeAlias(ref t) = t.inner {
1227                return to_serde_reflect_type(
1228                    &t.type_,
1229                    crate_map,
1230                    comment_map,
1231                    parent_args,
1232                    &parent_crate,
1233                    namespace,
1234                    type_map,
1235                );
1236            }
1237            dbg!(t);
1238            unimplemented!()
1239        }
1240        rustdoc_types::Type::DynTrait(_) => unimplemented!(),
1241        rustdoc_types::Type::Generic(p) => {
1242            if parent_args.len() == 1 {
1243                if let rustdoc_types::GenericArg::Type(t) = &parent_args[0] {
1244                    to_serde_reflect_type(
1245                        t,
1246                        crate_map,
1247                        comment_map,
1248                        Vec::new(),
1249                        parent_crate,
1250                        namespace,
1251                        type_map,
1252                    )
1253                } else {
1254                    unimplemented!("Only types are accepted here?")
1255                }
1256            } else {
1257                dbg!(parent_args);
1258                dbg!(p);
1259                unimplemented!("Unsure how to resolve multiple args here??")
1260            }
1261        }
1262        rustdoc_types::Type::Primitive(_) => reflect_primitive(t),
1263        rustdoc_types::Type::FunctionPointer(_) => unimplemented!(),
1264        rustdoc_types::Type::Tuple(tup) => {
1265            let mut out = Vec::new();
1266            let mut fields = Vec::with_capacity(tup.len());
1267            for f in tup {
1268                let r = to_serde_reflect_type(
1269                    f,
1270                    crate_map,
1271                    comment_map,
1272                    Vec::new(),
1273                    parent_crate,
1274                    namespace,
1275                    type_map,
1276                );
1277                let f = r.last().map(|a| a.0.clone()).unwrap();
1278                out.extend(r);
1279                fields.push(f);
1280            }
1281            out.push((Format::Tuple(fields), None));
1282            out
1283        }
1284        rustdoc_types::Type::Slice(_) => unimplemented!(),
1285        rustdoc_types::Type::Array { type_, len } => {
1286            let size = len.parse::<usize>().expect("Array len should be a number");
1287            let t = reflect_primitive(type_)[0].0.clone();
1288            vec![(
1289                Format::TupleArray {
1290                    content: Box::new(t),
1291                    size,
1292                },
1293                None,
1294            )]
1295        }
1296        rustdoc_types::Type::ImplTrait(_) => unimplemented!(),
1297        rustdoc_types::Type::Infer => unimplemented!(),
1298        rustdoc_types::Type::RawPointer { .. } => unimplemented!(),
1299        rustdoc_types::Type::Pat { .. } => unimplemented!(),
1300
1301        rustdoc_types::Type::BorrowedRef { type_, .. } => {
1302            if let rustdoc_types::Type::Generic(s) = &**type_ {
1303                if s == "Self" {
1304                    return Vec::new();
1305                }
1306            }
1307            dbg!(t);
1308            unimplemented!()
1309        }
1310        rustdoc_types::Type::QualifiedPath { .. } => unimplemented!(),
1311    };
1312
1313    type_map.insert(t.clone(), TypeCache::Cached(r.clone()));
1314    r
1315}
1316
1317fn extract_crate_from_span(t: &rustdoc_types::Item) -> Option<String> {
1318    let p = &t.span.as_ref()?.filename;
1319    let mut components = p.components().peekable();
1320    let crate_name = match components.next() {
1321        Some(Component::Normal(el)) => {
1322            // that's a relative path in the project itself
1323            // we do walk down from the source files to the actual crate
1324            // name
1325            // This only works for reasonable default source setups,
1326            // such as those that have a src directory and where
1327            // the parent directory is named as the crate?
1328            // Fixme: It might be useful to use actual metadata from
1329            // cargo metadata to make this more robust
1330            let mut rev_components = components
1331                .rev()
1332                // skip everything before src as it's inside the
1333                // source directory
1334                .skip_while(|el| *el != Component::Normal(OsStr::new("src")))
1335                .skip(1); // need to skip "src" itself
1336            let Component::Normal(next) = rev_components.next().unwrap_or(Component::Normal(el))
1337            else {
1338                panic!("Could not resolve source path");
1339            };
1340            let s = next.to_str().expect("We expect an UTF-8 Path");
1341            // crate names do not contain `-` but `_`
1342            s.replace('-', "_")
1343        }
1344        Some(Component::RootDir | Component::Prefix(_)) => {
1345            // that's likely a path tho the cargo registry
1346            // So go to `.cargo`, remove 3 additional directories
1347            // and get the crate name
1348            // It's something like
1349            // `/home/weiznich/.cargo/registry/src/github.com-1ecc6299db9ec823/giga-segy-core-0.3.2/src/header_structs.rs`
1350            loop {
1351                match components.next() {
1352                    Some(Component::Normal(e))
1353                        if (e == ".cargo" || e == "cargo")
1354                            && matches!(components.peek(), Some(Component::Normal(e)) if *e == "registry") =>
1355                    {
1356                        break;
1357                    }
1358                    None => panic!("Unexpected end of path: {}", p.display()),
1359                    _ => {}
1360                }
1361            }
1362            // "registry"
1363            components.next();
1364            // "src"
1365            components.next();
1366            // "github.com-*"
1367            components.next();
1368            let Some(Component::Normal(el)) = components.next() else {
1369                panic!("Expect a normal path element")
1370            };
1371            // that's cratename-version
1372            let s = el.to_str().expect("We expect an UTF-8 Path");
1373            // split from the back as the crate name might contain a `-` as well
1374            let Some((s, _)) = s.rsplit_once('-') else {
1375                panic!("Expect a versioned crate name")
1376            };
1377            // crate names do not contain `-` but `_`
1378            s.replace('-', "_")
1379        }
1380        _ => panic!("We expect a relative or absolute path here"),
1381    };
1382    Some(crate_name)
1383}
1384
1385// we can't simply replace `parent_crate` and `namespace` by `config` because this function will
1386// be called by `to_serde_reflect_type` which can't hold a `config` (because `parent_crate` will be
1387// changed by the function itself and needs to stay mutable)
1388#[allow(clippy::too_many_arguments)]
1389fn generate_exported_enum(
1390    e: &rustdoc_types::Enum,
1391    crate_map: &ItemResolver,
1392    comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
1393    p: &rustdoc_types::Path,
1394    parent_crate: &str,
1395    namespace: &str,
1396    type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
1397    recursive_type: bool,
1398) -> Vec<(
1399    serde_reflection::Format,
1400    Option<serde_reflection::ContainerFormat>,
1401)> {
1402    use serde_reflection::{ContainerFormat, Format};
1403
1404    let mut out = Vec::new();
1405    let container_format = if recursive_type {
1406        // we can skip that the second time
1407        None
1408    } else {
1409        let mut enum_def = BTreeMap::new();
1410        for (id, variant) in e.variants.iter().enumerate() {
1411            let v = crate_map.resolve_index(None, variant, parent_crate);
1412            if let Some(comment_map) = comment_map {
1413                if let Some(ref docs) = v.docs {
1414                    comment_map.insert(
1415                        vec![
1416                            namespace.to_owned(),
1417                            p.name.clone(),
1418                            v.name.clone().unwrap(),
1419                        ],
1420                        docs.clone(),
1421                    );
1422                }
1423            }
1424            match v.inner {
1425                rustdoc_types::ItemEnum::Variant(rustdoc_types::Variant {
1426                    kind: rustdoc_types::VariantKind::Plain,
1427                    ..
1428                }) => {
1429                    enum_def.insert(
1430                        id as u32,
1431                        serde_reflection::Named {
1432                            name: v.name.clone().unwrap(),
1433                            value: serde_reflection::VariantFormat::Unit,
1434                        },
1435                    );
1436                }
1437                rustdoc_types::ItemEnum::Variant(rustdoc_types::Variant {
1438                    kind: rustdoc_types::VariantKind::Tuple(ref t),
1439                    ..
1440                }) => {
1441                    let mut variants = Vec::new();
1442                    for id in t {
1443                        if let Some(t) = id
1444                            .as_ref()
1445                            .map(|id| crate_map.resolve_index(None, id, parent_crate))
1446                        {
1447                            if let rustdoc_types::ItemEnum::StructField(ref tpe) = t.inner {
1448                                // check for a custom serde attribute here
1449                                // this allows us to specify different types for the c++ side
1450                                // we expect that we always set a fully qualified path to an type there
1451                                // (we control that, as it's our source, so that shouldn't be an problem)
1452                                if let Some(serde_type) = t.attrs.iter().find_map(|a| {
1453                                    let pref = a.strip_prefix("#[serde(with = \"")?;
1454                                    Some(&pref[..pref.len() - 3])
1455                                }) {
1456                                    let item = crate_map.resolve_by_path(
1457                                        serde_type,
1458                                        parent_crate,
1459                                        rustdoc_types::ItemKind::Struct,
1460                                    );
1461                                    let tpe = rustdoc_types::Type::ResolvedPath(item);
1462                                    let tps = to_serde_reflect_type(
1463                                        &tpe,
1464                                        crate_map,
1465                                        comment_map,
1466                                        Vec::new(),
1467                                        parent_crate,
1468                                        namespace,
1469                                        type_map,
1470                                    );
1471                                    variants.push(tps.last().unwrap().0.clone());
1472                                    out.extend(tps);
1473                                } else {
1474                                    let tps = to_serde_reflect_type(
1475                                        tpe,
1476                                        crate_map,
1477                                        comment_map,
1478                                        Vec::new(),
1479                                        parent_crate,
1480                                        namespace,
1481                                        type_map,
1482                                    );
1483                                    variants.push(tps.last().unwrap().0.clone());
1484                                    out.extend(tps);
1485                                }
1486                            }
1487                        }
1488                    }
1489                    if variants.len() == 1 {
1490                        let x = Box::new(variants.pop().expect("We have one. See above."));
1491                        enum_def.insert(
1492                            id as u32,
1493                            serde_reflection::Named {
1494                                name: v.name.clone().unwrap(),
1495                                value: serde_reflection::VariantFormat::NewType(x),
1496                            },
1497                        );
1498                    } else {
1499                        enum_def.insert(
1500                            id as u32,
1501                            serde_reflection::Named {
1502                                name: v.name.clone().unwrap(),
1503                                value: serde_reflection::VariantFormat::Tuple(variants),
1504                            },
1505                        );
1506                    }
1507                }
1508                rustdoc_types::ItemEnum::Variant(rustdoc_types::Variant {
1509                    kind: rustdoc_types::VariantKind::Struct { ref fields, .. },
1510                    ..
1511                }) => {
1512                    let mut variants = Vec::new();
1513                    for id in fields {
1514                        let t = crate_map.resolve_index(None, id, parent_crate);
1515                        if let rustdoc_types::ItemEnum::StructField(ref tpe) = t.inner {
1516                            let tps = to_serde_reflect_type(
1517                                tpe,
1518                                crate_map,
1519                                comment_map,
1520                                Vec::new(),
1521                                parent_crate,
1522                                namespace,
1523                                type_map,
1524                            );
1525                            variants.push(serde_reflection::Named {
1526                                name: t.name.unwrap(),
1527                                value: tps.last().unwrap().0.clone(),
1528                            });
1529                            out.extend(tps);
1530                        }
1531                    }
1532
1533                    enum_def.insert(
1534                        id as u32,
1535                        serde_reflection::Named {
1536                            name: v.name.clone().unwrap(),
1537                            value: serde_reflection::VariantFormat::Struct(variants),
1538                        },
1539                    );
1540                }
1541                _ => unimplemented!(),
1542            }
1543        }
1544        Some(ContainerFormat::Enum(enum_def))
1545    };
1546    let name = get_name_without_path(&p.name);
1547    out.push((Format::TypeName(name.to_owned()), container_format));
1548    out
1549}
1550
1551#[allow(clippy::too_many_arguments)]
1552fn generate_exported_struct(
1553    fields: &[rustdoc_types::Id],
1554    crate_map: &ItemResolver,
1555    comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
1556    p: &rustdoc_types::Path,
1557    parent_args: Vec<rustdoc_types::GenericArg>,
1558    parent_crate: &str,
1559    namespace: &str,
1560    type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
1561    recursive_type: bool,
1562) -> Vec<(
1563    serde_reflection::Format,
1564    Option<serde_reflection::ContainerFormat>,
1565)> {
1566    use serde_reflection::{ContainerFormat, Format};
1567
1568    let mut out = Vec::new();
1569    let mut name = get_name_without_path(&p.name).to_owned();
1570    if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref() {
1571        for arg in args {
1572            if let rustdoc_types::GenericArg::Type(t) = arg {
1573                let tpe = to_serde_reflect_type(
1574                    t,
1575                    crate_map,
1576                    comment_map,
1577                    parent_args.clone(),
1578                    parent_crate,
1579                    namespace,
1580                    type_map,
1581                )
1582                .pop()
1583                .unwrap()
1584                .0;
1585                name = format!("{name}_{}", to_type_name(&tpe));
1586            }
1587        }
1588    }
1589    let container_format = if recursive_type {
1590        // we don't need that for a recursive type
1591        None
1592    } else {
1593        let fields = fields
1594            .iter()
1595            .map(|id| crate_map.resolve_index(None, id, parent_crate))
1596            .filter_map(|s| {
1597                if let Some(comment_map) = comment_map {
1598                    if let Some(ref doc) = s.docs {
1599                        comment_map.insert(
1600                            vec![
1601                                namespace.to_owned(),
1602                                p.name.clone(),
1603                                s.name.clone().unwrap(),
1604                            ],
1605                            doc.clone(),
1606                        );
1607                    }
1608                }
1609                if let rustdoc_types::ItemEnum::StructField(ref tpe) = s.inner {
1610                    let parent_args = if let Some(rustdoc_types::GenericArgs::AngleBracketed {
1611                        args,
1612                        constraints,
1613                    }) = p.args.as_deref()
1614                    {
1615                        if args.is_empty() && constraints.is_empty() {
1616                            Vec::new()
1617                        } else if parent_args.len() == 1
1618                            && args.len() == 1
1619                            && matches!(
1620                                &args[0],
1621                                rustdoc_types::GenericArg::Type(rustdoc_types::Type::Generic(_))
1622                            )
1623                        {
1624                            parent_args.clone()
1625                        } else {
1626                            args.clone()
1627                        }
1628                    } else {
1629                        Vec::new()
1630                    };
1631                    Some((
1632                        s.name.clone().unwrap(),
1633                        to_serde_reflect_type(
1634                            tpe,
1635                            crate_map,
1636                            comment_map,
1637                            parent_args,
1638                            parent_crate,
1639                            namespace,
1640                            type_map,
1641                        ),
1642                    ))
1643                } else {
1644                    None
1645                }
1646            })
1647            .collect::<Vec<_>>();
1648        let mut struct_fields = Vec::with_capacity(fields.len());
1649        for (name, tpe) in fields {
1650            let format = tpe.last().unwrap().0.clone();
1651            struct_fields.push(serde_reflection::Named {
1652                name,
1653                value: format,
1654            });
1655            out.extend(tpe);
1656        }
1657        Some(ContainerFormat::Struct(struct_fields))
1658    };
1659    out.push((Format::TypeName(name), container_format));
1660    out
1661}
1662
1663fn is_relevant_impl(item: &&rustdoc_types::Item) -> bool {
1664    if !item
1665        .attrs
1666        .contains(&String::from("#[cfg(not(generated_extern_impl))]"))
1667    {
1668        return false;
1669    }
1670    matches!(item.inner, rustdoc_types::ItemEnum::Impl(_))
1671}
1672
1673fn is_free_standing_impl(item: &&rustdoc_types::Item) -> bool {
1674    if !item
1675        .attrs
1676        .contains(&String::from("#[cfg(not(generated_extern_impl))]"))
1677    {
1678        return false;
1679    }
1680    matches!(item.inner, rustdoc_types::ItemEnum::Function(_))
1681}
1682
1683fn to_c_type(tpe: &rustdoc_types::Type) -> String {
1684    match tpe {
1685        rustdoc_types::Type::ResolvedPath(p) => {
1686            let mut ret = get_name_without_path(&p.name).trim().to_string();
1687            if ret == "c_char" {
1688                String::from("char")
1689            } else {
1690                if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) =
1691                    p.args.as_deref()
1692                {
1693                    for arg in args {
1694                        if let rustdoc_types::GenericArg::Type(t) = arg {
1695                            write!(ret, "_{}", to_c_type(t)).unwrap();
1696                        }
1697                    }
1698                }
1699                ret
1700            }
1701        }
1702        rustdoc_types::Type::DynTrait(_) => unimplemented!(),
1703        rustdoc_types::Type::Generic(_) => unimplemented!(),
1704        rustdoc_types::Type::Primitive(p) if p == "u8" => String::from("std::uint8_t"),
1705        rustdoc_types::Type::Primitive(p) if p == "usize" => String::from("size_t"),
1706        rustdoc_types::Type::Primitive(p) if p == "u16" => String::from("std::uint16_t"),
1707        rustdoc_types::Type::Primitive(p) => p.clone(),
1708        rustdoc_types::Type::FunctionPointer(_) => String::new(),
1709        rustdoc_types::Type::Tuple(_) => unimplemented!(),
1710        rustdoc_types::Type::Slice(_) => unimplemented!(),
1711        rustdoc_types::Type::Array { .. } => unimplemented!(),
1712        rustdoc_types::Type::ImplTrait(_) => unimplemented!(),
1713        rustdoc_types::Type::Infer => unimplemented!(),
1714        rustdoc_types::Type::RawPointer { is_mutable, type_ } => {
1715            let mut out = if *is_mutable {
1716                String::new()
1717            } else {
1718                String::from("const ")
1719            };
1720            write!(out, "{}*", to_c_type(type_)).unwrap();
1721            out
1722        }
1723        rustdoc_types::Type::BorrowedRef { .. } => String::new(),
1724        rustdoc_types::Type::QualifiedPath { .. } => unimplemented!(),
1725        rustdoc_types::Type::Pat { .. } => unimplemented!(),
1726    }
1727}
1728
1729fn generate_extern_c_function_def(name: &str, func: &rustdoc_types::Function) -> String {
1730    let mut out = String::from("extern \"C\" ");
1731    write!(
1732        out,
1733        "{} ",
1734        func.sig
1735            .output
1736            .as_ref()
1737            .map(to_c_type)
1738            .unwrap_or_else(|| "void".into())
1739    )
1740    .unwrap();
1741
1742    let args = func
1743        .sig
1744        .inputs
1745        .iter()
1746        .map(|(name, tpe)| {
1747            let mut out = to_c_type(tpe);
1748            write!(out, " {name}").unwrap();
1749            out
1750        })
1751        .collect::<Vec<_>>()
1752        .join(", ");
1753    write!(out, "{name}({args});").unwrap();
1754    out
1755}
1756
1757fn get_name_without_path(name: &str) -> &str {
1758    // sometimes the name include the full path now
1759    name.rsplit_once("::").map(|(_, e)| e).unwrap_or(name)
1760}