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