blob: f39b02bd0910841a58a419e72d7323051fcd99ef [file] [log] [blame]
// 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
#![allow(clippy::collapsible_else_if)]
use crate::{BindingsGenerator, Database, GeneratedItem};
use crate::rs_snippet::{should_derive_clone, should_derive_copy, RsTypeKind};
use arc_anyhow::{Context, Result};
use code_gen_utils::make_rs_ident;
use error_report::{bail, ensure};
use ir::*;
use itertools::Itertools;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{quote, ToTokens};
use std::collections::BTreeSet;
use std::iter;
use std::rc::Rc;
// TODO(jeanpierreda): Make this a method on RsTypeKind, or on Record?
pub(crate) fn should_implement_drop(record: &Record) -> bool {
match record.destructor {
// TODO(b/202258760): Only omit destructor if `Copy` is specified.
SpecialMemberFunc::Trivial => false,
// TODO(b/212690698): Avoid calling into the C++ destructor (e.g. let
// Rust drive `drop`-ing) to avoid (somewhat unergonomic) ManuallyDrop
// if we can ask Rust to preserve C++ field destruction order in
// NontrivialMembers case.
SpecialMemberFunc::NontrivialMembers => true,
// The `impl Drop` for NontrivialUserDefined needs to call into the
// user-defined destructor on C++ side.
SpecialMemberFunc::NontrivialUserDefined => true,
// TODO(b/213516512): Today the IR doesn't contain Func entries for
// deleted functions/destructors/etc. But, maybe we should generate
// `impl Drop` in this case? With `unreachable!`? With
// `std::mem::forget`?
SpecialMemberFunc::Unavailable => false,
}
}
/// Returns whether fields of type `ty` need to be wrapped in `ManuallyDrop<T>`
/// to prevent the fields from being destructed twice (once by the C++
/// destructor calkled from the `impl Drop` of the struct and once by `drop` on
/// the Rust side).
///
/// A type is safe to destroy twice if it implements `Copy`. Fields of such
/// don't need to be wrapped in `ManuallyDrop<T>` even if the struct
/// containing the fields provides an `impl Drop` that calles into a C++
/// destructor (in addition to dropping the fields on the Rust side).
///
/// Note that it is not enough to just be `!needs_drop<T>()`: Rust only
/// guarantees that it is safe to use-after-destroy for `Copy` types. See
/// e.g. the documentation for
/// [`drop_in_place`](https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html):
///
/// > if `T` is not `Copy`, using the pointed-to value after calling
/// > `drop_in_place` can cause undefined behavior
///
/// For non-Copy union fields, failing to use `ManuallyDrop<T>` would
/// additionally cause a compile-time error until https://github.com/rust-lang/rust/issues/55149 is stabilized.
fn needs_manually_drop(ty: &RsTypeKind) -> bool {
!ty.implements_copy()
}
/// Generates Rust source code for a given incomplete record declaration.
pub fn generate_incomplete_record(
db: &Database,
incomplete_record: &IncompleteRecord,
) -> Result<GeneratedItem> {
let ident = make_rs_ident(incomplete_record.rs_name.as_ref());
let namespace_qualifier = db.ir().namespace_qualifier(incomplete_record)?.format_for_cc()?;
let symbol = quote! {#namespace_qualifier #ident}.to_string();
Ok(quote! {
forward_declare::forward_declare!(
pub #ident __SPACE__ = __SPACE__ forward_declare::symbol!(#symbol)
);
}
.into())
}
fn make_rs_field_ident(field: &Field, field_index: usize) -> Ident {
match field.identifier.as_ref() {
None => make_rs_ident(&format!("__unnamed_field{}", field_index)),
Some(Identifier { identifier }) => make_rs_ident(identifier),
}
}
/// Gets the type of `field` for layout purposes.
///
/// Note that `get_field_rs_type_kind_for_layout` may return Err even if
/// `rs_type_kind` returns Ok.
///
/// In particular, this happens if the field has an attribute which is not
/// supported (with the current Crubit features). For example,
/// `[[no_unique_address]]`, or an unrecognized attribute.
///
/// Such unsupported fields should be replaced with a typeless, unaligned block
/// of memory, of a size that can fill up space to the next field.
///
/// See docs/struct_layout
fn get_field_rs_type_kind_for_layout(
db: &Database,
record: &Record,
field: &Field,
) -> Result<RsTypeKind> {
if field.is_no_unique_address {
bail!("`[[no_unique_address]]` attribute was present.");
}
if let Some(unknown_attr) = &field.unknown_attr {
// Both the template definition and its instantiation should enable experimental
// features.
for target in record.defining_target.iter().chain([&record.owning_target]) {
let required_features = db.ir().target_crubit_features(target);
ensure!(
required_features.contains(ir::CrubitFeature::Experimental),
"unknown field attributes are only supported with experimental features \
enabled on {target}\nUnknown attribute: {unknown_attr}`"
);
}
}
let type_kind = match &field.type_ {
Ok(t) => db.rs_type_kind(t.rs_type.clone())?,
Err(e) => bail!("{e}"),
};
// In supported, we replace nontrivial fields with opaque blobs.
// This is because we likely don't want the `ManuallyDrop<T>` solution to be the
// one users get.
//
// Users can still work around this with accessor functions.
if should_implement_drop(record) && !record.is_union() && needs_manually_drop(&type_kind) {
for target in record.defining_target.iter().chain([&record.owning_target]) {
let required_features = db.ir().target_crubit_features(target);
ensure!(
required_features.contains(ir::CrubitFeature::Experimental),
"nontrivial fields would be destroyed in the wrong order"
);
}
}
Ok(type_kind)
}
/// Returns 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.
fn bit_padding(padding_size_in_bits: usize) -> TokenStream {
let padding_size = Literal::usize_unsuffixed((padding_size_in_bits + 7) / 8);
quote! { [::core::mem::MaybeUninit<u8>; #padding_size] }
}
/// Generates Rust source code for a given `Record` and associated assertions as
/// a tuple.
pub fn generate_record(db: &Database, record: &Rc<Record>) -> Result<GeneratedItem> {
let ir = db.ir();
let crate_root_path = crate::crate_root_path_tokens(&ir);
let ident = make_rs_ident(record.rs_name.as_ref());
let namespace_qualifier = ir.namespace_qualifier(record)?.format_for_rs();
let qualified_ident = {
quote! { #crate_root_path:: #namespace_qualifier #ident }
};
let doc_comment = crate::generate_doc_comment(
record.doc_comment.as_deref(),
Some(&record.source_loc),
db.generate_source_loc_doc_comment(),
);
let mut field_copy_trait_assertions: Vec<TokenStream> = vec![];
let fields_with_bounds = (record.fields.iter())
.filter(|field| field.size != 0)
.map(|field| {
(
// We don't represent bitfields directly in Rust. We drop the field itself here
// and only retain the offset information. Adjacent bitfields then get merged in
// the next step.
if field.is_bitfield { None } else { Some(field) },
field.offset,
// We retain the end offset of fields only if we have a matching Rust type
// to represent them. Otherwise we'll fill up all the space to the next field.
// See: docs/struct_layout
match get_field_rs_type_kind_for_layout(db, record, field) {
// Regular field
Ok(_rs_type) => Some(field.offset + field.size),
// Opaque field
Err(_error) => {
if record.is_union() {
Some(field.size)
} else {
None
}
}
},
vec![format!(
"{} : {} bits",
field.identifier.as_ref().map(|i| i.identifier.clone()).unwrap_or("".into()),
field.size
)],
)
})
// Merge consecutive bitfields. This is necessary, because they may share storage in the
// same byte.
.coalesce(|first, second| match (first, second) {
((None, offset, _, desc1), (None, _, end, desc2)) => {
Ok((None, offset, end, [desc1, desc2].concat()))
}
pair => Err(pair),
});
let mut override_alignment = record.override_alignment;
// Pair up fields with the preceeding and following fields (if any):
// - the end offset of the previous field determines if we need to insert
// padding.
// - the start offset of the next field may be need to grow the current field to
// there.
// This uses two separate `map` invocations on purpose to limit available state.
let field_definitions = iter::once(None)
.chain(fields_with_bounds.clone().map(Some))
.chain(iter::once(None))
.tuple_windows()
.map(|(prev, cur, next)| {
let (field, offset, end, desc) = cur.unwrap();
let prev_end = prev.as_ref().and_then(|(_, _, e, _)| *e).unwrap_or(offset);
let next_offset = next.map(|(_, o, _, _)| o);
let end = end.or(next_offset).unwrap_or(record.size_align.size * 8);
if let Some((Some(prev_field), _, Some(prev_end), _)) = prev {
assert!(
record.is_union() || prev_end <= offset,
"Unexpected offset+size for field {:?} in record {}",
prev_field,
record.cc_name.as_ref()
);
}
(field, prev_end, offset, end, desc)
})
.enumerate()
.map(|(field_index, (field, prev_end, offset, end, desc))| {
// opaque blob representations are always unaligned, even though the actual C++
// field might be aligned. To put the current field at the right offset, we
// might need to insert some extra padding.
//
// No padding should be needed if the type of the current field is
// known (i.e. if the current field is correctly aligned based on
// its original type).
//
// We also don't need padding if we're in a union.
let padding_size_in_bits = if record.is_union()
|| (field.is_some()
&& get_field_rs_type_kind_for_layout(db, record, field.unwrap()).is_ok())
{
0
} else {
let padding_start = (prev_end + 7) / 8 * 8; // round up to byte boundary
offset - padding_start
};
let padding = if padding_size_in_bits == 0 {
quote! {}
} else {
let padding_name = make_rs_ident(&format!("__padding{}", field_index));
let padding_type = bit_padding(padding_size_in_bits);
quote! { #padding_name: #padding_type, }
};
// Bitfields get represented by private padding to ensure overall
// struct layout is compatible.
if field.is_none() {
let name = make_rs_ident(&format!("__bitfields{}", field_index));
let bitfield_padding = bit_padding(end - offset);
override_alignment = true;
return Ok(quote! {
__NEWLINE__ #( __COMMENT__ #desc )*
#padding #name: #bitfield_padding
});
}
let field = field.unwrap();
let ident = make_rs_field_ident(field, field_index);
let field_rs_type_kind = get_field_rs_type_kind_for_layout(db, record, field);
let doc_comment = match &field_rs_type_kind {
Ok(_) => crate::generate_doc_comment(
field.doc_comment.as_deref(),
None,
db.generate_source_loc_doc_comment(),
),
Err(msg) => {
override_alignment = true;
let supplemental_text = format!(
"Reason for representing this field as a blob of bytes:\n{:#}",
msg
);
let new_text = match &field.doc_comment {
None => supplemental_text,
Some(old_text) => format!("{}\n\n{}", old_text.as_ref(), supplemental_text),
};
crate::generate_doc_comment(
Some(new_text.as_str()),
None,
db.generate_source_loc_doc_comment(),
)
}
};
let access = if field.access == AccessSpecifier::Public && field_rs_type_kind.is_ok() {
quote! { pub }
} else {
quote! { pub(crate) }
};
let field_type = match field_rs_type_kind {
Err(_) => bit_padding(end - field.offset),
Ok(type_kind) => {
let mut formatted = quote! {#type_kind};
if should_implement_drop(record) || record.is_union() {
if needs_manually_drop(&type_kind) {
// TODO(b/212690698): Avoid (somewhat unergonomic) ManuallyDrop
// if we can ask Rust to preserve field destruction order if the
// destructor is the SpecialMemberFunc::NontrivialMembers
// case.
formatted = quote! { ::core::mem::ManuallyDrop<#formatted> }
} else {
field_copy_trait_assertions.push(quote! {
static_assertions::assert_impl_all!(#formatted: Copy);
});
}
};
formatted
}
};
Ok(quote! { #padding #doc_comment #access #ident: #field_type })
})
.collect::<Result<Vec<_>>>()?;
let field_offset_assertions = fields_with_bounds
.enumerate()
.map(|(field_index, (field, _, _, _))| {
if let Some(field) = field {
let field_ident = make_rs_field_ident(field, field_index);
// The assertion below reinforces that the division by 8 on the next line is
// justified (because the bitfields have been coallesced / filtered out
// earlier).
assert_eq!(field.offset % 8, 0);
let expected_offset = Literal::usize_unsuffixed(field.offset / 8);
let actual_offset_expr = quote! {
::core::mem::offset_of!(#qualified_ident, #field_ident)
};
quote! {
assert!(#actual_offset_expr == #expected_offset);
}
} else {
quote! {}
}
})
.collect_vec();
let mut features = BTreeSet::new();
let derives = generate_derives(record);
let derives = if derives.is_empty() {
quote! {}
} else {
quote! {#[derive( #(#derives),* )]}
};
let record_kind = if record.is_union() {
quote! { union }
} else {
quote! { struct }
};
let recursively_pinned_attribute = if record.is_unpin() {
quote! {}
} else {
// negative_impls are necessary for universal initialization due to Rust's
// coherence rules: PhantomPinned isn't enough to prove to Rust that a
// blanket impl that requires Unpin doesn't apply. See http://<internal link>=h.f6jp8ifzgt3n
features.insert(make_rs_ident("negative_impls"));
if should_implement_drop(record) {
quote! {#[::ctor::recursively_pinned(PinnedDrop)]}
} else {
quote! {#[::ctor::recursively_pinned]}
}
};
let mut repr_attributes = vec![quote! {C}];
if override_alignment && record.size_align.alignment > 1 {
let alignment = Literal::usize_unsuffixed(record.size_align.alignment);
repr_attributes.push(quote! {align(#alignment)});
}
// Adjust the struct to also include base class subobjects, vtables, etc.
let head_padding = if let Some(first_field) = record.fields.first() {
first_field.offset / 8
} else {
record.size_align.size
};
// Prevent direct initialization for non-aggregate structs.
//
// Technically, any implicit-lifetime type is going to be fine to initialize
// using direct initialization of the fields, even if it is not an aggregate,
// because this is "just" setting memory to the appropriate values, and
// implicit-lifetime types can automatically begin their lifetime without
// running a constructor at all.
//
// However, not all types used in interop are implicit-lifetime. For example,
// while any `Unpin` C++ value is, some `!Unpin` structs (e.g. `std::list`)
// will not be. So for consistency, we apply the same rule for both
// implicit-lifetime and non-implicit-lifetime types: the C++ rule, that the
// type must be an *aggregate* type.
//
// TODO(b/232969667): Protect unions from direct initialization, too.
let allow_direct_init = record.is_aggregate || record.is_union();
let head_padding = if head_padding > 0 || !allow_direct_init {
let n = proc_macro2::Literal::usize_unsuffixed(head_padding);
quote! {
__non_field_data: [::core::mem::MaybeUninit<u8>; #n],
}
} else {
quote! {}
};
let fully_qualified_cc_name = crate::cc_tagless_type_name_for_record(record, &ir)?.to_string();
let mut record_generated_items = record
.child_item_ids
.iter()
.map(|id| {
let item = ir.find_decl(*id).with_context(|| {
format!("Failed to look up `record.child_item_ids` for {:?}", record)
})?;
crate::generate_item(db, item)
})
.collect::<Result<Vec<_>>>()?;
// Both the template definition and its instantiation should enable experimental
// features.
let mut crubit_features = ir.target_crubit_features(&record.owning_target);
if let Some(defining_target) = &record.defining_target {
crubit_features |= ir.target_crubit_features(defining_target);
}
if crubit_features.contains(ir::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) {
quote! {
forward_declare::unsafe_define!(forward_declare::symbol!(#fully_qualified_cc_name), #qualified_ident);
}
} else {
quote! {}
};
let mut items = vec![];
let mut thunks_from_record_items = vec![];
let mut thunk_impls_from_record_items = vec![cc_struct_layout_assertion(db, record)?];
let mut assertions_from_record_items = vec![];
for generated in record_generated_items {
items.push(generated.item);
if !generated.thunks.is_empty() {
thunks_from_record_items.push(generated.thunks);
}
if !generated.assertions.is_empty() {
assertions_from_record_items.push(generated.assertions);
}
if !generated.thunk_impls.is_empty() {
thunk_impls_from_record_items.push(generated.thunk_impls);
}
features.extend(generated.features.clone());
}
let record_tokens = quote! {
#doc_comment
#derives
#recursively_pinned_attribute
#[repr(#( #repr_attributes ),*)]
#[__crubit::annotate(cc_type=#fully_qualified_cc_name)]
pub #record_kind #ident {
#head_padding
#( #field_definitions, )*
}
impl !Send for #ident {}
impl !Sync for #ident {}
#incomplete_definition
#no_unique_address_accessors
__NEWLINE__ __NEWLINE__
#( #items __NEWLINE__ __NEWLINE__)*
};
features.insert(make_rs_ident("negative_impls"));
// For #![register_tool(__crubit)] / #![__crubit::...]
features.insert(make_rs_ident("register_tool"));
let record_trait_assertions = {
let record_type_name = RsTypeKind::new_record(record.clone(), &ir)?.to_token_stream();
let mut assertions: Vec<TokenStream> = vec![];
let mut add_assertion = |assert_impl_macro: TokenStream, trait_name: TokenStream| {
assertions.push(quote! {
static_assertions::#assert_impl_macro (#record_type_name: #trait_name);
});
};
if should_derive_clone(record) {
add_assertion(quote! { assert_impl_all! }, quote! { Clone });
} else {
// Can't `assert_not_impl_any!` here, because `Clone` may be
// implemented rather than derived.
}
let mut add_conditional_assertion = |should_impl_trait: bool, trait_name: TokenStream| {
let assert_impl_macro = if should_impl_trait {
quote! { assert_impl_all! }
} else {
quote! { assert_not_impl_any! }
};
add_assertion(assert_impl_macro, trait_name);
};
add_conditional_assertion(should_derive_copy(record), quote! { Copy });
add_conditional_assertion(should_implement_drop(record), quote! { Drop });
assertions
};
let size_align_assertions = rs_size_align_assertions(qualified_ident, &record.size_align);
let assertion_tokens = quote! {
#size_align_assertions
#( #record_trait_assertions )*
#( #field_offset_assertions )*
#( #field_copy_trait_assertions )*
#( #assertions_from_record_items )*
};
let thunk_tokens = quote! {
#( #thunks_from_record_items )*
};
Ok(GeneratedItem {
item: record_tokens,
features,
assertions: assertion_tokens,
thunks: thunk_tokens,
thunk_impls: quote! {#(#thunk_impls_from_record_items __NEWLINE__ __NEWLINE__)*},
..Default::default()
})
}
pub fn rs_size_align_assertions(
type_name: impl ToTokens,
size_align: &ir::SizeAlign,
) -> TokenStream {
let type_name = type_name.into_token_stream();
let size = Literal::usize_unsuffixed(size_align.size);
let alignment = Literal::usize_unsuffixed(size_align.alignment);
quote! {
assert!(::core::mem::size_of::<#type_name>() == #size);
assert!(::core::mem::align_of::<#type_name>() == #alignment);
}
}
fn generate_derives(record: &Record) -> Vec<Ident> {
let mut derives = vec![];
if should_derive_clone(record) {
derives.push(make_rs_ident("Clone"));
}
if should_derive_copy(record) {
derives.push(make_rs_ident("Copy"));
}
derives
}
fn cc_struct_layout_assertion(db: &Database, record: &Record) -> Result<TokenStream> {
let record_ident = crate::format_cc_ident(record.cc_name.as_ref());
let namespace_qualifier = db.ir().namespace_qualifier(record)?.format_for_cc()?;
let tag_kind = crate::cc_tag_kind(record);
let field_assertions = record
.fields
.iter()
.filter(|f| f.access == AccessSpecifier::Public && f.identifier.is_some())
// https://en.cppreference.com/w/cpp/types/offsetof points out that "if member is [...]
// a bit-field [...] the behavior [of `offsetof` macro] is undefined.". In such
// scenario clang reports an error: cannot compute offset of bit-field 'field_name'.
.filter(|f| !f.is_bitfield)
.map(|field| {
// The IR contains the offset in bits, while `CRUBIT_OFFSET_OF` returns the
// offset in bytes, so we need to convert. We can assert that
// `field.offset` is always at field boundaries, because the
// bitfields have been filtered out earlier.
assert_eq!(field.offset % 8, 0);
let expected_offset = Literal::usize_unsuffixed(field.offset / 8);
let field_ident =
crate::format_cc_ident(&field.identifier.as_ref().unwrap().identifier);
let actual_offset = quote! {
CRUBIT_OFFSET_OF(#field_ident, #tag_kind #namespace_qualifier #record_ident)
};
quote! { static_assert( #actual_offset == #expected_offset); }
});
// only use CRUBIT_SIZEOF for alignment > 1, so as to simplify the generated
// code.
let size = Literal::usize_unsuffixed(record.size_align.size);
let alignment = Literal::usize_unsuffixed(record.size_align.alignment);
let sizeof = if record.size_align.alignment == 1 {
quote! {sizeof}
} else {
quote! {CRUBIT_SIZEOF}
};
Ok(quote! {
static_assert(#sizeof(#tag_kind #namespace_qualifier #record_ident) == #size);
static_assert(alignof(#tag_kind #namespace_qualifier #record_ident) == #alignment);
#( #field_assertions )*
})
}
/// Returns the accessor functions for no_unique_address member variables.
fn cc_struct_no_unique_address_impl(db: &Database, record: &Record) -> Result<TokenStream> {
let mut fields = vec![];
let mut types = vec![];
let mut field_offsets = vec![];
let mut doc_comments = vec![];
for field in &record.fields {
if field.access != AccessSpecifier::Public || !field.is_no_unique_address {
continue;
}
// `[[no_unique_address]]` cannot be applied to a bitfield.
// See e.g. https://en.cppreference.com/w/cpp/language/attributes/no_unique_address
// Indeed, this is a compilation error in Clang.
assert_eq!(field.offset % 8, 0, "invalid subobject: [[no_unique_address]] on a bitfield");
// Can't use `get_field_rs_type_kind_for_layout` here, because we want to dig
// into no_unique_address fields, despite laying them out as opaque
// blobs of bytes.
if let Ok(rs_type) = field.type_.as_ref().map(|t| t.rs_type.clone()) {
fields.push(make_rs_ident(
&field
.identifier
.as_ref()
.expect("Unnamed fields can't be annotated with [[no_unique_address]]")
.identifier,
));
let type_ident = db.rs_type_kind(rs_type).with_context(|| {
format!("Failed to format type for field {:?} on record {:?}", field, record)
})?;
types.push(type_ident);
field_offsets.push(Literal::usize_unsuffixed(field.offset / 8));
if field.size == 0 {
// These fields are not generated at all, so they need to be documented here.
doc_comments.push(crate::generate_doc_comment(
field.doc_comment.as_deref(),
None,
db.generate_source_loc_doc_comment(),
));
} else {
// all other fields already have a doc-comment at the point they were defined.
doc_comments.push(quote! {});
}
}
}
if fields.is_empty() {
return Ok(quote! {});
}
let ident = make_rs_ident(record.rs_name.as_ref());
// 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`.
Ok(quote! {
impl #ident {
#(
#doc_comments
pub fn #fields(&self) -> &#types {
unsafe {
let ptr = (self as *const Self as *const u8).offset(#field_offsets);
&*(ptr as *const #types)
}
}
)*
}
})
}
/// Returns the implementation of base class conversions, for converting a type
/// to its unambiguous public base classes.
fn cc_struct_upcast_impl(record: &Rc<Record>, ir: &IR) -> Result<GeneratedItem> {
let mut impls = Vec::with_capacity(record.unambiguous_public_bases.len());
let mut thunks = vec![];
let mut cc_impls = vec![];
for base in &record.unambiguous_public_bases {
let base_record: &Rc<Record> = ir
.find_decl(base.base_record_id)
.with_context(|| format!("Can't find a base record of {:?}", record))?;
let base_name = RsTypeKind::new_record(base_record.clone(), ir)?.into_token_stream();
let derived_name = RsTypeKind::new_record(record.clone(), ir)?.into_token_stream();
let body;
if let Some(offset) = base.offset {
let offset = Literal::i64_unsuffixed(offset);
body = quote! {(derived as *const _ as *const u8).offset(#offset) as *const #base_name};
} else {
let cast_fn_name = make_rs_ident(&format!(
"__crubit_dynamic_upcast__{derived}__to__{base}_{odr_suffix}",
derived = record.mangled_cc_name,
base = base_record.mangled_cc_name,
odr_suffix = record.owning_target.convert_to_cc_identifier(),
));
let base_cc_name = crate::cc_type_name_for_record(base_record.as_ref(), ir)?;
let derived_cc_name = crate::cc_type_name_for_record(record.as_ref(), ir)?;
cc_impls.push(quote! {
extern "C" const #base_cc_name& #cast_fn_name(const #derived_cc_name& from) {
return from;
}
});
thunks.push(quote! {
pub fn #cast_fn_name (from: *const #derived_name) -> *const #base_name;
});
let crate_root_path = crate::crate_root_path_tokens(ir);
body = quote! {
#crate_root_path::detail::#cast_fn_name(derived)
};
}
impls.push(quote! {
unsafe impl oops::Inherits<#base_name> for #derived_name {
unsafe fn upcast_ptr(derived: *const Self) -> *const #base_name {
#body
}
}
});
}
Ok(GeneratedItem {
item: quote! {#(#impls)*},
thunks: quote! {#(#thunks)*},
thunk_impls: quote! {#(#cc_impls)*},
..Default::default()
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
use crate::BindingsTokens;
use ir_testing::with_lifetime_macros;
use token_stream_matchers::{assert_cc_matches, assert_rs_matches, assert_rs_not_matches};
#[test]
fn test_template_in_dependency_and_alias_in_current_target() -> Result<()> {
// See also the test with the same name in `ir_from_cc_test.rs`.
let ir = {
let dependency_src = r#" #pragma clang lifetime_elision
template <typename T>
struct MyTemplate {
~MyTemplate();
T GetValue() { return field; }
T field;
}; "#;
let current_target_src = r#" #pragma clang lifetime_elision
using MyAliasOfTemplate = MyTemplate<int>; "#;
ir_from_cc_dependency(current_target_src, dependency_src)?
};
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C)]
#[__crubit::annotate(cc_type="MyTemplate < int >")]
pub struct __CcTemplateInst10MyTemplateIiE {
pub field: ::core::ffi::c_int,
}
}
);
assert_rs_matches!(
rs_api,
quote! {
impl __CcTemplateInst10MyTemplateIiE {
#[doc = " Generated from: google3/test/dependency_header.h;l=5"]
#[inline(always)]
pub fn GetValue<'a>(self: ... Pin<&'a mut Self>) -> ::core::ffi::c_int { unsafe {
crate::detail::__rust_thunk___ZN10MyTemplateIiE8GetValueEv__2f_2ftest_3atesting_5ftarget(
self)
}}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
pub type MyAliasOfTemplate = crate::__CcTemplateInst10MyTemplateIiE;
}
);
assert_rs_matches!(
rs_api,
quote! {
mod detail { ... extern "C" {
...
pub(crate) fn
__rust_thunk___ZN10MyTemplateIiE8GetValueEv__2f_2ftest_3atesting_5ftarget<'a>(
__this: ... Pin<&'a mut crate::__CcTemplateInst10MyTemplateIiE>
) -> ::core::ffi::c_int;
...
} }
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C"
int __rust_thunk___ZN10MyTemplateIiE8GetValueEv__2f_2ftest_3atesting_5ftarget(
struct MyTemplate<int>* __this) {
return __this->GetValue();
}
}
);
Ok(())
}
#[test]
fn test_template_with_out_of_line_definition() -> Result<()> {
// See also an end-to-end test in the `test/templates/out_of_line_definition`
// directory.
let ir = ir_from_cc(
r#" #pragma clang lifetime_elision
template <typename T>
class MyTemplate final {
public:
static MyTemplate Create(T value);
const T& value() const;
private:
T value_;
};
using MyTypeAlias = MyTemplate<int>; "#,
)?;
let BindingsTokens { rs_api_impl, .. } = generate_bindings_tokens(ir)?;
// Even though the member functions above are *not* defined inline (e.g.
// IR::Func::is_inline is false), they still need to have thunks generated for
// them (to force/guarantee that the class template and its members get
// instantiated). This is also covered in the following end-to-end
// tests:
// - test/templates/out_of_line_definition/ - without a thunk, the template
// won't be instantiated and Rust bindings won't be able to call the member
// function (there will be no instantiation of the member function in the C++
// object files)
// - test/templates/definition_in_cc/ - the instantiation happens in the .cc
// file and therefore the thunk is not *required* (but it doesn't hurt to have
// the thunk)
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void
__rust_thunk___ZN10MyTemplateIiE6CreateEi__2f_2ftest_3atesting_5ftarget(
class MyTemplate<int>* __return, int value) {
new (__return) auto(MyTemplate<int>::Create(value));
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" int const*
__rust_thunk___ZNK10MyTemplateIiE5valueEv__2f_2ftest_3atesting_5ftarget(
const class MyTemplate<int>*__this) {
return &__this->value();
}
}
);
Ok(())
}
#[test]
fn test_simple_struct() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
struct SomeStruct final {
~SomeStruct() {}
int public_int;
protected:
int protected_int;
private:
int private_int;
};
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[::ctor::recursively_pinned(PinnedDrop)]
#[repr(C, align(4))]
#[__crubit::annotate(cc_type="SomeStruct")]
pub struct SomeStruct {
__non_field_data: [::core::mem::MaybeUninit<u8>; 0],
pub public_int: ::core::ffi::c_int,
#[doc = " Reason for representing this field as a blob of bytes:\n Types of non-public C++ fields can be elided away"]
pub(crate) protected_int: [::core::mem::MaybeUninit<u8>; 4],
#[doc = " Reason for representing this field as a blob of bytes:\n Types of non-public C++ fields can be elided away"]
pub(crate) private_int: [::core::mem::MaybeUninit<u8>; 4],
}
}
);
assert_rs_matches!(
rs_api,
quote! {
const _ : () = {
...
assert!(::core::mem::size_of::<crate::SomeStruct>() == 12);
assert!(::core::mem::align_of::<crate::SomeStruct>() == 4);
static_assertions::assert_not_impl_any!(crate::SomeStruct: Copy);
static_assertions::assert_impl_all!(crate::SomeStruct: Drop);
assert!(::core::mem::offset_of!(crate::SomeStruct, public_int) == 0);
assert!(::core::mem::offset_of!(crate::SomeStruct, protected_int) == 4);
assert!(::core::mem::offset_of!(crate::SomeStruct, private_int) == 8);
...
};
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___ZN10SomeStructD1Ev(struct SomeStruct * __this) {
std::destroy_at(__this);
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
static_assert(CRUBIT_SIZEOF(struct SomeStruct) == 12);
static_assert(alignof(struct SomeStruct) == 4);
static_assert(CRUBIT_OFFSET_OF(public_int, struct SomeStruct) == 0);
}
);
Ok(())
}
#[test]
fn test_struct_vs_class() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
struct SomeStruct final {
SomeStruct() {}
int field;
};
class SomeClass final {
public:
SomeClass() {}
int field;
};
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
// A Rust `struct` is generated for both `SomeStruct` and `SomeClass`.
assert_rs_matches!(rs_api, quote! { pub struct SomeStruct },);
assert_rs_matches!(rs_api, quote! { pub struct SomeClass },);
// But in C++ we still should refer to `struct SomeStruct` and `class
// SomeClass`. See also b/238212337.
assert_cc_matches!(rs_api_impl, quote! { struct SomeStruct * __this });
assert_cc_matches!(rs_api_impl, quote! { class SomeClass * __this });
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_SIZEOF(struct SomeStruct) == 4); }
);
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_SIZEOF(class SomeClass) == 4); }
);
Ok(())
}
#[test]
fn test_struct_vs_typedefed_struct() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
struct SomeStruct final {
int x;
} __attribute__((aligned(16)));
typedef struct {
int x;
} SomeAnonStruct __attribute__((aligned(16)));
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
// A `struct` is generated for both `SomeStruct` and `SomeAnonStruct`, both
// in Rust and in C++.
assert_rs_matches!(rs_api, quote! { pub struct SomeStruct },);
assert_rs_matches!(rs_api, quote! { pub struct SomeAnonStruct },);
assert_rs_matches!(rs_api_impl, quote! { struct SomeStruct * __this },);
assert_rs_matches!(rs_api_impl, quote! { SomeAnonStruct * __this },);
// In C++, both have align == 16, but size for `SomeAnonStruct` is not aligned.
// `SomeAnonStruct` won't have `struct` in the assert.
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(alignof(struct SomeStruct) == 16); }
);
assert_cc_matches!(rs_api_impl, quote! { static_assert(alignof(SomeAnonStruct) == 16); });
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_SIZEOF(struct SomeStruct) == 16); }
);
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_SIZEOF(SomeAnonStruct) == 16); }
);
// In Rust, both have align == 16 and size == 16.
assert_rs_matches!(
rs_api,
quote! { assert!(::core::mem::size_of::<crate::SomeStruct>() == 16); }
);
assert_rs_matches!(
rs_api,
quote! { assert!(::core::mem::align_of::<crate::SomeStruct>() == 16); }
);
assert_rs_matches!(
rs_api,
quote! { assert!(::core::mem::size_of::<crate::SomeAnonStruct>() == 16); }
);
assert_rs_matches!(
rs_api,
quote! { assert!(::core::mem::align_of::<crate::SomeAnonStruct>() == 16); }
);
Ok(())
}
#[test]
fn test_record_with_unsupported_field_type() -> Result<()> {
// Using a nested struct because it's currently not supported.
// But... any other unsupported type would also work for this test.
let ir = ir_from_cc(
r#"
struct StructWithUnsupportedField {
struct NestedStruct {
int nested_field;
};
// Doc comment for `my_field`.
NestedStruct my_field;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(4))]
#[__crubit::annotate(cc_type="StructWithUnsupportedField")]
pub struct StructWithUnsupportedField {
#[doc = " Doc comment for `my_field`.\n \n Reason for representing this field as a blob of bytes:\n Unsupported type 'struct StructWithUnsupportedField::NestedStruct': No generated bindings found for 'NestedStruct'"]
pub(crate) my_field: [::core::mem::MaybeUninit<u8>; 4],
}
...
const _: () = {
...
assert!(
::core::mem::offset_of!(crate::StructWithUnsupportedField, my_field) == 0);
...
};
}
);
Ok(())
}
/// This is a regression test for b/283835873 where the alignment of the
/// generated struct was wrong/missing.
#[test]
fn test_struct_with_only_bitfields() -> Result<()> {
let ir = ir_from_cc(
r#"
struct SomeStruct {
char32_t code_point : 31;
enum : char32_t {
ok = 0,
error = 1
} status : 1;
};
static_assert(sizeof(SomeStruct) == 4);
static_assert(alignof(SomeStruct) == 4);
"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(4))]
#[__crubit::annotate(cc_type="SomeStruct")]
pub struct SomeStruct { ... }
}
);
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::size_of::<crate::SomeStruct>() == 4);
assert!(::core::mem::align_of::<crate::SomeStruct>() == 4);
...
};
}
);
Ok(())
}
#[test]
fn test_struct_with_unnamed_bitfield_member() -> Result<()> {
// This test input causes `field_decl->getName()` to return an empty string.
// This example is based on `struct timex` from
// /usr/grte/v5/include/bits/timex.h
let ir = ir_from_cc(
r#"
struct SomeStruct {
int first_field;
int :32;
int last_field;
}; "#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(4))]
#[__crubit::annotate(cc_type="SomeStruct")]
pub struct SomeStruct {
pub first_field: ::core::ffi::c_int, ...
__bitfields1: [::core::mem::MaybeUninit<u8>; 4],
pub last_field: ::core::ffi::c_int,
}
...
const _: () = {
...
assert!(::core::mem::offset_of!(crate::SomeStruct, first_field) == 0);
assert!(::core::mem::offset_of!(crate::SomeStruct, last_field) == 8);
...
};
}
);
Ok(())
}
/// Classes with a non-public destructor shouldn't be constructible, not
/// even via Copy/Clone.
#[test]
fn test_trivial_nonpublic_destructor() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct Indestructible final {
Indestructible() = default;
Indestructible(int);
Indestructible(const Indestructible&) = default;
void Foo() const;
private:
~Indestructible() = default;
};
Indestructible ReturnsValue();
void TakesValue(Indestructible);
void TakesReference(const Indestructible& x);
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
// It isn't available by value:
assert_rs_not_matches!(rs_api, quote! {Default});
assert_rs_not_matches!(rs_api, quote! {From});
assert_rs_not_matches!(rs_api, quote! {derive ( ... Copy ... )});
assert_rs_not_matches!(rs_api, quote! {derive ( ... Clone ... )});
assert_rs_not_matches!(rs_api, quote! {ReturnsValue});
assert_rs_not_matches!(rs_api, quote! {TakesValue});
// ... but it is otherwise available:
assert_rs_matches!(rs_api, quote! {struct Indestructible});
assert_rs_matches!(rs_api, quote! {fn Foo<'a>(&'a self)});
assert_rs_matches!(rs_api, quote! {fn TakesReference<'a>(x: &'a crate::Indestructible)});
Ok(())
}
#[test]
fn test_nontrivial_nonpublic_destructor() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct Indestructible final {
Indestructible() = default;
Indestructible(int);
Indestructible(const Indestructible&) = default;
void Foo() const;
private:
~Indestructible() {}
};
Indestructible ReturnsValue();
void TakesValue(Indestructible);
void TakesReference(const Indestructible& x);
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
// It isn't available by value:
assert_rs_not_matches!(rs_api, quote! {CtorNew});
assert_rs_not_matches!(rs_api, quote! {ReturnsValue});
assert_rs_not_matches!(rs_api, quote! {TakesValue});
// ... but it is otherwise available:
assert_rs_matches!(rs_api, quote! {struct Indestructible});
assert_rs_matches!(rs_api, quote! {fn Foo<'a>(&'a self)});
assert_rs_matches!(rs_api, quote! {fn TakesReference<'a>(x: &'a crate::Indestructible)});
Ok(())
}
/// trivial abstract structs shouldn't be constructible, not even via
/// Copy/Clone.
///
/// Right now, a struct can only be Copy/Clone if it's final, but that
/// restriction will likely be lifted later.
#[test]
fn test_trivial_abstract_by_value() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct Abstract final {
Abstract() = default;
Abstract(int);
Abstract(const Abstract&) = default;
virtual void Foo() const = 0;
void Nonvirtual() const;
};
void TakesAbstract(const Abstract& a);
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
// It isn't available by value:
assert_rs_not_matches!(rs_api, quote! {Default});
assert_rs_not_matches!(rs_api, quote! {From});
assert_rs_not_matches!(rs_api, quote! {derive ( ... Copy ... )});
assert_rs_not_matches!(rs_api, quote! {derive ( ... Clone ... )});
// ... but it is otherwise available:
assert_rs_matches!(rs_api, quote! {struct Abstract});
assert_rs_matches!(rs_api, quote! {fn Foo<'a>(&'a self)});
assert_rs_matches!(rs_api, quote! {fn Nonvirtual<'a>(&'a self)});
assert_rs_matches!(rs_api, quote! {fn TakesAbstract<'a>(a: &'a crate::Abstract)});
Ok(())
}
#[test]
fn test_nontrivial_abstract_by_value() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct Abstract final {
Abstract() {};
Abstract(int);
Abstract(const Abstract&) {}
virtual void Foo() const = 0;
void Nonvirtual() const;
};
void TakesAbstract(const Abstract& a);
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {CtorNew});
// ... but it is otherwise available:
assert_rs_matches!(rs_api, quote! {struct Abstract});
assert_rs_matches!(rs_api, quote! {fn Foo<'a>(&'a self)});
assert_rs_matches!(rs_api, quote! {fn Nonvirtual<'a>(&'a self)});
assert_rs_matches!(rs_api, quote! {fn TakesAbstract<'a>(a: &'a crate::Abstract)});
Ok(())
}
#[test]
fn test_struct_with_unnamed_struct_and_union_members() -> Result<()> {
// This test input causes `field_decl->getName()` to return an empty string.
// See also:
// - https://en.cppreference.com/w/c/language/struct: "[...] an unnamed member
// of a struct whose type is a struct without name is known as anonymous
// struct."
// - https://rust-lang.github.io/rfcs/2102-unnamed-fields.html
let ir = ir_from_cc(
r#"
struct StructWithUnnamedMembers {
int first_field;
struct {
int anonymous_struct_field_1;
int anonymous_struct_field_2;
};
union {
int anonymous_union_field_1;
int anonymous_union_field_2;
};
int last_field;
}; "#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
// TODO(b/200067824): Once nested structs anhd unions are supported,
// `__unnamed_field1` and `__unnamed_field2` should have a real, usable
// type.
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(4))]
#[__crubit::annotate(cc_type="StructWithUnnamedMembers")]
pub struct StructWithUnnamedMembers {
pub first_field: ::core::ffi::c_int,
#[doc =" Reason for representing this field as a blob of bytes:\n Unsupported type 'struct StructWithUnnamedMembers::(anonymous at ./ir_from_cc_virtual_header.h:7:15)': No generated bindings found for ''"]
pub(crate) __unnamed_field1: [::core::mem::MaybeUninit<u8>; 8],
#[doc =" Reason for representing this field as a blob of bytes:\n Unsupported type 'union StructWithUnnamedMembers::(anonymous at ./ir_from_cc_virtual_header.h:11:15)': No generated bindings found for ''"]
pub(crate) __unnamed_field2: [::core::mem::MaybeUninit<u8>; 4],
pub last_field: ::core::ffi::c_int,
}
...
const _: () = {
...
assert!(::core::mem::offset_of!(
crate::StructWithUnnamedMembers, first_field) == 0);
assert!(::core::mem::offset_of!(
crate::StructWithUnnamedMembers, __unnamed_field1) == 4);
assert!(::core::mem::offset_of!(
crate::StructWithUnnamedMembers, __unnamed_field2) == 12);
assert!(::core::mem::offset_of!(
crate::StructWithUnnamedMembers, last_field) == 16);
...
};
}
);
Ok(())
}
#[test]
fn test_copy_derives() {
let record = ir_record("S");
assert_eq!(generate_derives(&record), &["Clone", "Copy"]);
}
#[test]
fn test_copy_derives_not_is_trivial_abi() {
let mut record = ir_record("S");
record.is_trivial_abi = false;
assert_eq!(generate_derives(&record), &[""; 0]);
}
#[test]
fn test_copy_derives_ctor_deleted() {
let mut record = ir_record("S");
record.copy_constructor = ir::SpecialMemberFunc::Unavailable;
assert_eq!(generate_derives(&record), &[""; 0]);
}
#[test]
fn test_copy_derives_ctor_nontrivial_members() {
let mut record = ir_record("S");
record.copy_constructor = ir::SpecialMemberFunc::NontrivialMembers;
assert_eq!(generate_derives(&record), &[""; 0]);
}
#[test]
fn test_copy_derives_ctor_nontrivial_self() {
let mut record = ir_record("S");
record.copy_constructor = ir::SpecialMemberFunc::NontrivialUserDefined;
assert_eq!(generate_derives(&record), &[""; 0]);
}
/// In Rust, a Drop type cannot be Copy.
#[test]
fn test_copy_derives_dtor_nontrivial_self() {
let mut record = ir_record("S");
for definition in
[ir::SpecialMemberFunc::NontrivialUserDefined, ir::SpecialMemberFunc::NontrivialMembers]
{
record.destructor = definition;
assert_eq!(generate_derives(&record), &["Clone"]);
}
}
#[test]
fn test_base_class_subobject_layout() -> Result<()> {
let ir = ir_from_cc(
r#"
// We use a class here to force `Derived::z` to live inside the tail padding of `Base`.
// On the Itanium ABI, this would not happen if `Base` were a POD type.
class Base {__INT64_TYPE__ x; char y;};
struct Derived final : Base {__INT16_TYPE__ z;};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="Derived")]
pub struct Derived {
__non_field_data: [::core::mem::MaybeUninit<u8>; 10],
pub z: ::core::ffi::c_short,
}
}
);
Ok(())
}
/// The same as test_base_class_subobject_layout, but with multiple
/// inheritance.
#[test]
fn test_base_class_multiple_inheritance_subobject_layout() -> Result<()> {
let ir = ir_from_cc(
r#"
class Base1 {__INT64_TYPE__ x;};
class Base2 {char y;};
struct Derived final : Base1, Base2 {__INT16_TYPE__ z;};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="Derived")]
pub struct Derived {
__non_field_data: [::core::mem::MaybeUninit<u8>; 10],
pub z: ::core::ffi::c_short,
}
}
);
Ok(())
}
/// The same as test_base_class_subobject_layout, but with a chain of
/// inheritance.
#[test]
fn test_base_class_deep_inheritance_subobject_layout() -> Result<()> {
let ir = ir_from_cc(
r#"
class Base1 {__INT64_TYPE__ x;};
class Base2 : Base1 {char y;};
struct Derived final : Base2 {__INT16_TYPE__ z;};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="Derived")]
pub struct Derived {
__non_field_data: [::core::mem::MaybeUninit<u8>; 10],
pub z: ::core::ffi::c_short,
}
}
);
Ok(())
}
/// For derived classes with no data members, we can't use the offset of the
/// first member to determine the size of the base class subobjects.
#[test]
fn test_base_class_subobject_fieldless_layout() -> Result<()> {
let ir = ir_from_cc(
r#"
class Base {__INT64_TYPE__ x; char y;};
struct Derived final : Base {};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="Derived")]
pub struct Derived {
__non_field_data: [::core::mem::MaybeUninit<u8>; 16],
}
}
);
Ok(())
}
#[test]
fn test_base_class_subobject_empty_fieldless() -> Result<()> {
let ir = ir_from_cc(
r#"
class Base {};
struct Derived final : Base {};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C)]
#[__crubit::annotate(cc_type="Derived")]
pub struct Derived {
...
__non_field_data: [::core::mem::MaybeUninit<u8>; 1],
}
}
);
Ok(())
}
#[test]
fn test_base_class_subobject_empty() -> Result<()> {
let ir = ir_from_cc(
r#"
class Base {};
struct Derived final : Base {
__INT16_TYPE__ x;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[__crubit::annotate(cc_type="Derived")]
pub struct Derived {
pub x: ::core::ffi::c_short,
}
}
);
Ok(())
}
/// Non-aggregate structs can't be directly initialized, because we add
/// a zero-sized private field to the bindings.
#[test]
fn test_non_aggregate_struct_private_field() -> Result<()> {
let ir = ir_from_cc(
r#"
struct NonAggregate {
NonAggregate() {}
__INT16_TYPE__ x = 0;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
pub struct NonAggregate {
__non_field_data: [::core::mem::MaybeUninit<u8>; 0],
pub x: ::core::ffi::c_short,
}
}
);
Ok(())
}
/// When a field is [[no_unique_address]], it occupies the space up to the
/// next field.
#[test]
fn test_no_unique_address() -> Result<()> {
let ir = ir_from_cc(
r#"
class Field1 {__INT64_TYPE__ x;};
class Field2 {char y;};
struct Struct final {
[[no_unique_address]] Field1 field1;
[[no_unique_address]] Field2 field2;
__INT16_TYPE__ z;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="Struct")]
pub struct Struct {
...
pub(crate) field1: [::core::mem::MaybeUninit<u8>; 8],
...
pub(crate) field2: [::core::mem::MaybeUninit<u8>; 2],
pub z: ::core::ffi::c_short,
}
}
);
assert_rs_matches!(
rs_api,
quote! {
impl Struct {
pub fn field1(&self) -> &crate::Field1 {
unsafe {
let ptr = (self as *const Self as *const u8).offset(0);
&*(ptr as *const crate::Field1)
}
}
pub fn field2(&self) -> &crate::Field2 {
unsafe {
let ptr = (self as *const Self as *const u8).offset(8);
&*(ptr as *const crate::Field2)
}
}
}
}
);
Ok(())
}
/// When a [[no_unique_address]] field is the last one, it occupies the rest
/// of the object.
#[test]
fn test_no_unique_address_last_field() -> Result<()> {
let ir = ir_from_cc(
r#"
class Field1 {__INT64_TYPE__ x;};
class Field2 {char y;};
struct Struct final {
[[no_unique_address]] Field1 field1;
[[no_unique_address]] Field2 field2;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="Struct")]
pub struct Struct {
...
pub(crate) field1: [::core::mem::MaybeUninit<u8>; 8],
...
pub(crate) field2: [::core::mem::MaybeUninit<u8>; 8],
}
}
);
Ok(())
}
#[test]
fn test_no_unique_address_empty() -> Result<()> {
let ir = ir_from_cc(
r#"
class Field {};
struct Struct final {
// Doc comment for no_unique_address empty class type field.
[[no_unique_address]] Field field;
int x;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C)]
#[__crubit::annotate(cc_type="Struct")]
pub struct Struct {
pub x: ::core::ffi::c_int,
}
...
impl Struct {
# [doc = " Doc comment for no_unique_address empty class type field."]
pub fn field(&self) -> &crate::Field {
unsafe {
let ptr = (self as *const Self as *const u8).offset(0);
&*(ptr as *const crate::Field)
}
}
}
...
}
);
Ok(())
}
#[test]
fn test_base_class_subobject_empty_last_field() -> Result<()> {
let ir = ir_from_cc(
r#"
class Field {};
struct Struct final {
// Doc comment for no_unique_address empty class type field.
[[no_unique_address]] Field field;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C)]
#[__crubit::annotate(cc_type="Struct")]
pub struct Struct {}
...
impl Struct {
# [doc = " Doc comment for no_unique_address empty class type field."]
pub fn field(&self) -> &crate::Field {
unsafe {
let ptr = (self as *const Self as *const u8).offset(0);
&*(ptr as *const crate::Field)
}
}
}
...
}
);
Ok(())
}
#[test]
fn test_doc_comment_record() -> Result<()> {
let ir = ir_from_cc(
"// Doc Comment\n\
//\n\
// * with bullet\n\
struct SomeStruct final {\n\
// Field doc\n\
int field;\
};",
)?;
assert_rs_matches!(
generate_bindings_tokens(ir)?.rs_api,
quote! {
#[doc = " Doc Comment\n \n * with bullet\n \n Generated from: google3/ir_from_cc_virtual_header.h;l=6"]
#[derive(Clone, Copy)]
#[repr(C)]
#[__crubit::annotate(cc_type="SomeStruct")]
pub struct SomeStruct {
# [doc = " Field doc"]
pub field: ::core::ffi::c_int,
}
}
);
Ok(())
}
#[test]
fn test_basic_union() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
union SomeUnion {
int some_field;
long long some_bigger_field;
};
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[derive(Clone, Copy)]
#[repr(C)]
#[__crubit::annotate(cc_type="SomeUnion")]
pub union SomeUnion {
pub some_field: ::core::ffi::c_int,
pub some_bigger_field: ::core::ffi::c_longlong,
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___ZN9SomeUnionC1Ev(union SomeUnion*__this) {...}
}
);
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_SIZEOF(union SomeUnion)==8) }
);
assert_cc_matches!(rs_api_impl, quote! { static_assert(alignof(union SomeUnion)==8) });
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_OFFSET_OF(some_field, union SomeUnion)==0) }
);
assert_cc_matches!(
rs_api_impl,
quote! { static_assert(CRUBIT_OFFSET_OF(some_bigger_field, union SomeUnion)==0) }
);
Ok(())
}
#[test]
fn test_union_with_opaque_field() -> Result<()> {
let ir = ir_from_cc(
r#"
union MyUnion {
char first_field[56];
int second_field;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C, align(4))]
#[__crubit::annotate(cc_type="MyUnion")]
pub union MyUnion { ...
first_field: [::core::mem::MaybeUninit<u8>; 56],
pub second_field: ::core::ffi::c_int,
}
}
);
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::size_of::<crate::MyUnion>() == 56);
assert!(::core::mem::align_of::<crate::MyUnion>() == 4);
...
};
}
);
Ok(())
}
#[test]
fn test_currently_no_offset_assertions_for_unions() -> Result<()> {
let ir = ir_from_cc(
r#"
union SomeUnion {
int some_field;
long long some_bigger_field;
};
"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::offset_of!(
crate::SomeUnion, some_field) == 0);
assert!(::core::mem::offset_of!(
crate::SomeUnion, some_bigger_field) == 0);
...
};
}
);
Ok(())
}
#[test]
fn test_union_with_private_fields() -> Result<()> {
let ir = ir_from_cc(
r#"
union SomeUnionWithPrivateFields {
public:
int public_field;
private:
long long private_field;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[derive(Clone, Copy)]
#[repr(C, align(8))]
#[__crubit::annotate(cc_type="SomeUnionWithPrivateFields")]
pub union SomeUnionWithPrivateFields {
pub public_field: ::core::ffi::c_int,
#[doc = " Reason for representing this field as a blob of bytes:\n Types of non-public C++ fields can be elided away"]
pub(crate) private_field: [::core::mem::MaybeUninit<u8>; 8],
}
}
);
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::size_of::<crate::SomeUnionWithPrivateFields>() == 8);
assert!(::core::mem::align_of::<crate::SomeUnionWithPrivateFields>() == 8);
static_assertions::assert_impl_all!(crate::SomeUnionWithPrivateFields: Clone);
static_assertions::assert_impl_all!(crate::SomeUnionWithPrivateFields: Copy);
static_assertions::assert_not_impl_any!(crate::SomeUnionWithPrivateFields: Drop);
...
};
}
);
Ok(())
}
#[test]
fn test_nontrivial_unions() -> Result<()> {
let ir = ir_from_cc_dependency(
r#"
union UnionWithNontrivialField {
NonTrivialStruct my_field;
};
"#,
r#"
struct NonTrivialStruct {
NonTrivialStruct(NonTrivialStruct&&);
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {derive ( ... Copy ... )});
assert_rs_not_matches!(rs_api, quote! {derive ( ... Clone ... )});
assert_rs_matches!(
rs_api,
quote! {
#[::ctor::recursively_pinned]
#[repr(C)]
#[__crubit::annotate(cc_type="UnionWithNontrivialField")]
pub union UnionWithNontrivialField { ... }
}
);
Ok(())
}
#[test]
fn test_empty_struct() -> Result<()> {
let ir = ir_from_cc(
r#"
struct EmptyStruct final {};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[derive(Clone, Copy)]
#[repr(C)]
#[__crubit::annotate(cc_type="EmptyStruct")]
pub struct EmptyStruct {
...
__non_field_data: [::core::mem::MaybeUninit<u8>; 1],
}
}
);
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::size_of::<crate::EmptyStruct>() == 1);
assert!(::core::mem::align_of::<crate::EmptyStruct>() == 1);
...
};
}
);
Ok(())
}
#[test]
fn test_empty_union() -> Result<()> {
let ir = ir_from_cc(
r#"
union EmptyUnion {};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[derive(Clone, Copy)]
#[repr(C)]
#[__crubit::annotate(cc_type="EmptyUnion")]
pub union EmptyUnion {
...
__non_field_data: [::core::mem::MaybeUninit<u8>; 1],
}
}
);
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::size_of::<crate::EmptyUnion>() == 1);
assert!(::core::mem::align_of::<crate::EmptyUnion>() == 1);
...
};
}
);
Ok(())
}
#[test]
fn test_union_field_with_nontrivial_destructor() -> Result<()> {
let ir = ir_from_cc(
r#"
struct NontrivialStruct { ~NontrivialStruct(); };
union UnionWithNontrivialField {
int trivial_field;
NontrivialStruct nontrivial_field;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[repr(C)]
#[__crubit::annotate(cc_type="UnionWithNontrivialField")]
pub union UnionWithNontrivialField {
pub trivial_field: ::core::ffi::c_int,
pub nontrivial_field: ::core::mem::ManuallyDrop<crate::NontrivialStruct>,
}
}
);
assert_rs_matches!(
rs_api,
quote! {
const _: () = {
...
assert!(::core::mem::size_of::<crate::UnionWithNontrivialField>() == 4);
assert!(::core::mem::align_of::<crate::UnionWithNontrivialField>() == 4);
...
};
}
);
Ok(())
}
#[test]
fn test_union_with_constructors() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
union UnionWithDefaultConstructors {
int a;
};
"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
#[derive(Clone, Copy)]
#[repr(C)]
#[__crubit::annotate(cc_type="UnionWithDefaultConstructors")]
pub union UnionWithDefaultConstructors {
pub a: ::core::ffi::c_int,
}
}
);
assert_rs_matches!(
rs_api,
quote! {
impl Default for UnionWithDefaultConstructors {
#[inline(always)]
fn default() -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN28UnionWithDefaultConstructorsC1Ev(&mut tmp);
tmp.assume_init()
}
}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
impl<'b> From<::ctor::RvalueReference<'b, Self>> for UnionWithDefaultConstructors {
#[inline(always)]
fn from(__param_0: ::ctor::RvalueReference<'b, Self>) -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN28UnionWithDefaultConstructorsC1EOS_(&mut tmp, __param_0);
tmp.assume_init()
}
}
}
}
);
Ok(())
}
#[test]
fn test_unambiguous_public_bases() -> Result<()> {
let ir = ir_from_cc_dependency(
"
struct VirtualBase {};
struct PrivateBase {};
struct ProtectedBase {};
struct UnambiguousPublicBase {};
struct AmbiguousPublicBase {};
struct MultipleInheritance : UnambiguousPublicBase, AmbiguousPublicBase {};
struct Derived : private PrivateBase, protected ProtectedBase, MultipleInheritance, AmbiguousPublicBase, virtual VirtualBase {};
",
"",
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
unsafe impl oops::Inherits<crate::VirtualBase> for crate::Derived {
unsafe fn upcast_ptr(derived: *const Self) -> *const crate::VirtualBase {
crate::detail::__crubit_dynamic_upcast__7Derived__to__11VirtualBase___2f_2ftest_3atesting_5ftarget(derived)
}
}
}
);
assert_rs_matches!(
rs_api,
quote! { unsafe impl oops::Inherits<crate::UnambiguousPublicBase> for crate::Derived }
);
assert_rs_matches!(
rs_api,
quote! { unsafe impl oops::Inherits<crate::MultipleInheritance> for crate::Derived }
);
assert_rs_not_matches!(
rs_api,
quote! {unsafe impl oops::Inherits<crate::PrivateBase> for crate::Derived}
);
assert_rs_not_matches!(
rs_api,
quote! {unsafe impl oops::Inherits<crate::ProtectedBase> for crate::Derived}
);
assert_rs_not_matches!(
rs_api,
quote! {unsafe impl oops::Inherits<crate::AmbiguousPublicBase> for crate::Derived}
);
Ok(())
}
/// Contrary to intuitions: a base class conversion is ambiguous even if the
/// ambiguity is from a private base class cast that you can't even
/// perform.
///
/// Explanation (courtesy James Dennett):
///
/// > Once upon a time, there was a rule in C++ that changing all access
/// > specifiers to "public" would not change the meaning of code.
/// > That's no longer true, but some of its effects can still be seen.
///
/// So, we need to be sure to not allow casting to privately-ambiguous
/// bases.
#[test]
fn test_unambiguous_public_bases_private_ambiguity() -> Result<()> {
let ir = ir_from_cc_dependency(
"
struct Base {};
struct Intermediate : public Base {};
struct Derived : Base, private Intermediate {};
",
"",
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_not_matches!(
rs_api,
quote! { unsafe impl oops::Inherits<crate::Base> for Derived }
);
Ok(())
}
#[test]
fn test_virtual_thunk() -> Result<()> {
let ir = ir_from_cc("struct Polymorphic { virtual void Foo(); };")?;
assert_cc_matches!(
generate_bindings_tokens(ir)?.rs_api_impl,
quote! {
extern "C" void __rust_thunk___ZN11Polymorphic3FooEv(struct Polymorphic * __this)
}
);
Ok(())
}
/// A trivially relocatable final struct is safe to use in Rust as normal,
/// and is Unpin.
#[test]
fn test_no_negative_impl_unpin() -> Result<()> {
let ir = ir_from_cc("struct Trivial final {};")?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {#[::ctor::recursively_pinned]});
Ok(())
}
#[test]
fn test_no_aligned_attr() {
let ir = ir_from_cc("struct SomeStruct {};").unwrap();
let rs_api = generate_bindings_tokens(ir).unwrap().rs_api;
assert_rs_matches! {rs_api, quote! {
#[repr(C)]
#[__crubit::annotate(cc_type="SomeStruct")]
pub struct SomeStruct { ... }
}};
}
#[test]
fn test_aligned_attr() {
let ir = ir_from_cc("struct SomeStruct {} __attribute__((aligned(64)));").unwrap();
let rs_api = generate_bindings_tokens(ir).unwrap().rs_api;
assert_rs_matches! {rs_api, quote! {
#[repr(C, align(64))]
#[__crubit::annotate(cc_type="SomeStruct")]
pub struct SomeStruct { ... }
}
};
}
#[test]
fn test_forward_declared() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct ForwardDeclared;"#,
)?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
forward_declare::forward_declare!(pub ForwardDeclared = forward_declare::symbol!("ForwardDeclared"));
}
);
assert_rs_not_matches!(rs_api, quote! {struct ForwardDeclared});
Ok(())
}
#[test]
fn test_private_struct_not_present() -> Result<()> {
let ir = ir_from_cc(&with_lifetime_macros(
r#"#pragma clang lifetime_elision
template <typename T> class MyTemplate {};
class HasPrivateType {
private:
struct PrivateType {
using Foo = MyTemplate<PrivateType>;
Foo* get();
};
protected:
HasPrivateType(MyTemplate<PrivateType> x) {}
};"#,
))?;
let rs_api = generate_bindings_tokens(ir)?.rs_api;
assert_rs_not_matches!(
rs_api,
quote! { __CcTemplateInst10MyTemplateIN14HasPrivateType11PrivateTypeEE }
);
Ok(())
}
#[test]
fn test_implicit_template_specializations_are_sorted_by_mangled_name() -> Result<()> {
let bindings = generate_bindings_tokens(ir_from_cc(
r#"
template <typename T>
struct MyStruct {
T getT();
};
using Alias1 = MyStruct<int>;
using Alias2 = MyStruct<double>;
namespace test_namespace_bindings {
using Alias3 = MyStruct<bool>;
}
"#,
)?)?;
// Mangled name order: bool < double < int
let my_struct_bool = make_rs_ident("__CcTemplateInst8MyStructIbE");
let my_struct_double = make_rs_ident("__CcTemplateInst8MyStructIdE");
let my_struct_int = make_rs_ident("__CcTemplateInst8MyStructIiE");
assert_rs_matches!(
&bindings.rs_api,
quote! {
...
pub struct #my_struct_bool {...}
...
pub struct #my_struct_double {...}
...
pub struct #my_struct_int {...}
...
const _: () = {
...
assert!(::core::mem::size_of::<crate::#my_struct_bool>() == 1);
...
assert!(::core::mem::size_of::<crate::#my_struct_double>() == 1);
...
assert!(::core::mem::size_of::<crate::#my_struct_int>() == 1);
...
}
...
}
);
// User defined methods in mangled name order
let my_struct_bool_method =
make_rs_ident("__rust_thunk___ZN8MyStructIbE4getTEv__2f_2ftest_3atesting_5ftarget");
let my_struct_double_method =
make_rs_ident("__rust_thunk___ZN8MyStructIdE4getTEv__2f_2ftest_3atesting_5ftarget");
let my_struct_int_method =
make_rs_ident("__rust_thunk___ZN8MyStructIiE4getTEv__2f_2ftest_3atesting_5ftarget");
assert_cc_matches!(
&bindings.rs_api_impl,
quote! {
...
extern "C" bool #my_struct_bool_method(struct MyStruct<bool>*__this) {...} ...
extern "C" double #my_struct_double_method(struct MyStruct<double>*__this) {...} ...
extern "C" int #my_struct_int_method(struct MyStruct<int>*__this) {...} ...
}
);
Ok(())
}
#[test]
fn test_implicit_template_specialization_namespace_qualifier() -> Result<()> {
let rs_api = generate_bindings_tokens(ir_from_cc(
r#" #pragma clang lifetime_elision
namespace test_namespace_bindings {
template <typename T>
struct MyTemplate final {
T value_;
};
using MyTypeAlias = MyTemplate<int>;
}"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
...
pub mod test_namespace_bindings {
...
pub type MyTypeAlias = crate::__CcTemplateInstN23test_namespace_bindings10MyTemplateIiEE;
...
}
...
pub struct __CcTemplateInstN23test_namespace_bindings10MyTemplateIiEE {
pub value_: ::core::ffi::c_int,
}
...
}
);
Ok(())
}
#[test]
fn test_forward_declared_class_template_specialization_symbol() -> Result<()> {
let rs_api = generate_bindings_tokens(ir_from_cc(
r#"
namespace test_namespace_bindings {
template <typename T>
struct MyTemplate {
void processT(T t);
};
struct Param {};
template<> struct MyTemplate<Param>;
using MyTypeAlias = MyTemplate<Param>;
}"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
...
forward_declare::forward_declare!(pub __CcTemplateInstN23test_namespace_bindings10MyTemplateINS_5ParamEEE = forward_declare::symbol!("__CcTemplateInstN23test_namespace_bindings10MyTemplateINS_5ParamEEE"));
...
}
);
Ok(())
}
/// Unsupported fields on supported structs are replaced with opaque blobs.
///
/// This is hard to test any other way than token comparison!
#[test]
fn test_supported_suppressed_field_types() -> Result<()> {
let mut ir = ir_from_cc(
r#"
struct Nontrivial {
~Nontrivial();
};
struct Trivial {
Nontrivial* hidden_field;
};
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
ir::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
struct Trivial {
...
pub(crate) hidden_field: [::core::mem::MaybeUninit<u8>; 8],
}}
);
Ok(())
}
/// Nontrivial fields are replaced with opaque blobs, even if they're
/// supported!
#[test]
fn test_supported_nontrivial_field() -> Result<()> {
let mut ir = ir_from_cc(
r#"
struct [[clang::trivial_abi]] Inner {~Inner();};
struct [[clang::trivial_abi]] Outer {Inner inner_field;};
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
ir::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.
assert_rs_matches!(rs_api, quote! {pub struct Inner});
// But it _is_ being replaced by a blob!
assert_rs_matches!(
rs_api,
quote! {
pub struct Outer {
...
pub(crate) inner_field: [::core::mem::MaybeUninit<u8>; 1],
}}
);
Ok(())
}
#[test]
fn test_supported_no_unique_address_field() -> Result<()> {
let mut ir = ir_from_cc(
r#"
struct Struct final {
[[no_unique_address]] char field;
};
"#,
)?;
*ir.target_crubit_features_mut(&ir.current_target().clone()) =
ir::CrubitFeature::Supported.into();
let BindingsTokens { rs_api, .. } = generate_bindings_tokens(ir)?;
assert_rs_matches!(
rs_api,
quote! {
pub struct Struct {
...
pub(crate) field: [::core::mem::MaybeUninit<u8>; 1],
}
}
);
assert_rs_not_matches!(rs_api, quote! {pub fn field});
Ok(())
}
}