#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
use rustdoc_types::Attribute;
use serde::{Deserialize, Serialize};
use serde_generate::SourceInstaller;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Write as _;
use std::fs;
use std::fs::File;
use std::io::BufWriter;
use std::io::Write as _;
use std::path::PathBuf;
use std::path::{Component, Path};
use std::process::{Output, Stdio};
pub use self::traits::SafeTypeMapping;
pub use bincode;
pub use buffi_macro::*;
mod traits;
mod buffi_annotation_attributes;
const FUNCTION_PREFIX: &str = "buffi";
type FieldList = Vec<(
String,
Vec<(
serde_reflection::Format,
Option<serde_reflection::ContainerFormat>,
)>,
)>;
#[derive(Debug, serde::Deserialize)]
struct WorkspaceMetadata {
target_directory: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub namespace: String,
pub api_lib_name: String,
pub parent_crate: String,
pub rustdoc_crates: Vec<String>,
pub file_prefix: Option<String>,
pub copyright_header: Option<String>,
pub generated_by_header: Option<String>,
pub crate_feature_flags: Option<Vec<String>>,
pub rustdoc_flags: Option<Vec<String>>,
}
impl Config {
pub fn new(
namespace: String,
api_lib_name: String,
parent_crate: String,
rustdoc_crates: Vec<String>,
) -> Self {
Self {
namespace,
api_lib_name,
parent_crate,
rustdoc_crates,
file_prefix: None,
copyright_header: None,
generated_by_header: None,
crate_feature_flags: None,
rustdoc_flags: None,
}
}
pub fn extend_rustdoc_flags(&mut self, flags: Vec<String>) {
if let Some(rustdoc_flags) = self.rustdoc_flags.as_mut() {
rustdoc_flags.extend(flags);
} else {
self.rustdoc_flags = Some(flags);
}
}
}
struct ItemResolver {
base_path: String,
doc_types: rustdoc_types::Crate,
other_crates: RefCell<HashMap<String, rustdoc_types::Crate>>,
}
impl ItemResolver {
fn new(json_path: String, api_lib_name: &str) -> Self {
let full_path = json_path.clone() + api_lib_name + ".json";
let content = std::fs::read_to_string(full_path.clone())
.unwrap_or_else(|_| panic!("Failed to read file: {full_path}"));
let doc_types = serde_json::from_str(&content).unwrap();
Self {
base_path: json_path,
doc_types,
other_crates: RefCell::new(HashMap::new()),
}
}
fn resolve_by_path(
&self,
path: &str,
parent_crate: &str,
requested_item: rustdoc_types::ItemKind,
) -> (String, rustdoc_types::Path) {
let mut parts = path.split("::").map(|c| c.to_owned()).collect::<Vec<_>>();
let id = {
let mut other_crates = self.other_crates.borrow_mut();
let map = if parts[0] == parent_crate || parts[0] == "crate" || parts.len() == 1 {
&self.doc_types
} else {
other_crates.entry(parts[0].to_owned()).or_insert_with(|| {
self.load_extern_crate_doc(&parts[0], &format!("(needed for {path:?})"))
})
};
if parts[0] == "crate" {
if let Some(guess) = map.paths.iter().find_map(|(_, i)| {
(i.path[0] == parent_crate).then_some(parent_crate.to_owned())
}) {
parts[0] = guess;
} else if let Some(guess) = map
.paths
.iter()
.find_map(|(_, i)| (i.crate_id == 0).then_some(i.path[0].clone()))
{
parts[0] = guess;
} else {
panic!("Cannot resolve parent crate");
}
}
let (id, summary) = map
.paths
.iter()
.find(|(_, i)| i.kind == requested_item && i.path.ends_with(&parts))
.unwrap_or_else(|| panic!("Failed to lookup type {parts:?}"));
if summary.kind == requested_item {
*id
} else {
panic!(
"Incompatible type: Expected {requested_item:?}, Got {:?}",
summary.kind
);
}
};
let parent_crate = parts[0].to_owned();
(
parent_crate,
rustdoc_types::Path {
path: parts[parts.len() - 1].to_owned(),
id,
args: None,
},
)
}
fn resolve_index(
&self,
t: Option<&rustdoc_types::Path>,
id: &rustdoc_types::Id,
parent_crate: &str,
) -> rustdoc_types::Item {
let mut other_crates = self.other_crates.borrow_mut();
let candidates = std::iter::once(&self.doc_types)
.chain(other_crates.values())
.filter_map(|c| c.index.get(id))
.filter(|i| extract_crate_from_span(i) == Some(parent_crate.into()))
.collect::<Vec<_>>();
match &candidates as &[&rustdoc_types::Item] {
[i] => return rustdoc_types::Item::clone(i),
[] => {
}
items => {
let matches_parent_crate = items
.iter()
.position(|i| extract_crate_from_span(i) == Some(parent_crate.into()));
match matches_parent_crate {
Some(t) => {
return rustdoc_types::Item::clone(items[t]);
}
_ => {
panic!("Cannot decide what's the correct candidate")
}
}
}
}
let mut matched_ids = Vec::with_capacity(1);
if let Some(item) = self.doc_types.paths.get(id) {
let input_name = t.and_then(|t| t.path.split("::").last());
if input_name == item.path.last().map(|x| x.as_str()) {
matched_ids.push(item.clone());
}
}
for c in other_crates.values() {
if let Some(s) = c.paths.get(id) {
let input_name = t.and_then(|t| t.path.split("::").last());
if input_name == s.path.last().map(|x| x.as_str()) {
matched_ids.push(s.clone());
}
}
}
for crate_id in matched_ids {
let crate_name = crate_id.path.first().unwrap().clone();
let other_index = other_crates.entry(crate_name.clone()).or_insert_with(|| {
self.load_extern_crate_doc(&crate_name, &format!("(needed for {t:?})"))
});
let name = crate_id.path.last().unwrap();
let item = other_index.index.values().find(|i| {
i.name.as_ref() == Some(name)
&& matches!(
(&i.inner, &crate_id.kind),
(
rustdoc_types::ItemEnum::Struct(_),
rustdoc_types::ItemKind::Struct
) | (
rustdoc_types::ItemEnum::Enum(_),
rustdoc_types::ItemKind::Enum
) | (
rustdoc_types::ItemEnum::Function(_),
rustdoc_types::ItemKind::Function
)
)
});
if let Some(item) = item {
return item.clone();
}
}
panic!("Unknown id: {id:?}, crate: {parent_crate:?} (full type:{t:?})");
}
fn load_extern_crate_doc(
&self,
crate_name: &str,
additional_message: &str,
) -> rustdoc_types::Crate {
let content = std::fs::read_to_string(self.base_path.clone() + crate_name + ".json")
.unwrap_or_else(|_| {
panic!(
"Failed to find docs for `{}` {}",
&crate_name, additional_message
);
});
serde_json::from_str(&content).unwrap()
}
}
#[derive(Debug)]
enum TypeCache {
NeedToPopulate,
Cached(
Vec<(
serde_reflection::Format,
Option<serde_reflection::ContainerFormat>,
)>,
),
}
pub fn generate_bindings(out_dir: &Path, config: Config) {
if !out_dir.exists() {
panic!("Out directory does not exist");
}
let (target_directory, handle) = generate_docs(
&config.api_lib_name,
&config.rustdoc_crates,
config.crate_feature_flags.as_ref().unwrap_or(&Vec::new()),
config.rustdoc_flags.as_ref().unwrap_or(&Vec::new()),
);
let mut failed = false;
if let Ok(handle) = handle {
if handle.status.success() {
let resolver = ItemResolver::new(target_directory + "/doc/", &config.api_lib_name);
let mut type_map = HashMap::new();
let out_dir = out_dir.display().to_string();
generate_type_definitions(&resolver, &out_dir, &mut type_map, &config);
generate_function_definitions(
resolver,
&out_dir,
&mut type_map,
FUNCTION_PREFIX,
&config,
);
} else {
failed = true;
}
} else {
failed = true;
}
if !failed {
println!("Finished, wrote bindings to `{}`", out_dir.display());
}
if failed {
eprintln!("Failed to generate bindings");
std::process::exit(1);
}
}
pub fn generate_docs(
api_lib_name: &String,
rustdoc_crates: &[String],
crate_flags: &[String],
rustdoc_flags: &[String],
) -> (String, Result<Output, std::io::Error>) {
print!("Gather workspace metadata:");
std::io::stdout().flush().expect("Flushing does not fail");
let metadata = std::process::Command::new("cargo")
.arg("metadata")
.arg("--format-version=1")
.stderr(Stdio::inherit())
.output()
.expect("Failed to get workspace metadata");
println!(" OK");
let WorkspaceMetadata { target_directory } = serde_json::from_slice(&metadata.stdout).unwrap();
let doc_directory = target_directory.to_owned() + "/doc";
if matches!(fs::exists(&doc_directory), Ok(true)) {
for entry in fs::read_dir(doc_directory).unwrap() {
let file_path = entry.unwrap().path();
if file_path.extension().and_then(|s| s.to_str()) == Some("json") {
fs::remove_file(file_path).unwrap();
}
}
}
if rustdoc_crates.is_empty() {
eprintln!("Need at least one input crate to create bindings!");
std::process::exit(1);
}
let mut args = vec!["--no-deps"];
let crate_args: Vec<_> = rustdoc_crates
.iter()
.flat_map(|crate_name| vec!["-p", crate_name])
.collect();
let crate_flag_args: Vec<_> = crate_flags
.iter()
.flat_map(|crate_and_flag| vec!["-F", crate_and_flag])
.collect();
args.extend(crate_args);
args.extend(crate_flag_args);
args.extend(rustdoc_flags.iter().map(|s| s as &str));
let bootstrap_crates = vec![api_lib_name].into_iter().chain(rustdoc_crates).fold(
String::new(),
|cumulated, crate_name| {
let crate_name = crate_name.replace("-", "_");
cumulated + &format!(",{crate_name}")
},
);
let bootstrap_crates = &bootstrap_crates[1..bootstrap_crates.len()];
println!("Compile rustdocs:");
let mut rustdoc_command = std::process::Command::new("cargo");
rustdoc_command
.arg("doc")
.args(args)
.env("RUSTC_BOOTSTRAP", bootstrap_crates)
.env("RUSTDOCFLAGS", "-Z unstable-options --output-format json ")
.env("CARGO_TARGET_DIR", &target_directory)
.stderr(Stdio::inherit())
.stdout(Stdio::inherit());
let handle = rustdoc_command.output();
(target_directory, handle)
}
fn generate_function_definitions(
res: ItemResolver,
out_dir: &str,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
function_prefix: &str,
config: &Config,
) {
let namespace = &config.namespace;
let file_prefix = config.file_prefix.as_ref().unwrap_or(&config.api_lib_name);
let out_dir = PathBuf::from(out_dir);
let mut extern_c_functions = res
.doc_types
.index
.values()
.filter_map(|item| {
if let rustdoc_types::ItemEnum::Function(ref func) = item.inner {
if matches!(func.header.abi, rustdoc_types::Abi::C { .. }) {
let s = generate_extern_c_function_def(item.name.as_deref().unwrap(), func);
Some(s)
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();
extern_c_functions.sort();
let mut free_standing_functions = res
.doc_types
.index
.values()
.filter(is_free_standing_impl)
.collect::<Vec<_>>();
free_standing_functions.sort_by_key(|f| f.name.as_ref());
let mut relevant_impls = res
.doc_types
.index
.values()
.filter(is_relevant_impl)
.flat_map(|item| {
if let rustdoc_types::ItemEnum::Impl(ref impl_) = item.inner {
impl_
.items
.iter()
.map(|id| res.resolve_index(None, id, &config.parent_crate))
.filter(|item| matches!(item.inner, rustdoc_types::ItemEnum::Function(_)))
.map(move |i| (&impl_.for_, i))
} else {
unreachable!()
}
})
.fold(HashMap::<_, Vec<_>>::new(), |mut acc, (t, i)| {
acc.entry(t).or_default().push(i);
acc
})
.into_iter()
.map(|(n, mut items)| {
items.sort_by_key(|i| i.name.clone());
(n, items)
})
.collect::<Vec<_>>();
relevant_impls.sort_by_key(|(t, _)| {
if let rustdoc_types::Type::ResolvedPath(p) = t {
get_name_without_path(&p.path)
} else {
unreachable!()
}
});
let extern_c_header = out_dir.join(format!("{file_prefix}_api_functions.hpp"));
let mut extern_c_header = BufWriter::new(File::create(extern_c_header).unwrap());
write_function_header(&mut extern_c_header, config);
writeln!(extern_c_header, "#include <cstdint>").unwrap();
writeln!(extern_c_header).unwrap();
for (t, _) in relevant_impls.iter() {
if let rustdoc_types::Type::ResolvedPath(p) = t {
let name = get_name_without_path(&p.path);
writeln!(extern_c_header, "struct {name};\n").unwrap();
} else {
unreachable!()
}
}
for function in extern_c_functions {
writeln!(extern_c_header, "{function}").unwrap();
}
extern_c_header.flush().unwrap();
for (t, impls) in relevant_impls {
if let rustdoc_types::Type::ResolvedPath(p) = t {
let name = get_name_without_path(&p.path);
let type_header =
out_dir.join(format!("{file_prefix}_{}.hpp", name.to_ascii_lowercase()));
let mut writer = BufWriter::new(File::create(type_header).unwrap());
write_function_header(&mut writer, config);
writeln!(writer, "#include \"{file_prefix}_api_functions.hpp\"\n").unwrap();
writeln!(writer, "#include \"{namespace}.hpp\"\n").unwrap();
writeln!(writer).unwrap();
writeln!(writer, "namespace {namespace} {{").unwrap();
writeln!(writer).unwrap();
writeln!(writer, "class {name}Holder {{").unwrap();
writeln!(writer, " {name}* inner;").unwrap();
writeln!(writer, "public:").unwrap();
writeln!(writer, " {name}Holder({name}* ptr) {{").unwrap();
writeln!(writer, " this->inner = ptr;").unwrap();
writeln!(writer, " }}\n").unwrap();
for impl_ in impls {
if let rustdoc_types::ItemEnum::Function(ref m) = impl_.inner {
generate_function_def(
m,
&res,
&impl_,
&mut writer,
type_map,
function_prefix,
config,
Some(t),
);
}
}
writeln!(writer, "}};\n").unwrap();
writeln!(writer, "}} // end of namespace {namespace}").unwrap();
writer.flush().unwrap();
}
}
let free_standing_function_header =
out_dir.join(format!("{file_prefix}_free_standing_functions.hpp"));
let mut free_standing_function_header =
BufWriter::new(File::create(free_standing_function_header).unwrap());
write_function_header(&mut free_standing_function_header, config);
writeln!(
free_standing_function_header,
"#include \"{file_prefix}_api_functions.hpp\"\n"
)
.unwrap();
writeln!(
free_standing_function_header,
"#include \"{namespace}.hpp\"\n"
)
.unwrap();
writeln!(free_standing_function_header).unwrap();
writeln!(free_standing_function_header, "namespace {namespace} {{").unwrap();
writeln!(free_standing_function_header).unwrap();
for item in &free_standing_functions {
if let rustdoc_types::ItemEnum::Function(ref f) = item.inner {
generate_function_def(
f,
&res,
item,
&mut free_standing_function_header,
type_map,
function_prefix,
config,
None,
);
writeln!(free_standing_function_header).unwrap();
}
}
writeln!(
free_standing_function_header,
"}} // end of namespace {namespace}"
)
.unwrap();
free_standing_function_header.flush().unwrap();
}
fn write_function_header(out_functions: &mut BufWriter<File>, config: &Config) {
if let Some(copyright_header) = &config.copyright_header {
writeln!(out_functions, "// {copyright_header}").unwrap();
}
if let Some(generated_by) = &config.generated_by_header {
writeln!(out_functions, "// {generated_by}").unwrap();
}
if config.copyright_header.is_some() || config.generated_by_header.is_some() {
writeln!(out_functions).unwrap();
}
writeln!(out_functions, "#pragma once\n").unwrap();
writeln!(out_functions, "#include <cstddef>").unwrap();
writeln!(out_functions, "#include <limits>").unwrap();
}
#[allow(clippy::too_many_arguments)]
fn generate_function_def(
m: &rustdoc_types::Function,
res: &ItemResolver,
item: &rustdoc_types::Item,
out_functions: &mut BufWriter<File>,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
prefix: &str,
config: &Config,
impl_type: Option<&rustdoc_types::Type>,
) {
let output_type = if let Some(ref tpe) = m.sig.output {
let tpe = to_serde_reflect_type(
tpe,
res,
&mut None,
Vec::new(),
&config.parent_crate,
&config.namespace,
type_map,
);
to_cpp_type_name(&tpe.last().unwrap().0)
} else {
unimplemented!()
};
let inputs = m
.sig
.inputs
.iter()
.map(|(name, tpe)| {
if name == "self" {
let impl_type_path = impl_type
.map(|tpe| {
let rustdoc_types::Type::ResolvedPath(path) = tpe else {
panic!("Impl type must be a resolved path");
};
path
})
.expect("we have an impl type for impl functions");
return (name, get_name_without_path(&impl_type_path.path).to_owned());
}
let reflect_type = to_serde_reflect_type(
tpe,
res,
&mut None,
Vec::new(),
&config.parent_crate,
&config.namespace,
type_map,
);
let type_string = reflect_type
.last()
.map(|(f, _)| to_cpp_type_name(f))
.unwrap_or_else(|| panic!("Unknown type: {tpe:?}"));
(name, type_string)
})
.collect::<Vec<_>>();
let return_output_type = match m.sig.output {
Some(rustdoc_types::Type::ResolvedPath(ref p))
if get_name_without_path(&p.path) == "Result" =>
{
if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref()
{
if let rustdoc_types::GenericArg::Type(tpe) = &args[0] {
let tpe = to_serde_reflect_type(
tpe,
res,
&mut None,
Vec::new(),
&config.parent_crate,
&config.namespace,
type_map,
);
Cow::Owned(to_cpp_type_name(&tpe.last().unwrap().0))
} else {
unreachable!()
}
} else {
unreachable!()
}
}
Some(rustdoc_types::Type::ResolvedPath(ref p))
if get_name_without_path(&p.path) == "String" =>
{
Cow::Owned(to_cpp_type_name(&serde_reflection::Format::Str))
}
_ => Cow::Borrowed(&output_type as &str),
};
if let Some(ref docs) = item.docs {
for line in docs.lines() {
writeln!(out_functions, " // {line}").unwrap()
}
}
write!(
out_functions,
" inline {return_output_type} {}(",
item.name.as_ref().unwrap()
)
.unwrap();
for (idx, (name, tpe)) in inputs.iter().filter(|(n, _)| *n != "self").enumerate() {
if idx != 0 {
write!(out_functions, ", ").unwrap();
}
write!(out_functions, "const {tpe}& {name}").unwrap();
}
writeln!(out_functions, ") {{").unwrap();
for (name, tpe) in &inputs {
if *name == "self" {
continue;
}
writeln!(
out_functions,
" auto serializer_{name} = serde::BincodeSerializer();"
)
.unwrap();
writeln!(
out_functions,
" serde::Serializable<{tpe}>::serialize({name}, serializer_{name});"
)
.unwrap();
writeln!(
out_functions,
" std::vector<uint8_t> {name}_serialized = std::move(serializer_{name}).bytes();"
)
.unwrap();
}
writeln!(out_functions, " uint8_t* out_ptr = nullptr;").unwrap();
writeln!(out_functions).unwrap();
write!(
out_functions,
" size_t res_size = {}_{}(",
prefix,
item.name.as_deref().unwrap(),
)
.unwrap();
for (name, _) in inputs.iter() {
if *name == "self" {
write!(out_functions, "this->inner, ").unwrap();
} else {
write!(
out_functions,
"{name}_serialized.data(), {name}_serialized.size(), "
)
.unwrap();
}
}
writeln!(out_functions, "&out_ptr);").unwrap();
writeln!(out_functions).unwrap();
writeln!(
out_functions,
" std::vector<uint8_t> serialized_result(out_ptr, out_ptr + res_size);"
)
.unwrap();
writeln!(
out_functions,
" {output_type} out = {output_type}::bincodeDeserialize(serialized_result);"
)
.unwrap();
writeln!(
out_functions,
" {prefix}_free_byte_buffer(out_ptr, res_size);"
)
.unwrap();
writeln!(out_functions).unwrap();
if matches!(m.sig.output, Some(rustdoc_types::Type::ResolvedPath(ref p)) if get_name_without_path(&p.path) == "Result")
{
writeln!(
out_functions,
" if (out.value.index() == 0) {{ // Ok"
)
.unwrap();
if return_output_type == "void" {
writeln!(out_functions, " return;").unwrap();
} else {
writeln!(
out_functions,
" auto ok = std::get<0>(out.value);"
)
.unwrap();
writeln!(out_functions, " return std::get<0>(ok.value);").unwrap();
}
writeln!(out_functions, " }} else {{ // Err").unwrap();
writeln!(
out_functions,
" auto err = std::get<1>(out.value);"
)
.unwrap();
writeln!(
out_functions,
" auto error = std::get<0>(err.value);"
)
.unwrap();
writeln!(out_functions, " throw error;").unwrap();
writeln!(out_functions, " }}").unwrap();
} else {
writeln!(out_functions, " return out;").unwrap();
}
writeln!(out_functions, " }}\n").unwrap();
}
fn generate_type_definitions(
res: &ItemResolver,
out_types: &str,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
config: &Config,
) {
let comments = serde_generate::DocComments::new();
let mut comments = Some(comments);
let mut types_for_impls = res
.doc_types
.index
.values()
.filter(|i| is_relevant_impl(i) || is_free_standing_impl(i))
.flat_map(|item| {
if let rustdoc_types::ItemEnum::Impl(ref impl_) = item.inner {
impl_
.items
.iter()
.map(|id| res.resolve_index(None, id, &config.parent_crate))
.filter(|item| matches!(item.inner, rustdoc_types::ItemEnum::Function(_)))
.collect()
} else if let rustdoc_types::ItemEnum::Function(ref _f) = item.inner {
vec![item.clone()]
} else {
unreachable!()
}
})
.flat_map(|m| {
if let rustdoc_types::ItemEnum::Function(ref m) = m.inner {
m.sig
.inputs
.iter()
.map(|(_, t)| t.clone())
.chain(
m.sig
.output
.as_ref()
.map(|e| vec![e.clone()])
.unwrap_or_default(),
)
.collect::<Vec<_>>()
} else {
unreachable!()
}
})
.collect::<Vec<_>>();
types_for_impls.dedup();
let registry = types_for_impls
.into_iter()
.map(|t| {
to_serde_reflect_type(
&t,
res,
&mut comments,
Vec::new(),
&config.parent_crate,
&config.namespace,
type_map,
)
})
.flat_map(|types| {
types.into_iter().filter_map(|(format, container)| {
let container = container?;
if let serde_reflection::Format::TypeName(n) = format {
Some((n, container))
} else {
None
}
})
})
.collect::<serde_reflection::Registry>();
let config = serde_generate::CodeGeneratorConfig::new(config.namespace.to_owned())
.with_comments(comments.unwrap())
.with_encodings([serde_generate::Encoding::Bincode]);
let installer = serde_generate::cpp::Installer::new(PathBuf::from(out_types));
installer.install_module(&config, ®istry).unwrap();
installer.install_serde_runtime().unwrap();
installer.install_bincode_runtime().unwrap();
}
fn to_cpp_type_name(f: &serde_reflection::Format) -> String {
match f {
serde_reflection::Format::Variable(_) => unimplemented!(),
serde_reflection::Format::TypeName(_) => to_type_name(f).into_owned(),
serde_reflection::Format::Unit => unimplemented!(),
serde_reflection::Format::Bool => String::from("bool"),
serde_reflection::Format::I8 => String::from("int8_t"),
serde_reflection::Format::I16 => String::from("int16_t"),
serde_reflection::Format::I32 => String::from("int32_t"),
serde_reflection::Format::I64 => String::from("int64_t"),
serde_reflection::Format::I128 => unimplemented!(),
serde_reflection::Format::U8 => String::from("uint8_t"),
serde_reflection::Format::U16 => String::from("uint16_t"),
serde_reflection::Format::U32 => String::from("uint32_t"),
serde_reflection::Format::U64 => String::from("uint64_t"),
serde_reflection::Format::U128 => unimplemented!(),
serde_reflection::Format::F32 => String::from("float"),
serde_reflection::Format::F64 => String::from("double"),
serde_reflection::Format::Char => unimplemented!(),
serde_reflection::Format::Str => String::from("std::string"),
serde_reflection::Format::Bytes => unimplemented!(),
serde_reflection::Format::Option(t) => {
format!("std::optional<{}>", to_cpp_type_name(t))
}
serde_reflection::Format::Seq(p) => {
format!("std::vector<{}>", to_cpp_type_name(p))
}
serde_reflection::Format::Map { .. } => unimplemented!(),
serde_reflection::Format::Tuple(d) if d.is_empty() => String::from("void"),
serde_reflection::Format::Tuple(_) => unimplemented!(),
serde_reflection::Format::TupleArray { .. } => unimplemented!(),
}
}
fn to_type_name(f: &serde_reflection::Format) -> Cow<'_, str> {
match f {
serde_reflection::Format::Variable(_) => unimplemented!(),
serde_reflection::Format::TypeName(n) => Cow::Borrowed(n),
serde_reflection::Format::Unit => unimplemented!(),
serde_reflection::Format::Bool => Cow::Borrowed("bool"),
serde_reflection::Format::I8 => Cow::Borrowed("i8"),
serde_reflection::Format::I16 => Cow::Borrowed("i16"),
serde_reflection::Format::I32 => Cow::Borrowed("i32"),
serde_reflection::Format::I64 => Cow::Borrowed("i64"),
serde_reflection::Format::I128 => unimplemented!(),
serde_reflection::Format::U8 => Cow::Borrowed("u8"),
serde_reflection::Format::U16 => Cow::Borrowed("u16"),
serde_reflection::Format::U32 => Cow::Borrowed("u32"),
serde_reflection::Format::U64 => Cow::Borrowed("u64"),
serde_reflection::Format::U128 => unimplemented!(),
serde_reflection::Format::F32 => Cow::Borrowed("f32"),
serde_reflection::Format::F64 => Cow::Borrowed("f64"),
serde_reflection::Format::Char => unimplemented!(),
serde_reflection::Format::Str => Cow::Borrowed("String"),
serde_reflection::Format::Bytes => unimplemented!(),
serde_reflection::Format::Option(t) => Cow::Owned(format!("Option_{}", to_type_name(t))),
serde_reflection::Format::Seq(t) => Cow::Owned(format!("Vec_{}", to_type_name(t))),
serde_reflection::Format::Map { .. } => unimplemented!(),
serde_reflection::Format::Tuple(d) if d.is_empty() => Cow::Borrowed("void"),
serde_reflection::Format::Tuple(d) => {
dbg!(d);
unimplemented!()
}
serde_reflection::Format::TupleArray { .. } => unimplemented!(),
}
}
fn to_serde_reflect_type(
t: &rustdoc_types::Type,
crate_map: &ItemResolver,
comment_map: &mut Option<serde_generate::DocComments>,
parent_args: Vec<rustdoc_types::GenericArg>,
parent_crate: &str,
namespace: &str,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
) -> Vec<(
serde_reflection::Format,
Option<serde_reflection::ContainerFormat>,
)> {
use serde_reflection::{ContainerFormat, Format};
fn reflect_primitive(p: &rustdoc_types::Type) -> Vec<(Format, Option<ContainerFormat>)> {
let rustdoc_types::Type::Primitive(p) = p else {
unreachable!("Primitive!")
};
match p.as_ref() {
"i64" => {
vec![(Format::I64, None)]
}
"i32" => {
vec![(Format::I32, None)]
}
"i16" => {
vec![(Format::I16, None)]
}
"i8" => {
vec![(Format::I8, None)]
}
"bool" => {
vec![(Format::Bool, None)]
}
"f64" => {
vec![(Format::F64, None)]
}
"f32" => {
vec![(Format::F32, None)]
}
"u8" => {
vec![(Format::U8, None)]
}
"u16" => {
vec![(Format::U16, None)]
}
"u32" => {
vec![(Format::U32, None)]
}
"u64" => {
vec![(Format::U64, None)]
}
"usize" if size_of::<usize>() == 8 => {
vec![(Format::U64, None)]
}
"usize" if size_of::<usize>() == 4 => {
vec![(Format::U32, None)]
}
"usize" => {
panic!("Invalid size of usize.");
}
_ => {
dbg!(p);
unimplemented!()
}
}
}
let recursive_type = match type_map.get(t) {
Some(TypeCache::Cached(t)) => return t.clone(),
Some(TypeCache::NeedToPopulate) => true,
None => {
type_map.insert(t.clone(), TypeCache::NeedToPopulate);
false
}
};
let r = match t {
rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.path) == "Result" => {
let mut out = Vec::new();
let (ok, error) = if let Some(rustdoc_types::GenericArgs::AngleBracketed {
args, ..
}) = p.args.as_deref()
{
let ok = &args[0];
let ok = if let rustdoc_types::GenericArg::Type(tpe) = ok {
to_serde_reflect_type(
tpe,
crate_map,
comment_map,
Vec::new(),
parent_crate,
namespace,
type_map,
)
} else {
unreachable!()
};
let err = if let Some((id, _)) =
crate_map.doc_types.index.iter().find(|(_, item)| {
item.name.as_deref().map(get_name_without_path) == Some("SerializableError")
}) {
let t = rustdoc_types::Type::ResolvedPath(rustdoc_types::Path {
path: "SerializableError".into(),
id: *id,
args: None,
});
to_serde_reflect_type(
&t,
crate_map,
comment_map,
Vec::new(),
parent_crate,
namespace,
type_map,
)
} else {
unreachable!(
"Could not find docs for `SerializableError`! Maybe the `errors` module or the type itself is still private?"
)
};
(ok, err)
} else {
unreachable!()
};
let mut result_enum = BTreeMap::new();
result_enum.insert(
0,
serde_reflection::Named {
name: "Ok".into(),
value: serde_reflection::VariantFormat::Tuple(vec![
ok.last().unwrap().0.clone(),
]),
},
);
result_enum.insert(
1,
serde_reflection::Named {
name: "Err".into(),
value: serde_reflection::VariantFormat::Tuple(vec![
error.last().unwrap().0.clone(),
]),
},
);
let ok_name = to_type_name(&ok.last().unwrap().0);
let err_name = to_type_name(&error.last().unwrap().0);
let name = format!("Result_{ok_name}_{err_name}");
out.extend(ok);
out.extend(error);
out.push((
Format::TypeName(name),
Some(ContainerFormat::Enum(result_enum)),
));
out
}
rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.path) == "String" => {
vec![(Format::Str, None)]
}
rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.path) == "Vec" => {
if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref()
{
if let rustdoc_types::GenericArg::Type(tpe) = &args[0] {
let mut inner = to_serde_reflect_type(
tpe,
crate_map,
comment_map,
Vec::new(),
parent_crate,
namespace,
type_map,
);
let last = inner.last().unwrap().0.clone();
inner.push((Format::Seq(Box::new(last)), None));
inner
} else {
unreachable!()
}
} else {
unreachable!()
}
}
rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.path) == "Option" => {
if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref()
{
if let rustdoc_types::GenericArg::Type(tpe) = &args[0] {
let mut inner = to_serde_reflect_type(
tpe,
crate_map,
comment_map,
Vec::new(),
parent_crate,
namespace,
type_map,
);
let last = inner.last().unwrap().0.clone();
inner.push((Format::Option(Box::new(last)), None));
inner
} else {
unreachable!()
}
} else {
unreachable!()
}
}
rustdoc_types::Type::ResolvedPath(p) if get_name_without_path(&p.path) == "Box" => {
let t = match p.args.as_deref() {
Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. })
if args.len() == 1 =>
{
if let Some(rustdoc_types::GenericArg::Type(t)) = args.first() {
t
} else {
unreachable!()
}
}
Some(_) | None => unreachable!(),
};
if recursive_type {
let name = match t {
rustdoc_types::Type::ResolvedPath(p) => get_name_without_path(&p.path),
_ => unreachable!(),
};
return vec![(Format::TypeName(name.to_owned()), None)];
} else {
to_serde_reflect_type(
t,
crate_map,
comment_map,
parent_args,
parent_crate,
namespace,
type_map,
)
}
}
rustdoc_types::Type::ResolvedPath(p) => {
let t = crate_map.resolve_index(Some(p), &p.id, parent_crate);
let parent_crate = extract_crate_from_span(&t).expect("parent crate is set");
if let Some(comment_map) = comment_map
&& let Some(ref doc) = t.docs
{
comment_map.insert(vec![namespace.to_owned(), p.path.clone()], doc.clone());
}
match t.inner {
rustdoc_types::ItemEnum::Struct(rustdoc_types::Struct {
kind: rustdoc_types::StructKind::Plain { ref fields, .. },
..
}) => generate_exported_struct(
fields,
crate_map,
comment_map,
p,
parent_args,
&parent_crate,
namespace,
type_map,
recursive_type,
),
rustdoc_types::ItemEnum::Struct(rustdoc_types::Struct {
kind: rustdoc_types::StructKind::Unit,
..
}) => generate_exported_struct(
&[],
crate_map,
comment_map,
p,
parent_args,
&parent_crate,
namespace,
type_map,
recursive_type,
),
rustdoc_types::ItemEnum::Enum(ref e) => generate_exported_enum(
e,
crate_map,
comment_map,
p,
&parent_crate,
namespace,
type_map,
recursive_type,
),
rustdoc_types::ItemEnum::TypeAlias(ref t) => to_serde_reflect_type(
&t.type_,
crate_map,
comment_map,
parent_args,
&parent_crate,
namespace,
type_map,
),
t => {
dbg!(t);
unimplemented!();
}
}
}
rustdoc_types::Type::DynTrait(_) => unimplemented!(),
rustdoc_types::Type::Generic(p) => {
if parent_args.len() == 1 {
if let rustdoc_types::GenericArg::Type(t) = &parent_args[0] {
to_serde_reflect_type(
t,
crate_map,
comment_map,
Vec::new(),
parent_crate,
namespace,
type_map,
)
} else {
unimplemented!("Only types are accepted here?")
}
} else {
dbg!(parent_args);
dbg!(p);
unimplemented!("Unsure how to resolve multiple args here??")
}
}
rustdoc_types::Type::Primitive(_) => reflect_primitive(t),
rustdoc_types::Type::FunctionPointer(_) => unimplemented!(),
rustdoc_types::Type::Tuple(tup) => {
let mut out = Vec::new();
let mut fields = Vec::with_capacity(tup.len());
for f in tup {
let r = to_serde_reflect_type(
f,
crate_map,
comment_map,
Vec::new(),
parent_crate,
namespace,
type_map,
);
let f = r.last().map(|a| a.0.clone()).unwrap();
out.extend(r);
fields.push(f);
}
out.push((Format::Tuple(fields), None));
out
}
rustdoc_types::Type::Slice(_) => unimplemented!(),
rustdoc_types::Type::Array { type_, len } => {
let size = len.parse::<usize>().expect("Array len should be a number");
let t = reflect_primitive(type_)[0].0.clone();
vec![(
Format::TupleArray {
content: Box::new(t),
size,
},
None,
)]
}
rustdoc_types::Type::ImplTrait(_) => unimplemented!(),
rustdoc_types::Type::Infer => unimplemented!(),
rustdoc_types::Type::RawPointer { .. } => unimplemented!(),
rustdoc_types::Type::Pat { .. } => unimplemented!(),
rustdoc_types::Type::BorrowedRef { type_, .. } => {
if let rustdoc_types::Type::Generic(s) = &**type_
&& s == "Self"
{
return Vec::new();
}
dbg!(t);
unimplemented!()
}
rustdoc_types::Type::QualifiedPath { .. } => unimplemented!(),
};
type_map.insert(t.clone(), TypeCache::Cached(r.clone()));
r
}
fn extract_crate_from_span(t: &rustdoc_types::Item) -> Option<String> {
let p = &t.span.as_ref()?.filename;
let mut components = p.components().peekable();
let crate_name = match components.next() {
Some(Component::Normal(el)) => {
let mut rev_components = components
.rev()
.skip_while(|el| *el != Component::Normal(OsStr::new("src")))
.skip(1); let Component::Normal(next) = rev_components.next().unwrap_or(Component::Normal(el))
else {
panic!("Could not resolve source path");
};
let s = next.to_str().expect("We expect an UTF-8 Path");
s.replace('-', "_")
}
Some(Component::RootDir | Component::Prefix(_)) => {
loop {
match components.next()? {
Component::Normal(e)
if (e == ".cargo" || e == "cargo")
&& matches!(components.peek(), Some(Component::Normal(e)) if *e == "registry") =>
{
break;
}
_ => {}
}
}
components.next();
components.next();
components.next();
let Some(Component::Normal(el)) = components.next() else {
panic!("Expect a normal path element")
};
let s = el.to_str().expect("We expect an UTF-8 Path");
let Some((s, _)) = s.rsplit_once('-') else {
panic!("Expect a versioned crate name")
};
s.replace('-', "_")
}
_ => panic!("We expect a relative or absolute path here"),
};
Some(crate_name)
}
#[allow(clippy::too_many_arguments)]
fn generate_exported_enum(
e: &rustdoc_types::Enum,
crate_map: &ItemResolver,
comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
p: &rustdoc_types::Path,
parent_crate: &str,
namespace: &str,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
recursive_type: bool,
) -> Vec<(
serde_reflection::Format,
Option<serde_reflection::ContainerFormat>,
)> {
use serde_reflection::{ContainerFormat, Format};
let mut out = Vec::new();
let container_format = if recursive_type {
None
} else {
let mut enum_def = BTreeMap::new();
for (id, variant) in e.variants.iter().enumerate() {
let v = crate_map.resolve_index(None, variant, parent_crate);
if let Some(comment_map) = comment_map
&& let Some(ref docs) = v.docs
{
comment_map.insert(
vec![
namespace.to_owned(),
p.path.clone(),
v.name.clone().unwrap(),
],
docs.clone(),
);
}
match v.inner {
rustdoc_types::ItemEnum::Variant(rustdoc_types::Variant {
kind: rustdoc_types::VariantKind::Plain,
..
}) => {
enum_def.insert(
id as u32,
serde_reflection::Named {
name: v.name.clone().unwrap(),
value: serde_reflection::VariantFormat::Unit,
},
);
}
rustdoc_types::ItemEnum::Variant(rustdoc_types::Variant {
kind: rustdoc_types::VariantKind::Tuple(ref t),
..
}) => {
let mut variants = Vec::new();
for id in t {
if let Some(t) = id
.as_ref()
.map(|id| crate_map.resolve_index(None, id, parent_crate))
&& let rustdoc_types::ItemEnum::StructField(ref tpe) = t.inner
{
let args = HandleDatatypeFieldArgs {
crate_map,
attrs: &t.attrs,
tpe,
parent_crate,
namespace,
parent_args: Vec::new(),
field_name: v.name.as_deref(),
};
for (field_name, tps) in
handle_datatype_field(args, type_map, comment_map)
{
variants.push(serde_reflection::Named {
name: field_name,
value: tps.last().unwrap().0.clone(),
});
out.extend(tps);
}
}
}
if variants.len() == 1 {
let x = Box::new(variants.pop().expect("We have one. See above."));
enum_def.insert(
id as u32,
serde_reflection::Named {
name: x.name,
value: serde_reflection::VariantFormat::NewType(Box::new(x.value)),
},
);
} else {
let variants = variants.into_iter().map(|v| v.value).collect();
enum_def.insert(
id as u32,
serde_reflection::Named {
name: v.name.clone().unwrap(),
value: serde_reflection::VariantFormat::Tuple(variants),
},
);
}
}
rustdoc_types::ItemEnum::Variant(rustdoc_types::Variant {
kind: rustdoc_types::VariantKind::Struct { ref fields, .. },
..
}) => {
let mut variants = Vec::new();
for id in fields {
let t = crate_map.resolve_index(None, id, parent_crate);
if let rustdoc_types::ItemEnum::StructField(ref tpe) = t.inner {
let args = HandleDatatypeFieldArgs {
crate_map,
attrs: &t.attrs,
tpe,
parent_crate,
namespace,
parent_args: Vec::new(),
field_name: t.name.as_deref(),
};
for (field_name, tps) in
handle_datatype_field(args, type_map, comment_map)
{
variants.push(serde_reflection::Named {
name: field_name,
value: tps.last().unwrap().0.clone(),
});
out.extend(tps);
}
}
}
enum_def.insert(
id as u32,
serde_reflection::Named {
name: v.name.clone().unwrap(),
value: serde_reflection::VariantFormat::Struct(variants),
},
);
}
_ => unimplemented!(),
}
}
Some(ContainerFormat::Enum(enum_def))
};
let name = get_name_without_path(&p.path);
out.push((Format::TypeName(name.to_owned()), container_format));
out
}
#[derive(Default, Debug)]
struct FieldAttributes {
serde_with: Option<String>,
buffi_type: Option<syn::Path>,
buffi_skip: bool,
}
struct AttributeHelper(syn::Attribute);
impl syn::parse::Parse for AttributeHelper {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut attrs = syn::Attribute::parse_outer(input)?;
assert_eq!(attrs.len(), 1, "Only expect a single attribute here");
let attr = attrs.pop().unwrap();
Ok(Self(attr))
}
}
impl FieldAttributes {
fn parse_attributes(attrs: &[rustdoc_types::Attribute]) -> Self {
let mut field_attrs = Self::default();
for attr in attrs {
if let rustdoc_types::Attribute::Other(r) = attr
&& let Ok(AttributeHelper(attr)) = syn::parse_str(r)
{
if attr.path().is_ident("serde")
&& let syn::Meta::List(args) = &attr.meta
&& let Ok(attr) = args.parse_args::<SerdeAttribute>()
{
match attr {
SerdeAttribute::Flatten => {
unimplemented!("`#[serde(flatten)]` is not supported by bincode")
}
SerdeAttribute::With(w) => field_attrs.serde_with = Some(w),
SerdeAttribute::Unknown(_) => {}
}
} else if attr.path().is_ident("buffi")
&& let Ok(attr) =
attr.parse_args::<buffi_annotation_attributes::BuffiAnnotation>()
{
match attr {
buffi_annotation_attributes::BuffiAnnotation::Tpe(tpe) => {
field_attrs.buffi_type = Some(tpe)
}
buffi_annotation_attributes::BuffiAnnotation::Skip => {
field_attrs.buffi_skip = true
}
}
}
}
}
field_attrs
}
fn valid_combination(&self) -> bool {
match (
self.buffi_type.is_some(),
self.serde_with.is_some(),
self.buffi_skip,
) {
(false, false, false)
| (true, false, false)
| (false, true, false)
| (false, false, true) => true,
_ => {
panic!("Invalid attribute configuration: {self:?}")
}
}
}
fn resolve_types(
&self,
args: HandleDatatypeFieldArgs<'_>,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
) -> FieldList {
assert!(self.valid_combination());
match (
self.serde_with.as_deref(),
self.buffi_type.as_ref(),
self.buffi_skip,
) {
(None, None, true) => Vec::new(),
(Some(with), None, false) => {
let (parent_crate, item) = args.crate_map.resolve_by_path(
with,
args.parent_crate,
rustdoc_types::ItemKind::Struct,
);
let tpe = rustdoc_types::Type::ResolvedPath(item);
vec![(
args.field_name.unwrap_or_default().to_owned(),
to_serde_reflect_type(
&tpe,
args.crate_map,
comment_map,
args.parent_args,
&parent_crate,
args.namespace,
type_map,
),
)]
}
(None, Some(syn_path), false) => {
let (parent_crate, item) = syn_path_to_rustdoc_item(&args, syn_path);
let tpe = rustdoc_types::Type::ResolvedPath(item);
vec![(
args.field_name.unwrap_or_default().to_owned(),
to_serde_reflect_type(
&tpe,
args.crate_map,
comment_map,
args.parent_args,
&parent_crate,
args.namespace,
type_map,
),
)]
}
(None, None, false) => vec![(
args.field_name.unwrap_or_default().to_owned(),
to_serde_reflect_type(
args.tpe,
args.crate_map,
comment_map,
args.parent_args,
args.parent_crate,
args.namespace,
type_map,
),
)],
_ => unreachable!("We checked for these patterns above, they should not occur"),
}
}
}
fn syn_path_to_rustdoc_item(
infer_args: &HandleDatatypeFieldArgs<'_>,
syn_path: &syn::Path,
) -> (String, rustdoc_types::Path) {
let path = syn_path
.segments
.iter()
.map(|s| {
s.ident.to_string()
})
.collect::<Vec<_>>()
.join("::");
let (parent_crate, mut item) = infer_args.crate_map.resolve_by_path(
&path,
infer_args.parent_crate,
rustdoc_types::ItemKind::Struct,
);
if let syn::PathArguments::AngleBracketed(args) = &syn_path
.segments
.last()
.expect("There is at least one segment")
.arguments
{
let out = args
.args
.iter()
.map(|a| match a {
syn::GenericArgument::Type(syn::Type::Path(path)) => {
let (_parent_crate, item) = syn_path_to_rustdoc_item(infer_args, &path.path);
rustdoc_types::GenericArg::Type(rustdoc_types::Type::ResolvedPath(item))
}
_ => unimplemented!("Only type arguments are supported by buffi"),
})
.collect::<Vec<_>>();
if !out.is_empty() {
item.args = Some(Box::new(rustdoc_types::GenericArgs::AngleBracketed {
args: out,
constraints: Vec::new(),
}));
}
}
(parent_crate, item)
}
#[derive(Debug)]
enum SerdeAttribute {
Flatten,
With(String),
#[expect(dead_code)]
Unknown(String),
}
impl syn::parse::Parse for SerdeAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
match ident {
i if i == "with" => {
let _ = input.parse::<syn::Token![=]>()?;
let string = input.parse::<syn::LitStr>()?;
Ok(Self::With(string.value()))
}
i if i == "flatten" => Ok(Self::Flatten),
i => Ok(Self::Unknown(i.to_string())),
}
}
}
struct HandleDatatypeFieldArgs<'a> {
crate_map: &'a ItemResolver,
parent_crate: &'a str,
namespace: &'a str,
attrs: &'a [rustdoc_types::Attribute],
tpe: &'a rustdoc_types::Type,
parent_args: Vec<rustdoc_types::GenericArg>,
field_name: Option<&'a str>,
}
fn handle_datatype_field(
args: HandleDatatypeFieldArgs,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
) -> FieldList {
let attrs = FieldAttributes::parse_attributes(args.attrs);
attrs.resolve_types(args, type_map, comment_map)
}
#[allow(clippy::too_many_arguments)]
fn generate_exported_struct(
fields: &[rustdoc_types::Id],
crate_map: &ItemResolver,
comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
p: &rustdoc_types::Path,
parent_args: Vec<rustdoc_types::GenericArg>,
parent_crate: &str,
namespace: &str,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
recursive_type: bool,
) -> Vec<(
serde_reflection::Format,
Option<serde_reflection::ContainerFormat>,
)> {
use serde_reflection::{ContainerFormat, Format};
let mut out = Vec::new();
let mut name = get_name_without_path(&p.path).to_owned();
if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) = p.args.as_deref() {
for arg in args {
if let rustdoc_types::GenericArg::Type(t) = arg {
let tpe = to_serde_reflect_type(
t,
crate_map,
comment_map,
parent_args.clone(),
parent_crate,
namespace,
type_map,
)
.pop()
.unwrap()
.0;
name = format!("{name}_{}", to_type_name(&tpe));
}
}
}
let container_format = if recursive_type {
None
} else {
let fields = generate_struct_fields(
fields,
crate_map,
comment_map,
p.path.clone(),
p,
parent_args,
parent_crate,
namespace,
type_map,
);
let mut struct_fields = Vec::with_capacity(fields.len());
for (name, tpe) in fields {
let format = tpe.last().unwrap().0.clone();
struct_fields.push(serde_reflection::Named {
name,
value: format,
});
out.extend(tpe);
}
Some(ContainerFormat::Struct(struct_fields))
};
out.push((Format::TypeName(name), container_format));
out
}
#[allow(clippy::too_many_arguments)]
fn generate_struct_fields(
fields: &[rustdoc_types::Id],
crate_map: &ItemResolver,
comment_map: &mut Option<BTreeMap<Vec<String>, String>>,
field_path: String,
p: &rustdoc_types::Path,
parent_args: Vec<rustdoc_types::GenericArg>,
parent_crate: &str,
namespace: &str,
type_map: &mut HashMap<rustdoc_types::Type, TypeCache>,
) -> FieldList {
fields
.iter()
.map(|id| crate_map.resolve_index(None, id, parent_crate))
.flat_map(|s| {
if let Some(comment_map) = comment_map
&& let Some(ref doc) = s.docs
{
comment_map.insert(
vec![
namespace.to_owned(),
field_path.clone(),
s.name.clone().unwrap(),
],
doc.clone(),
);
}
if let rustdoc_types::ItemEnum::StructField(ref tpe) = s.inner {
let parent_args =
if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, constraints }) =
p.args.as_deref()
{
if args.is_empty() && constraints.is_empty() {
Vec::new()
} else if parent_args.len() == 1
&& args.len() == 1
&& matches!(
&args[0],
rustdoc_types::GenericArg::Type(rustdoc_types::Type::Generic(_))
)
{
parent_args.clone()
} else {
args.clone()
}
} else {
Vec::new()
};
let args = HandleDatatypeFieldArgs {
crate_map,
parent_crate,
namespace,
attrs: &s.attrs,
tpe,
parent_args,
field_name: s.name.as_deref(),
};
handle_datatype_field(args, type_map, comment_map)
} else {
vec![]
}
})
.collect::<Vec<_>>()
}
fn has_marker_attribute(item: &rustdoc_types::Item) -> bool {
item.attrs.iter().any(|a| {
if let Attribute::Other(o) = a {
o.starts_with("#[attr = CfgTrace([Not(NameValue { name: \"generated_extern_impl\"")
} else {
false
}
})
}
fn is_relevant_impl(item: &&rustdoc_types::Item) -> bool {
matches!(item.inner, rustdoc_types::ItemEnum::Impl(_)) && has_marker_attribute(item)
}
fn is_free_standing_impl(item: &&rustdoc_types::Item) -> bool {
matches!(item.inner, rustdoc_types::ItemEnum::Function(_)) && has_marker_attribute(item)
}
fn to_c_type(tpe: &rustdoc_types::Type) -> String {
match tpe {
rustdoc_types::Type::ResolvedPath(p) => {
let mut ret = get_name_without_path(&p.path).trim().to_string();
if ret == "c_char" {
String::from("char")
} else {
if let Some(rustdoc_types::GenericArgs::AngleBracketed { args, .. }) =
p.args.as_deref()
{
for arg in args {
if let rustdoc_types::GenericArg::Type(t) = arg {
write!(ret, "_{}", to_c_type(t)).unwrap();
}
}
}
ret
}
}
rustdoc_types::Type::DynTrait(_) => unimplemented!(),
rustdoc_types::Type::Generic(_) => unimplemented!(),
rustdoc_types::Type::Primitive(p) if p == "u8" => String::from("std::uint8_t"),
rustdoc_types::Type::Primitive(p) if p == "usize" => String::from("size_t"),
rustdoc_types::Type::Primitive(p) if p == "u16" => String::from("std::uint16_t"),
rustdoc_types::Type::Primitive(p) => p.clone(),
rustdoc_types::Type::FunctionPointer(_) => String::new(),
rustdoc_types::Type::Tuple(_) => unimplemented!(),
rustdoc_types::Type::Slice(_) => unimplemented!(),
rustdoc_types::Type::Array { .. } => unimplemented!(),
rustdoc_types::Type::ImplTrait(_) => unimplemented!(),
rustdoc_types::Type::Infer => unimplemented!(),
rustdoc_types::Type::RawPointer { is_mutable, type_ } => {
let mut out = if *is_mutable {
String::new()
} else {
String::from("const ")
};
write!(out, "{}*", to_c_type(type_)).unwrap();
out
}
rustdoc_types::Type::BorrowedRef { .. } => String::new(),
rustdoc_types::Type::QualifiedPath { .. } => unimplemented!(),
rustdoc_types::Type::Pat { .. } => unimplemented!(),
}
}
fn generate_extern_c_function_def(name: &str, func: &rustdoc_types::Function) -> String {
let mut out = String::from("extern \"C\" ");
write!(
out,
"{} ",
func.sig
.output
.as_ref()
.map(to_c_type)
.unwrap_or_else(|| "void".into())
)
.unwrap();
let args = func
.sig
.inputs
.iter()
.map(|(name, tpe)| {
let mut out = to_c_type(tpe);
write!(out, " {name}").unwrap();
out
})
.collect::<Vec<_>>()
.join(", ");
write!(out, "{name}({args});").unwrap();
out
}
fn get_name_without_path(name: &str) -> &str {
name.rsplit_once("::").map(|(_, e)| e).unwrap_or(name)
}