blob: 4ee23d415c4e1ba51518551d43c39d8cf8cc7e26 [file]
// 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, &param_type, &|| {
format!("the type of {} (parameter #{i})", &param.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) = &param.unknown_attr {
require_any_feature(
&mut missing_features,
crubit_feature::CrubitFeature::Experimental.into(),
&|| {
format!(
"param {param} has unknown attribute(s): {unknown_attr}",
param = &param.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);
}
}