use std::vec;
use quote::ToTokens;
use crate::RbConfig;
const OPAQUE_STRUCTS: [&str; 4] = ["RString", "RArray", "RData", "RTypedData"];
const VERSIONED_OPAQUE_STRUCTS: &[(&str, (u32, u32), Option<(u32, u32)>)] = &[
("rb_matchext_struct", (3, 3), Some((4, 1))), ("rb_internal_thread_event_data", (3, 3), None),
("rb_io_internal_buffer", (3, 3), None),
];
pub fn opaqueify_bindings(
rbconfig: &RbConfig,
bindings: bindgen::Builder,
wrapper_h: &mut String,
) -> bindgen::Builder {
let version_specific_opaque_structs =
get_version_specific_opaque_structs(rbconfig.major_minor());
let structs_to_opaque = OPAQUE_STRUCTS
.iter()
.chain(&version_specific_opaque_structs);
structs_to_opaque.fold(bindings, |bindings, name| {
gen_opaque_struct(bindings, name, wrapper_h)
})
}
pub fn categorize_bindings(syntax: &mut syn::File) {
let mut normal_items = Vec::new();
let mut unstable_items = Vec::new();
let mut internal_items = Vec::new();
let mut excluded_items = Vec::new();
let mut opaque_items = Vec::new();
let mut opaque_idents_to_swap = Vec::new();
for item in syntax.items.iter_mut() {
if let syn::Item::Struct(s) = item {
if s.ident.to_string().contains("rb_sys__Opaque__") {
let new_name = s.ident.to_string().replace("rb_sys__Opaque__", "");
s.ident = syn::Ident::new(&new_name, s.ident.span());
opaque_idents_to_swap.push(new_name);
opaque_items.push(item.clone());
} else {
normal_items.push(item.clone());
}
} else if let syn::Item::Type(t) = item {
if t.ident.to_string().contains("rb_sys__Opaque__") {
let new_name = t.ident.to_string().replace("rb_sys__Opaque__", "");
t.ident = syn::Ident::new(&new_name, t.ident.span());
opaque_idents_to_swap.push(new_name);
opaque_items.push(item.clone());
} else {
normal_items.push(item.clone());
}
} else {
if let syn::Item::Fn(ref mut f) = item {
if f.sig.ident.to_string().contains("bindgen_test_") {
let body = &mut f.block;
let code = body.clone().to_token_stream().to_string();
let new_code = code.replace("rb_sys__Opaque__", "super::stable::");
let new_code = syn::parse_str::<syn::Block>(&new_code).unwrap();
*body = syn::parse_quote! {
{
#[allow(unused)]
use super::internal::*;
#new_code;
}
};
}
}
normal_items.push(item.clone());
}
}
for item in normal_items.iter_mut() {
if let syn::Item::Type(ref mut t) = item {
if let Ok(syn::Type::Path(ref mut type_path)) =
syn::parse2::<syn::Type>(t.ty.to_token_stream())
{
if opaque_idents_to_swap.contains(&type_path.path.segments[0].ident.to_string()) {
let new_ident = syn::Ident::new(
&type_path.path.segments[0].ident.to_string(),
type_path.path.segments[0].ident.span(),
);
t.ty = syn::parse_quote! { crate::internal::#new_ident };
}
}
}
}
for mut item in normal_items {
if let syn::Item::Struct(s) = &mut item {
if opaque_idents_to_swap.contains(&s.ident.to_string()) {
internal_items.push(syn::Item::Struct(s.clone()));
s.attrs.push(syn::parse_quote! {
#[deprecated(note = "To improve API stability with ruby-head, direct usage of Ruby internal structs has been deprecated. To migrate, please replace the usage of this internal struct with its counterpart in the `rb_sys::stable` module. For example, instead of `use rb_sys::rb_sys__Opaque__ExampleStruct;`, use `use rb_sys::stable::ExampleStruct;`. If you need to access the internals of these items, you can use the provided `rb-sys::macros` instead.")]
});
unstable_items.push(item);
} else {
excluded_items.push(item);
}
} else if let syn::Item::Type(t) = &mut item {
if opaque_idents_to_swap.contains(&t.ident.to_string()) {
internal_items.push(syn::Item::Type(t.clone()));
t.attrs.push(syn::parse_quote! {
#[deprecated(note = "To improve API stability with ruby-head, direct usage of Ruby internal structs has been deprecated. To migrate, please replace the usage of this internal struct with its counterpart in the `rb_sys::stable` module. For example, instead of `use rb_sys::rb_sys__Opaque__ExampleStruct;`, use `use rb_sys::stable::ExampleStruct;`. If you need to access the internals of these items, you can use the provided `rb-sys::macros` instead.")]
});
unstable_items.push(item);
} else {
excluded_items.push(item);
}
} else {
excluded_items.push(item);
}
}
for item in excluded_items.iter_mut() {
if let syn::Item::Impl(ref mut impl_item) = item {
if let Some((_, syn::Path { segments, .. }, _)) = impl_item.trait_.as_ref() {
if segments.iter().any(|segment| segment.ident == "Debug") {
if let syn::Type::Path(ref mut path) = *impl_item.self_ty {
if opaque_idents_to_swap.contains(&path.to_token_stream().to_string()) {
*impl_item.self_ty = syn::parse_quote! { crate::internal::#path };
}
}
}
}
}
}
for item in excluded_items.iter_mut() {
if let syn::Item::Const(ref mut c) = item {
let code = c.expr.to_token_stream().to_string();
let references_opaque = code.contains("rb_sys__Opaque__")
|| opaque_idents_to_swap.iter().any(|s| {
code.contains(&format!("< {} >", s))
|| code.contains(&format!("< {} ,", s))
|| code.contains(&format!("! ({} ,", s))
});
if references_opaque {
let mut new_code = code.replace("rb_sys__Opaque__", "super::stable::");
for name in &opaque_idents_to_swap {
new_code = new_code.replace(
&format!("< {} >", name),
&format!("< super::internal::{} >", name),
);
new_code = new_code.replace(
&format!("< {} ,", name),
&format!("< super::internal::{} ,", name),
);
new_code = new_code.replace(
&format!("! ({} ,", name),
&format!("! (super::internal::{} ,", name),
);
}
if let Ok(new_expr) = syn::parse_str::<syn::Expr>(&new_code) {
c.expr = Box::new(new_expr);
}
}
}
}
*syntax = syn::parse_quote! {
pub mod uncategorized {
#(#excluded_items)*
}
pub mod unstable {
use super::uncategorized::*;
#(#unstable_items)*
}
pub mod stable {
#(#opaque_items)*
}
#[allow(dead_code)]
pub (crate) mod internal {
use super::uncategorized::*;
#(#internal_items)*
}
};
}
fn gen_opaque_struct(
bindings: bindgen::Builder,
name: &str,
wrapper_h: &mut String,
) -> bindgen::Builder {
let struct_name = format!("rb_sys__Opaque__{}", name);
wrapper_h.push_str(&format!(
"struct {} {{ struct {} dummy; }};\n",
struct_name, name
));
bindings
.opaque_type(&struct_name)
.allowlist_type(struct_name)
}
fn get_version_specific_opaque_structs(major_minor: Option<(u32, u32)>) -> Vec<&'static str> {
let Some(v) = major_minor else {
return vec![];
};
VERSIONED_OPAQUE_STRUCTS
.iter()
.filter(|(_, added, removed)| v >= *added && removed.map_or(true, |r| v < r))
.map(|(name, _, _)| *name)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_version_specific_opaque_structs() {
assert!(get_version_specific_opaque_structs(None).is_empty());
assert!(get_version_specific_opaque_structs(Some((3, 2))).is_empty());
assert_eq!(
get_version_specific_opaque_structs(Some((3, 3))),
vec![
"rb_matchext_struct",
"rb_internal_thread_event_data",
"rb_io_internal_buffer"
]
);
assert_eq!(
get_version_specific_opaque_structs(Some((3, 4))),
vec![
"rb_matchext_struct",
"rb_internal_thread_event_data",
"rb_io_internal_buffer"
]
);
assert_eq!(
get_version_specific_opaque_structs(Some((4, 0))),
vec![
"rb_matchext_struct",
"rb_internal_thread_event_data",
"rb_io_internal_buffer"
]
);
assert_eq!(
get_version_specific_opaque_structs(Some((4, 1))),
vec!["rb_internal_thread_event_data", "rb_io_internal_buffer"]
);
assert_eq!(
get_version_specific_opaque_structs(Some((4, 2))),
vec!["rb_internal_thread_event_data", "rb_io_internal_buffer"]
);
assert!(get_version_specific_opaque_structs(Some((2, 7))).is_empty());
}
}