Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions bindgen-tests/tests/expectations/tests/field_attr_annotation.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions bindgen-tests/tests/expectations/tests/field_attr_cli.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions bindgen-tests/tests/headers/field_attr_annotation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <div rustbindgen deriveDebug></div>
struct Point {
/// <div rustbindgen attribute="cfg(test)"></div>
int x;
/// <div rustbindgen attribute="allow(dead_code)"></div>
int y;
};

/// <div rustbindgen deriveDebug></div>
union Data {
/// <div rustbindgen attribute="allow(dead_code)"></div>
int i;
float f;
};

/// <div rustbindgen attribute="cfg(test)"></div>
typedef int Handle;
13 changes: 13 additions & 0 deletions bindgen-tests/tests/headers/field_attr_cli.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// bindgen-flags: --field-attr "Point::x=cfg(test)" --field-attr "Point::y=allow(dead_code)" --field-attr "Data::i=allow(dead_code)" --field-attr "Handle::0=cfg(test)" --new-type-alias "Handle"

struct Point {
int x;
int y;
};

union Data {
int i;
float f;
};

typedef int Handle;
50 changes: 50 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,35 @@ pub trait ParseCallbacks: fmt::Debug {
vec![]
}

/// Provide a list of custom attributes for struct/union fields.
///
/// These attributes will be applied to the field in the generated Rust code.
/// If no additional attributes are wanted, this function should return an
/// empty `Vec`.
///
/// # Example
///
/// ```
/// # use bindgen::callbacks::{ParseCallbacks, FieldAttributeInfo};
/// # #[derive(Debug)]
/// # struct MyCallbacks;
/// # impl ParseCallbacks for MyCallbacks {
/// fn field_attributes(&self, info: &FieldAttributeInfo<'_>) -> Vec<String> {
/// if info.field_name == "internal" {
/// vec!["serde(skip)".to_string()]
/// } else if info.field_name == "0" {
/// // Newtype tuple field
/// vec!["serde(transparent)".to_string()]
/// } else {
/// vec![]
/// }
/// }
/// # }
/// ```
fn field_attributes(&self, _info: &FieldAttributeInfo<'_>) -> Vec<String> {
vec![]
}

/// Process a source code comment.
fn process_comment(&self, _comment: &str) -> Option<String> {
None
Expand Down Expand Up @@ -334,6 +363,27 @@ pub struct FieldInfo<'a> {
pub field_type_name: Option<&'a str>,
}

/// Relevant information about a field to which new attributes will be added using
/// [`ParseCallbacks::field_attributes`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct FieldAttributeInfo<'a> {
/// The name of the containing type (struct/union).
pub type_name: &'a str,

/// The kind of the containing type.
pub type_kind: TypeKind,

/// The name of the field.
///
/// For newtype tuple structs (when using `--default-alias-style=new_type`),
/// this will be `"0"` for the inner field.
pub field_name: &'a str,

/// The name of the field's type, if available.
pub field_type_name: Option<&'a str>,
}

/// Location in the source code. Roughly equivalent to the same type
/// within `clang_sys`.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down
90 changes: 87 additions & 3 deletions bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use self::struct_layout::StructLayoutTracker;
use super::BindgenOptions;

use crate::callbacks::{
AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, FieldInfo,
TypeKind as DeriveTypeKind,
AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId,
FieldAttributeInfo, FieldInfo, TypeKind as DeriveTypeKind,
};
use crate::codegen::error::Error;
use crate::ir::analysis::{HasVtable, Sizedness};
Expand Down Expand Up @@ -212,6 +212,40 @@ fn append_custom_derives<'a>(
}
}

/// Collects field attributes from multiple sources (annotations, callbacks, and CLI/Builder patterns).
fn collect_field_attributes(
ctx: &BindgenContext,
annotations: &Annotations,
type_name: &str,
type_kind: DeriveTypeKind,
field_name: &str,
field_type_name: Option<&str>,
) -> Vec<String> {
let mut all_field_attributes = Vec::new();

// 1. Get attributes from annotations
all_field_attributes.extend(annotations.attributes().iter().cloned());

// 2. Get custom attributes from callbacks
all_field_attributes.extend(ctx.options().all_callbacks(|cb| {
cb.field_attributes(&FieldAttributeInfo {
type_name,
type_kind,
field_name,
field_type_name,
})
}));

// 3. Get attributes from CLI/Builder patterns
for (type_pat, field_pat, attr) in &ctx.options().field_attr_patterns {
if type_pat.as_ref() == type_name && field_pat.as_ref() == field_name {
all_field_attributes.push(attr.to_string());
}
}

all_field_attributes
}

