diff --git a/libbindgen/src/chooser.rs b/libbindgen/src/chooser.rs index 10a77dc9c4..51392d70e1 100644 --- a/libbindgen/src/chooser.rs +++ b/libbindgen/src/chooser.rs @@ -1,6 +1,7 @@ //! A public API for more fine-grained customization of bindgen behavior. pub use ir::int::IntKind; +pub use ir::enum_ty::{EnumVariantValue, EnumVariantCustomBehavior}; use std::fmt; /// A trait to allow configuring different kinds of types in different @@ -11,4 +12,15 @@ pub trait TypeChooser: fmt::Debug { fn int_macro(&self, _name: &str, _value: i64) -> Option { None } + + /// This function should return whether, given the a given enum variant + /// name, and value, returns whether this enum variant will forcibly be a + /// constant. + fn enum_variant_behavior(&self, + _enum_name: Option<&str>, + _variant_name: &str, + _variant_value: EnumVariantValue) + -> Option { + None + } } diff --git a/libbindgen/src/codegen/mod.rs b/libbindgen/src/codegen/mod.rs index 24b862bec5..1ff5bf873f 100644 --- a/libbindgen/src/codegen/mod.rs +++ b/libbindgen/src/codegen/mod.rs @@ -1,6 +1,5 @@ mod helpers; - use aster; use ir::annotations::FieldAccessorKind; @@ -21,7 +20,7 @@ use self::helpers::{BlobTyBuilder, attributes}; use std::borrow::Cow; use std::cell::Cell; -use std::collections::HashSet; +use std::collections::{HashSet, VecDeque}; use std::collections::hash_map::{Entry, HashMap}; use std::fmt::Write; use std::iter; @@ -1667,7 +1666,22 @@ impl CodeGenerator for Enum { Some(&name) }; - for variant in self.variants().iter() { + // NB: We defer the creation of constified variants, in case we find + // another variant with the same value (which is the common thing to + // do). + let mut constified_variants = VecDeque::new(); + + let mut iter = self.variants().iter().peekable(); + while let Some(variant) = iter.next().or_else(|| constified_variants.pop_front()) { + if variant.hidden() { + continue; + } + + if variant.force_constification() && iter.peek().is_some() { + constified_variants.push_back(variant); + continue; + } + match seen_values.entry(variant.val()) { Entry::Occupied(ref entry) => { if is_rust_enum { @@ -1707,9 +1721,11 @@ impl CodeGenerator for Enum { let variant_name = ctx.rust_mangle(variant.name()); - // If it's an unnamed enum, we also generate a constant so - // it can be properly accessed. - if is_rust_enum && enum_ty.name().is_none() { + // If it's an unnamed enum, or constification is enforced, + // we also generate a constant so it can be properly + // accessed. + if (is_rust_enum && enum_ty.name().is_none()) || + variant.force_constification() { let mangled_name = if is_toplevel { variant_name.clone() } else { diff --git a/libbindgen/src/ir/annotations.rs b/libbindgen/src/ir/annotations.rs index 3598c78cd2..98be0540f2 100644 --- a/libbindgen/src/ir/annotations.rs +++ b/libbindgen/src/ir/annotations.rs @@ -20,12 +20,17 @@ pub enum FieldAccessorKind { } /// Annotations for a given item, or a field. +/// +/// You can see the kind of comments that are accepted in the Doxygen +/// documentation: +/// +/// http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html #[derive(Clone, PartialEq, Debug)] pub struct Annotations { /// Whether this item is marked as opaque. Only applies to types. opaque: bool, /// Whether this item should be hidden from the output. Only applies to - /// types. + /// types, or enum variants. hide: bool, /// Whether this type should be replaced by another. The name is a /// namespace-aware path. @@ -39,6 +44,20 @@ pub struct Annotations { /// The kind of accessor this field will have. Also can be applied to /// structs so all the fields inside share it by default. accessor_kind: Option, + /// Whether this enum variant should be constified. + /// + /// This is controlled by the `constant` attribute, this way: + /// + /// ```cpp + /// enum Foo { + /// Bar = 0, /**<
*/ + /// Baz = 0, + /// }; + /// ``` + /// + /// In that case, bindgen will generate a constant for `Bar` instead of + /// `Baz`. + constify_enum_variant: bool, } fn parse_accessor(s: &str) -> FieldAccessorKind { @@ -59,6 +78,7 @@ impl Default for Annotations { disallow_copy: false, private_fields: None, accessor_kind: None, + constify_enum_variant: false, } } } @@ -150,6 +170,7 @@ impl Annotations { "accessor" => { self.accessor_kind = Some(parse_accessor(&attr.value)) } + "constant" => self.constify_enum_variant = true, _ => {} } } @@ -159,4 +180,9 @@ impl Annotations { self.parse(&child, matched); } } + + /// Returns whether we've parsed a "constant" attribute. + pub fn constify_enum_variant(&self) -> bool { + self.constify_enum_variant + } } diff --git a/libbindgen/src/ir/context.rs b/libbindgen/src/ir/context.rs index e3dc338499..2d87792008 100644 --- a/libbindgen/src/ir/context.rs +++ b/libbindgen/src/ir/context.rs @@ -2,6 +2,7 @@ use BindgenOptions; use cexpr; +use chooser::TypeChooser; use clang::{self, Cursor}; use parse::ClangItemParser; use std::borrow::Cow; @@ -184,6 +185,11 @@ impl<'ctx> BindgenContext<'ctx> { me } + /// Get the user-provided type chooser by reference, if any. + pub fn type_chooser(&self) -> Option<&TypeChooser> { + self.options().type_chooser.as_ref().map(|t| &**t) + } + /// Define a new item. /// /// This inserts it into the internal items set, and its type into the diff --git a/libbindgen/src/ir/enum_ty.rs b/libbindgen/src/ir/enum_ty.rs index 5c1ead4587..ca4e77db5e 100644 --- a/libbindgen/src/ir/enum_ty.rs +++ b/libbindgen/src/ir/enum_ty.rs @@ -1,11 +1,21 @@ //! Intermediate representation for C/C++ enumerations. use clang; +use ir::annotations::Annotations; use parse::{ClangItemParser, ParseError}; use super::context::{BindgenContext, ItemId}; use super::item::Item; use super::ty::TypeKind; +/// An enum representing custom handling that can be given to a variant. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum EnumVariantCustomBehavior { + /// This variant will be constified, that is, forced to generate a constant. + Constify, + /// This variant will be hidden entirely from the resulting enum. + Hide, +} + /// A C/C++ enumeration. #[derive(Debug)] pub struct Enum { @@ -66,6 +76,10 @@ impl Enum { } }); + let type_name = ty.spelling(); + let type_name = if type_name.is_empty() { None } else { Some(type_name) }; + let type_name = type_name.as_ref().map(String::as_str); + declaration.visit(|cursor| { if cursor.kind() == CXCursor_EnumConstantDecl { let value = if is_signed { @@ -75,8 +89,25 @@ impl Enum { }; if let Some(val) = value { let name = cursor.spelling(); + let custom_behavior = ctx.type_chooser() + .and_then(|t| { + t.enum_variant_behavior(type_name, &name, val) + }) + .or_else(|| { + Annotations::new(&cursor).and_then(|anno| { + if anno.hide() { + Some(EnumVariantCustomBehavior::Hide) + } else if anno.constify_enum_variant() { + Some(EnumVariantCustomBehavior::Constify) + } else { + None + } + }) + }); + let comment = cursor.raw_comment(); - variants.push(EnumVariant::new(name, comment, val)); + variants.push( + EnumVariant::new(name, comment, val, custom_behavior)); } } CXChildVisit_Continue @@ -96,6 +127,9 @@ pub struct EnumVariant { /// The integer value of the variant. val: EnumVariantValue, + + /// The custom behavior this variant may have, if any. + custom_behavior: Option, } /// A constant value assigned to an enumeration variant. @@ -112,12 +146,14 @@ impl EnumVariant { /// Construct a new enumeration variant from the given parts. pub fn new(name: String, comment: Option, - val: EnumVariantValue) + val: EnumVariantValue, + custom_behavior: Option) -> Self { EnumVariant { name: name, comment: comment, val: val, + custom_behavior: custom_behavior, } } @@ -130,4 +166,18 @@ impl EnumVariant { pub fn val(&self) -> EnumVariantValue { self.val } + + /// Returns whether this variant should be enforced to be a constant by code + /// generation. + pub fn force_constification(&self) -> bool { + self.custom_behavior + .map_or(false, |b| b == EnumVariantCustomBehavior::Constify) + } + + /// Returns whether the current variant should be hidden completely from the + /// resulting rust enum. + pub fn hidden(&self) -> bool { + self.custom_behavior + .map_or(false, |b| b == EnumVariantCustomBehavior::Hide) + } } diff --git a/libbindgen/src/ir/var.rs b/libbindgen/src/ir/var.rs index e18af91b19..329393fa5e 100644 --- a/libbindgen/src/ir/var.rs +++ b/libbindgen/src/ir/var.rs @@ -147,9 +147,7 @@ impl ClangSubItemParser for Var { (TypeKind::Pointer(char_ty), VarType::String(val)) } EvalResult::Int(Wrapping(value)) => { - let kind = ctx.options() - .type_chooser - .as_ref() + let kind = ctx.type_chooser() .and_then(|c| c.int_macro(&name, value)) .unwrap_or_else(|| { if value < 0 { diff --git a/libbindgen/tests/expectations/tests/constify-enum.rs b/libbindgen/tests/expectations/tests/constify-enum.rs new file mode 100644 index 0000000000..989c5197ac --- /dev/null +++ b/libbindgen/tests/expectations/tests/constify-enum.rs @@ -0,0 +1,20 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + + +pub const nsCSSPropertyID_eCSSProperty_COUNT_unexistingVariantValue: + nsCSSPropertyID = + nsCSSPropertyID::eCSSProperty_COUNT_unexistingVariantValue; +pub const nsCSSPropertyID_eCSSProperty_COUNT: nsCSSPropertyID = + nsCSSPropertyID::eCSSPropertyAlias_aa; +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum nsCSSPropertyID { + eCSSProperty_a = 0, + eCSSProperty_b = 1, + eCSSPropertyAlias_aa = 2, + eCSSPropertyAlias_bb = 3, + eCSSProperty_COUNT_unexistingVariantValue = 4, +} diff --git a/libbindgen/tests/headers/constify-enum.h b/libbindgen/tests/headers/constify-enum.h new file mode 100644 index 0000000000..a5b4052c78 --- /dev/null +++ b/libbindgen/tests/headers/constify-enum.h @@ -0,0 +1,13 @@ + +enum nsCSSPropertyID { + eCSSProperty_a, + eCSSProperty_b, + + eCSSProperty_COUNT, /**<
*/ + eCSSProperty_COUNT_DUMMY2 = eCSSProperty_COUNT - 1, /**<
*/ + + eCSSPropertyAlias_aa, + eCSSPropertyAlias_bb, + + eCSSProperty_COUNT_unexistingVariantValue, /**<
*/ +};