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) = &param.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))
                     ...
                 }
             }