Move Crubit feature enum (and serde support) to a crate in crubit/common.
This allows both `rs_bindings_from_cc` and `cc_bindings_from_rs` to share the same parsing/etc. code for dealing with feature flags.
PiperOrigin-RevId: 665049327
Change-Id: I65bb18b12a0a751c9ac39c9fd3899d355d4430c2
diff --git a/common/BUILD b/common/BUILD
index 09360f5..e02942d 100644
--- a/common/BUILD
+++ b/common/BUILD
@@ -70,6 +70,15 @@
],
)
+rust_library(
+ name = "crubit_feature",
+ srcs = ["crubit_feature.rs"],
+ deps = [
+ "@crate_index//:flagset",
+ "@crate_index//:serde",
+ ],
+)
+
cc_library(
name = "file_io",
srcs = ["file_io.cc"],
diff --git a/common/crubit_feature.rs b/common/crubit_feature.rs
new file mode 100644
index 0000000..a0bd520
--- /dev/null
+++ b/common/crubit_feature.rs
@@ -0,0 +1,60 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+//! Supporting types to read and display Crubit feature flags
+//! (<internal link>)
+
+flagset::flags! {
+ pub enum CrubitFeature : u8 {
+ Supported,
+ /// Experimental is never *set* without also setting Supported, but we allow it to be
+ /// *required* without also requiring Supported, so that error messages can be more direct.
+ Experimental,
+ }
+}
+
+impl CrubitFeature {
+ /// The name of this feature.
+ pub fn short_name(&self) -> &'static str {
+ match self {
+ Self::Supported => "supported",
+ Self::Experimental => "experimental",
+ }
+ }
+
+ /// The aspect hint required to enable this feature.
+ pub fn aspect_hint(&self) -> &'static str {
+ match self {
+ Self::Supported => "//features:supported",
+ Self::Experimental => "//features:experimental",
+ }
+ }
+}
+
+/// A newtype around a flagset of features, so that it can be deserialized from
+/// an array of strings instead of an integer.
+#[derive(Debug, Default, PartialEq, Eq, Clone)]
+pub struct SerializedCrubitFeatures(pub flagset::FlagSet<CrubitFeature>);
+
+impl<'de> serde::Deserialize<'de> for SerializedCrubitFeatures {
+ fn deserialize<D>(deserializer: D) -> Result<SerializedCrubitFeatures, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let mut features = flagset::FlagSet::<CrubitFeature>::default();
+ for feature in <Vec<String> as serde::Deserialize<'de>>::deserialize(deserializer)? {
+ features |= match &*feature {
+ "all" => flagset::FlagSet::<CrubitFeature>::full(),
+ "supported" => CrubitFeature::Supported.into(),
+ "experimental" => CrubitFeature::Experimental.into(),
+ other => {
+ return Err(<D::Error as serde::de::Error>::custom(format!(
+ "Unexpected Crubit feature: {other}"
+ )));
+ }
+ };
+ }
+ Ok(SerializedCrubitFeatures(features))
+ }
+}
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index fcc5c32..fc64591 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -346,6 +346,7 @@
deps = [
"//common:arc_anyhow",
"//common:code_gen_utils",
+ "//common:crubit_feature",
"//common:error_report",
"@crate_index//:flagset",
"@crate_index//:itertools",
@@ -374,6 +375,7 @@
":ir",
":json_from_cc",
"//common:arc_anyhow",
+ "//common:crubit_feature",
"//common:ffi_types",
"//common:multiplatform_testing",
"@crate_index//:flagset",
diff --git a/rs_bindings_from_cc/generate_bindings/BUILD b/rs_bindings_from_cc/generate_bindings/BUILD
index c78a1dc..558a070 100644
--- a/rs_bindings_from_cc/generate_bindings/BUILD
+++ b/rs_bindings_from_cc/generate_bindings/BUILD
@@ -19,6 +19,7 @@
deps = [
"//common:arc_anyhow",
"//common:code_gen_utils",
+ "//common:crubit_feature",
"//common:error_report",
"//common:ffi_types",
"//common:memoized",
diff --git a/rs_bindings_from_cc/generate_bindings/generate_record.rs b/rs_bindings_from_cc/generate_bindings/generate_record.rs
index 41ab668..b5ac025 100644
--- a/rs_bindings_from_cc/generate_bindings/generate_record.rs
+++ b/rs_bindings_from_cc/generate_bindings/generate_record.rs
@@ -115,7 +115,7 @@
for target in record.defining_target.iter().chain([&record.owning_target]) {
let enabled_features = db.ir().target_crubit_features(target);
ensure!(
- enabled_features.contains(ir::CrubitFeature::Experimental),
+ enabled_features.contains(crubit_feature::CrubitFeature::Experimental),
"unknown field attributes are only supported with experimental features \
enabled on {target}\nUnknown attribute: {unknown_attr}`"
);
@@ -145,7 +145,7 @@
for target in record.defining_target.iter().chain([&record.owning_target]) {
let enabled_features = db.ir().target_crubit_features(target);
ensure!(
- enabled_features.contains(ir::CrubitFeature::Experimental),
+ enabled_features.contains(crubit_feature::CrubitFeature::Experimental),
"nontrivial fields would be destroyed in the wrong order"
);
}
@@ -557,15 +557,18 @@
if let Some(defining_target) = &record.defining_target {
crubit_features |= ir.target_crubit_features(defining_target);
}
- if crubit_features.contains(ir::CrubitFeature::Experimental) {
+ if crubit_features.contains(crubit_feature::CrubitFeature::Experimental) {
record_generated_items.push(cc_struct_upcast_impl(record, &ir)?);
}
- let no_unique_address_accessors = if crubit_features.contains(ir::CrubitFeature::Experimental) {
- cc_struct_no_unique_address_impl(db, record)?
- } else {
- quote! {}
- };
- let incomplete_definition = if crubit_features.contains(ir::CrubitFeature::Experimental) {
+ let no_unique_address_accessors =
+ if crubit_features.contains(crubit_feature::CrubitFeature::Experimental) {
+ cc_struct_no_unique_address_impl(db, record)?
+ } else {
+ quote! {}
+ };
+ let incomplete_definition = if crubit_features
+ .contains(crubit_feature::CrubitFeature::Experimental)
+ {
quote! {
forward_declare::unsafe_define!(forward_declare::symbol!(#fully_qualified_cc_name), #qualified_ident);
}
@@ -2633,7 +2636,7 @@
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
@@ -2660,7 +2663,7 @@
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
// Note: inner is a supported type, so it isn't being replaced by a blob because
// it's unsupporter or anything.
@@ -2687,7 +2690,7 @@
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
diff --git a/rs_bindings_from_cc/generate_bindings/lib.rs b/rs_bindings_from_cc/generate_bindings/lib.rs
index 88bcaee..45fbcfd 100644
--- a/rs_bindings_from_cc/generate_bindings/lib.rs
+++ b/rs_bindings_from_cc/generate_bindings/lib.rs
@@ -616,7 +616,7 @@
/// RequiredCrubitFeature {
/// target: "//foo".into(),
/// item: "kFoo".into(),
-/// missing_features: ir::CrubitFeature::Experimental.into(),
+/// missing_features: CrubitFeature::Experimental.into(),
/// capability_description: "int addition".into(),
/// }
/// ```
@@ -624,7 +624,7 @@
pub struct RequiredCrubitFeature {
pub target: BazelLabel,
pub item: Rc<str>,
- pub missing_features: flagset::FlagSet<ir::CrubitFeature>,
+ pub missing_features: flagset::FlagSet<crubit_feature::CrubitFeature>,
pub capability_description: Rc<str>,
}
@@ -751,7 +751,7 @@
let require_any_feature =
|missing_features: &mut Vec<RequiredCrubitFeature>,
- alternative_required_features: flagset::FlagSet<ir::CrubitFeature>,
+ alternative_required_features: flagset::FlagSet<crubit_feature::CrubitFeature>,
capability_description: &dyn Fn() -> Rc<str>| {
// We refuse to generate bindings if either the definition of an item, or
// instantiation (if it is a template) of an item are in a translation unit
@@ -795,9 +795,11 @@
};
if let Some(unknown_attr) = item.unknown_attr() {
- require_any_feature(&mut missing_features, ir::CrubitFeature::Experimental.into(), &|| {
- format!("unknown attribute(s): {unknown_attr}").into()
- });
+ require_any_feature(
+ &mut missing_features,
+ crubit_feature::CrubitFeature::Experimental.into(),
+ &|| format!("unknown attribute(s): {unknown_attr}").into(),
+ );
}
match item {
Item::UnsupportedItem(..) => {}
@@ -808,7 +810,7 @@
// particular case, it's safe.
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Supported.into(),
+ crubit_feature::CrubitFeature::Supported.into(),
&|| "destructors".into(),
);
} else {
@@ -823,41 +825,41 @@
if func.is_extern_c {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Supported.into(),
+ crubit_feature::CrubitFeature::Supported.into(),
&|| "extern \"C\" function".into(),
);
} else {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Supported.into(),
+ crubit_feature::CrubitFeature::Supported.into(),
&|| "non-extern \"C\" function".into(),
);
}
if !func.has_c_calling_convention {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| "non-C calling convention".into(),
);
}
if func.is_noreturn {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| "[[noreturn]] attribute".into(),
);
}
if func.nodiscard.is_some() {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| "[[nodiscard]] attribute".into(),
);
}
if func.deprecated.is_some() {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| "[[deprecated]] attribute".into(),
);
}
@@ -865,7 +867,7 @@
if let Some(unknown_attr) = ¶m.unknown_attr {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| {
format!(
"param {param} has unknown attribute(s): {unknown_attr}",
@@ -902,14 +904,14 @@
Item::Namespace(_) => {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Supported.into(),
+ crubit_feature::CrubitFeature::Supported.into(),
&|| "namespace".into(),
);
}
Item::IncompleteRecord(_) => {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| "incomplete type".into(),
);
}
@@ -917,7 +919,7 @@
Item::TypeMapOverride { .. } => {
require_any_feature(
&mut missing_features,
- ir::CrubitFeature::Experimental.into(),
+ crubit_feature::CrubitFeature::Experimental.into(),
&|| "type map override".into(),
);
}
@@ -3004,7 +3006,7 @@
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(rs_api, quote! {pub struct Enum});
assert_rs_not_matches!(rs_api, quote! {kHidden});
@@ -3032,7 +3034,7 @@
"#
))?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
// The namespace, and everything in it or using it, will be missing from the
// output.
@@ -3061,7 +3063,7 @@
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
// The namespace, and everything in it or using it, will be missing from the
// output.
@@ -3088,7 +3090,7 @@
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
- ir::CrubitFeature::Supported.into();
+ crubit_feature::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
// The namespace, and everything in it or using it, will be missing from the
// output.
diff --git a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
index 05eaec2..8445004 100644
--- a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
+++ b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
@@ -8,6 +8,7 @@
use arc_anyhow::Result;
use code_gen_utils::make_rs_ident;
use code_gen_utils::NamespaceQualifier;
+use crubit_feature::CrubitFeature;
use error_report::bail;
use ir::*;
use itertools::Itertools;
@@ -432,18 +433,17 @@
/// merge these two functions.
pub fn required_crubit_features(
&self,
- enabled_features: flagset::FlagSet<ir::CrubitFeature>,
- ) -> (flagset::FlagSet<ir::CrubitFeature>, String) {
+ enabled_features: flagset::FlagSet<CrubitFeature>,
+ ) -> (flagset::FlagSet<CrubitFeature>, String) {
// TODO(b/318006909): Explain why a given feature is required, don't just return
// a FlagSet.
- let mut missing_features = <flagset::FlagSet<ir::CrubitFeature>>::default();
+ let mut missing_features = <flagset::FlagSet<CrubitFeature>>::default();
let mut reasons = <std::collections::BTreeSet<std::borrow::Cow<'static, str>>>::new();
let mut require_feature =
- |required_feature: ir::CrubitFeature,
+ |required_feature: CrubitFeature,
reason: Option<&dyn Fn() -> std::borrow::Cow<'static, str>>| {
- let required_features =
- <flagset::FlagSet<ir::CrubitFeature>>::from(required_feature);
+ let required_features = <flagset::FlagSet<CrubitFeature>>::from(required_feature);
let missing = required_features - enabled_features;
if !missing.is_empty() {
missing_features |= missing;
@@ -1109,11 +1109,8 @@
},
] {
let (missing_features, reason) =
- func_ptr.required_crubit_features(<flagset::FlagSet<ir::CrubitFeature>>::default());
- assert_eq!(
- missing_features,
- ir::CrubitFeature::Experimental | ir::CrubitFeature::Supported
- );
+ func_ptr.required_crubit_features(<flagset::FlagSet<CrubitFeature>>::default());
+ assert_eq!(missing_features, CrubitFeature::Experimental | CrubitFeature::Supported);
assert_eq!(reason, "references are not supported");
}
}
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 028cc13..f537840 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -8,6 +8,7 @@
use arc_anyhow::{anyhow, bail, ensure, Context, Error, Result};
use code_gen_utils::{make_rs_ident, NamespaceQualifier};
+use crubit_feature::CrubitFeature;
use once_cell::unsync::OnceCell;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
@@ -79,7 +80,9 @@
crate_root_path,
crubit_features: crubit_features
.into_iter()
- .map(|(label, features)| (label, CrubitFeaturesIR(features.into())))
+ .map(|(label, features)| {
+ (label, crubit_feature::SerializedCrubitFeatures(features.into()))
+ })
.collect(),
})
}
@@ -1166,60 +1169,6 @@
}
}
-flagset::flags! {
- pub enum CrubitFeature : u8 {
- Supported,
- /// Experimental is never *set* without also setting Supported, but we allow it to be
- /// *required* without also requiring Supported, so that error messages can be more direct.
- Experimental,
- }
-}
-
-impl CrubitFeature {
- /// The name of this feature.
- pub fn short_name(&self) -> &'static str {
- match self {
- Self::Supported => "supported",
- Self::Experimental => "experimental",
- }
- }
-
- /// The aspect hint required to enable this feature.
- pub fn aspect_hint(&self) -> &'static str {
- match self {
- Self::Supported => "//features:supported",
- Self::Experimental => "//features:experimental",
- }
- }
-}
-
-/// A newtype around a flagset of features, so that it can be deserialized from
-/// an array of strings instead of an integer.
-#[derive(Debug, Default, PartialEq, Eq, Clone)]
-struct CrubitFeaturesIR(pub(crate) flagset::FlagSet<CrubitFeature>);
-
-impl<'de> serde::Deserialize<'de> for CrubitFeaturesIR {
- fn deserialize<D>(deserializer: D) -> Result<CrubitFeaturesIR, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let mut features = flagset::FlagSet::<CrubitFeature>::default();
- for feature in <Vec<String> as serde::Deserialize<'de>>::deserialize(deserializer)? {
- features |= match &*feature {
- "all" => flagset::FlagSet::<CrubitFeature>::full(),
- "supported" => CrubitFeature::Supported.into(),
- "experimental" => CrubitFeature::Experimental.into(),
- other => {
- return Err(<D::Error as serde::de::Error>::custom(format!(
- "Unexpected Crubit feature: {other}"
- )));
- }
- };
- }
- Ok(CrubitFeaturesIR(features))
- }
-}
-
#[derive(PartialEq, Eq, Clone, Deserialize)]
#[serde(deny_unknown_fields, rename(deserialize = "IR"))]
struct FlatIR {
@@ -1233,7 +1182,7 @@
#[serde(default)]
crate_root_path: Option<Rc<str>>,
#[serde(default)]
- crubit_features: HashMap<BazelLabel, CrubitFeaturesIR>,
+ crubit_features: HashMap<BazelLabel, crubit_feature::SerializedCrubitFeatures>,
}
/// A custom debug impl that wraps the HashMap in rustfmt-friendly notation.
diff --git a/rs_bindings_from_cc/ir_testing.rs b/rs_bindings_from_cc/ir_testing.rs
index 1f1244f..eba4150 100644
--- a/rs_bindings_from_cc/ir_testing.rs
+++ b/rs_bindings_from_cc/ir_testing.rs
@@ -33,8 +33,9 @@
/// Name of the current target used by `ir_from_cc` and `ir_from_cc_dependency`.
pub const TESTING_TARGET: &str = "//test:testing_target";
-static TESTING_FEATURES: Lazy<flagset::FlagSet<ir::CrubitFeature>> =
- Lazy::new(|| ir::CrubitFeature::Experimental | ir::CrubitFeature::Supported);
+static TESTING_FEATURES: Lazy<flagset::FlagSet<crubit_feature::CrubitFeature>> = Lazy::new(|| {
+ crubit_feature::CrubitFeature::Experimental | crubit_feature::CrubitFeature::Supported
+});
/// Update the IR to have common test-only items.
///
@@ -55,7 +56,7 @@
/* top_level_item_ids= */ vec![],
/* crate_root_path= */ None,
/* crubit_features= */
- <HashMap<ir::BazelLabel, flagset::FlagSet<ir::CrubitFeature>>>::new(),
+ <HashMap<ir::BazelLabel, flagset::FlagSet<crubit_feature::CrubitFeature>>>::new(),
);
update_test_ir(&mut ir);
ir
@@ -160,7 +161,7 @@
quote! {
crubit_features: hash_map!{
...
- BazelLabel("//test:testing_target"): CrubitFeaturesIR(FlagSet(Supported|Experimental))
+ BazelLabel("//test:testing_target"): SerializedCrubitFeatures(FlagSet(Supported|Experimental))
...
}
}
@@ -174,7 +175,7 @@
quote! {
crubit_features: hash_map!{
...
- BazelLabel("//test:testing_target"): CrubitFeaturesIR(FlagSet(Supported|Experimental))
+ BazelLabel("//test:testing_target"): SerializedCrubitFeatures(FlagSet(Supported|Experimental))
...
}
}