rust-bindgen icon indicating copy to clipboard operation
rust-bindgen copied to clipboard

Custom attribute and derive generation order

Open kriswuollett opened this issue 1 year ago • 2 comments

The interface and/or implementation of bindgen::callbacks::ParseCallbacks may need some changes to address a compile warning that may become an error in the future, see https://github.com/rust-lang/rust/issues/79202. I'm guessing that the very least derives need to appear before other attributes?

I wish I remember what library it was, but I believe I already encountered a derive macro in the past that was very sensitive to order of everything and needed to be the very first one listed as well otherwise it would cause a compile error. If I come across it again, I'll be sure to update this issue.

Details

From a header file with a type like:

typedef enum {
    color_carmine_red = 0,
    color_cornflower_blue = 1,
    color_lime_green = 2,
} color_t;

Using the item_name, add_derives, add_attributes, and enum_variant_name parse callbacks:

/* automatically generated by rust-bindgen 0.70.1 */

#[repr(u32)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, strum :: AsRefStr, strum :: IntoStaticStr)]
pub enum Color {
    CarmineRed = 0,
    CornflowerBlue = 1,
    LimeGreen = 2,
}

Which causes a warning for derive helper being used before it is introduced, rust-lang/rust#79202:

warning: derive helper attribute is used before it is introduced
 --> src/bindings.rs:6:3
  |
6 | #[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
  |   ^^^^^
7 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, strum :: AsRefStr, strum :: IntoStaticStr)]
  |                                                   ----------------- the attribute is introduced here
  |
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #79202 <https://github.com/rust-lang/rust/issues/79202>
  = note: `#[warn(legacy_derive_helpers)]` on by default

An example project is at https://github.com/kriswuollett/example-bindgen-derive-ordering/tree/0130dcd8fa3f8d0820ed53c711375152faa0cec2.

kriswuollett avatar Oct 31 '24 12:10 kriswuollett

Hi @kriswuollett, I tried to add a test to our suite so we could catch any regressions but I wasn't able to reproduce it: https://github.com/rust-lang/rust-bindgen/pull/3034.

Check the expectation file specifically where the derive attributes are always generated before the other custom attributes. I disabled formatting to be sure it wasn't interfering with the output.

I also checked the source code and here's the relevant section. The derive attributes are always pushed before the custom attributes.

pvdrz avatar Dec 04 '24 20:12 pvdrz

I have the same issue, here is my code (I apologize for not having time to minimize it at the moment):

use std::{env, path::PathBuf};

use bindgen::callbacks::{
    AttributeInfo, DeriveInfo, EnumVariantValue, ItemInfo, ItemKind, ParseCallbacks, TypeKind,
};
use inflector::Inflector;

fn bindgen_mm_enums() -> Result<(), Box<dyn std::error::Error>> {
    #[derive(Debug)]
    struct CustomCallbacks;

    impl ParseCallbacks for CustomCallbacks {
        fn enum_variant_name(
            &self,
            enum_name: Option<&str>,
            variant_name: &str,
            _: EnumVariantValue,
        ) -> Option<String> {
            enum_name
                .and_then(|enum_name| {
                    variant_name
                        .strip_prefix(&enum_name.to_screaming_snake_case())
                        .map(|name| name.to_pascal_case())
                })
                .map(|name| {
                    name.starts_with(|ch: char| ch.is_ascii_digit())
                        .then(|| format!("_{name}"))
                        .unwrap_or(name)
                })
        }

        fn item_name(&self, item_info: ItemInfo) -> Option<String> {
            matches!(item_info.kind, ItemKind::Type)
                .then(|| item_info.name.strip_prefix("MM").map(Into::into))
                .flatten()
        }

        fn add_derives(&self, info: &DeriveInfo) -> Vec<String> {
            let mut derives = vec![];

            if let TypeKind::Enum = info.kind {
                derives.push("num_derive::FromPrimitive".into());
                derives.push("num_derive::ToPrimitive".into());
                derives.push("strum::Display".into());
            }
            derives
        }

        fn add_attributes(&self, info: &AttributeInfo) -> Vec<String> {
            if let TypeKind::Enum = info.kind {
                return vec!["#[strum(serialize_all = \"SCREAMING-KEBAB-CASE\")]".into()];
            }
            vec![]
        }
    }

    const MM_ENUMS_URL: &str = "https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/raw/0b2d7d638859e006bb4aa8667ac0ccb8008d6c1f/include/ModemManager-enums.h";

    let c_code = reqwest::blocking::get(MM_ENUMS_URL)?.text()?;
    let c_code = format!("#define __MODEM_MANAGER_H_INSIDE__\n\n{c_code}");
    bindgen::Builder::default()
        .header_contents("ModemManager-enums.h", &c_code)
        .rustified_enum(".*")
        .parse_callbacks(Box::new(CustomCallbacks))
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()?
        .write_to_file(PathBuf::from(env::var("OUT_DIR")?).join("modem_manager_enums.rs"))?;
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    bindgen_mm_enums()
}

The generated enums are like

#[repr(u32)]
#[doc = "..."]
#[strum(serialize_all = "SCREAMING-KEBAB-CASE")]
#[derive(
    Debug,
    Copy,
    Clone,
    Hash,
    PartialEq,
    Eq,
    num_derive :: FromPrimitive,
    num_derive :: ToPrimitive,
    strum :: Display,
)]
pub enum ModemCapability {
    None = 0,
    Pots = 1,
    CdmaEvdo = 2,
    GsmUmts = 4,
    Lte = 8,
    Iridium = 32,
    _5Gnr = 64,
    Tds = 128,
    Any = 4294967295,
}

Version:

bindgen = "0.72.0"

SpriteOvO avatar Jun 20 '25 00:06 SpriteOvO