use std::vec; use quote::ToTokens; use crate::RbConfig; const OPAQUE_STRUCTS: [&str; 2] = ["RString", "RArray"]; const OPAQUE_STRUCTS_RUBY_3_3: [&str; 3] = [ "rb_matchext_struct", "rb_internal_thread_event_data", "rb_io_internal_buffer", ]; /// Generate opaque structs for the given bindings. 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) }) } /// Categorize all bindings into stable, unstable, and internal. 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::(&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::(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); } } // Perform a pass on all std::fmt::Debug implementations to fully qualify opaque structs 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 }; } } } } } } *syntax = syn::parse_quote! { /// Contains all items that are not yet categorized by ABI stability. /// These items are candidates for promotion to `stable` or `unstable` /// in the future. pub mod uncategorized { #(#excluded_items)* } /// Contains all items that are considered unstable ABI and should be /// avoided. Any items in this list offer a stable alternative for most /// use cases. pub mod unstable { use super::uncategorized::*; #(#unstable_items)* } /// Contains all items that are considered stable ABI and are safe to /// use. These items are intentionally opaque to prevent accidental /// compatibility issues. /// /// If you need to access the internals of these items, please open an /// issue. pub mod stable { #(#opaque_items)* } /// Unstable items for usage internally in rb_sys to avoid deprecated warnings. #[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(major_minor) = major_minor else { return vec![]; }; let mut result = vec![]; let (major, minor) = major_minor; if major == 3 && minor >= 3 { result.extend(OPAQUE_STRUCTS_RUBY_3_3) } result }