impl From<DerivableTraits> for Vec<&'static str> {
fn from(derivable_traits: DerivableTraits) -> Vec<&'static str> {
[
Expand Down Expand Up @@ -1138,8 +1172,33 @@ impl CodeGenerator for Type {
})
.unwrap_or(ctx.options().default_visibility);
let access_spec = access_specifier(visibility);

// Collect field attributes for newtype tuple field
let type_name = item.canonical_name(ctx);
let all_field_attributes = collect_field_attributes(
ctx,
item.annotations(),
&type_name,
DeriveTypeKind::Struct,
"0",
inner_item.expect_type().name(),
);

// Build the field with attributes
let mut field_tokens = quote! {};
for attr in &all_field_attributes {
let attr_tokens: proc_macro2::TokenStream =
attr.parse().expect("Invalid field attribute");
field_tokens.append_all(quote! {
#[#attr_tokens]
});
}
field_tokens.append_all(quote! {
#access_spec #inner_rust_type
});

quote! {
(#access_spec #inner_rust_type) ;
(#field_tokens) ;
}
}
});
Expand Down Expand Up @@ -1569,6 +1628,31 @@ impl FieldCodegen<'_> for FieldData {
let accessor_kind =
self.annotations().accessor_kind().unwrap_or(accessor_kind);

// Collect field attributes from multiple sources
let type_name = parent_item.canonical_name(ctx);
let type_kind = if parent.is_union() {
DeriveTypeKind::Union
} else {
DeriveTypeKind::Struct
};
let all_field_attributes = collect_field_attributes(
ctx,
self.annotations(),
&type_name,
type_kind,
field_name,
field_ty.name(),
);

// Apply all custom attributes to the field
for attr in &all_field_attributes {
let attr_tokens: proc_macro2::TokenStream =
attr.parse().expect("Invalid field attribute");
field.append_all(quote! {
#[#attr_tokens]
});
}

match visibility {
FieldVisibilityKind::Private => {
field.append_all(quote! {
Expand Down
30 changes: 30 additions & 0 deletions bindgen/options/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ fn parse_custom_attribute(
Ok((attributes, regex.to_owned()))
}

fn parse_field_attr(
field_attr: &str,
) -> Result<(String, String, String), Error> {
// Parse format: TYPE::FIELD=ATTR
// We need to split on the first '=' after finding the '::'
let (type_field, attr) = field_attr.split_once('=').ok_or_else(|| {
Error::raw(ErrorKind::InvalidValue, "Missing `=` in field-attr")
})?;

let (type_name, field_name) =
type_field.rsplit_once("::").ok_or_else(|| {
Error::raw(
ErrorKind::InvalidValue,
"Missing `::` in field-attr. Expected format: TYPE::FIELD=ATTR",
)
})?;

// Validate the attribute is valid Rust syntax
if let Err(err) = TokenStream::from_str(attr) {
return Err(Error::raw(ErrorKind::InvalidValue, err));
}

Ok((type_name.to_owned(), field_name.to_owned(), attr.to_owned()))
}

#[derive(Parser, Debug)]
#[clap(
about = "Generates Rust bindings from C/C++ headers.",
Expand Down Expand Up @@ -531,6 +556,9 @@ struct BindgenCommand {
/// be called.
#[arg(long)]
generate_private_functions: bool,
/// Add a custom attribute to a field. The SPEC value must be of the shape TYPE::FIELD=ATTR.
#[arg(long, value_name = "SPEC", value_parser = parse_field_attr)]
field_attr: Vec<(String, String, String)>,
/// Whether to emit diagnostics or not.
#[cfg(feature = "experimental")]
#[arg(long, requires = "experimental")]
Expand Down Expand Up @@ -684,6 +712,7 @@ where
generate_deleted_functions,
generate_pure_virtual_functions,
generate_private_functions,
field_attr,
#[cfg(feature = "experimental")]
emit_diagnostics,
generate_shell_completions,
Expand Down Expand Up @@ -981,6 +1010,7 @@ where
generate_deleted_functions,
generate_pure_virtual_functions,
generate_private_functions,
field_attr => |b, (type_name, field_name, attr)| b.field_attribute(type_name, field_name, attr),
}
);

Expand Down
Loading