diff --git a/Cargo.lock b/Cargo.lock index 4e88c224f5..7b7ea3eab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,7 @@ version = "0.1.0" dependencies = [ "bindgen", "cc", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b0a5bbb082..7ba27c1658 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ clang-sys = "1" clap = "4" clap_complete = "4" env_logger = "0.10.0" -itertools = { version = ">=0.10,<0.14", default-features = false } +itertools = { version = ">=0.10,<0.15", default-features = false, features = ["use_alloc"] } libloading = "0.8" log = "0.4" objc = "0.2" diff --git a/bindgen-cli/Cargo.toml b/bindgen-cli/Cargo.toml index 52fcaaeb1b..628e800249 100644 --- a/bindgen-cli/Cargo.toml +++ b/bindgen-cli/Cargo.toml @@ -22,7 +22,7 @@ path = "main.rs" name = "bindgen" [dependencies] -bindgen = { workspace = true, features = ["__cli", "experimental", "prettyplease"] } +bindgen = { workspace = true, features = ["__cli", "experimental"] } env_logger = { workspace = true, optional = true } log = { workspace = true, optional = true } proc-macro2.workspace = true @@ -33,7 +33,6 @@ default = ["logging", "runtime"] logging = ["bindgen/logging", "dep:env_logger", "dep:log"] static = ["bindgen/static"] runtime = ["bindgen/runtime"] -prettyplease = ["bindgen/prettyplease"] ## The following features are for internal use and they shouldn't be used if ## you're not hacking on bindgen diff --git a/bindgen-integration/Cargo.toml b/bindgen-integration/Cargo.toml index 5c8c89d528..4d5c064aae 100644 --- a/bindgen-integration/Cargo.toml +++ b/bindgen-integration/Cargo.toml @@ -10,6 +10,9 @@ build = "build.rs" rust-version.workspace = true edition.workspace = true +[dependencies] +syn = { workspace = true, features = ["full", "visit"] } + [build-dependencies] bindgen = { workspace = true, default-features = true, features = ["experimental"] } cc.workspace = true diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs index 583f46ea93..e6598570bb 100644 --- a/bindgen-integration/build.rs +++ b/bindgen-integration/build.rs @@ -1,7 +1,8 @@ extern crate bindgen; use bindgen::callbacks::{ - DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks, + AttributeItemKind, DeriveInfo, FunctionKind, IntKind, MacroParsingBehavior, + ParseCallbacks, }; use bindgen::{Builder, EnumVariation, Formatter}; use std::collections::HashSet; @@ -140,7 +141,14 @@ impl ParseCallbacks for MacroCallback { info: &bindgen::callbacks::AttributeInfo<'_>, ) -> Vec { if info.name == "Test" { + assert_eq!(info.kind, AttributeItemKind::Struct); vec!["#[cfg_attr(test, derive(PartialOrd))]".into()] + } else if info.name == "coord" { + assert_eq!( + info.kind, + AttributeItemKind::Function(FunctionKind::Function) + ); + vec!["#[must_use]".into()] } else { vec![] } diff --git a/bindgen-integration/src/lib.rs b/bindgen-integration/src/lib.rs index 13f5bd889a..b13068255b 100755 --- a/bindgen-integration/src/lib.rs +++ b/bindgen-integration/src/lib.rs @@ -297,6 +297,62 @@ fn test_custom_derive() { assert!(!(test1 > test2)); } +#[test] +fn test_custom_fn_attribute() { + use std::env; + use std::fs; + use std::path::Path; + use syn::visit::Visit; + use syn::{parse_file, File, ForeignItem, Item, ItemForeignMod}; + + let out_dir = + std::env::var("OUT_DIR").expect("OUT_DIR environment variable not set"); + let test_file_path = Path::new(&out_dir).join("test.rs"); + let file_content = fs::read_to_string(&test_file_path) + .expect("Failed to read test.rs file"); + let syntax_tree: File = + parse_file(&file_content).expect("Failed to parse test.rs"); + + struct FunctionVisitor { + found_coord: bool, + has_must_use: bool, + } + + impl<'ast> Visit<'ast> for FunctionVisitor { + fn visit_item_foreign_mod( + &mut self, + foreign_mod: &'ast ItemForeignMod, + ) { + for foreign_item in &foreign_mod.items { + if let ForeignItem::Fn(item_fn) = foreign_item { + if item_fn.sig.ident == "coord" { + self.found_coord = true; + self.has_must_use = item_fn + .attrs + .iter() + .any(|attr| attr.path().is_ident("must_use")); + } + } + } + } + } + + let mut visitor = FunctionVisitor { + found_coord: false, + has_must_use: false, + }; + visitor.visit_file(&syntax_tree); + + assert!( + visitor.found_coord, + "The function 'coord' was not found in the source." + ); + assert!( + visitor.has_must_use, + "The function 'coord' does not have the #[must_use] attribute." + ); +} + #[test] fn test_custom_attributes() { // The `add_attributes` callback should have added `#[cfg_attr(test, derive(PartialOrd))])` diff --git a/bindgen-tests/build.rs b/bindgen-tests/build.rs index b261ed0a35..8fb33716a1 100644 --- a/bindgen-tests/build.rs +++ b/bindgen-tests/build.rs @@ -23,7 +23,7 @@ pub fn main() { for entry in entries { // TODO: file_is_cpp() in bindgen/lib.rs checks for hpp,hxx,hh, and h++ - should this be consistent? - if entry.path().extension().map_or(false, |ext| { + if entry.path().extension().is_some_and(|ext| { ext.eq_ignore_ascii_case("h") || ext.eq_ignore_ascii_case("hpp") }) { let func = entry diff --git a/bindgen-tests/tests/expectations/tests/attribute-custom-cli.rs b/bindgen-tests/tests/expectations/tests/attribute-custom-cli.rs index 55353116d3..0340e9b8b3 100644 --- a/bindgen-tests/tests/expectations/tests/attribute-custom-cli.rs +++ b/bindgen-tests/tests/expectations/tests/attribute-custom-cli.rs @@ -14,8 +14,8 @@ const _: () = { ][::std::mem::offset_of!(foo_struct, inner) - 0usize]; }; #[repr(u32)] -#[cfg_attr(test, derive(PartialOrd, Copy))] #[derive(Clone, Hash, PartialEq, Eq)] +#[cfg_attr(test, derive(PartialOrd, Copy))] pub enum foo_enum { inner = 0, } @@ -46,3 +46,9 @@ const _: () = { "Offset of field: non_matching::inner", ][::std::mem::offset_of!(non_matching, inner) - 0usize]; }; +unsafe extern "C" { + #[doc(hidden)] + #[must_use] + #[doc(hidden)] + pub fn foo_function() -> ::std::os::raw::c_int; +} diff --git a/bindgen-tests/tests/expectations/tests/attribute-custom.rs b/bindgen-tests/tests/expectations/tests/attribute-custom.rs index 6d616d3f3e..2b0df9e526 100644 --- a/bindgen-tests/tests/expectations/tests/attribute-custom.rs +++ b/bindgen-tests/tests/expectations/tests/attribute-custom.rs @@ -20,3 +20,9 @@ pub struct my_type2 { pub struct my_type3 { pub a: ::std::os::raw::c_ulong, } +unsafe extern "C" { + #[must_use] + #[doc(hidden)] + ///
+ pub fn function_attributes() -> ::std::os::raw::c_int; +} diff --git a/bindgen-tests/tests/expectations/tests/enum-doc-bitfield.rs b/bindgen-tests/tests/expectations/tests/enum-doc-bitfield.rs index ba73a8ea3e..bf00f0dbc6 100644 --- a/bindgen-tests/tests/expectations/tests/enum-doc-bitfield.rs +++ b/bindgen-tests/tests/expectations/tests/enum-doc-bitfield.rs @@ -52,7 +52,7 @@ impl ::std::ops::BitAndAssign for B { self.0 &= rhs.0; } } -#[repr(transparent)] /// Document enum +#[repr(transparent)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct B(pub ::std::os::raw::c_uint); diff --git a/bindgen-tests/tests/expectations/tests/enum-doc-mod.rs b/bindgen-tests/tests/expectations/tests/enum-doc-mod.rs index 2b18b35df0..d3e2cbfe08 100644 --- a/bindgen-tests/tests/expectations/tests/enum-doc-mod.rs +++ b/bindgen-tests/tests/expectations/tests/enum-doc-mod.rs @@ -1,4 +1,5 @@ #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +/// Document enum pub mod B { /// Document enum pub type Type = ::std::os::raw::c_uint; diff --git a/bindgen-tests/tests/expectations/tests/func_return_must_use.rs b/bindgen-tests/tests/expectations/tests/func_return_must_use.rs index 904c71cbe3..aa640f2ae2 100644 --- a/bindgen-tests/tests/expectations/tests/func_return_must_use.rs +++ b/bindgen-tests/tests/expectations/tests/func_return_must_use.rs @@ -4,9 +4,9 @@ unsafe extern "C" { #[must_use] pub fn return_int() -> MustUseInt; } +#[must_use] #[repr(C)] #[derive(Debug, Copy, Clone)] -#[must_use] pub struct MustUseStruct { _unused: [u8; 0], } @@ -24,9 +24,9 @@ unsafe extern "C" { pub fn return_plain_int() -> ::std::os::raw::c_int; } ///
+#[must_use] #[repr(C)] #[derive(Debug, Default, Copy, Clone)] -#[must_use] pub struct AnnotatedStruct {} #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { diff --git a/bindgen-tests/tests/expectations/tests/issue-710-must-use-type.rs b/bindgen-tests/tests/expectations/tests/issue-710-must-use-type.rs index d7006dd011..c00bdba1f5 100644 --- a/bindgen-tests/tests/expectations/tests/issue-710-must-use-type.rs +++ b/bindgen-tests/tests/expectations/tests/issue-710-must-use-type.rs @@ -1,14 +1,14 @@ #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[must_use] #[repr(C)] #[derive(Debug, Copy, Clone)] -#[must_use] pub struct A { _unused: [u8; 0], } ///
+#[must_use] #[repr(C)] #[derive(Debug, Copy, Clone)] -#[must_use] pub struct B { _unused: [u8; 0], } diff --git a/bindgen-tests/tests/headers/attribute-custom-cli.h b/bindgen-tests/tests/headers/attribute-custom-cli.h index a5f73c78e5..cb004d45e7 100644 --- a/bindgen-tests/tests/headers/attribute-custom-cli.h +++ b/bindgen-tests/tests/headers/attribute-custom-cli.h @@ -1,4 +1,4 @@ -// bindgen-flags: --default-enum-style rust --default-non-copy-union-style manually_drop --no-default=".*" --no-hash=".*" --no-partialeq=".*" --no-debug=".*" --no-copy=".*" --with-attribute-custom="foo_[^e].*=#[doc(hidden)]" --with-attribute-custom-struct="foo.*=#[derive(Default)]" --with-attribute-custom-enum="foo.*=#[cfg_attr(test, derive(PartialOrd, Copy))]" --with-attribute-custom-union="foo.*=#[derive(Clone)],#[derive(Copy)]" +// bindgen-flags: --default-enum-style rust --default-non-copy-union-style manually_drop --no-default=".*" --no-hash=".*" --no-partialeq=".*" --no-debug=".*" --no-copy=".*" --with-attribute-custom="foo_[^e].*=#[doc(hidden)]" --with-attribute-custom-struct="foo.*=#[derive(Default)]" --with-attribute-custom-enum="foo.*=#[cfg_attr(test, derive(PartialOrd, Copy))]" --with-attribute-custom-union="foo.*=#[derive(Clone)],#[derive(Copy)]" --with-attribute-custom-function="foo.*=#[must_use],#[doc(hidden)]" struct foo_struct { int inner; }; @@ -12,3 +12,4 @@ union foo_union { struct non_matching { int inner; }; +int foo_function() { return 1; } diff --git a/bindgen-tests/tests/headers/attribute-custom.h b/bindgen-tests/tests/headers/attribute-custom.h index dd382bf8cd..f825811836 100644 --- a/bindgen-tests/tests/headers/attribute-custom.h +++ b/bindgen-tests/tests/headers/attribute-custom.h @@ -26,3 +26,8 @@ struct my_type2 { struct my_type3 { unsigned long a; }; + +/** + *
+ */ +int function_attributes() { return 1; } diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index b808dfcd43..3cc7cbe415 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -27,7 +27,7 @@ fn should_overwrite_expected() -> bool { return true; } assert!( - var == "0" || var == "", + var == "0" || var.is_empty(), "Invalid value of BINDGEN_OVERWRITE_EXPECTED" ); } @@ -57,7 +57,7 @@ fn error_diff_mismatch( if let Some(var) = env::var_os("BINDGEN_TESTS_DIFFTOOL") { //usecase: var = "meld" -> You can hand check differences let Some(std::path::Component::Normal(name)) = - filename.components().last() + filename.components().next_back() else { panic!("Why is the header variable so weird?") }; @@ -187,7 +187,7 @@ fn compare_generated_header( header.display(), looked_at, ), - }; + } let (builder, roundtrip_builder) = builder.into_builder(check_roundtrip)?; diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index c01f8f0c44..f3822c2b07 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -34,7 +34,7 @@ clap = { workspace = true, features = ["derive"], optional = true } clap_complete = { workspace = true, optional = true } itertools = { workspace = true } log = { workspace = true, optional = true } -prettyplease = { workspace = true, optional = true, features = ["verbatim"] } +prettyplease = { workspace = true, features = ["verbatim"] } proc-macro2.workspace = true quote.workspace = true regex = { workspace = true, features = ["std", "unicode-perl"] } @@ -43,7 +43,7 @@ shlex.workspace = true syn = { workspace = true, features = ["full", "extra-traits", "visit-mut"] } [features] -default = ["logging", "prettyplease", "runtime"] +default = ["logging", "runtime"] logging = ["dep:log"] static = ["clang-sys/static"] runtime = ["clang-sys/runtime"] diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 8a21e98dea..0cbcadd1a5 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -3,6 +3,7 @@ pub use crate::ir::analysis::DeriveTrait; pub use crate::ir::derive::CanDerive as ImplementsTrait; pub use crate::ir::enum_ty::{EnumVariantCustomBehavior, EnumVariantValue}; +pub use crate::ir::function::FunctionKind; pub use crate::ir::int::IntKind; use std::fmt; @@ -133,10 +134,19 @@ pub trait ParseCallbacks: fmt::Debug { /// /// If no additional attributes are wanted, this function should return an /// empty `Vec`. + // TODO: Call this process_attributes function in codegen/mod.rs fn add_attributes(&self, _info: &AttributeInfo<'_>) -> Vec { vec![] } + /// Process an item's attribute + fn process_attributes( + &self, + _info: &AttributeInfo<'_>, + _attributes: &mut Vec, + ) { + } + /// Process a source code comment. fn process_comment(&self, _comment: &str) -> Option { None @@ -231,15 +241,30 @@ pub struct DeriveInfo<'a> { pub kind: TypeKind, } -/// Relevant information about a type to which new attributes will be added using +/// Relevant information about an item to which new attributes will be added using /// [`ParseCallbacks::add_attributes`]. #[derive(Debug)] #[non_exhaustive] pub struct AttributeInfo<'a> { - /// The name of the type. + /// The name of the item. pub name: &'a str, - /// The kind of the type. - pub kind: TypeKind, + /// The kind of the item. + pub kind: AttributeItemKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The kind of the current item. +pub enum AttributeItemKind { + /// The item is a Rust `struct`. + Struct, + /// The item is a Rust `enum`. + Enum, + /// The item is a Rust `union`. + Union, + /// The item is a Rust variable. + Var, + /// The item is a Rust `fn`. + Function(FunctionKind), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index f58a234117..e7dc1d4262 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -21,8 +21,8 @@ use self::struct_layout::StructLayoutTracker; use super::BindgenOptions; use crate::callbacks::{ - AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, FieldInfo, - TypeKind as DeriveTypeKind, + AttributeInfo, AttributeItemKind, DeriveInfo, DiscoveredItem, + DiscoveredItemId, FieldInfo, TypeKind as DeriveTypeKind, }; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; @@ -55,8 +55,11 @@ use crate::ir::template::{ use crate::ir::ty::{Type, TypeKind}; use crate::ir::var::Var; -use proc_macro2::{Ident, Span}; -use quote::{ToTokens, TokenStreamExt}; +use crate::quote::{ToTokens, TokenStreamExt}; +use itertools::Itertools; +use prettyplease::unparse; +use proc_macro2::{Ident, Span, TokenStream}; +use syn::{parse_quote, Attribute}; use crate::{Entry, HashMap, HashSet}; use std::borrow::Cow; @@ -64,7 +67,8 @@ use std::cell::Cell; use std::collections::VecDeque; use std::ffi::CStr; use std::fmt::{self, Write}; -use std::ops; +use std::mem::MaybeUninit; +use std::ops::{Deref, DerefMut}; use std::str::{self, FromStr}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -93,10 +97,7 @@ impl fmt::Display for CodegenError { // Name of type defined in constified enum module pub(crate) static CONSTIFIED_ENUM_MODULE_REPR_NAME: &str = "Type"; -fn top_level_path( - ctx: &BindgenContext, - item: &Item, -) -> Vec { +fn top_level_path(ctx: &BindgenContext, item: &Item) -> Vec { let mut path = vec![quote! { self }]; if ctx.options().enable_cxx_namespaces { @@ -108,10 +109,7 @@ fn top_level_path( path } -fn root_import( - ctx: &BindgenContext, - module: &Item, -) -> proc_macro2::TokenStream { +fn root_import(ctx: &BindgenContext, module: &Item) -> TokenStream { assert!(ctx.options().enable_cxx_namespaces, "Somebody messed it up"); assert!(module.is_module()); @@ -220,13 +218,133 @@ impl From for Vec<&'static str> { } } +fn parse_tokens(it: I) -> Vec +where + I: IntoIterator, + T: AsRef, +{ + it.into_iter() + .map(|s| s.as_ref().parse().expect("tokens are valid")) + .collect() +} + +// ["# [repr (C)]", "# [repr (align (4))]", "# [derive (Debug , Default , Copy , Clone)]"] +// ["#[repr(C)]", "#[repr(align(4))]", "#[derive(Debug, Default, Copy, Clone)]"] +fn format_attribute_tokens(attrs: &[TokenStream]) -> Vec { + if attrs.is_empty() || !attrs.iter().any(|attr| !attr.is_empty()) { + return vec![]; + } + + // If this ever errors, we may have to - depending on the attributes - use different dummy items to + // attach the attributes to. This is necessary to get a valid unparse from prettyplease/rustfmt + let attrs_with_body = quote! { + #(#attrs)* + fn body() {} + }; + + let mut attrs = vec![]; + let mut comments = vec![]; + let mut block_comment = false; + + for line in unparse(&syn::parse_quote!(#attrs_with_body)) + .split('\n') + .take_while(|line| !line.starts_with("fn body() {}")) + .join("\n") + .lines() + { + if line.starts_with("/*") { + block_comment = true; + } + + let cleaned = line + .trim_start_matches('/') + .trim_start_matches('*') + .trim_start_matches('!') + .trim_end_matches('/') + .trim_end_matches('*'); + + if block_comment || line.starts_with("///") || line.starts_with("//") { + comments.push(cleaned.to_string()); + } else if line.starts_with('#') { + attrs.push(line.to_string()); + } + + if line.ends_with("*/") { + block_comment = false; + } + } + + let comment = comments.join("\n"); + + // Only insert the attribute if there are formatted comments + if !comment.is_empty() { + attrs.insert(0, format!("#[doc = \"{}\"]", comment)); + } + + attrs +} + +fn process_attributes( + result: &mut CodegenResult, + item: &Item, + ctx: &BindgenContext, + attrs: &[TokenStream], + kind: AttributeItemKind, +) -> Vec { + let mut attrs = format_attribute_tokens(attrs); + + // TODO: Call add_attributes and renormalize attributes + ctx.options().for_each_callback_mut(|cb| { + cb.process_attributes( + &AttributeInfo { + name: &item.canonical_name(ctx), + kind, + }, + &mut attrs, + ); + }); + + let attrs = parse_tokens(attrs); + result.set_attributes(item.id(), attrs.clone()); + attrs +} + +fn attrs_for_item(item: &Item, ctx: &BindgenContext) -> Vec { + let mut attrs = vec![]; + attrs.extend(parse_tokens(item.annotations().attributes())); + + if let Some(comment) = item.comment(ctx) { + attrs.push(attributes::doc(&comment)); + } + + if item.must_use(ctx) { + attrs.push(attributes::must_use()); + } + + attrs +} + +fn combine_must_use(attrs: &mut Vec, must_use: bool) { + let must_use_tokens = attributes::must_use(); + let must_use_str = must_use_tokens.to_string(); + let before = attrs.len(); + attrs.retain(|attr| attr.to_string() != must_use_str); + let after = attrs.len(); + + if must_use { + attrs.push(must_use_tokens); + } else if before != after { + attrs.push(must_use_tokens); + } +} + struct WrapAsVariadic { new_name: String, idx_of_va_list_arg: usize, } struct CodegenResult<'a> { - items: Vec, + items: Vec, dynamic_items: DynamicItems, /// A monotonic counter used to add stable unique ID's to stuff that doesn't @@ -276,6 +394,9 @@ struct CodegenResult<'a> { /// List of items to serialize. With optionally the argument for the wrap as /// variadic transformation to be applied. items_to_serialize: Vec<(ItemId, Option)>, + + /// Tracks attributes of items during codegen + item_attributes: HashMap>, } impl<'a> CodegenResult<'a> { @@ -294,9 +415,22 @@ impl<'a> CodegenResult<'a> { vars_seen: Default::default(), overload_counters: Default::default(), items_to_serialize: Default::default(), + item_attributes: Default::default(), } } + fn set_attributes( + &mut self, + item_id: ItemId, + attributes: Vec, + ) { + *self.item_attributes.entry(item_id).or_default() = attributes; + } + + fn get_attributes(&self, item_id: ItemId) -> Option<&Vec> { + self.item_attributes.get(&item_id) + } + fn dynamic_items(&mut self) -> &mut DynamicItems { &mut self.dynamic_items } @@ -355,7 +489,7 @@ impl<'a> CodegenResult<'a> { self.vars_seen.insert(name.into()); } - fn inner(&mut self, cb: F) -> Vec + fn inner(&mut self, cb: F) -> Vec where F: FnOnce(&mut Self), { @@ -373,15 +507,15 @@ impl<'a> CodegenResult<'a> { } } -impl ops::Deref for CodegenResult<'_> { - type Target = Vec; +impl Deref for CodegenResult<'_> { + type Target = Vec; fn deref(&self) -> &Self::Target { &self.items } } -impl ops::DerefMut for CodegenResult<'_> { +impl DerefMut for CodegenResult<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items } @@ -612,9 +746,7 @@ impl CodeGenerator for Module { if let Some(raw_lines) = ctx.options().module_lines.get(&path) { for raw_line in raw_lines { found_any = true; - result.push( - proc_macro2::TokenStream::from_str(raw_line).unwrap(), - ); + result.push(TokenStream::from_str(raw_line).unwrap()); } } @@ -675,111 +807,16 @@ impl CodeGenerator for Var { return; } - let mut attrs = vec![]; - if let Some(comment) = item.comment(ctx) { - attrs.push(attributes::doc(&comment)); - } + let mut attrs = attrs_for_item(item, ctx); let var_ty = self.ty(); let ty = var_ty.to_rust_ty_or_opaque(ctx, &()); - if let Some(val) = self.val() { - match *val { - VarType::Bool(val) => { - result.push(quote! { - #(#attrs)* - pub const #canonical_ident : #ty = #val ; - }); - } - VarType::Int(val) => { - let int_kind = var_ty - .into_resolver() - .through_type_aliases() - .through_type_refs() - .resolve(ctx) - .expect_type() - .as_integer() - .unwrap(); - let val = if int_kind.is_signed() { - helpers::ast_ty::int_expr(val) - } else { - helpers::ast_ty::uint_expr(val as _) - }; - result.push(quote! { - #(#attrs)* - pub const #canonical_ident : #ty = #val ; - }); - } - VarType::String(ref bytes) => { - let prefix = ctx.trait_prefix(); + let is_extern_var = self.val().is_none(); + let mut maybe_link_name = MaybeUninit::<&str>::uninit(); - let options = ctx.options(); - let rust_features = options.rust_features; - - let mut cstr_bytes = bytes.clone(); - cstr_bytes.push(0); - let len = proc_macro2::Literal::usize_unsuffixed( - cstr_bytes.len(), - ); - let cstr = - if options.generate_cstr && rust_features.const_cstr { - CStr::from_bytes_with_nul(&cstr_bytes).ok() - } else { - None - }; - - if let Some(cstr) = cstr { - let cstr_ty = quote! { ::#prefix::ffi::CStr }; - if rust_features.literal_cstr { - let cstr = proc_macro2::Literal::c_string(cstr); - result.push(quote! { - #(#attrs)* - pub const #canonical_ident: &#cstr_ty = #cstr; - }); - } else { - let bytes = - proc_macro2::Literal::byte_string(&cstr_bytes); - result.push(quote! { - #(#attrs)* - #[allow(unsafe_code)] - pub const #canonical_ident: &#cstr_ty = unsafe { - #cstr_ty::from_bytes_with_nul_unchecked(#bytes) - }; - }); - } - } else { - // TODO: Here we ignore the type we just made up, probably - // we should refactor how the variable type and ty ID work. - let array_ty = quote! { [u8; #len] }; - let bytes = - proc_macro2::Literal::byte_string(&cstr_bytes); - let lifetime = - if true { None } else { Some(quote! { 'static }) } - .into_iter(); - - result.push(quote! { - #(#attrs)* - pub const #canonical_ident: &#(#lifetime )*#array_ty = #bytes ; - }); - } - } - VarType::Float(f) => { - if let Ok(expr) = helpers::ast_ty::float_expr(ctx, f) { - result.push(quote! { - #(#attrs)* - pub const #canonical_ident : #ty = #expr ; - }); - } - } - VarType::Char(c) => { - result.push(quote! { - #(#attrs)* - pub const #canonical_ident : #ty = #c ; - }); - } - } - } else { - let symbol: &str = self.link_name().unwrap_or_else(|| { + if is_extern_var { + maybe_link_name.write(self.link_name().unwrap_or_else(|| { let link_name = self.mangled_name().unwrap_or_else(|| self.name()); if utils::names_will_be_identical_after_mangling( @@ -792,8 +829,20 @@ impl CodeGenerator for Var { attrs.push(attributes::link_name::(link_name)); link_name } - }); + })); + } + let attrs = process_attributes( + result, + item, + ctx, + &attrs, + AttributeItemKind::Var, + ); + + if is_extern_var { + // SAFETY: We've already initialized `maybe_link_name` when dealing with extern variables. + let link_name: &str = unsafe { maybe_link_name.assume_init() }; let maybe_mut = if self.is_const() { quote! {} } else { @@ -816,7 +865,7 @@ impl CodeGenerator for Var { if ctx.options().dynamic_library_name.is_some() { result.dynamic_items().push_var( &canonical_ident, - symbol, + link_name, &self .ty() .to_rust_ty_or_opaque(ctx, &()) @@ -827,6 +876,105 @@ impl CodeGenerator for Var { } else { result.push(tokens); } + + // Required for SAFETY of the match below. + return; + } + + // SAFETY: This part only runs if `is_extern_var` is false, + // meaning `self.val()` must be `Some(_)`. + // Note: `maybe_link_name` remains uninitialized here. + match *unsafe { self.val().unwrap_unchecked() } { + VarType::Bool(val) => { + result.push(quote! { + #(#attrs)* + pub const #canonical_ident : #ty = #val ; + }); + } + VarType::Int(val) => { + let int_kind = var_ty + .into_resolver() + .through_type_aliases() + .through_type_refs() + .resolve(ctx) + .expect_type() + .as_integer() + .unwrap(); + let val = if int_kind.is_signed() { + helpers::ast_ty::int_expr(val) + } else { + helpers::ast_ty::uint_expr(val as _) + }; + result.push(quote! { + #(#attrs)* + pub const #canonical_ident : #ty = #val ; + }); + } + VarType::String(ref bytes) => { + let prefix = ctx.trait_prefix(); + + let options = ctx.options(); + let rust_features = options.rust_features; + + let mut cstr_bytes = bytes.clone(); + cstr_bytes.push(0); + let len = + proc_macro2::Literal::usize_unsuffixed(cstr_bytes.len()); + let cstr = if options.generate_cstr && rust_features.const_cstr + { + CStr::from_bytes_with_nul(&cstr_bytes).ok() + } else { + None + }; + + if let Some(cstr) = cstr { + let cstr_ty = quote! { ::#prefix::ffi::CStr }; + if rust_features.literal_cstr { + let cstr = proc_macro2::Literal::c_string(cstr); + result.push(quote! { + #(#attrs)* + pub const #canonical_ident: &#cstr_ty = #cstr; + }); + } else { + let bytes = + proc_macro2::Literal::byte_string(&cstr_bytes); + result.push(quote! { + #(#attrs)* + #[allow(unsafe_code)] + pub const #canonical_ident: &#cstr_ty = unsafe { + #cstr_ty::from_bytes_with_nul_unchecked(#bytes) + }; + }); + } + } else { + // TODO: Here we ignore the type we just made up, probably + // we should refactor how the variable type and ty ID work. + let array_ty = quote! { [u8; #len] }; + let bytes = proc_macro2::Literal::byte_string(&cstr_bytes); + let lifetime = + if true { None } else { Some(quote! { 'static }) } + .into_iter(); + + result.push(quote! { + #(#attrs)* + pub const #canonical_ident: &#(#lifetime )*#array_ty = #bytes ; + }); + } + } + VarType::Float(f) => { + if let Ok(expr) = helpers::ast_ty::float_expr(ctx, f) { + result.push(quote! { + #(#attrs)* + pub const #canonical_ident : #ty = #expr ; + }); + } + } + VarType::Char(c) => { + result.push(quote! { + #(#attrs)* + pub const #canonical_ident : #ty = #c ; + }); + } } } } @@ -1007,12 +1155,6 @@ impl CodeGenerator for Type { ); }); - let mut tokens = if let Some(comment) = item.comment(ctx) { - attributes::doc(&comment) - } else { - quote! {} - }; - let alias_style = if ctx.options().type_alias.matches(&name) { AliasVariation::TypeAlias } else if ctx.options().new_type_alias.matches(&name) { @@ -1023,6 +1165,52 @@ impl CodeGenerator for Type { ctx.options().default_alias_style }; + let mut attrs = attrs_for_item(item, ctx); + attrs.retain(|attr| { + attr.to_string() != attributes::must_use().to_string() + }); + /* + TODO: See if this disappears: + +++ generated from: "/home/runner/work/rust-bindgen/rust-bindgen/bindgen-tests/tests/headers/dynamic_loading_attributes.h" + 24 24 | let baz = __library.get(b"baz\0").map(|sym| *sym)?; + 25 25 | Ok(TestLib { __library, foo, baz }) + 26 26 | } + 27 | - #[must_use] + */ + + if let Some(inner_attrs) = + result.get_attributes(inner_item.id()) + { + // Only apply attributes through type aliases when they are relevant to compilation + attrs.extend( + inner_attrs + .iter() + .filter(|t| !t.is_empty()) + .map(|t| parse_quote! {#t}) + .filter_map(|attr: Attribute| { + if attr.path().is_ident("cfg") || + attr.path().is_ident("link") + { + Some(attr.to_token_stream()) + } else { + None + } + }), + ); + } + + let attrs = process_attributes( + result, + item, + ctx, + &attrs, + AttributeItemKind::Struct, + ); + + let mut tokens = quote! { + #( #attrs )* + }; + // We prefer using `pub use` over `pub type` because of: // https://github.com/rust-lang/rust/issues/26264 if matches!(inner_rust_type, syn::Type::Path(_)) && @@ -1048,8 +1236,14 @@ impl CodeGenerator for Type { pub type #rust_name }, AliasVariation::NewType | AliasVariation::NewTypeDeref => { - let mut attributes = - vec![attributes::repr("transparent")]; + // NB: Attributes for the actual item as well as its `process_attribute` callback + // have already been processed. It may be a good idea to emit these attributes + // earlier, to have them included in the callback. + // + // All of these attributes are additional and quoted below the processed attributes + // TODO: Finally introduce a TypeAlias variant to at least forward the alias_style, + // TODO: it should also include from and to type (cannonical name) + let mut attrs = vec![attributes::repr("transparent")]; let packed = false; // Types can't be packed in Rust. let derivable_traits = derives_of_item(item, ctx, packed); @@ -1066,23 +1260,10 @@ impl CodeGenerator for Type { // In most cases this will be a no-op, since custom_derives will be empty. derives .extend(custom_derives.iter().map(|s| s.as_str())); - attributes.push(attributes::derives(&derives)); - - let custom_attributes = - ctx.options().all_callbacks(|cb| { - cb.add_attributes(&AttributeInfo { - name: &name, - kind: DeriveTypeKind::Struct, - }) - }); - attributes.extend( - custom_attributes - .iter() - .map(|s| s.parse().unwrap()), - ); + attrs.push(attributes::derives(&derives)); quote! { - #( #attributes )* + #( #attrs )* pub struct #rust_name } } @@ -1145,6 +1326,7 @@ impl CodeGenerator for Type { if alias_style == AliasVariation::NewTypeDeref { let prefix = ctx.trait_prefix(); tokens.append_all(quote! { + #( #attrs )* impl ::#prefix::ops::Deref for #rust_name { type Target = #inner_rust_type; #[inline] @@ -1152,6 +1334,7 @@ impl CodeGenerator for Type { &self.0 } } + #( #attrs )* impl ::#prefix::ops::DerefMut for #rust_name { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { @@ -1228,10 +1411,10 @@ impl CodeGenerator for Vtable<'_> { let TypeKind::Function(ref signature) = signature_item.expect_type().kind() else { panic!("Function signature type mismatch") }; // FIXME: Is there a canonical name without the class prepended? - let function_name = function_item.canonical_name(ctx); + let canonical_name = function_item.canonical_name(ctx); // FIXME: Need to account for overloading with times_seen (separately from regular function path). - let function_name = ctx.rust_ident(function_name); + let function_name = ctx.rust_ident(&canonical_name); let mut args = utils::fnsig_arguments(ctx, signature); let ret = utils::fnsig_return_ty(ctx, signature); @@ -1393,8 +1576,8 @@ trait FieldCodegen<'a> { methods: &mut M, extra: Self::Extra, ) where - F: Extend, - M: Extend; + F: Extend, + M: Extend; } impl FieldCodegen<'_> for Field { @@ -1414,8 +1597,8 @@ impl FieldCodegen<'_> for Field { methods: &mut M, _: (), ) where - F: Extend, - M: Extend, + F: Extend, + M: Extend, { match *self { Field::DataMember(ref data) => { @@ -1492,8 +1675,8 @@ impl FieldCodegen<'_> for FieldData { methods: &mut M, _: (), ) where - F: Extend, - M: Extend, + F: Extend, + M: Extend, { // Bitfields are handled by `FieldCodegen` implementations for // `BitfieldUnit` and `Bitfield`. @@ -1531,6 +1714,8 @@ impl FieldCodegen<'_> for FieldData { }; let mut field = quote! {}; + // NB: Item::comment(str) does this internally, fields aren't + // actually Items so we do this manually here. if ctx.options().generate_comments { if let Some(raw_comment) = self.comment() { let comment = ctx.options().process_comment(raw_comment); @@ -1638,7 +1823,7 @@ impl FieldCodegen<'_> for FieldData { impl BitfieldUnit { /// Get the constructor name for this bitfield unit. - fn ctor_name(&self) -> proc_macro2::TokenStream { + fn ctor_name(&self) -> TokenStream { let ctor_name = Ident::new( &format!("new_bitfield_{}", self.nth()), Span::call_site(), @@ -1656,9 +1841,9 @@ impl Bitfield { fn extend_ctor_impl( &self, ctx: &BindgenContext, - param_name: &proc_macro2::TokenStream, - mut ctor_impl: proc_macro2::TokenStream, - ) -> proc_macro2::TokenStream { + param_name: &TokenStream, + mut ctor_impl: TokenStream, + ) -> TokenStream { let bitfield_ty = ctx.resolve_type(self.ty()); let bitfield_ty_layout = bitfield_ty .layout(ctx) @@ -1689,9 +1874,7 @@ impl Bitfield { } } -fn access_specifier( - visibility: FieldVisibilityKind, -) -> proc_macro2::TokenStream { +fn access_specifier(visibility: FieldVisibilityKind) -> TokenStream { match visibility { FieldVisibilityKind::Private => quote! {}, FieldVisibilityKind::PublicCrate => quote! { pub(crate) }, @@ -1753,8 +1936,8 @@ impl FieldCodegen<'_> for BitfieldUnit { methods: &mut M, _: (), ) where - F: Extend, - M: Extend, + F: Extend, + M: Extend, { use crate::ir::ty::RUST_DERIVE_IN_ARRAY_LIMIT; @@ -1887,7 +2070,7 @@ impl FieldCodegen<'_> for BitfieldUnit { fn bitfield_getter_name( ctx: &BindgenContext, bitfield: &Bitfield, -) -> proc_macro2::TokenStream { +) -> TokenStream { let name = bitfield.getter_name(); let name = ctx.rust_ident_raw(name); quote! { #name } @@ -1896,7 +2079,7 @@ fn bitfield_getter_name( fn bitfield_raw_getter_name( ctx: &BindgenContext, bitfield: &Bitfield, -) -> proc_macro2::TokenStream { +) -> TokenStream { let name = bitfield.getter_name(); let name = ctx.rust_ident_raw(format!("{name}_raw")); quote! { #name } @@ -1905,7 +2088,7 @@ fn bitfield_raw_getter_name( fn bitfield_setter_name( ctx: &BindgenContext, bitfield: &Bitfield, -) -> proc_macro2::TokenStream { +) -> TokenStream { let setter = bitfield.setter_name(); let setter = ctx.rust_ident_raw(setter); quote! { #setter } @@ -1914,7 +2097,7 @@ fn bitfield_setter_name( fn bitfield_raw_setter_name( ctx: &BindgenContext, bitfield: &Bitfield, -) -> proc_macro2::TokenStream { +) -> TokenStream { let setter = bitfield.setter_name(); let setter = ctx.rust_ident_raw(format!("{setter}_raw")); quote! { #setter } @@ -1952,8 +2135,8 @@ impl<'a> FieldCodegen<'a> for Bitfield { &'a mut FieldVisibilityKind, ), ) where - F: Extend, - M: Extend, + F: Extend, + M: Extend, { let prefix = ctx.trait_prefix(); let getter_name = bitfield_getter_name(ctx, self); @@ -2404,15 +2587,12 @@ impl CodeGenerator for CompInfo { (quote! {}, quote! {}, quote! {}) }; - let mut attributes = vec![]; + let mut attrs = attrs_for_item(item, ctx); let mut needs_clone_impl = false; let mut needs_default_impl = false; let mut needs_debug_impl = false; let mut needs_partialeq_impl = false; let needs_flexarray_impl = flex_array_generic.is_some(); - if let Some(comment) = item.comment(ctx) { - attributes.push(attributes::doc(&comment)); - } // if a type has both a "packed" attribute and an "align(N)" attribute, then check if the // "packed" attr is redundant, and do not include it if so. @@ -2428,9 +2608,9 @@ impl CodeGenerator for CompInfo { } else { format!("packed({n})") }; - attributes.push(attributes::repr_list(&["C", &packed_repr])); + attrs.push(attributes::repr_list(&["C", &packed_repr])); } else { - attributes.push(attributes::repr("C")); + attrs.push(attributes::repr("C")); } if true { @@ -2438,7 +2618,7 @@ impl CodeGenerator for CompInfo { // Ensure that the struct has the correct alignment even in // presence of alignas. let explicit = helpers::ast_ty::int_expr(explicit as i64); - attributes.push(quote! { + attrs.push(quote! { #[repr(align(#explicit))] }); } @@ -2521,40 +2701,38 @@ impl CodeGenerator for CompInfo { derives.extend(custom_derives.iter().map(|s| s.as_str())); if !derives.is_empty() { - attributes.push(attributes::derives(&derives)); + attrs.push(attributes::derives(&derives)); } - attributes.extend( - item.annotations() - .attributes() - .iter() - .map(|s| s.parse().unwrap()), + let attrs = process_attributes( + result, + item, + ctx, + &attrs, + if is_rust_union { + AttributeItemKind::Union + } else { + AttributeItemKind::Struct + }, ); - let custom_attributes = ctx.options().all_callbacks(|cb| { - cb.add_attributes(&AttributeInfo { - name: &canonical_name, - kind: if is_rust_union { - DeriveTypeKind::Union - } else { - DeriveTypeKind::Struct - }, + let cfg_attrs = attrs + .iter() + .filter(|t| !t.is_empty()) + .map(|t| parse_quote! {#t}) + .filter(|attr: &Attribute| { + attr.path().is_ident("cfg") || attr.path().is_ident("link") }) - }); - attributes.extend(custom_attributes.iter().map(|s| s.parse().unwrap())); - - if item.must_use(ctx) { - attributes.push(attributes::must_use()); - } + .collect_vec(); let mut tokens = if is_rust_union { quote! { - #( #attributes )* + #( #attrs )* pub union #canonical_ident } } else { quote! { - #( #attributes )* + #( #attrs )* pub struct #canonical_ident } }; @@ -2753,6 +2931,7 @@ impl CodeGenerator for CompInfo { if needs_clone_impl { result.push(quote! { + #( #cfg_attrs )* impl #impl_generics_labels Clone for #ty_for_impl { fn clone(&self) -> Self { *self } } @@ -2760,6 +2939,7 @@ impl CodeGenerator for CompInfo { } if needs_flexarray_impl { + // TODO: Expand cfg_attrs if necessary result.push(self.generate_flexarray( ctx, &canonical_ident, @@ -2793,6 +2973,7 @@ impl CodeGenerator for CompInfo { // non-zero padding bytes, especially when forwards/backwards compatibility is // involved. result.push(quote! { + #( #cfg_attrs )* impl #impl_generics_labels Default for #ty_for_impl { fn default() -> Self { #body @@ -2812,6 +2993,7 @@ impl CodeGenerator for CompInfo { let prefix = ctx.trait_prefix(); result.push(quote! { + #( #cfg_attrs )* impl #impl_generics_labels ::#prefix::fmt::Debug for #ty_for_impl { #impl_ } @@ -2836,6 +3018,7 @@ impl CodeGenerator for CompInfo { let prefix = ctx.trait_prefix(); result.push(quote! { + #( #cfg_attrs )* impl #impl_generics_labels ::#prefix::cmp::PartialEq for #ty_for_impl #partialeq_bounds { #impl_ } @@ -2845,6 +3028,7 @@ impl CodeGenerator for CompInfo { if !methods.is_empty() { result.push(quote! { + #( #cfg_attrs )* impl #impl_generics_labels #ty_for_impl { #( #methods )* } @@ -2858,10 +3042,10 @@ impl CompInfo { &self, ctx: &BindgenContext, canonical_ident: &Ident, - flex_inner_ty: Option<&proc_macro2::TokenStream>, + flex_inner_ty: Option<&TokenStream>, generic_param_names: &[Ident], - impl_generics_labels: &proc_macro2::TokenStream, - ) -> proc_macro2::TokenStream { + impl_generics_labels: &TokenStream, + ) -> TokenStream { let prefix = ctx.trait_prefix(); let flex_array = flex_inner_ty.as_ref().map(|ty| quote! { [ #ty ] }); @@ -2993,7 +3177,7 @@ impl Method { fn codegen_method( &self, ctx: &BindgenContext, - methods: &mut Vec, + methods: &mut Vec, method_names: &mut HashSet, result: &mut CodegenResult<'_>, _parent: &CompInfo, @@ -3063,11 +3247,11 @@ impl Method { method_names.insert(name.clone()); - let mut function_name = function_item.canonical_name(ctx); + let mut canonical_name = function_item.canonical_name(ctx); if times_seen > 0 { - write!(&mut function_name, "{times_seen}").unwrap(); + write!(&mut canonical_name, "{times_seen}").unwrap(); } - let function_name = ctx.rust_ident(function_name); + let function_name = ctx.rust_ident(&canonical_name); let mut args = utils::fnsig_arguments(ctx, signature); let mut ret = utils::fnsig_return_ty(ctx, signature); @@ -3089,6 +3273,10 @@ impl Method { ret = quote! { -> Self }; } + // TODO: Why can't we use + // TODO: let mut exprs = utils::fnsig_arguments(ctx, signature); + // TODO: here instead? + // TODO: Seems like `arguments_from_signature` is a less powerful implementation of that. let mut exprs = helpers::ast_ty::arguments_from_signature(signature, ctx); @@ -3123,7 +3311,7 @@ impl Method { exprs[0] = quote! { self }; - }; + } let call = quote! { #function_name (#( #exprs ),* ) @@ -3145,11 +3333,21 @@ impl Method { let block = ctx.wrap_unsafe_ops(quote! ( #( #stmts );*)); - let mut attrs = vec![attributes::inline()]; + let mut attrs = attrs_for_item(function_item, ctx); + attrs.push(attributes::inline()); - if signature.must_use() { + /*if signature.must_use() { attrs.push(attributes::must_use()); - } + }*/ + combine_must_use(&mut attrs, signature.must_use()); + + let attrs = process_attributes( + result, + function_item, + ctx, + &attrs, + AttributeItemKind::Function(FunctionKind::Method(self.kind())), + ); let name = ctx.rust_ident(&name); methods.push(quote! { @@ -3267,23 +3465,24 @@ impl FromStr for EnumVariation { /// A helper type to construct different enum variations. enum EnumBuilder<'a> { Rust { - attrs: Vec, + attrs: Vec, ident: Ident, - tokens: proc_macro2::TokenStream, + tokens: TokenStream, emitted_any_variants: bool, }, NewType { canonical_name: &'a str, - tokens: proc_macro2::TokenStream, + tokens: TokenStream, is_bitfield: bool, is_global: bool, }, Consts { - variants: Vec, + variants: Vec, }, ModuleConsts { module_name: &'a str, - module_items: Vec, + module_items: Vec, + attrs: Vec, }, } @@ -3297,7 +3496,7 @@ impl<'a> EnumBuilder<'a> { /// the representation, and which variation it should be generated as. fn new( name: &'a str, - mut attrs: Vec, + mut attrs: Vec, repr: &syn::Type, enum_variation: EnumVariation, has_typedef: bool, @@ -3356,6 +3555,7 @@ impl<'a> EnumBuilder<'a> { EnumBuilder::ModuleConsts { module_name: name, module_items: vec![type_definition], + attrs, } } } @@ -3383,6 +3583,8 @@ impl<'a> EnumBuilder<'a> { }; let mut doc = quote! {}; + // NB: Item::comment(str) does this internally, fields aren't + // actually Items so we do this manually here. if ctx.options().generate_comments { if let Some(raw_comment) = variant.comment() { let comment = ctx.options().process_comment(raw_comment); @@ -3419,6 +3621,7 @@ impl<'a> EnumBuilder<'a> { let enum_ident = ctx.rust_ident(canonical_name); let variant_ident = ctx.rust_ident(variant_name); + // TODO: Make sure attributes are emitted properly result.push(quote! { impl #enum_ident { #doc @@ -3460,6 +3663,7 @@ impl<'a> EnumBuilder<'a> { EnumBuilder::ModuleConsts { module_name, mut module_items, + attrs, } => { let name = ctx.rust_ident(variant_name); let ty = ctx.rust_ident(CONSTIFIED_ENUM_MODULE_REPR_NAME); @@ -3471,6 +3675,7 @@ impl<'a> EnumBuilder<'a> { EnumBuilder::ModuleConsts { module_name, module_items, + attrs, } } } @@ -3481,7 +3686,7 @@ impl<'a> EnumBuilder<'a> { ctx: &BindgenContext, rust_ty: &syn::Type, result: &mut CodegenResult<'_>, - ) -> proc_macro2::TokenStream { + ) -> TokenStream { match self { EnumBuilder::Rust { attrs, @@ -3562,10 +3767,12 @@ impl<'a> EnumBuilder<'a> { EnumBuilder::ModuleConsts { module_items, module_name, + attrs, .. } => { let ident = ctx.rust_ident(module_name); quote! { + #( #attrs )* pub mod #ident { #( #module_items )* } @@ -3652,7 +3859,7 @@ impl CodeGenerator for Enum { } }; - let mut attrs = vec![]; + let mut attrs = attrs_for_item(item, ctx); // TODO(emilio): Delegate this to the builders? match variation { @@ -3675,14 +3882,6 @@ impl CodeGenerator for Enum { } } _ => {} - }; - - if let Some(comment) = item.comment(ctx) { - attrs.push(attributes::doc(&comment)); - } - - if item.must_use(ctx) { - attrs.push(attributes::must_use()); } if !variation.is_const() { @@ -3715,24 +3914,15 @@ impl CodeGenerator for Enum { // In most cases this will be a no-op, since custom_derives will be empty. derives.extend(custom_derives.iter().map(|s| s.as_str())); - attrs.extend( - item.annotations() - .attributes() - .iter() - .map(|s| s.parse().unwrap()), - ); - - // The custom attribute callback may return a list of attributes; - // add them to the end of the list. - let custom_attributes = ctx.options().all_callbacks(|cb| { - cb.add_attributes(&AttributeInfo { - name: &name, - kind: DeriveTypeKind::Enum, - }) - }); - attrs.extend(custom_attributes.iter().map(|s| s.parse().unwrap())); - attrs.push(attributes::derives(&derives)); + + attrs = process_attributes( + result, + item, + ctx, + &attrs, + AttributeItemKind::Enum, + ); } fn add_constant( @@ -3768,8 +3958,30 @@ impl CodeGenerator for Enum { let repr = repr.to_rust_ty_or_opaque(ctx, item); let has_typedef = ctx.is_enum_typedef_combo(item.id()); - let mut builder = - EnumBuilder::new(&name, attrs, &repr, variation, has_typedef); + let attrs = process_attributes( + result, + item, + ctx, + &attrs, + AttributeItemKind::Enum, + ); + + let cfg_attrs = attrs + .iter() + .filter(|t| !t.is_empty()) + .map(|t| parse_quote! {#t}) + .filter(|attr: &Attribute| { + attr.path().is_ident("cfg") || attr.path().is_ident("link") + }) + .collect_vec(); + + let mut builder = EnumBuilder::new( + &name, + attrs.clone(), + &repr, + variation, + has_typedef, + ); // A map where we keep a value -> variant relation. let mut seen_values = HashMap::<_, Ident>::default(); @@ -3832,7 +4044,9 @@ impl CodeGenerator for Enum { let enum_canonical_name = &ident; let variant_name = ctx.rust_ident_raw(&*mangled_name); + // TODO: Only quote in #(#cfg_attrs)* result.push(quote! { + #(#cfg_attrs)* impl #enum_rust_ty { pub const #variant_name : #enum_rust_ty = #enum_canonical_name :: #existing_variant_name ; @@ -4305,8 +4519,7 @@ impl TryToRustTy for Type { } TypeKind::Enum(..) => { let path = item.namespace_aware_canonical_path(ctx); - let path = proc_macro2::TokenStream::from_str(&path.join("::")) - .unwrap(); + let path = TokenStream::from_str(&path.join("::")).unwrap(); Ok(syn::parse_quote!(#path)) } TypeKind::TemplateInstantiation(ref inst) => { @@ -4590,26 +4803,17 @@ impl CodeGenerator for Function { result.saw_function(seen_symbol_name); } - let mut attributes = vec![]; + let mut attrs = attrs_for_item(item, ctx); - if true { - let must_use = signature.must_use() || { - let ret_ty = signature + combine_must_use( + &mut attrs, + signature .return_type() .into_resolver() .through_type_refs() - .resolve(ctx); - ret_ty.must_use(ctx) - }; - - if must_use { - attributes.push(attributes::must_use()); - } - } - - if let Some(comment) = item.comment(ctx) { - attributes.push(attributes::doc(&comment)); - } + .resolve(ctx) + .must_use(ctx), + ); let abi = match signature.abi(ctx, Some(name)) { Err(err) => { @@ -4647,12 +4851,12 @@ impl CodeGenerator for Function { mangled_name, Some(abi), )) - .then(|| mangled_name) + .then_some(mangled_name) }); if let Some(link_name) = link_name_attr { if !is_dynamic_function { - attributes.push(attributes::link_name::(link_name)); + attrs.push(attributes::link_name::(link_name)); } } @@ -4670,7 +4874,7 @@ impl CodeGenerator for Function { if should_wrap { let name = canonical_name.clone() + ctx.wrap_static_fns_suffix(); - attributes.push(attributes::link_name::(&name)); + attrs.push(attributes::link_name::(&name)); } let wrap_as_variadic = if should_wrap && !signature.is_variadic() { @@ -4715,10 +4919,18 @@ impl CodeGenerator for Function { .unsafe_extern_blocks .then(|| quote!(unsafe)); + let attrs = process_attributes( + result, + item, + ctx, + &attrs, + AttributeItemKind::Function(FunctionKind::Function), + ); + let tokens = quote! { #wasm_link_attribute #safety extern #abi { - #(#attributes)* + #( #attrs )* pub fn #ident ( #( #args ),* ) #ret; } }; @@ -4747,7 +4959,7 @@ impl CodeGenerator for Function { &args_identifiers, &ret, &ret_ty, - &attributes, + &attrs, ctx, ); } else { @@ -4852,7 +5064,7 @@ fn variadic_fn_diagnostic( fn objc_method_codegen( ctx: &BindgenContext, method: &ObjCMethod, - methods: &mut Vec, + methods: &mut Vec, class_name: Option<&str>, rust_class_name: &str, prefix: &str, @@ -5116,7 +5328,7 @@ impl CodeGenerator for ObjCInterface { pub(crate) fn codegen( context: BindgenContext, -) -> Result<(proc_macro2::TokenStream, BindgenOptions), CodegenError> { +) -> Result<(TokenStream, BindgenOptions), CodegenError> { context.gen(|context| { let _t = context.timer("codegen"); let counter = Cell::new(0); diff --git a/bindgen/codegen/postprocessing/merge_cfg_attributes.rs b/bindgen/codegen/postprocessing/merge_cfg_attributes.rs new file mode 100644 index 0000000000..cc60ce7f23 --- /dev/null +++ b/bindgen/codegen/postprocessing/merge_cfg_attributes.rs @@ -0,0 +1,254 @@ +use crate::HashMap; +use itertools::Itertools; +use proc_macro2::Span; +use quote::{quote, ToTokens}; +use syn::token::Unsafe; +use syn::Abi; +use syn::{ + Attribute, File, ForeignItem, Ident, Item, ItemConst, ItemEnum, ItemFn, + ItemForeignMod, ItemImpl, ItemMod, ItemStatic, ItemStruct, ItemType, + ItemUnion, ItemUse, +}; + +pub fn merge_cfg_attributes(file: &mut File) { + let mut visitor = Visitor::new(); + visitor.visit_file(file); +} + +struct SyntheticMod { + attrs: AttributeSet, + unsafety: Option, + abi: Option, + items: Vec, +} + +impl SyntheticMod { + pub fn new(attrs: AttributeSet) -> Self { + Self { + attrs, + unsafety: None, + abi: None, + items: vec![], + } + } +} + +#[derive(Default, Clone)] +struct AttributeSet { + cfg_attrs: Vec, + cc_attrs: Vec, + other_attrs: Vec, + unsafety: Option, + abi: Option, +} + +impl AttributeSet { + fn new( + attrs: &[Attribute], + unsafety: Option, + abi: Option, + ) -> Self { + let mut attribute_set = AttributeSet::default(); + + for attr in attrs { + let target_set = if let Some(ident) = attr.path().get_ident() { + match ident.to_string().as_str() { + "cfg" => &mut attribute_set.cfg_attrs, + "link" => &mut attribute_set.cc_attrs, + _ => &mut attribute_set.other_attrs, + } + } else { + &mut attribute_set.other_attrs + }; + target_set.push(attr.clone()); + } + attribute_set.unsafety = unsafety; + attribute_set.abi = abi; + + attribute_set + } + + fn extend( + &mut self, + attrs: &[Attribute], + unsafety: Option, + abi: Option, + ) { + let other = AttributeSet::new(attrs, unsafety, abi); + self.other_attrs.extend(other.other_attrs); + self.cfg_attrs.extend(other.cfg_attrs); + self.cc_attrs.extend(other.cc_attrs); + + self.unsafety = other.unsafety.or(self.unsafety); + self.abi = other.abi.or(self.abi.clone()); + } + + fn ident(&self) -> Ident { + Ident::new( + Itertools::intersperse( + self.unsafety + .map(|r#unsafe| r#unsafe.to_token_stream().to_string()) + .into_iter() + .chain( + self.abi + .as_ref() + .map(|abi| abi.to_token_stream().to_string()), + ) + .chain( + self.cfg_attrs + .iter() + .chain(self.cc_attrs.iter()) + .map(|attr| attr.to_token_stream().to_string()) + .sorted(), + ), + "_".to_string(), + ) + .collect::() + .replace(|c: char| !c.is_alphanumeric(), "_") + .chars() + .coalesce(|a, b| { + if a == '_' && b == '_' { + Ok(a) + } else { + Err((a, b)) + } + }) + .collect::() + .trim_matches('_'), + Span::call_site(), + ) + } +} + +struct Visitor { + synthetic_mods: HashMap, + new_items: Vec, +} + +impl Visitor { + fn new() -> Self { + Self { + synthetic_mods: HashMap::default(), + new_items: Vec::new(), + } + } + + fn visit_file(&mut self, file: &mut File) { + self.visit_items(&mut file.items); + + for ( + ref mut ident, + SyntheticMod { + ref mut attrs, + ref mut unsafety, + ref mut abi, + ref mut items, + }, + ) in self.synthetic_mods.drain() + { + let cfg_attrs = attrs.cfg_attrs.iter().collect::>(); + let cc_attrs = attrs.cc_attrs.iter().collect::>(); + let synthetic_mod = if abi.is_some() { + quote! { + #(#cfg_attrs)* + pub mod #ident { + #(#cc_attrs)* + #unsafety #abi { + #(#items)* + } + } + } + } else { + quote! { + #(#cfg_attrs)* + pub mod #ident { + #(#items)* + } + } + }; + + self.new_items.push(Item::Verbatim(quote! { + #synthetic_mod + + #(#cfg_attrs)* + pub use #ident::*; + })); + } + + file.items = std::mem::take(&mut self.new_items); + } + + fn visit_items(&mut self, items: &mut Vec) { + for mut item in std::mem::take(items) { + match &mut item { + Item::Const(ItemConst { ref mut attrs, .. }) | + Item::Struct(ItemStruct { ref mut attrs, .. }) | + Item::Enum(ItemEnum { ref mut attrs, .. }) | + Item::Union(ItemUnion { ref mut attrs, .. }) | + Item::Type(ItemType { ref mut attrs, .. }) | + Item::Use(ItemUse { ref mut attrs, .. }) | + Item::Static(ItemStatic { ref mut attrs, .. }) | + Item::Mod(ItemMod { ref mut attrs, .. }) | + Item::Impl(ItemImpl { ref mut attrs, .. }) | + Item::Fn(ItemFn { ref mut attrs, .. }) => { + let attr_set = AttributeSet::new(attrs, None, None); + attrs.clone_from(&attr_set.other_attrs); + self.insert_item_into_mod(attr_set, item); + } + Item::ForeignMod(foreign_mod) => { + self.visit_foreign_mod(foreign_mod); + } + _ => { + self.new_items.push(item); + } + } + } + } + + fn visit_foreign_mod(&mut self, foreign_mod: &mut ItemForeignMod) { + for mut foreign_item in std::mem::take(&mut foreign_mod.items) { + // When MSRV >= 1.79.0 we can return &Vec::new() in the generic case as this wll get lifetime extended, + // see also https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html#extending-automatic-temporary-lifetime-extension. + let mut _attrs = vec![]; + let (inner_attrs, inner_unsafety, inner_abi) = + match &mut foreign_item { + ForeignItem::Fn(f) => { + (&mut f.attrs, f.sig.unsafety, f.sig.abi.clone()) + } + ForeignItem::Static(s) => (&mut s.attrs, None, None), + ForeignItem::Type(t) => (&mut t.attrs, None, None), + ForeignItem::Macro(m) => (&mut m.attrs, None, None), + _ => (&mut _attrs, None, None), + }; + + let mut attr_set = AttributeSet::new( + &foreign_mod.attrs, + foreign_mod.unsafety, + Some(foreign_mod.abi.clone()), + ); + attr_set.extend(inner_attrs, inner_unsafety, inner_abi); + *inner_attrs = attr_set.other_attrs.clone(); + + self.insert_item_into_mod( + attr_set, + Item::Verbatim(quote! { #foreign_item }), + ); + } + } + + fn insert_item_into_mod(&mut self, attr_set: AttributeSet, item: Item) { + if !attr_set.cfg_attrs.is_empty() || !attr_set.cc_attrs.is_empty() { + let ident = attr_set.ident(); + let synthetic_mod = self + .synthetic_mods + .entry(ident) + .or_insert_with(|| SyntheticMod::new(attr_set.clone())); + synthetic_mod.items.push(item); + synthetic_mod.unsafety = attr_set.unsafety; + synthetic_mod.abi = attr_set.abi.clone(); + synthetic_mod.attrs = attr_set; + } else { + self.new_items.push(item); + } + } +} diff --git a/bindgen/codegen/postprocessing/mod.rs b/bindgen/codegen/postprocessing/mod.rs index 9641698521..15a5914f14 100644 --- a/bindgen/codegen/postprocessing/mod.rs +++ b/bindgen/codegen/postprocessing/mod.rs @@ -4,9 +4,11 @@ use syn::{parse2, File}; use crate::BindgenOptions; +mod merge_cfg_attributes; mod merge_extern_blocks; mod sort_semantically; +use merge_cfg_attributes::merge_cfg_attributes; use merge_extern_blocks::merge_extern_blocks; use sort_semantically::sort_semantically; @@ -26,8 +28,11 @@ macro_rules! pass { }; } -const PASSES: &[PostProcessingPass] = - &[pass!(merge_extern_blocks), pass!(sort_semantically)]; +const PASSES: &[PostProcessingPass] = &[ + pass!(merge_cfg_attributes), + pass!(merge_extern_blocks), + pass!(sort_semantically), +]; pub(crate) fn postprocessing( items: Vec, diff --git a/bindgen/codegen/serialize.rs b/bindgen/codegen/serialize.rs index c7bb6cecef..9af48aa8ff 100644 --- a/bindgen/codegen/serialize.rs +++ b/bindgen/codegen/serialize.rs @@ -368,7 +368,7 @@ impl<'a> CSerialize<'a> for Type { match comp_info.kind() { CompKind::Struct => write!(writer, "struct {name}")?, CompKind::Union => write!(writer, "union {name}")?, - }; + } } TypeKind::Enum(_enum_ty) => { if self.is_const() { @@ -384,7 +384,7 @@ impl<'a> CSerialize<'a> for Type { loc: get_loc(item), }) } - }; + } if !stack.is_empty() { write!(writer, " ")?; diff --git a/bindgen/ir/analysis/derive.rs b/bindgen/ir/analysis/derive.rs index 6c66998bee..eaa20fff46 100644 --- a/bindgen/ir/analysis/derive.rs +++ b/bindgen/ir/analysis/derive.rs @@ -197,7 +197,7 @@ impl CannotDerive<'_> { self.derive_trait ); } - }; + } return layout_can_derive; } @@ -355,7 +355,7 @@ impl CannotDerive<'_> { self.derive_trait ); } - }; + } return layout_can_derive; } } diff --git a/bindgen/ir/annotations.rs b/bindgen/ir/annotations.rs index 7f5d74b3ee..34eada2988 100644 --- a/bindgen/ir/annotations.rs +++ b/bindgen/ir/annotations.rs @@ -172,7 +172,7 @@ impl Annotations { } /// The list of attributes that have been specified in this annotation. - pub(crate) fn attributes(&self) -> &[String] { + pub(crate) fn attributes(&self) -> &Vec { &self.attributes } diff --git a/bindgen/ir/comment.rs b/bindgen/ir/comment.rs index a4ba320186..d556cf0e6f 100644 --- a/bindgen/ir/comment.rs +++ b/bindgen/ir/comment.rs @@ -2,7 +2,7 @@ /// The type of a comment. #[derive(Debug, PartialEq, Eq)] -enum Kind { +pub(crate) enum Kind { /// A `///` comment, or something of the like. /// All lines in a comment should start with the same symbol. SingleLines, diff --git a/bindgen/ir/comp.rs b/bindgen/ir/comp.rs index 15f9cb4655..0179ef9687 100644 --- a/bindgen/ir/comp.rs +++ b/bindgen/ir/comp.rs @@ -32,7 +32,7 @@ pub(crate) enum CompKind { /// The kind of C++ method. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub(crate) enum MethodKind { +pub enum MethodKind { /// A constructor. We represent it as method for convenience, to avoid code /// duplication. Constructor, @@ -1855,7 +1855,7 @@ impl IsOpaque for CompInfo { // See https://github.com/rust-lang/rust-bindgen/issues/537 and // https://github.com/rust-lang/rust/issues/33158 if self.is_packed(ctx, layout.as_ref()) && - layout.map_or(false, |l| l.align > 1) + layout.is_some_and(|l| l.align > 1) { warn!("Found a type that is both packed and aligned to greater than \ 1; Rust before version 1.33 doesn't have `#[repr(packed(N))]`, so we \ diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index c6bc9025ec..458298c61b 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -933,7 +933,7 @@ If you encounter an error missing from this list, please file an issue or a PR!" *ty.kind() { typerefs.push((id, *ty, loc, parent_id)); - }; + } } typerefs } @@ -3095,7 +3095,7 @@ impl TemplateParameters for PartialType { num_params += 1; } _ => {} - }; + } clang_sys::CXChildVisit_Continue }); num_params diff --git a/bindgen/ir/dot.rs b/bindgen/ir/dot.rs index 0ccee42c0d..1562057b0e 100644 --- a/bindgen/ir/dot.rs +++ b/bindgen/ir/dot.rs @@ -41,7 +41,7 @@ where if is_allowlisted { "black" } else { "gray" } )?; item.dot_attributes(ctx, &mut dot_file)?; - writeln!(&mut dot_file, r#" >];"#)?; + writeln!(&mut dot_file, r" >];")?; item.trace( ctx, diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs index 83b748a5f4..cdf5c59cd1 100644 --- a/bindgen/ir/function.rs +++ b/bindgen/ir/function.rs @@ -19,7 +19,7 @@ const RUST_DERIVE_FUNPTR_LIMIT: usize = 12; /// What kind of function are we looking at? #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub(crate) enum FunctionKind { +pub enum FunctionKind { /// A plain, free function. Function, /// A method of some kind. diff --git a/bindgen/lib.rs b/bindgen/lib.rs index 0a8f29d158..9687b08dff 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -86,7 +86,7 @@ pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_"; const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern"; fn file_is_cpp(name_file: &str) -> bool { - Path::new(name_file).extension().map_or(false, |ext| { + Path::new(name_file).extension().is_some_and(|ext| { ext.eq_ignore_ascii_case("hpp") || ext.eq_ignore_ascii_case("hxx") || ext.eq_ignore_ascii_case("hh") || @@ -174,7 +174,6 @@ pub enum Formatter { None, /// Use `rustfmt` to format the bindings. Rustfmt, - #[cfg(feature = "prettyplease")] /// Use `prettyplease` to format the bindings. Prettyplease, } @@ -192,7 +191,6 @@ impl FromStr for Formatter { match s { "none" => Ok(Self::None), "rustfmt" => Ok(Self::Rustfmt), - #[cfg(feature = "prettyplease")] "prettyplease" => Ok(Self::Prettyplease), _ => Err(format!("`{s}` is not a valid formatter")), } @@ -204,7 +202,6 @@ impl std::fmt::Display for Formatter { let s = match self { Self::None => "none", Self::Rustfmt => "rustfmt", - #[cfg(feature = "prettyplease")] Self::Prettyplease => "prettyplease", }; @@ -563,7 +560,7 @@ impl BindgenOptions { self.parse_callbacks .iter() .filter_map(|cb| f(cb.as_ref())) - .last() + .next_back() } fn all_callbacks( @@ -580,6 +577,13 @@ impl BindgenOptions { self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref())); } + fn for_each_callback_mut( + &self, + mut f: impl FnMut(&dyn callbacks::ParseCallbacks), + ) { + self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref())); + } + fn process_comment(&self, comment: &str) -> String { let comment = comment::preprocess(comment); self.last_callback(|cb| cb.process_comment(&comment)) @@ -769,7 +773,7 @@ impl Bindings { 0, format!("--target={effective_target}").into_boxed_str(), ); - }; + } fn detect_include_paths(options: &mut BindgenOptions) { if !options.detect_include_paths { @@ -938,7 +942,7 @@ impl Bindings { writer.write_all(NL.as_bytes())?; } - match self.format_tokens(&self.module) { + match format_tokens(&self.options, &self.module) { Ok(formatted_bindings) => { writer.write_all(formatted_bindings.as_bytes())?; } @@ -951,102 +955,101 @@ impl Bindings { } Ok(()) } +} - /// Gets the rustfmt path to rustfmt the generated bindings. - fn rustfmt_path(&self) -> io::Result> { - debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); - if let Some(ref p) = self.options.rustfmt_path { - return Ok(Cow::Borrowed(p)); - } - if let Ok(rustfmt) = env::var("RUSTFMT") { - return Ok(Cow::Owned(rustfmt.into())); - } - // No rustfmt binary was specified, so assume that the binary is called - // "rustfmt" and that it is in the user's PATH. - Ok(Cow::Owned("rustfmt".into())) +/// Gets the rustfmt path to rustfmt the generated bindings. +pub(crate) fn rustfmt_path( + options: &BindgenOptions, +) -> io::Result> { + debug_assert!(matches!(options.formatter, Formatter::Rustfmt)); + if let Some(ref p) = options.rustfmt_path { + return Ok(Cow::Borrowed(p)); + } + if let Ok(rustfmt) = env::var("RUSTFMT") { + return Ok(Cow::Owned(rustfmt.into())); } + // No rustfmt binary was specified, so assume that the binary is called + // "rustfmt" and that it is in the user's PATH. + Ok(Cow::Owned("rustfmt".into())) +} - /// Formats a token stream with the formatter set up in `BindgenOptions`. - fn format_tokens( - &self, - tokens: &proc_macro2::TokenStream, - ) -> io::Result { - let _t = time::Timer::new("rustfmt_generated_string") - .with_output(self.options.time_phases); - - match self.options.formatter { - Formatter::None => return Ok(tokens.to_string()), - #[cfg(feature = "prettyplease")] - Formatter::Prettyplease => { - return Ok(prettyplease::unparse(&syn::parse_quote!(#tokens))); - } - Formatter::Rustfmt => (), +/// Formats a token stream with the formatter set up in `BindgenOptions`. +fn format_tokens( + options: &BindgenOptions, + tokens: &proc_macro2::TokenStream, +) -> io::Result { + let _t = time::Timer::new("rustfmt_generated_string") + .with_output(options.time_phases); + + match options.formatter { + Formatter::None => return Ok(tokens.to_string()), + Formatter::Prettyplease => { + return Ok(prettyplease::unparse(&syn::parse_quote!(#tokens))); } + Formatter::Rustfmt => (), + } - let rustfmt = self.rustfmt_path()?; - let mut cmd = Command::new(&*rustfmt); + let rustfmt = rustfmt_path(options)?; + let mut cmd = Command::new(&*rustfmt); - cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); - if let Some(path) = self - .options - .rustfmt_configuration_file - .as_ref() - .and_then(|f| f.to_str()) - { - cmd.args(["--config-path", path]); - } + if let Some(path) = options + .rustfmt_configuration_file + .as_ref() + .and_then(|f| f.to_str()) + { + cmd.args(["--config-path", path]); + } - let edition = self - .options - .rust_edition - .unwrap_or_else(|| self.options.rust_target.latest_edition()); - cmd.args(["--edition", &format!("{edition}")]); + let edition = options + .rust_edition + .unwrap_or_else(|| options.rust_target.latest_edition()); + cmd.args(["--edition", &format!("{edition}")]); - let mut child = cmd.spawn()?; - let mut child_stdin = child.stdin.take().unwrap(); - let mut child_stdout = child.stdout.take().unwrap(); + let mut child = cmd.spawn()?; + let mut child_stdin = child.stdin.take().unwrap(); + let mut child_stdout = child.stdout.take().unwrap(); - let source = tokens.to_string(); + let source = tokens.to_string(); - // Write to stdin in a new thread, so that we can read from stdout on this - // thread. This keeps the child from blocking on writing to its stdout which - // might block us from writing to its stdin. - let stdin_handle = ::std::thread::spawn(move || { - let _ = child_stdin.write_all(source.as_bytes()); - source - }); + // Write to stdin in a new thread, so that we can read from stdout on this + // thread. This keeps the child from blocking on writing to its stdout which + // might block us from writing to its stdin. + let stdin_handle = ::std::thread::spawn(move || { + let _ = child_stdin.write_all(source.as_bytes()); + source + }); - let mut output = vec![]; - io::copy(&mut child_stdout, &mut output)?; + let mut output = vec![]; + io::copy(&mut child_stdout, &mut output)?; - let status = child.wait()?; - let source = stdin_handle.join().expect( - "The thread writing to rustfmt's stdin doesn't do \ - anything that could panic", - ); + let status = child.wait()?; + let source = stdin_handle.join().expect( + "The thread writing to rustfmt's stdin doesn't do \ + anything that could panic", + ); - match String::from_utf8(output) { - Ok(bindings) => match status.code() { - Some(0) => Ok(bindings), - Some(2) => Err(io::Error::new( - io::ErrorKind::Other, - "Rustfmt parsing errors.".to_string(), - )), - Some(3) => { - rustfmt_non_fatal_error_diagnostic( - "Rustfmt could not format some lines", - &self.options, - ); - Ok(bindings) - } - _ => Err(io::Error::new( - io::ErrorKind::Other, - "Internal rustfmt error".to_string(), - )), - }, - _ => Ok(source), - } + match String::from_utf8(output) { + Ok(bindings) => match status.code() { + Some(0) => Ok(bindings), + Some(2) => Err(io::Error::new( + io::ErrorKind::Other, + "Rustfmt parsing errors.".to_string(), + )), + Some(3) => { + rustfmt_non_fatal_error_diagnostic( + "Rustfmt could not format some lines", + options, + ); + Ok(bindings) + } + _ => Err(io::Error::new( + io::ErrorKind::Other, + "Internal rustfmt error".to_string(), + )), + }, + _ => Ok(source), } } @@ -1182,7 +1185,7 @@ pub fn clang_version() -> ClangVersion { }; } } - }; + } ClangVersion { parsed: None, full: raw_v.clone(), diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index 8c4c05bc84..f1e1a77c72 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -3,9 +3,11 @@ use crate::{ builder, callbacks::{ - AttributeInfo, DeriveInfo, ItemInfo, ParseCallbacks, TypeKind, + AttributeInfo, AttributeItemKind, DeriveInfo, ItemInfo, ParseCallbacks, + TypeKind, }, features::{RustEdition, EARLIEST_STABLE_RUST}, + ir::function::FunctionKind, regex_set::RegexSet, Abi, AliasVariation, Builder, CodegenConfig, EnumVariation, FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle, @@ -477,7 +479,7 @@ struct BindgenCommand { /// Derive custom traits on a `union`. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros. #[arg(long, value_name = "CUSTOM", value_parser = parse_custom_derive)] with_derive_custom_union: Vec<(Vec, String)>, - /// Add custom attributes on any kind of type. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes. + /// Add custom attributes on any item. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes. #[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)] with_attribute_custom: Vec<(Vec, String)>, /// Add custom attributes on a `struct`. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes. @@ -489,6 +491,12 @@ struct BindgenCommand { /// Add custom attributes on a `union`. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes. #[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)] with_attribute_custom_union: Vec<(Vec, String)>, + /// Add custom attributes on a variable. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes. + #[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)] + with_attribute_custom_var: Vec<(Vec, String)>, + /// Add custom attributes on an `fn`. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes. + #[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)] + with_attribute_custom_function: Vec<(Vec, String)>, /// Generate wrappers for `static` and `static inline` functions. #[arg(long)] wrap_static_fns: bool, @@ -645,6 +653,8 @@ where with_attribute_custom_struct, with_attribute_custom_enum, with_attribute_custom_union, + with_attribute_custom_var, + with_attribute_custom_function, wrap_static_fns, wrap_static_fns_path, wrap_static_fns_suffix, @@ -745,7 +755,7 @@ where #[derive(Debug)] struct CustomAttributeCallback { attributes: Vec, - kind: Option, + kind: Option, regex_set: RegexSet, } @@ -755,9 +765,17 @@ where let flag = match &self.kind { None => "--with-attribute-custom", - Some(TypeKind::Struct) => "--with-attribute-custom-struct", - Some(TypeKind::Enum) => "--with-attribute-custom-enum", - Some(TypeKind::Union) => "--with-attribute-custom-union", + Some(AttributeItemKind::Struct) => { + "--with-attribute-custom-struct" + } + Some(AttributeItemKind::Enum) => "--with-attribute-custom-enum", + Some(AttributeItemKind::Union) => { + "--with-attribute-custom-union" + } + Some(AttributeItemKind::Var) => "--with-attribute-custom-var", + Some(AttributeItemKind::Function(_)) => { + "--with-attribute-custom-function" + } }; let attributes = self.attributes.join(","); @@ -772,13 +790,16 @@ where args } - fn add_attributes(&self, info: &AttributeInfo<'_>) -> Vec { + fn process_attributes( + &self, + info: &AttributeInfo<'_>, + attrs: &mut Vec, + ) { if self.kind.map_or(true, |kind| kind == info.kind) && self.regex_set.matches(info.name) { - return self.attributes.clone(); + attrs.extend(self.attributes.clone()); } - vec![] } } @@ -1010,19 +1031,29 @@ where (with_attribute_custom, None, "--with-attribute-custom"), ( with_attribute_custom_struct, - Some(TypeKind::Struct), + Some(AttributeItemKind::Struct), "--with-attribute-custom-struct", ), ( with_attribute_custom_enum, - Some(TypeKind::Enum), + Some(AttributeItemKind::Enum), "--with-attribute-custom-enum", ), ( with_attribute_custom_union, - Some(TypeKind::Union), + Some(AttributeItemKind::Union), "--with-attribute-custom-union", ), + ( + with_attribute_custom_var, + Some(AttributeItemKind::Var), + "--with-attribute-custom-var", + ), + ( + with_attribute_custom_function, + Some(AttributeItemKind::Function(FunctionKind::Function)), + "--with-attribute-custom-function", + ), ] { #[cfg(feature = "experimental")] let name = emit_diagnostics.then_some(_name); diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index 6bf652d4e1..bd2b768366 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -1985,6 +1985,19 @@ options! { }, as_args: "--merge-extern-blocks", }, + /// Whether to deduplicate `cfg` attributes. + merge_cfg_attributes: bool { + methods: { + /// Merge all unique combinations of cfg attributes into a module + /// + /// Cfg attributes are not merged by default. + pub fn merge_cfg_attributes(mut self, doit: bool) -> Self { + self.options.merge_cfg_attributes = doit; + self + } + }, + as_args: "--merge-cfg-attributes", + }, /// Whether to wrap unsafe operations in unsafe blocks. wrap_unsafe_ops: bool { methods: {