| // 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 |
| |
| /// Generate the final bindings, including structures for code snippet, feature |
| /// gating, etc. |
| use crate::db::BindingsGenerator; |
| use crate::rs_snippet::RsTypeKind; |
| use arc_anyhow::{anyhow, bail, ensure, Error, Result}; |
| use code_gen_utils::{expect_format_cc_type_name, make_rs_ident, CcInclude}; |
| use ffi_types::FfiU8SliceBox; |
| use flagset::FlagSet; |
| use heck::ToSnakeCase; |
| use ir::{BazelLabel, GenericItem, Item, ItemId, Namespace, RecordType, UnqualifiedIdentifier, IR}; |
| use proc_macro2::{Ident, Literal, TokenStream}; |
| use quote::{quote, ToTokens}; |
| use std::collections::HashMap; |
| use std::fmt::{Display, Formatter}; |
| use std::num::NonZeroUsize; |
| use std::rc::Rc; |
| |
| /// FFI equivalent of `Bindings`. |
| #[repr(C)] |
| pub struct FfiBindings { |
| pub rs_api: FfiU8SliceBox, |
| pub rs_api_impl: FfiU8SliceBox, |
| pub error_report: FfiU8SliceBox, |
| pub fatal_errors: FfiU8SliceBox, |
| } |
| |
| #[derive(Clone, Debug, Default)] |
| pub struct ApiSnippets { |
| /// Mapping from an item to the abstract representation of its generated bindings. |
| /// |
| /// The ordering of the items is irrelevant; the GeneratedItems are quoted by traversing the |
| /// IR's top level items in order which is deterministic, and traversing their children in |
| /// order, which is also deterministic. |
| pub generated_items: HashMap<ItemId, GeneratedItem>, |
| |
| /// Rust implementation details - for example: |
| /// - A Rust declaration of an `extern "C"` thunk, |
| /// - Rust static assertions about struct size, aligment, and field offsets. |
| pub thunks: Vec<Thunk>, |
| pub assertions: Vec<Assertion>, |
| |
| /// C++ implementation details - for example: |
| /// - A C++ implementation of an `extern "C"` thunk, |
| /// - C++ static assertions about struct size, aligment, and field offsets. |
| pub cc_details: Vec<ThunkImpl>, |
| |
| pub features: FlagSet<Feature>, |
| } |
| |
| impl ApiSnippets { |
| pub fn append(&mut self, other: ApiSnippets) { |
| for (item_id, generated_item) in other.generated_items { |
| use std::collections::hash_map::Entry::*; |
| match self.generated_items.entry(item_id) { |
| Vacant(vacant) => { |
| // Other has generated bindings for an Item that self hasn't. |
| vacant.insert(generated_item); |
| } |
| Occupied(mut occupied) => { |
| occupied.get_mut().merge(generated_item); |
| } |
| } |
| } |
| self.thunks.extend(other.thunks); |
| self.assertions.extend(other.assertions); |
| self.cc_details.extend(other.cc_details); |
| self.features |= other.features; |
| } |
| } |
| |
| /// Source code for generated bindings. |
| pub struct Bindings { |
| // Rust source code. |
| pub rs_api: String, |
| // C++ source code. |
| pub rs_api_impl: String, |
| } |
| |
| /// Source code for generated bindings, as tokens. |
| /// |
| /// This is public within the crate for testing purposes. |
| pub struct BindingsTokens { |
| // Rust source code. |
| pub rs_api: TokenStream, |
| // C++ source code. |
| pub rs_api_impl: TokenStream, |
| } |
| |
| /// A missing set of crubit features caused by a capability that requires that |
| /// feature. |
| /// |
| /// For example, if addition is not implemented due to missing the Experimental |
| /// feature on //foo, then we might have something like: |
| /// |
| /// ``` |
| /// RequiredCrubitFeature { |
| /// target: "//foo".into(), |
| /// item: "kFoo".into(), |
| /// missing_features: CrubitFeature::Experimental.into(), |
| /// capability_description: "int addition".into(), |
| /// } |
| /// ``` |
| #[derive(Clone, PartialEq, Eq, Debug)] |
| pub struct RequiredCrubitFeature { |
| pub target: BazelLabel, |
| pub item: Rc<str>, |
| pub missing_features: flagset::FlagSet<crubit_feature::CrubitFeature>, |
| pub capability_description: Rc<str>, |
| } |
| |
| impl Display for RequiredCrubitFeature { |
| fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { |
| let Self { target, item, missing_features, capability_description } = self; |
| let feature_strings: Vec<&str> = |
| missing_features.into_iter().map(|feature| feature.aspect_hint()).collect(); |
| write!(f, "{target} needs [{features}] for {item}", features = feature_strings.join(", "),)?; |
| if !capability_description.is_empty() { |
| write!(f, " ({capability_description})")?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Returns the list of features required to use the item which are not yet |
| /// enabled. |
| /// |
| /// If the item doesn't have a defining target, the return value is meaningless, |
| /// and bindings will always be generated. |
| /// |
| /// If the item does have a defining target, and it doesn't enable the specified |
| /// features, then bindings are suppressed for this item. |
| pub fn required_crubit_features( |
| db: &dyn BindingsGenerator, |
| item: &Item, |
| ) -> Result<Vec<RequiredCrubitFeature>> { |
| let mut missing_features = vec![]; |
| |
| let ir = &db.ir(); |
| |
| let require_any_feature = |
| |missing_features: &mut Vec<RequiredCrubitFeature>, |
| 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 |
| // which doesn't have the required Crubit features. |
| for target in item.defining_target().into_iter().chain(item.owning_target().as_ref()) { |
| let enabled_features = ir.target_crubit_features(target); |
| if (alternative_required_features & enabled_features).is_empty() { |
| missing_features.push(RequiredCrubitFeature { |
| target: target.clone(), |
| item: item.debug_name(ir), |
| missing_features: alternative_required_features, |
| capability_description: capability_description(), |
| }); |
| } |
| } |
| }; |
| |
| let require_rs_type_kind = |missing_features: &mut Vec<RequiredCrubitFeature>, |
| rs_type_kind: &RsTypeKind, |
| context: &dyn Fn() -> Rc<str>| { |
| for target in item.defining_target().into_iter().chain(item.owning_target().as_ref()) { |
| let (missing, desc) = |
| rs_type_kind.required_crubit_features(db, ir.target_crubit_features(target)); |
| if !missing.is_empty() { |
| let context = context(); |
| let capability_description = if desc.is_empty() { |
| context |
| } else if context.is_empty() { |
| desc.into() |
| } else { |
| format!("{context}: {desc}").into() |
| }; |
| missing_features.push(RequiredCrubitFeature { |
| target: target.clone(), |
| item: item.debug_name(ir), |
| missing_features: missing, |
| capability_description, |
| }); |
| } |
| } |
| }; |
| |
| if let Some(unknown_attr) = item.unknown_attr() { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| format!("unknown attribute(s): {unknown_attr}").into(), |
| ); |
| } |
| match item { |
| Item::UnsupportedItem(..) => {} |
| Item::Func(func) => { |
| if func.rs_name == UnqualifiedIdentifier::Destructor { |
| // We support destructors in supported even though they use some features we |
| // don't generally support with that feature set, because in this |
| // particular case, it's safe. |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Supported.into(), |
| &|| "destructors".into(), |
| ); |
| } else { |
| let return_type = db.rs_type_kind(func.return_type.clone())?; |
| require_rs_type_kind(&mut missing_features, &return_type, &|| "return type".into()); |
| for (i, param) in func.params.iter().enumerate() { |
| let param_type = db.rs_type_kind(param.type_.clone())?; |
| require_rs_type_kind(&mut missing_features, ¶m_type, &|| { |
| format!("the type of {} (parameter #{i})", ¶m.identifier).into() |
| }); |
| } |
| if func.is_extern_c { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Supported.into(), |
| &|| "extern \"C\" function".into(), |
| ); |
| } else { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Supported.into(), |
| &|| "non-extern \"C\" function".into(), |
| ); |
| } |
| if !func.has_c_calling_convention { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| "non-C calling convention".into(), |
| ); |
| } |
| if func.is_variadic { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| "variadic function".into(), |
| ); |
| } |
| if func.is_noreturn { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| "[[noreturn]] attribute".into(), |
| ); |
| } |
| if func.nodiscard.is_some() { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| "[[nodiscard]] attribute".into(), |
| ); |
| } |
| if func.deprecated.is_some() { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| "[[deprecated]] attribute".into(), |
| ); |
| } |
| for param in &func.params { |
| if let Some(unknown_attr) = ¶m.unknown_attr { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Experimental.into(), |
| &|| { |
| format!( |
| "param {param} has unknown attribute(s): {unknown_attr}", |
| param = ¶m.identifier.identifier |
| ) |
| .into() |
| }, |
| ); |
| } |
| } |
| } |
| } |
| Item::Record(_) | Item::TypeAlias(_) | Item::Enum(_) => { |
| require_rs_type_kind( |
| &mut missing_features, |
| // We use from_item_raw here because required_crubit_features is itself called |
| // by `BindingsGenerator::rs_type_kind()` in order to decide if it should return |
| // an error. |
| &RsTypeKind::from_item_raw( |
| db, |
| item.clone(), |
| /*have_reference_param=*/ false, |
| /*is_return_type=*/ true, |
| )?, |
| &|| "".into(), |
| ); |
| } |
| Item::GlobalVar(_) => {} |
| Item::Namespace(_) => { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Supported.into(), |
| &|| "namespace".into(), |
| ); |
| } |
| Item::IncompleteRecord(_) => { |
| require_any_feature( |
| &mut missing_features, |
| crubit_feature::CrubitFeature::Wrapper.into(), |
| &|| "incomplete type".into(), |
| ); |
| } |
| Item::Comment { .. } | Item::ExistingRustType { .. } | Item::UseMod { .. } => {} |
| } |
| Ok(missing_features) |
| } |
| |
| /// Visibility of an item. |
| /// |
| /// Generally speaking, if an error occurs (e.g. a bindings doesn't exist), then |
| /// the way to "keep going" to catch more errors is to pretend that the missing |
| /// item is `Public`. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] |
| pub enum Visibility { |
| /// The item has `pub` visibility. |
| #[default] |
| Public, |
| /// The item has `pub(crate)` visibility. |
| PubCrate, |
| } |
| |
| impl ToTokens for Visibility { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| Visibility::Public => quote! {pub}.to_tokens(tokens), |
| Visibility::PubCrate => quote! {pub(crate)}.to_tokens(tokens), |
| } |
| } |
| } |
| |
| /// Information about the bindings that this item will have. |
| /// |
| /// When this is returned, and the item is not a `Func`, then the bindings are |
| /// guaranteed to exist. |
| #[derive(Copy, Clone, PartialEq, Eq, Default)] |
| pub struct BindingsInfo { |
| pub visibility: Visibility, |
| } |
| |
| /// Information about why bindings do not exist. |
| #[derive(Clone, PartialEq, Eq)] |
| pub enum NoBindingsReason { |
| MissingRequiredFeatures { |
| context: Rc<str>, |
| missing_features: Vec<RequiredCrubitFeature>, |
| }, |
| DependencyFailed { |
| context: Rc<str>, |
| error: Error, |
| }, |
| LeadingDunder { |
| name: Rc<str>, |
| }, |
| /// This is directly unsupported. |
| Unsupported { |
| context: Rc<str>, |
| error: Error, |
| }, |
| /// This item's parent was a record, but no nested items module could be generated because there |
| /// were other records with nested items whose nested items module mapped to the same name. |
| ParentModuleNameNotUnique { |
| conflicting_name: Rc<str>, |
| /// Invariant: more than 1 element. |
| parent_names_that_map_to_same_name: Vec<Rc<str>>, |
| }, |
| /// This item's parent was a record, but no nested items module could be generated because there |
| /// were other items that occupied the name in that parent's namespace. For example, a struct |
| /// called `foo` would not be able to receive nested items because its nested module name would |
| /// also be `foo`. `Foo` would be fine though, because it gets `foo`. |
| ParentModuleNameOverwritten { |
| conflicting_name: Rc<str>, |
| }, |
| } |
| |
| impl Display for NoBindingsReason { |
| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
| <Error as Display>::fmt(&Error::from(self.clone()), f) |
| } |
| } |
| |
| impl std::fmt::Debug for NoBindingsReason { |
| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
| <Error as std::fmt::Debug>::fmt(&Error::from(self.clone()), f) |
| } |
| } |
| |
| impl From<NoBindingsReason> for Error { |
| fn from(reason: NoBindingsReason) -> Error { |
| match reason { |
| NoBindingsReason::MissingRequiredFeatures { context, missing_features } => { |
| // This maybe could use .context(), but the ordering is backward. |
| let mut all_missing = vec![]; |
| for missing in missing_features { |
| all_missing.push(missing.to_string()); |
| } |
| anyhow!( |
| "Can't generate bindings for {context}, because of missing required features (<internal link>):\n{}", |
| all_missing.join("\n") |
| ) |
| } |
| NoBindingsReason::DependencyFailed { context, error } => error.context(format!( |
| "Can't generate bindings for {context} due to missing bindings for its dependency" |
| )), |
| NoBindingsReason::LeadingDunder { name } => { |
| anyhow!("Skipping generating bindings for '{name}' because it has a leading `__`") |
| } |
| NoBindingsReason::Unsupported { context, error } => error.context(format!( |
| "Can't generate bindings for {context}, because it is unsupported" |
| )), |
| NoBindingsReason::ParentModuleNameNotUnique { |
| conflicting_name, |
| parent_names_that_map_to_same_name, |
| } => { |
| anyhow!( |
| "records {parent_names_that_map_to_same_name:?} all have nested items, but all map to the same nested module name: `{conflicting_name}`", |
| ) |
| } |
| NoBindingsReason::ParentModuleNameOverwritten { conflicting_name } => { |
| anyhow!( |
| "parent record has nested items, but the module to contain them could not be generated because another item named `{conflicting_name}` already exists", |
| ) |
| } |
| } |
| } |
| } |
| |
| /// The thing that a type name resolves to. |
| pub enum ResolvedTypeName { |
| Namespace { |
| /// Namespaces with the same canonical namespace id are coalesced together. |
| canonical_namespace_id: ItemId, |
| }, |
| /// An item that is explicitly generated (as opposed to RecordNestedItems, which is implicitly |
| /// generated). |
| ExplicitItem(ItemId), |
| /// The module that is generated to hold the nested items of a record. |
| /// If there's more than one, that means the multiple records with nested items, when |
| /// snake_cased, map to the same name. For now, this is treated as an error, but we may want to |
| /// behave differently in the future. |
| RecordNestedItems { |
| /// Invariant: at least one item. |
| parent_records_that_map_to_this_name: Vec<ItemId>, |
| }, |
| } |
| |
| impl ResolvedTypeName { |
| /// If two names resolve to different ResolvedTypeNames, try and coalesce them together. |
| /// For example, namespaces that correspond to the same canonical namespace id can be flattened. |
| pub fn coalesce(&mut self, other: ResolvedTypeName) -> Result<()> { |
| match (self, other) { |
| // RecordNestedItems coalesce together. Right now, this is just to provide a better |
| // error message ("several record nested items modules map to the same name"), but we |
| // can later change this to actually coalesce them (may lead to nested name conflicts |
| // though) or pick one or something else. |
| ( |
| Self::RecordNestedItems { parent_records_that_map_to_this_name }, |
| Self::RecordNestedItems { |
| parent_records_that_map_to_this_name: |
| mut other_parent_records_that_map_to_this_name, |
| }, |
| ) => { |
| parent_records_that_map_to_this_name |
| .append(&mut other_parent_records_that_map_to_this_name); |
| Ok(()) |
| } |
| // RecordNestedItems are always overwritten by other resolved names, because they are |
| // implicitly generated and therefore have low priority. |
| (_, Self::RecordNestedItems { .. }) => Ok(()), |
| (this @ Self::RecordNestedItems { .. }, other) => { |
| *this = other; |
| Ok(()) |
| } |
| // Namespaces with the same canonical namespace id flatten into a single resolved name. |
| ( |
| Self::Namespace { canonical_namespace_id }, |
| Self::Namespace { canonical_namespace_id: other_canonical_namespace_id }, |
| ) => { |
| ensure!( |
| *canonical_namespace_id == other_canonical_namespace_id, |
| "multiple namespaces with the same name but differing canonical namespace ids" |
| ); |
| Ok(()) |
| } |
| // Everything else is a conflict, and should never happen. |
| _ => bail!("conflicting name occupants"), |
| } |
| } |
| } |
| |
| pub fn generated_items_to_token_stream( |
| generated_items: &HashMap<ItemId, GeneratedItem>, |
| ir: &IR, |
| elements: &[ItemId], |
| ) -> TokenStream { |
| let mut tokens = quote! {}; |
| generated_items_to_tokens(generated_items, ir, elements, &mut tokens); |
| tokens |
| } |
| |
| pub fn generated_items_to_tokens( |
| generated_items: &HashMap<ItemId, GeneratedItem>, |
| ir: &IR, |
| elements: &[ItemId], |
| tokens: &mut TokenStream, |
| ) { |
| for &id in elements { |
| let Some(generated_item) = generated_items.get(&id) else { |
| continue; |
| }; |
| |
| match generated_item { |
| GeneratedItem::Comment { message } => quote! { __COMMENT__ #message }.to_tokens(tokens), |
| GeneratedItem::Enum(enum_item) => enum_item.to_tokens(tokens), |
| GeneratedItem::Func(function_tokens) => function_tokens.to_tokens(tokens), |
| GeneratedItem::Record(record_item) => { |
| let Record { |
| doc_comment_attr, |
| derive_attr, |
| recursively_pinned_attr, |
| must_use_attr, |
| align, |
| crubit_annotation, |
| visibility, |
| struct_or_union, |
| ident, |
| head_padding, |
| field_definitions, |
| implements_send, |
| implements_sync, |
| cxx_impl, |
| incomplete_definition, |
| upcast_impls, |
| no_unique_address_accessors, |
| items, |
| nested_items, |
| indirect_functions, |
| } = record_item.as_ref(); |
| |
| let repr_attrs = std::iter::once(quote! { C }).chain(align.map(|align| { |
| let align = Literal::usize_unsuffixed(align); |
| quote! { align(#align) } |
| })); |
| |
| let head_padding = head_padding.map(|n| { |
| let n = Literal::usize_unsuffixed(n); |
| quote! { __non_field_data: [::core::mem::MaybeUninit<u8>; #n], } |
| }); |
| |
| let send_impl = match implements_send { |
| true => quote! { unsafe impl Send for #ident {} }, |
| false => quote! { impl !Send for #ident {} }, |
| }; |
| let sync_impl = match implements_sync { |
| true => quote! { unsafe impl Sync for #ident {} }, |
| false => quote! { impl !Sync for #ident {} }, |
| }; |
| |
| let cxx_impl = match cxx_impl { |
| Some(CxxExternTypeImpl { id, kind }) => quote! { |
| unsafe impl ::cxx::ExternType for #ident { |
| type Id = ::cxx::type_id!(#id); |
| type Kind = #kind; |
| } |
| }, |
| _ => quote! {}, |
| }; |
| |
| let no_unique_address_accessors_impl = if !no_unique_address_accessors.is_empty() { |
| Some(quote! { |
| impl #ident { |
| #( #no_unique_address_accessors )* |
| } |
| }) |
| } else { |
| None |
| }; |
| |
| quote! { |
| #doc_comment_attr |
| #derive_attr |
| #recursively_pinned_attr |
| #must_use_attr |
| #[repr(#(#repr_attrs),*)] |
| #crubit_annotation |
| #visibility #struct_or_union #ident { |
| #head_padding |
| #( #field_definitions )* |
| } |
| |
| #send_impl |
| #sync_impl |
| #cxx_impl |
| |
| #incomplete_definition |
| |
| #no_unique_address_accessors_impl |
| |
| __NEWLINE__ __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| |
| generated_items_to_tokens(generated_items, ir, items, tokens); |
| |
| quote! { #( #indirect_functions __NEWLINE__ __NEWLINE__ )* }.to_tokens(tokens); |
| |
| for upcast_impl_or_err in upcast_impls { |
| match upcast_impl_or_err { |
| Ok(UpcastImpl { base_name, derived_name, body }) => { |
| let body = match body { |
| UpcastImplBody::PointerOffset { offset } => { |
| let offset = Literal::i64_unsuffixed(*offset); |
| quote! { (derived as *const _ as *const u8).offset(#offset) as *const #base_name } |
| } |
| UpcastImplBody::CastThunk { crate_root_path, cast_fn_name } => { |
| let path = if let Some(crate_root_path) = crate_root_path { |
| quote! { crate :: #crate_root_path } |
| } else { |
| quote! { crate } |
| }; |
| quote! { #path::detail::#cast_fn_name(derived) } |
| } |
| }; |
| |
| quote! { |
| unsafe impl oops::Inherits<#base_name> for #derived_name { |
| unsafe fn upcast_ptr(derived: *const Self) -> *const #base_name { |
| #body |
| } |
| } |
| __NEWLINE__ |
| __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| } |
| Err(err) => { |
| quote! { |
| __NEWLINE__ |
| __COMMENT__ #err |
| __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| } |
| } |
| } |
| |
| if !nested_items.is_empty() { |
| let snake_case_name = make_rs_ident(&ident.to_string().to_snake_case()); |
| let nested_items_to_tokens = |
| generated_items_to_token_stream(generated_items, ir, nested_items); |
| quote! { |
| pub mod #snake_case_name { |
| #[allow(unused_imports)] |
| use super::*; __NEWLINE__ |
| __NEWLINE__ |
| #nested_items_to_tokens |
| } |
| } |
| .to_tokens(tokens); |
| } |
| } |
| GeneratedItem::NonCanonicalNamespace | GeneratedItem::CanonicalNamespace { .. } => { |
| // For a given namespace, canonical_namespace_id is not necessarily in this target, |
| // meaning it may never be visited if we branch down just from the top level items in |
| // this target. To mitigate this issue, we instead agree to pick the last reopened |
| // namespace _in this target_ as the representative that gets to generate all the items |
| // in the canonical namespace. |
| // The reason this occurs is because although Crubit only generates items for this |
| // target, Clang looks at all the includes, meaning it can see the same namespace in |
| // headers from different targets. The canonical namespace is picked by Clang, resulting |
| // in sometimes getting a canonical namespace that's not in our target. |
| // We do not have to worry about getting items from other targets though because Crubit |
| // only generates items for this target. |
| let namespace = |
| ir.find_decl::<Rc<Namespace>>(id).expect("should always be a namespace"); |
| let is_last_reopened_namespace_in_this_target = ir |
| .is_last_reopened_namespace(id, namespace.canonical_namespace_id) |
| .expect("should always be a namespace"); |
| |
| if !is_last_reopened_namespace_in_this_target { |
| // It is not the representative, so we don't generate any items in order to |
| // avoid generating duplicate bindings. |
| continue; |
| } |
| // It is the representative, so we generate all the items keyed under the |
| // canonical namespace id. |
| |
| let Some(GeneratedItem::CanonicalNamespace { items }) = |
| generated_items.get(&namespace.canonical_namespace_id) |
| else { |
| panic!("the entry we generated for the canonical namespace should be a GeneratedItem::CanonicalNamespace"); |
| }; |
| |
| let namespace_tokens = generated_items_to_token_stream(generated_items, ir, items); |
| |
| let canonical_namespace: &Rc<ir::Namespace> = ir |
| .find_decl(namespace.canonical_namespace_id) |
| .expect("should always be a namespace"); |
| let name = make_rs_ident(&canonical_namespace.rs_name.identifier); |
| |
| quote! { |
| pub mod #name { |
| #namespace_tokens |
| } |
| __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| |
| if canonical_namespace.is_inline { |
| // TODO(b/308949532): Skip re-export if the canonical module is empty |
| // (transitively). |
| quote! { |
| __HASH_TOKEN__ [allow(unused_imports)] |
| pub use #name::*; |
| } |
| .to_tokens(tokens); |
| } |
| } |
| GeneratedItem::ForwardDeclare { visibility, ident, symbol } => { |
| quote! { |
| forward_declare::forward_declare!( |
| #visibility #ident __SPACE__ = __SPACE__ forward_declare::symbol!(#symbol) |
| ); |
| } |
| .to_tokens(tokens); |
| } |
| GeneratedItem::UseMod { path, mod_name } => quote! { |
| #[path = #path] |
| mod #mod_name; |
| __HASH_TOKEN__ [allow(unused_imports)] |
| pub use #mod_name::*; |
| } |
| .to_tokens(tokens), |
| GeneratedItem::GlobalVar { link_name, visibility, is_mut, ident, type_tokens } => { |
| let link_name_attr = |
| link_name.as_deref().map(|link_name| quote! { #[link_name = #link_name] }); |
| let mut_kw = if *is_mut { Some(quote! { mut }) } else { None }; |
| quote! { |
| extern "C" { |
| #link_name_attr |
| #visibility static #mut_kw #ident: #type_tokens; |
| } |
| } |
| .to_tokens(tokens); |
| } |
| GeneratedItem::TypeAlias { doc_comment, visibility, ident, underlying_type } => { |
| quote! { |
| #doc_comment |
| #visibility type #ident = #underlying_type; |
| } |
| .to_tokens(tokens); |
| } |
| } |
| quote! { |
| __NEWLINE__ |
| __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum GeneratedItem { |
| Comment { |
| message: Rc<str>, |
| }, |
| Enum(TokenStream), |
| Func(TokenStream), |
| // Box used to mitigate disproportionaly large enum variant lint |
| Record(Box<Record>), |
| NonCanonicalNamespace, |
| CanonicalNamespace { |
| /// List of all the items from all the namespaces |
| items: Vec<ItemId>, |
| }, |
| ForwardDeclare { |
| visibility: Visibility, |
| ident: Ident, |
| symbol: String, |
| }, |
| UseMod { |
| path: Rc<str>, |
| mod_name: Ident, |
| }, |
| GlobalVar { |
| link_name: Option<Rc<str>>, |
| visibility: Visibility, |
| is_mut: bool, |
| ident: Ident, |
| type_tokens: TokenStream, |
| }, |
| TypeAlias { |
| doc_comment: Option<DocCommentAttr>, |
| visibility: Visibility, |
| ident: Ident, |
| underlying_type: TokenStream, |
| }, |
| } |
| |
| impl GeneratedItem { |
| fn merge(&mut self, other: GeneratedItem) { |
| match (self, other) { |
| ( |
| GeneratedItem::CanonicalNamespace { items }, |
| GeneratedItem::CanonicalNamespace { items: other_items }, |
| ) => { |
| items.extend(other_items); |
| } |
| ( |
| GeneratedItem::Comment { message }, |
| GeneratedItem::Comment { message: other_message }, |
| ) => { |
| assert_eq!(message.as_ref(), other_message.as_ref()); |
| } |
| (this, other) => { |
| // The bindings are not mergable, this should never happen. |
| unreachable!("Two ApiSnippets generated bindings for the same ItemId that's not a canonical namespace: {this:#?} and {other:#?}"); |
| } |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct Record { |
| pub doc_comment_attr: Option<DocCommentAttr>, |
| pub derive_attr: DeriveAttr, |
| pub recursively_pinned_attr: Option<RecursivelyPinnedAttr>, |
| pub must_use_attr: Option<MustUseAttr>, |
| pub align: Option<usize>, |
| pub crubit_annotation: DocCommentAttr, |
| pub visibility: Visibility, |
| pub struct_or_union: StructOrUnion, |
| pub ident: Ident, |
| pub head_padding: Option<usize>, |
| pub field_definitions: Vec<FieldDefinition>, |
| pub implements_send: bool, |
| pub implements_sync: bool, |
| pub cxx_impl: Option<CxxExternTypeImpl>, |
| pub incomplete_definition: Option<TokenStream>, |
| pub upcast_impls: Vec<Result<UpcastImpl, String>>, |
| pub no_unique_address_accessors: Vec<NoUniqueAddressAccessor>, |
| pub items: Vec<ItemId>, |
| pub nested_items: Vec<ItemId>, |
| /// Functions that get attached either by a trait or from a base class. |
| pub indirect_functions: Vec<TokenStream>, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct UpcastImpl { |
| pub base_name: TokenStream, |
| pub derived_name: TokenStream, |
| pub body: UpcastImplBody, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum UpcastImplBody { |
| PointerOffset { offset: i64 }, |
| CastThunk { crate_root_path: Option<Ident>, cast_fn_name: Ident }, |
| } |
| |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct DocCommentAttr(pub Rc<str>); |
| |
| impl ToTokens for DocCommentAttr { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let Self(doc_comment) = self; |
| quote! { #[doc = #doc_comment] }.to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct DeriveAttr(pub Vec<TokenStream>); |
| |
| impl ToTokens for DeriveAttr { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let Self(derives) = self; |
| if !derives.is_empty() { |
| quote! { #[derive(#(#derives),*)] }.to_tokens(tokens); |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct RecursivelyPinnedAttr { |
| pub pinned_drop: bool, |
| } |
| |
| impl ToTokens for RecursivelyPinnedAttr { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self.pinned_drop { |
| true => quote! { #[::ctor::recursively_pinned(PinnedDrop)] }, |
| false => quote! { #[::ctor::recursively_pinned] }, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct MustUseAttr(pub Rc<str>); |
| |
| impl ToTokens for MustUseAttr { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self.0.as_ref() { |
| "" => quote! { #[must_use] }, |
| message => quote! { #[must_use = #message] }, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct CxxExternTypeImpl { |
| pub id: Rc<str>, |
| pub kind: CxxKind, |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| pub enum CxxKind { |
| Opaque, |
| Trivial, |
| } |
| |
| impl ToTokens for CxxKind { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| CxxKind::Opaque => quote! {::cxx::kind::Opaque}, |
| CxxKind::Trivial => quote! {::cxx::kind::Trivial}, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum StructOrUnion { |
| Struct, |
| Union, |
| } |
| |
| impl ToTokens for StructOrUnion { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| StructOrUnion::Struct => quote! { struct }, |
| StructOrUnion::Union => quote! { union }, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| /// Quotes as the type of a type-less, unaligned block of memory that can hold a |
| /// specified number of bits, rounded up to the next multiple of 8. |
| #[derive(Copy, Clone, Debug)] |
| pub struct BitPadding(pub NonZeroUsize); |
| |
| impl BitPadding { |
| fn padding_size_in_bytes(self) -> usize { |
| self.0.get().div_ceil(8) |
| } |
| } |
| |
| impl ToTokens for BitPadding { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let n = Literal::usize_unsuffixed(self.padding_size_in_bytes()); |
| quote! { [::core::mem::MaybeUninit<u8>; #n] }.to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct BitfieldComment { |
| pub field_name: Option<Rc<str>>, |
| pub bits: NonZeroUsize, |
| } |
| |
| impl ToTokens for BitfieldComment { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let s = format!("{} : {} bits", self.field_name.as_deref().unwrap_or(""), self.bits.get()); |
| quote! { __COMMENT__ #s }.to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum FieldDefinition { |
| Bitfield { |
| field_index: usize, |
| desc: Vec<BitfieldComment>, |
| padding: Option<BitPadding>, |
| bits: BitPadding, |
| }, |
| Field { |
| field_index: usize, |
| padding: Option<BitPadding>, |
| doc_comment: Option<DocCommentAttr>, |
| visibility: Visibility, |
| ident: Ident, |
| field_type: FieldType, |
| }, |
| } |
| |
| impl ToTokens for FieldDefinition { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| FieldDefinition::Bitfield { field_index, desc, padding, bits } => { |
| let padding_field = padding.map(|padding| { |
| let padding_name = |
| syn::parse_str::<Ident>(&format!("__padding{field_index}")).unwrap(); |
| quote! { #padding_name: #padding, } |
| }); |
| let bitfield_name = |
| syn::parse_str::<Ident>(&format!("__bitfields{field_index}")).unwrap(); |
| quote! { |
| __NEWLINE__ #( #desc )* |
| #padding_field |
| #bitfield_name: #bits, |
| } |
| .to_tokens(tokens); |
| } |
| FieldDefinition::Field { |
| field_index, |
| padding, |
| doc_comment, |
| visibility, |
| ident, |
| field_type, |
| } => { |
| let padding_field = padding.map(|padding| { |
| let padding_name = |
| syn::parse_str::<Ident>(&format!("__padding{field_index}")).unwrap(); |
| quote! { #padding_name: #padding, } |
| }); |
| quote! { |
| #padding_field |
| #doc_comment |
| #visibility #ident: #field_type, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum FieldType { |
| Erased(BitPadding), |
| Type { needs_manually_drop: bool, ty: TokenStream }, |
| } |
| |
| impl ToTokens for FieldType { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| FieldType::Erased(padding) => padding.to_tokens(tokens), |
| FieldType::Type { needs_manually_drop, ty } => { |
| if *needs_manually_drop { |
| quote! { ::core::mem::ManuallyDrop<#ty> }.to_tokens(tokens) |
| } else { |
| ty.to_tokens(tokens) |
| } |
| } |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct NoUniqueAddressAccessor { |
| pub doc_comment: Option<DocCommentAttr>, |
| pub field: Ident, |
| pub type_: TokenStream, |
| pub byte_offset: usize, |
| } |
| |
| impl ToTokens for NoUniqueAddressAccessor { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let Self { doc_comment, field, type_, byte_offset } = self; |
| let byte_offset = Literal::usize_unsuffixed(*byte_offset); |
| |
| // SAFETY: even if there is a named field in Rust for this subobject, it is not |
| // safe to just cast the pointer. A `struct S {[[no_unique_address]] A a; |
| // char b};` will be represented in Rust using a too-short field `a` (e.g. |
| // with `[MaybeUninit<u8>; 3]`, where the trailing fourth byte is actually |
| // `b`). We cannot cast this to something wider, which includes `b`, even |
| // though the `a` object does in fact include `b` in C++. This is Rust, and |
| // these are distinct object allocations. We don't have provenance. |
| // |
| // However, we can start from the pointer to **S** and perform pointer |
| // arithmetic on it to get a correctly-sized `A` reference. This is |
| // equivalent to transmuting the type to one where the potentially-overlapping |
| // subobject exists, but the fields next to it, which it overlaps, do not. |
| // As if it were `struct S {A a;};`. However, we do not use transmutes, and |
| // instead reimplement field access using pointer arithmetic. |
| // |
| // The resulting pointer is valid and correctly aligned, and does not violate |
| // provenance. It also does not result in mutable aliasing, because this |
| // borrows `self`, not just `a`. |
| quote! { |
| #doc_comment |
| pub fn #field(&self) -> &#type_ { |
| unsafe { |
| let ptr = (self as *const Self as *const u8).offset(#byte_offset); |
| &*(ptr as *const #type_) |
| } |
| } |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| flagset::flags! { |
| #[allow(non_camel_case_types)] |
| pub enum Feature: u32 { |
| // <internal link> start |
| allocator_api, |
| arbitrary_self_types, |
| cfg_sanitize, |
| custom_inner_attributes, |
| impl_trait_in_assoc_type, |
| negative_impls, |
| register_tool, |
| // <internal link> end |
| } |
| } |
| |
| impl ToTokens for Feature { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| Feature::allocator_api => quote! { allocator_api }, |
| Feature::arbitrary_self_types => quote! { arbitrary_self_types }, |
| Feature::cfg_sanitize => quote! { cfg_sanitize }, |
| Feature::custom_inner_attributes => quote! { custom_inner_attributes }, |
| Feature::impl_trait_in_assoc_type => quote! { impl_trait_in_assoc_type }, |
| Feature::negative_impls => quote! { negative_impls }, |
| Feature::register_tool => quote! { register_tool }, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| /// Compile-time assertions for the generated bindings. |
| #[derive(Clone, Debug)] |
| pub enum Assertion { |
| /// Asserts that a type has a certain size and alignment. |
| SizeAlign { type_name: TokenStream, size: usize, alignment: usize }, |
| /// Asserts that a type implements (or does not implement) certain traits. |
| Impls { |
| type_name: TokenStream, |
| /// Assert that all of these traits are implemented. |
| all_of: FlagSet<AssertableTrait>, |
| /// Assert that none of these traits are implemented. |
| none_of: FlagSet<AssertableTrait>, |
| }, |
| /// Asserts that a list of fields have their expected offsets using the [`core::mem::offset_of`] |
| /// macro. |
| FieldOffsets { qualified_ident: TokenStream, fields_and_expected_offsets: Vec<(Ident, usize)> }, |
| } |
| |
| flagset::flags! { |
| /// Traits that can be asserted as implemented or not implemented on a type, using the |
| /// `static_assertions` macros. |
| // Note: ordering of variants determines iteration order on `FlagSet<AssertableTrait>`. |
| pub enum AssertableTrait: u32 { |
| Copy, |
| Clone, |
| Drop, |
| } |
| } |
| |
| impl ToTokens for Assertion { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| Assertion::SizeAlign { type_name, size, alignment } => { |
| let size = Literal::usize_unsuffixed(*size); |
| let alignment = Literal::usize_unsuffixed(*alignment); |
| quote! { |
| assert!(::core::mem::size_of::<#type_name>() == #size); |
| assert!(::core::mem::align_of::<#type_name>() == #alignment); |
| } |
| .to_tokens(tokens); |
| } |
| Assertion::Impls { type_name, all_of, none_of } => { |
| assert!( |
| all_of.is_disjoint(*none_of), |
| "Found contradicting impl assertions, this is a bug" |
| ); |
| if !all_of.is_empty() { |
| let all_of_iter = all_of.into_iter(); |
| quote! { |
| static_assertions::assert_impl_all! (#type_name: #(#all_of_iter),*); |
| } |
| .to_tokens(tokens); |
| } |
| if !none_of.is_empty() { |
| let none_of_iter = none_of.into_iter(); |
| quote! { |
| static_assertions::assert_not_impl_any! (#type_name: #(#none_of_iter),*); |
| } |
| .to_tokens(tokens); |
| } |
| } |
| Assertion::FieldOffsets { qualified_ident, fields_and_expected_offsets } => { |
| for (field_ident, expected_offset) in fields_and_expected_offsets { |
| let expected_offset = Literal::usize_unsuffixed(*expected_offset); |
| quote! { |
| assert!(::core::mem::offset_of!(#qualified_ident, #field_ident) == #expected_offset); |
| } |
| .to_tokens(tokens); |
| } |
| } |
| } |
| } |
| } |
| |
| impl ToTokens for AssertableTrait { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| AssertableTrait::Copy => quote! { Copy }, |
| AssertableTrait::Clone => quote! { Clone }, |
| AssertableTrait::Drop => quote! { Drop }, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| /// A Rust function thunk that appears in an `unsafe extern "C"` block. |
| #[derive(Clone, Debug)] |
| pub enum Thunk { |
| /// Generates a thunk for upcasting from a derived type to a base type. |
| Upcast { cast_fn_name: Ident, derived_name: TokenStream, base_name: TokenStream }, |
| /// Generates a thunk for a function. |
| Function { |
| mangled_name: Option<Rc<str>>, |
| thunk_ident: Ident, |
| generic_params: TokenStream, |
| param_idents: Vec<Ident>, |
| param_types: Vec<TokenStream>, |
| return_type_fragment: Option<TokenStream>, |
| }, |
| } |
| |
| impl ToTokens for Thunk { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| Thunk::Upcast { cast_fn_name, derived_name, base_name } => { |
| quote! { |
| pub fn #cast_fn_name(from: *const #derived_name) -> *const #base_name; |
| } |
| .to_tokens(tokens); |
| } |
| Thunk::Function { |
| mangled_name, |
| thunk_ident, |
| generic_params, |
| param_idents, |
| param_types, |
| return_type_fragment, |
| } => { |
| if let Some(mangled_name) = mangled_name { |
| quote! { |
| #[link_name = #mangled_name] |
| } |
| .to_tokens(tokens); |
| } |
| |
| let return_type_fragment = |
| return_type_fragment.as_ref().map(|return_type| quote! { -> #return_type }); |
| |
| // Note: some of these are `safe`, but _all_ of them are currently wrapped by a |
| // (possibly safe) function, so we leave them all `unsafe` for convenience. |
| quote! { |
| pub(crate) unsafe fn #thunk_ident #generic_params( |
| #( #param_idents: #param_types ),* |
| ) #return_type_fragment ; |
| } |
| .to_tokens(tokens); |
| } |
| } |
| } |
| } |
| |
| /// Abstract representation of generated C++ code that implements the Rust thunk. |
| #[derive(Clone, Debug)] |
| pub enum ThunkImpl { |
| /// A function that upcasts from a derived type to a base type. |
| Upcast { |
| base_cc_name: TokenStream, |
| cast_fn_name: Ident, |
| derived_cc_name: TokenStream, |
| }, |
| /// A function that implements a Rust function thunk. |
| Function { |
| conversion_externs: TokenStream, |
| return_type_name: TokenStream, |
| thunk_ident: Ident, |
| param_types: Vec<TokenStream>, |
| param_idents: Vec<Ident>, |
| conversion_stmts: TokenStream, |
| return_stmt: TokenStream, |
| }, |
| /// A set of `static_assert`s that check the layout of a record. |
| LayoutAssertion { |
| tag_kind: Option<RecordType>, |
| namespace_qualifier: TokenStream, |
| record_ident: Rc<str>, |
| sizeof_impl: SizeofImpl, |
| size: usize, |
| alignment: usize, |
| fields_and_expected_offsets: Vec<(TokenStream, usize)>, |
| }, |
| FuntionTypeAssertation { |
| implementation_function: TokenStream, |
| cc_function_type: TokenStream, |
| }, |
| } |
| |
| impl ToTokens for ThunkImpl { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| ThunkImpl::Upcast { base_cc_name, cast_fn_name, derived_cc_name } => { |
| quote! { |
| extern "C" const #base_cc_name& #cast_fn_name(const #derived_cc_name& from) { |
| return from; |
| } |
| } |
| .to_tokens(tokens); |
| } |
| ThunkImpl::Function { |
| conversion_externs, |
| return_type_name, |
| thunk_ident, |
| param_types, |
| param_idents, |
| conversion_stmts, |
| return_stmt, |
| } => { |
| quote! { |
| #conversion_externs |
| |
| extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) { |
| #conversion_stmts |
| #return_stmt; |
| } |
| } |
| .to_tokens(tokens); |
| } |
| ThunkImpl::LayoutAssertion { |
| tag_kind, |
| namespace_qualifier, |
| record_ident, |
| sizeof_impl, |
| size, |
| alignment, |
| fields_and_expected_offsets, |
| } => { |
| let size = Literal::usize_unsuffixed(*size); |
| let alignment = Literal::usize_unsuffixed(*alignment); |
| |
| let record_ident = expect_format_cc_type_name(record_ident.as_ref()); |
| quote! { |
| static_assert(#sizeof_impl(#tag_kind #namespace_qualifier #record_ident) == #size); |
| static_assert(alignof(#tag_kind #namespace_qualifier #record_ident) == #alignment); |
| }.to_tokens(tokens); |
| |
| for (field_ident, expected_offset) in fields_and_expected_offsets { |
| let expected_offset = Literal::usize_unsuffixed(*expected_offset); |
| |
| quote! { |
| static_assert(CRUBIT_OFFSET_OF(#field_ident, #tag_kind #namespace_qualifier #record_ident) == #expected_offset); |
| }.to_tokens(tokens); |
| } |
| } |
| ThunkImpl::FuntionTypeAssertation { implementation_function, cc_function_type } => { |
| quote! { |
| static_assert( ( #cc_function_type ) & #implementation_function); |
| } |
| .to_tokens(tokens); |
| } |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct CppIncludes { |
| pub internal_includes: TokenStream, |
| pub ir_includes: Vec<CcInclude>, |
| } |
| |
| impl ToTokens for CppIncludes { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let CppIncludes { internal_includes, ir_includes } = self; |
| quote! { |
| #internal_includes |
| __NEWLINE__ |
| __COMMENT__ "Public headers of the C++ library being wrapped." |
| #( #ir_includes )* __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum SizeofImpl { |
| /// The `sizeof` keyword. |
| Builtin, |
| /// Like sizeof, but rounds up to alignment in case the type has a strange |
| /// sizeof. |
| /// |
| /// In particular, this is true of type aliases which override alignment but |
| /// not size, as in e.g. `typedef __attribute__((aligned(N)) struct {} MyAlias;`. |
| RoundUpToAlignment, |
| } |
| |
| impl ToTokens for SizeofImpl { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| SizeofImpl::Builtin => quote! { sizeof }, |
| SizeofImpl::RoundUpToAlignment => quote! { CRUBIT_SIZEOF }, |
| } |
| .to_tokens(tokens); |
| } |
| } |
| |
| /// Abstract representation of a *_rs_api_impl file. |
| pub struct CppDetails { |
| includes: CppIncludes, |
| // The "pragma clang diagnostic push/pop" is automatically inserted around the thunks. |
| thunks: Vec<ThunkImpl>, |
| } |
| |
| impl CppDetails { |
| pub fn new(includes: CppIncludes, thunks: Vec<ThunkImpl>) -> Self { |
| CppDetails { includes, thunks } |
| } |
| } |
| |
| impl ToTokens for CppDetails { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let CppDetails { includes, thunks } = self; |
| quote! { |
| #includes |
| __NEWLINE__ |
| __HASH_TOKEN__ pragma clang diagnostic push __NEWLINE__ |
| // Disable Clang thread-safety-analysis warnings that would otherwise |
| // complain about thunks that call mutex locking functions in an unpaired way. |
| __HASH_TOKEN__ pragma clang diagnostic ignored "-Wthread-safety-analysis" __NEWLINE__ __NEWLINE__ |
| |
| #( #thunks __NEWLINE__ __NEWLINE__ )* |
| |
| __HASH_TOKEN__ pragma clang diagnostic pop __NEWLINE__ |
| } |
| .to_tokens(tokens); |
| } |
| } |