blob: 3d52e6d94fedd11a8bf5b8c98dde7a21f3272b8a [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
#![allow(clippy::collapsible_else_if)]
//! Vocabulary types and code generation functions for generating Rust code.
use crate::code_snippet::{Feature, Visibility};
use crate::BindingsGenerator;
use arc_anyhow::Result;
use code_gen_utils::make_rs_ident;
use code_gen_utils::NamespaceQualifier;
use crubit_abi_type::FullyQualifiedPath;
use crubit_feature::CrubitFeature;
use error_report::{anyhow, bail, ensure};
use flagset::FlagSet;
use ir::*;
use itertools::Itertools;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use std::collections::HashSet;
use std::rc::Rc;
use token_stream_printer::write_unformatted_tokens;
const SLICE_REF_NAME_RS: &str = "&[]";
/// A struct with information associated with the formatted Rust code snippet.
#[derive(Clone, Debug)]
pub struct RsSnippet {
pub tokens: TokenStream,
// The Rust features that are needed for `tokens` to work.
pub features: FlagSet<Feature>,
}
impl RsSnippet {
/// Convenience function to initialize RsSnippet with empty `features`.
pub fn new(tokens: TokenStream) -> RsSnippet {
RsSnippet { tokens, features: FlagSet::empty() }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Mutability {
Const,
Mut,
}
impl Mutability {
pub fn is_const(self) -> bool {
match self {
Mutability::Const => true,
Mutability::Mut => false,
}
}
pub fn format_for_pointer(self) -> TokenStream {
match self {
Mutability::Mut => quote! {mut},
Mutability::Const => quote! {const},
}
}
pub fn format_for_reference(self) -> TokenStream {
match self {
Mutability::Mut => quote! {mut},
Mutability::Const => quote! {},
}
}
}
/// Either a named lifetime, or the magic `'_` elided lifetime.
///
/// Warning: elided lifetimes are not always valid, and sometimes named
/// lifetimes are required. In particular, this should never be used for
/// output lifetimes.
///
/// However, because output lifetimes are never elided, a lifetime that only
/// occurs in a single input position can always be elided.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Lifetime(pub Rc<str>);
impl From<&ir::LifetimeName> for Lifetime {
fn from(lifetime_name: &ir::LifetimeName) -> Self {
Lifetime(lifetime_name.name.clone())
}
}
impl Lifetime {
pub fn new(name: &str) -> Self {
Lifetime(Rc::from(name))
}
pub fn elided() -> Self {
Lifetime(Rc::from("_"))
}
pub fn is_elided(&self) -> bool {
&*self.0 == "_"
}
/// Formats a lifetime for use as a reference lifetime parameter.
///
/// In this case, elided lifetimes are empty.
pub fn format_for_reference(&self) -> TokenStream {
match &*self.0 {
"_" => quote! {},
_ => quote! {#self},
}
}
}
/// Formats a lifetime for use anywhere.
///
/// For the specific context of references, prefer `format_for_reference`, as it
/// gives a more idiomatic formatting for elided lifetimes.
impl ToTokens for Lifetime {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self(name) = self;
let lifetime = syn::Lifetime::new(&format!("'{name}"), proc_macro2::Span::call_site());
lifetime.to_tokens(tokens);
}
}
/// Qualified path from the root of the crate to the module containing the type.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CratePath {
/// `Some("other_crate")` or `None` for paths within the current crate.
crate_ident: Option<Ident>,
crate_root_path: NamespaceQualifier,
namespace_qualifier: NamespaceQualifier,
}
impl CratePath {
pub fn new(
ir: &IR,
namespace_qualifier: NamespaceQualifier,
crate_ident: Option<Ident>,
) -> CratePath {
let crate_root_path = NamespaceQualifier::new(ir.crate_root_path());
CratePath { crate_ident, crate_root_path, namespace_qualifier }
}
pub fn to_fully_qualified_path(&self, item: Ident) -> FullyQualifiedPath {
let crate_ident = self
.crate_ident
.as_ref()
.cloned()
.unwrap_or_else(|| Ident::new("crate", proc_macro2::Span::call_site()));
FullyQualifiedPath {
start_with_colon2: self.crate_ident.is_some(),
parts: std::iter::once(crate_ident)
.chain(self.crate_root_path.parts_with_snake_case_record_names())
.chain(self.namespace_qualifier.parts_with_snake_case_record_names())
.chain(std::iter::once(item))
.collect(),
}
}
}
impl ToTokens for CratePath {
fn to_tokens(&self, tokens: &mut TokenStream) {
let crate_ident = match self.crate_ident.as_ref() {
None => quote! { crate },
Some(ident) => quote! { #ident },
};
let crate_root_path = self.crate_root_path.format_for_rs();
let namespace_qualifier = self.namespace_qualifier.format_for_rs();
quote! { #crate_ident :: #crate_root_path #namespace_qualifier }.to_tokens(tokens)
}
}
pub fn unique_lifetimes<'a>(
types: impl IntoIterator<Item = &'a RsTypeKind> + 'a,
) -> impl Iterator<Item = Lifetime> + 'a {
let mut unordered_lifetimes = HashSet::new();
types
.into_iter()
.flat_map(|ty| ty.lifetimes())
.filter(|lifetime| !lifetime.is_elided())
.filter(move |lifetime| unordered_lifetimes.insert(lifetime.clone()))
}
pub fn format_generic_params<'a, T: ToTokens>(
lifetimes: impl IntoIterator<Item = &'a Lifetime>,
types: impl IntoIterator<Item = T>,
) -> TokenStream {
let mut lifetimes = lifetimes.into_iter().filter(|lifetime| &*lifetime.0 != "_").peekable();
let mut types = types.into_iter().peekable();
if types.peek().is_none() {
if lifetimes.peek().is_none() {
quote! {}
} else {
quote! { < #( #lifetimes ),* > }
}
} else {
// Note: the comma is inside the lifetimes glob because a trailing comma is
// needed if there are types that follow.
quote! { < #( #lifetimes, )* #( #types ),*> }
}
}
pub fn format_generic_params_replacing_by_self<'a>(
db: &dyn BindingsGenerator,
types: impl IntoIterator<Item = &'a RsTypeKind>,
trait_record: Option<&Record>,
) -> TokenStream {
format_generic_params(
[],
types.into_iter().map(|ty| ty.to_token_stream_replacing_by_self(db, trait_record)),
)
}
// TODO(jeanpierreda): These functions are at a weird level of abstraction (using
// ir::Record). It's possible that, instead, we should just ask "does the
// RsTypeKind implement clone" (etc.).
//
// Otherwise, these functions should be moved into a separate module.
pub fn should_derive_clone(record: &Record) -> bool {
match record.trait_derives.clone {
TraitImplPolarity::Positive => true,
TraitImplPolarity::Negative => false,
TraitImplPolarity::None => {
if record.is_union() {
// `union`s (unlike `struct`s) should only derive `Clone` if they are `Copy`.
should_derive_copy(record)
} else {
record.is_unpin()
&& record.copy_constructor == SpecialMemberFunc::Trivial
&& check_by_value(record).is_ok()
}
}
}
}
pub fn should_derive_copy(record: &Record) -> bool {
match record.trait_derives.copy {
TraitImplPolarity::Positive => true,
TraitImplPolarity::Negative => false,
TraitImplPolarity::None => {
record.is_unpin()
&& record.copy_constructor == SpecialMemberFunc::Trivial
&& record.destructor == SpecialMemberFunc::Trivial
&& check_by_value(record).is_ok()
&& record.trait_derives.clone != TraitImplPolarity::Negative
}
}
}
pub fn check_by_value(record: &Record) -> Result<()> {
ensure!(
record.destructor != SpecialMemberFunc::Unavailable,
"Can't directly construct values of type `{}` as it has a non-public or deleted destructor",
record.cc_name
);
ensure!(
!record.is_abstract,
"Can't directly construct values of type `{}`: it is abstract",
record.cc_name
);
Ok(())
}
/// Location where a type is used.
// TODO: Merge with `TypeLocation` in the other direction.
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub enum TypeLocation {
FnReturn,
FnParam,
Other,
}
/// Options for how lifetimes can be elided in function parameters.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct LifetimeOptions {
/// If true, references lifetimes will be inferred. This option is only set for select function
/// parameters and return types.
pub infer_lifetimes: bool,
/// Are there any reference parameters to the function whose return type we are interested in?
pub have_reference_param: bool,
pub is_return_type: bool,
}
/// A type with template type arguments that has a uniform representation regardless of `T` and
/// should be mapped to a handwritten Rust type.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum UniformReprTemplateType {
/// std::vector<T, std::allocator<T>>
StdVector {
element_type: RsTypeKind,
},
/// std::unique_ptr<T, std::default_delete<T>>
StdUniquePtr {
element_type: RsTypeKind,
},
AbslSpan {
is_const: bool,
include_lifetime: bool,
element_type: RsTypeKind,
},
}
pub fn template_arg_to_record(
db: &dyn BindingsGenerator,
template_arg: &TemplateArg,
) -> Result<Option<Rc<Record>>> {
let arg_type = match &template_arg.type_ {
Ok(arg_type) => arg_type.clone(),
Err(e) => bail!("{e}"),
};
let CcTypeVariant::Decl(id) = arg_type.variant else {
return Ok(None);
};
let Item::Record(record) = db.ir().find_untyped_decl(id) else {
return Ok(None);
};
Ok(Some(record.clone()))
}
fn is_default_delete(
db: &dyn BindingsGenerator,
template_type: &TemplateArg,
deleter: &TemplateArg,
) -> Result<bool> {
let Some(deleter) = template_arg_to_record(db, deleter)? else {
return Ok(false);
};
Ok(deleter.template_specialization.as_ref().is_some_and(|deleter| {
deleter.template_name.as_ref() == "std::default_delete"
&& deleter.template_args.len() == 1
&& deleter.template_args[0] == *template_type
}))
}
// Returns true if allocator is std::allocator<template_type>
fn is_std_allocator(
db: &dyn BindingsGenerator,
template_type: &TemplateArg,
allocator: &TemplateArg,
) -> Result<bool> {
let Some(allocator) = template_arg_to_record(db, allocator)? else {
return Ok(false);
};
Ok(allocator.template_specialization.as_ref().is_some_and(|allocator| {
allocator.template_name.as_ref() == "std::allocator"
&& allocator.template_args.len() == 1
&& allocator.template_args[0] == *template_type
}))
}
impl UniformReprTemplateType {
/// Returns the `UniformReprTemplateType` for a `TemplateSpecialization`.
/// Returns an error if the template arguments (if any) fail to db.rs_type_kind(T).
/// Returns none if the template specialization is not for a known type corresponding with
/// one of `UniformReprTemplateType`s variants.
fn new(
db: &dyn BindingsGenerator,
template_specialization: Option<&TemplateSpecialization>,
is_return_type: bool,
) -> Result<Option<Rc<Self>>> {
let Some(template_specialization) = template_specialization else {
return Ok(None);
};
let type_arg = |template_arg: &TemplateArg| -> Result<RsTypeKind> {
let arg_type = match &template_arg.type_ {
Ok(arg_type) => arg_type.clone(),
Err(e) => bail!("{e}"),
};
// Importantly, `is_return_type` is not propagated through inner types.
let arg_type_kind = db.rs_type_kind(arg_type)?;
ensure!(
!arg_type_kind.is_bridge_type(),
"Bridge types cannot be used as template arguments"
);
// We don't do this in required_crubit_features() because it doesn't know which
// template arguments actually need to be free of errors. (For example,
// allocator/deleter do not.)
if let RsTypeKind::Error { error, .. } = arg_type_kind {
return Err(error);
}
Ok(arg_type_kind)
};
let this = match (
template_specialization.template_name.as_ref(),
&template_specialization.template_args[..],
) {
("std::unique_ptr", [t, deleter]) => {
let has_std_deleter = is_default_delete(db, t, deleter)?;
let t = type_arg(t)?;
ensure!(t.is_complete(), "Rust std::unique_ptr<T> cannot be used with incomplete types, and `{}` is incomplete", t.display(db));
ensure!(t.is_destructible(), "Rust std::unique_ptr<T> requires that `T` be destructible, but the destructor of `{}` is non-public or deleted", t.display(db));
if !has_std_deleter {
return Ok(None);
}
if t.overloads_operator_delete() {
return Ok(None);
}
Self::StdUniquePtr { element_type: t }
}
("std::vector", [t, allocator]) => {
let has_std_allocator = is_std_allocator(db, t, allocator)?;
let t = type_arg(t)?;
ensure!(t.is_destructible(), "Rust std::vector<T> requires that `T` be destructible, but the destructor of `{}` is non-public or deleted", t.display(db));
if !has_std_allocator {
return Ok(None);
}
if t.overloads_operator_delete() {
return Ok(None);
}
if t.is_bool() {
// The rust implementation doesn't specialize bool like C++ does.
return Ok(None);
}
Self::StdVector { element_type: t }
}
("absl::Span", [t]) => {
// Revisit the CcType of _t to see if it is const.
let element_type = type_arg(t)?;
let is_const = t.type_.as_ref().expect("should be valid because type_args is the successful result of get_template_args").is_const;
Self::AbslSpan {
is_const,
// We always accept lifetime-bound spans as parameters. A C++ function
// shouldn't be using an array referenced by a span after it returns.
//
// Spans returned by a C++ function have an unclear lifetime, and so must be
// returned as a raw span.
include_lifetime: !is_return_type,
element_type,
}
}
_ if template_specialization.is_string_view
|| template_specialization.is_wstring_view =>
{
return Ok(None);
}
_ => {
// If all else fails, it's some unknown template type. Read any errors from the
// template arguments.
for t in &template_specialization.template_args {
type_arg(t)?;
}
return Ok(None);
}
};
Ok(Some(Rc::new(this)))
}
fn to_token_stream(&self, db: &dyn BindingsGenerator) -> TokenStream {
match self {
Self::StdVector { element_type } => {
let element_type_tokens = element_type.to_token_stream(db);
quote! { ::cc_std::std::vector::<#element_type_tokens> }
}
Self::StdUniquePtr { element_type } => {
let element_type_tokens = element_type.to_token_stream(db);
quote! { ::cc_std::std::unique_ptr::<#element_type_tokens> }
}
Self::AbslSpan { is_const, include_lifetime, element_type } => {
let element_type_tokens = element_type.to_token_stream(db);
// Use Span when we have a lifetime parameter, and RawSpan otherwise.
//
// See http://<internal link>.
match (*is_const, *include_lifetime) {
(true, true) => quote! { ::span::absl::Span<'_, #element_type_tokens> },
(false, true) => quote! { ::span::absl::SpanMut<'_, #element_type_tokens> },
(true, false) => quote! { ::span::absl::RawSpan<#element_type_tokens> },
(false, false) => quote! { ::span::absl::RawSpanMut<#element_type_tokens> },
}
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum RustPtrKind {
/// The C++ pointer type that this was derived from, which is used for spelling the original
/// C++ type back out even if we only treat it as a raw pointer in Rust.
CcPtr(PointerTypeKind),
/// This is the `rs_std::SliceRef` type.
Slice,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum RsTypeKind {
/// An error occurred while generating the type.
///
/// Error types are only exposed in `:wrapper` mode, where they become
/// opaque types, or where a visibility override is specified.
Error {
symbol: Rc<str>,
error: arc_anyhow::Error,
visibility_override: Option<Visibility>,
},
Pointer {
pointee: Rc<RsTypeKind>,
kind: RustPtrKind,
mutability: Mutability,
},
Reference {
referent: Rc<RsTypeKind>,
mutability: Mutability,
lifetime: Lifetime,
},
RvalueReference {
referent: Rc<RsTypeKind>,
mutability: Mutability,
lifetime: Lifetime,
},
FuncPtr {
option: bool,
cc_calling_conv: CcCallingConv,
return_type: Rc<RsTypeKind>,
param_types: Rc<[RsTypeKind]>,
},
/// An incomplete record type.
IncompleteRecord {
incomplete_record: Rc<IncompleteRecord>,
crate_path: Rc<CratePath>,
},
/// A complete record type.
Record {
record: Rc<Record>,
crate_path: Rc<CratePath>,
/// If this record is an instantiation of a `UniformReprTemplateType`, this will be set.
uniform_repr_template_type: Option<Rc<UniformReprTemplateType>>,
},
Enum {
enum_: Rc<Enum>,
crate_path: Rc<CratePath>,
},
TypeAlias {
type_alias: Rc<TypeAlias>,
underlying_type: Rc<RsTypeKind>,
crate_path: Rc<CratePath>,
},
Primitive(Primitive),
/// Types that require custom logic to translate.
BridgeType {
bridge_type: BridgeRsTypeKind,
original_type: Rc<Record>,
},
/// Types that can be reinterpreted in place, e.g., signed char <-> i8
/// This variant comes from the `CRUBIT_INTERNAL_RUST_TYPE` attribute macro in C++,
/// which is used on types like `SliceRef`, `StrRef`, and C++ types generated from Rust
/// types by cc_bindings_from_rs.
ExistingRustType(Rc<ExistingRustType>),
/// c9::Co<T>
C9Co {
have_reference_param: bool,
result_type: Rc<RsTypeKind>,
original_type: Rc<Record>,
},
}
fn new_c9_co_record(
have_reference_param: bool,
record: Rc<Record>,
db: &dyn BindingsGenerator,
) -> Result<Option<RsTypeKind>> {
let Some(ts) = record.template_specialization.as_ref() else {
return Ok(None);
};
if ts.template_name.as_ref() != "c9::Co" {
return Ok(None);
}
let arg_type = ts.template_args[0]
.type_
.as_ref()
.map_err(|e: &String| anyhow!("c9::Co T argument is not Crubit compatible: {e}"))?
.clone();
let arg_type_kind = db.rs_type_kind(arg_type)?;
if let RsTypeKind::Error { error, .. } = arg_type_kind {
return Err(error);
};
Ok(Some(RsTypeKind::C9Co {
have_reference_param,
result_type: Rc::new(arg_type_kind),
original_type: record,
}))
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum BridgeRsTypeKind {
BridgeVoidConverters {
rust_name: Rc<str>,
cpp_to_rust_converter: Rc<str>,
rust_to_cpp_converter: Rc<str>,
},
Bridge {
rust_name: Rc<str>,
abi_rust: Rc<str>,
abi_cpp: Rc<str>,
generic_types: Rc<[RsTypeKind]>,
},
ProtoMessageBridge {
rust_name: Rc<str>,
abi_rust: Rc<str>,
abi_cpp: Rc<str>,
},
StdOptional(Rc<RsTypeKind>),
StdPair(Rc<RsTypeKind>, Rc<RsTypeKind>),
StdString {
in_cc_std: bool,
},
}
impl BridgeRsTypeKind {
/// If the record is a bridge type, returns the corresponding BridgeRsTypeKind.
/// Otherwise, returns None. This may also return an error if db.rs_type_kind fails, or if the
/// record has template parameters that cannot be translated.
pub fn new(record: &Record, db: &dyn BindingsGenerator) -> Result<Option<BridgeRsTypeKind>> {
let Some(bridge_type) = &record.bridge_type else {
return Ok(None);
};
let bridge_rs_type_kind = match bridge_type.clone() {
BridgeType::BridgeVoidConverters {
rust_name,
cpp_to_rust_converter,
rust_to_cpp_converter,
} => BridgeRsTypeKind::BridgeVoidConverters {
rust_name,
cpp_to_rust_converter,
rust_to_cpp_converter,
},
BridgeType::ProtoMessageBridge { rust_name, abi_rust, abi_cpp } => {
BridgeRsTypeKind::ProtoMessageBridge { rust_name, abi_rust, abi_cpp }
}
BridgeType::Bridge { rust_name, abi_rust, abi_cpp } => BridgeRsTypeKind::Bridge {
rust_name,
abi_rust,
abi_cpp,
generic_types: record
.template_specialization
.as_ref()
.map(|template_spec| &template_spec.template_args[..])
.unwrap_or_default()
.iter()
.map(|template_arg: &TemplateArg| {
let type_ = template_arg.type_.as_ref().map_err(|err| {
anyhow!("Failed to get type from template arg: {}", err)
})?;
db.rs_type_kind(type_.clone())
})
.collect::<Result<Rc<[RsTypeKind]>>>()?,
},
BridgeType::StdOptional(t) => {
BridgeRsTypeKind::StdOptional(Rc::new(db.rs_type_kind(t)?))
}
BridgeType::StdPair(t1, t2) => BridgeRsTypeKind::StdPair(
Rc::new(db.rs_type_kind(t1)?),
Rc::new(db.rs_type_kind(t2)?),
),
BridgeType::StdString => {
let in_cc_std = db.ir().is_current_target(&record.owning_target)
&& record.owning_target.target_name_escaped() == "cc_std";
BridgeRsTypeKind::StdString { in_cc_std }
}
};
Ok(Some(bridge_rs_type_kind))
}
pub fn is_void_converters_bridge_type(&self) -> bool {
matches!(self, BridgeRsTypeKind::BridgeVoidConverters { .. })
}
}
impl RsTypeKind {
/// Directly creates an `RsTypeKind` from an `Item` that defines a type.
///
/// WARNING: this is a low-level function that bypasses the validity checks
/// that are normally performed by `BindingsGenerator::rs_type_kind()`. In particular,
/// this function should only be called by functions that are themselves called by
/// `rs_type_kind()`, in order to avoid cycles while introspecting types.
///
/// Returns an error if the item does not define a type (e.g. it is a function declaration),
/// or if the `RsTypeKind` cannot be created (e.g. a type alias which points to a type that
/// cannot receive an `RsTypeKind`).
pub fn from_item_raw(
db: &dyn BindingsGenerator,
item: Item,
have_reference_param: bool,
is_return_type: bool,
) -> Result<Self> {
match item {
Item::IncompleteRecord(incomplete_record) => {
RsTypeKind::new_incomplete_record(db, incomplete_record)
}
Item::Record(record) => {
RsTypeKind::new_record(db, record, have_reference_param, is_return_type)
}
Item::Enum(enum_) => RsTypeKind::new_enum(db, enum_),
Item::TypeAlias(type_alias) => RsTypeKind::new_type_alias(db, type_alias),
Item::ExistingRustType(existing_rust_type) => {
RsTypeKind::new_existing_rust_type(db, existing_rust_type)
}
other_item => bail!("Item does not define a type: {other_item:?}"),
}
}
fn new_type_alias(db: &dyn BindingsGenerator, type_alias: Rc<TypeAlias>) -> Result<Self> {
let ir = db.ir();
let underlying_type = db.rs_type_kind(type_alias.underlying_type.clone())?;
// Note: we don't need to call `.unalias()` for these checks, because we already checked
// this, recursively.
// Bridge types cannot be aliased
if underlying_type.is_bridge_type() {
return Ok(underlying_type);
}
// Records cannot be aliased unless they are part of the same translation unit as the alias.
// This is not for an especially principled reason: unless it's in the same translation
// unit, we don't know if the record is incomplete from the point of view of the alias.
// For example, perhaps the alias is to a forward declaration, and then later, we completed
// the forward declaration.
if let RsTypeKind::Record { record, .. } = &underlying_type {
if record.owning_target != type_alias.owning_target
&& record.defining_target() != Some(&type_alias.owning_target)
{
return Ok(underlying_type);
}
}
let crate_path = Rc::new(CratePath::new(
&ir,
ir.namespace_qualifier(&type_alias),
rs_imported_crate_name(&type_alias.owning_target, &ir),
));
Ok(RsTypeKind::TypeAlias {
type_alias,
crate_path,
underlying_type: Rc::new(underlying_type),
})
}
fn new_record(
db: &dyn BindingsGenerator,
record: Rc<Record>,
have_reference_param: bool,
is_return_type: bool,
) -> Result<Self> {
let ir = db.ir();
if let Some(bridge_type) = BridgeRsTypeKind::new(&record, db)? {
return Ok(RsTypeKind::BridgeType { bridge_type, original_type: record });
}
if let Some(c9_co) = new_c9_co_record(have_reference_param, Rc::clone(&record), db)? {
return Ok(c9_co);
}
let crate_path = Rc::new(CratePath::new(
ir,
ir.namespace_qualifier(&record),
rs_imported_crate_name(&record.owning_target, ir),
));
Ok(RsTypeKind::Record {
uniform_repr_template_type: UniformReprTemplateType::new(
db,
record.template_specialization.as_ref(),
is_return_type,
)?,
record,
crate_path,
})
}
fn new_incomplete_record(
db: &dyn BindingsGenerator,
incomplete_record: Rc<IncompleteRecord>,
) -> Result<Self> {
let ir = db.ir();
let crate_path = Rc::new(CratePath::new(
ir,
ir.namespace_qualifier(&incomplete_record),
rs_imported_crate_name(&incomplete_record.owning_target, ir),
));
Ok(RsTypeKind::IncompleteRecord { incomplete_record, crate_path })
}
fn new_enum(db: &dyn BindingsGenerator, enum_: Rc<Enum>) -> Result<Self> {
let ir = db.ir();
let crate_path = Rc::new(CratePath::new(
ir,
ir.namespace_qualifier(&enum_),
rs_imported_crate_name(&enum_.owning_target, ir),
));
Ok(RsTypeKind::Enum { enum_, crate_path })
}
fn new_existing_rust_type(
db: &dyn BindingsGenerator,
existing_rust_type: Rc<ExistingRustType>,
) -> Result<Self> {
if existing_rust_type.rs_name.as_ref() == SLICE_REF_NAME_RS {
let [inner_cc_type] = &existing_rust_type.type_parameters[..] else {
bail!(
"SliceRef has {} type parameters, expected 1",
existing_rust_type.type_parameters.len()
);
};
let inner_rs_type_kind = db.rs_type_kind(inner_cc_type.clone())?;
ensure!(
inner_rs_type_kind.allowed_behind_multi_element_ptr(),
"SliceRef pointee type is not allowed behind a multi element pointer: {}",
inner_rs_type_kind.display(db),
);
return Ok(RsTypeKind::Pointer {
pointee: Rc::new(inner_rs_type_kind),
kind: RustPtrKind::Slice,
mutability: if inner_cc_type.is_const {
Mutability::Const
} else {
Mutability::Mut
},
});
}
Ok(RsTypeKind::ExistingRustType(existing_rust_type))
}
/// Returns true if the type is known to be `Unpin`, false otherwise.
pub fn is_unpin(&self) -> bool {
match self.unalias() {
RsTypeKind::Error { .. } | RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, uniform_repr_template_type, .. } => {
uniform_repr_template_type.is_some() || record.is_unpin()
}
RsTypeKind::BridgeType { .. } => true,
_ => true,
}
}
/// Recursively follows type aliases until an underlying nonalias type is reached.
pub fn unalias(&self) -> &Self {
let mut unaliased = self;
while let RsTypeKind::TypeAlias { underlying_type, .. } = unaliased {
unaliased = underlying_type;
}
unaliased
}
pub fn is_bridge_type(&self) -> bool {
matches!(self.unalias(), RsTypeKind::BridgeType { .. })
}
pub fn as_c9_co(&self) -> Option<&RsTypeKind> {
match self.unalias() {
RsTypeKind::C9Co { result_type, .. } => Some(result_type),
_ => None,
}
}
pub fn is_pointer_bridge_type(&self) -> bool {
matches!(
self.unalias(),
RsTypeKind::BridgeType {
bridge_type: BridgeRsTypeKind::BridgeVoidConverters { .. },
..
}
)
}
pub fn is_crubit_abi_bridge_type(&self) -> bool {
self.is_bridge_type() && !self.is_pointer_bridge_type()
}
pub fn is_proto_message_bridge_type(&self) -> bool {
matches!(
self.unalias(),
RsTypeKind::BridgeType { bridge_type: BridgeRsTypeKind::ProtoMessageBridge { .. }, .. }
)
}
pub fn is_primitive(&self) -> bool {
matches!(self.unalias(), RsTypeKind::Primitive(_))
}
/// Returns the features required to use this type which are not already
/// enabled, which might depend on where the type is used.
///
/// If a function accepts or returns this type, or an alias refers to this
/// type, then the function or type alias will itself also require this
/// feature. However, in the case of fields inside compound data types,
/// only those fields require the feature, not the entire type.
///
/// This isn't inlined into `db.rs_type_kind()` because `db.rs_type_kind()`
/// does not know which target is requesting the type, and it's a bit
/// tricky. Consider that a templated item needs to perform this check
/// for both the template definition and its instantiation, and so both
/// would need to be passed in to rs_type_kind() in order to be able to
/// merge these two functions.
pub fn required_crubit_features(
&self,
db: &dyn BindingsGenerator,
enabled_features: flagset::FlagSet<CrubitFeature>,
) -> (flagset::FlagSet<CrubitFeature>, String) {
let mut missing_features = <flagset::FlagSet<CrubitFeature>>::default();
let mut reasons = <std::collections::BTreeSet<std::borrow::Cow<'static, str>>>::new();
let mut require_feature =
|required_feature: CrubitFeature,
reason: Option<&dyn Fn() -> std::borrow::Cow<'static, str>>| {
let required_features = <flagset::FlagSet<CrubitFeature>>::from(required_feature);
let missing = required_features - enabled_features;
if !missing.is_empty() {
missing_features |= missing;
if let Some(reason) = reason {
reasons.insert(reason());
}
}
};
for rs_type_kind in self.dfs_iter() {
match rs_type_kind {
RsTypeKind::Error { error, visibility_override, .. } => {
if visibility_override.is_some() {
require_feature(CrubitFeature::Supported, None)
} else {
require_feature(
CrubitFeature::Wrapper,
Some(&|| std::borrow::Cow::from(format!("error: {error}"))),
)
}
}
RsTypeKind::Pointer { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::Reference { .. } | RsTypeKind::RvalueReference { .. } => {
require_feature(
CrubitFeature::Experimental,
Some(&|| "references are not supported".into()),
);
}
RsTypeKind::FuncPtr { cc_calling_conv, .. } => {
if *cc_calling_conv == CcCallingConv::C {
require_feature(CrubitFeature::Supported, None);
} else {
require_feature(
CrubitFeature::Experimental,
Some(&|| "functions must be not use a non-C calling convention".into()),
);
}
}
RsTypeKind::IncompleteRecord { .. } => require_feature(
CrubitFeature::Wrapper,
Some(&|| {
format!("{} is not a complete type)", rs_type_kind.display(db)).into()
}),
),
RsTypeKind::Record { uniform_repr_template_type: Some(x), .. }
if matches!(**x, UniformReprTemplateType::StdVector { .. }) =>
{
require_feature(
CrubitFeature::StdVector,
Some(&|| "std::vector is being released: b/356638830".into()),
);
}
RsTypeKind::Record { uniform_repr_template_type: Some(x), .. }
if matches!(**x, UniformReprTemplateType::StdUniquePtr { .. }) =>
{
require_feature(
CrubitFeature::StdUniquePtr,
Some(&|| "std::unique_ptr is being released: b/369994952".into()),
);
}
// Here, we can very carefully be non-recursive into the _structure_ of the type.
//
// Whether a record type is supported in rust does _not_ depend on whether each
// field is supported in Rust -- we can, if those fields are unsupported, replace
// them with opaque blobs.
//
// Instead, what matters is the abstract properties of the struct itself!
RsTypeKind::Record { record, .. } => {
// Types which aren't rust-movable, or which are general template
// instantiations, are only supported experimentally.
// But we do want to allow some commonly used template instantiations such as
// std::string_view so we create an allow list fo them. This is just a temporary
// solution until we have a better way to handle template
// instantiations.
if record.is_disallowed_template_instantiation() {
require_feature(
CrubitFeature::Wrapper,
Some(&|| {
format!("{} is a template instantiation", rs_type_kind.display(db),)
.into()
}),
)
} else {
require_feature(CrubitFeature::Supported, None)
}
}
RsTypeKind::Enum { .. } => require_feature(CrubitFeature::Supported, None),
// the alias itself is supported, but the overall features require depends on the
// aliased type, which is also visited by dfs_iter.
RsTypeKind::TypeAlias { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::Primitive { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::BridgeType { bridge_type, original_type } => {
let is_pointer_bridge =
matches!(bridge_type, BridgeRsTypeKind::BridgeVoidConverters { .. });
if original_type.is_disallowed_template_instantiation() && is_pointer_bridge {
require_feature(
CrubitFeature::Experimental,
Some(&|| {
format!(
"{} is a bridged template instantiation",
rs_type_kind.display(db),
)
.into()
}),
)
} else {
require_feature(CrubitFeature::Supported, None)
}
}
RsTypeKind::ExistingRustType(_) => require_feature(CrubitFeature::Supported, None),
RsTypeKind::C9Co { .. } => require_feature(CrubitFeature::Supported, None),
}
}
(missing_features, reasons.into_iter().join(", "))
}
/// Returns true if the type can be passed by value through `extern "C"` ABI
/// thunks.
pub fn is_c_abi_compatible_by_value(&self) -> bool {
match self.unalias() {
RsTypeKind::Error { .. } => true,
RsTypeKind::Pointer { .. } => true,
RsTypeKind::Reference { .. } => true,
RsTypeKind::RvalueReference { .. } => true,
RsTypeKind::FuncPtr { .. } => true,
RsTypeKind::IncompleteRecord { .. } => {
// Incomplete record (forward declaration) as parameter type or return type is
// unusual but it's a valid cc_library and such a header can be made to work
// when its user code includes headers that define the forward-declared type.
// Thus we don't panic here and simply return false, to allow
// Crubit to generate bindings for other un-impacted APIs.
false
}
// `rs_bindings_from_cc` can change the type of fields (e.g. using a blob of bytes for
// unsupported field types, or for no_unique_address fields). Changing the type
// of fields may change the ABI, which means that we can no longer assume
// that `extern "C"` ABI thunks can pass such types by value.
//
// TODO(b/274177296): Return `true` for structs where bindings replicate the type of
// all the fields.
RsTypeKind::Record { .. } => false,
RsTypeKind::Enum { .. } => true,
RsTypeKind::TypeAlias { .. } => unreachable!(),
RsTypeKind::Primitive(_) => true,
RsTypeKind::BridgeType { .. } => false,
RsTypeKind::ExistingRustType(existing_rust_type) => existing_rust_type.is_same_abi,
RsTypeKind::C9Co { .. } => false,
}
}
/// Returns true if the type is known to be move-constructible, false
/// otherwise.
///
/// For the purposes of this method, references are considered
/// move-constructible (as if they were pointers).
pub fn is_move_constructible(&self) -> bool {
match self.unalias() {
RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => {
record.move_constructor != ir::SpecialMemberFunc::Unavailable
}
RsTypeKind::BridgeType { .. } => true,
_ => true,
}
}
/// Returns Ok if the type can be used by value, or an error describing why
/// it can't.
pub fn check_by_value(&self) -> Result<()> {
match self.unalias() {
RsTypeKind::Error { error, .. } => bail!("Cannot use an error type by value: {error}"),
RsTypeKind::Record { record, .. } => check_by_value(record),
RsTypeKind::IncompleteRecord { incomplete_record, .. } => {
bail!(
"Attempted to pass incomplete record type `{}` by-value",
incomplete_record.cc_name
)
}
_ => Ok(()),
}
}
/// Returns true if the type is allowed to be passed as an element behind a single-element
/// pointer, otherwise false. The contrary to this is `allowed_behind_multi_element_pointer`,
/// which is more restrictive because the side of the type must be known.
pub fn allowed_behind_single_element_ptr(&self) -> bool {
match self.unalias() {
RsTypeKind::Error { .. } => false,
RsTypeKind::Pointer { .. } => true,
RsTypeKind::Reference { .. } => true,
RsTypeKind::RvalueReference { .. } => true,
RsTypeKind::FuncPtr { .. } => true,
RsTypeKind::IncompleteRecord { .. } => true,
RsTypeKind::Record { record, .. } => {
// Records that are bridged do not support being passed behind a pointer.
record.bridge_type.is_none()
}
RsTypeKind::Enum { .. } => true,
RsTypeKind::TypeAlias { .. } => unreachable!(),
RsTypeKind::Primitive(_) => true,
RsTypeKind::BridgeType { .. } => false,
RsTypeKind::ExistingRustType(_) => true,
RsTypeKind::C9Co { .. } => false,
}
}
/// Returns true if the type is allowed to be passed as an element behind a multi-element
/// pointer, otherwise false.
pub fn allowed_behind_multi_element_ptr(&self) -> bool {
match self.unalias() {
RsTypeKind::IncompleteRecord { .. } => {
// Incomplete records are disallowed because the stride is unknown.
false
}
RsTypeKind::Primitive(Primitive::Void) => {
// Void pointers are disallowed because they are type erased pointers,
// so the stride is unknown.
false
}
_ => self.allowed_behind_single_element_ptr(),
}
}
pub fn format_as_return_type_fragment(
&self,
db: &dyn BindingsGenerator,
self_record: Option<&Record>,
) -> Option<TokenStream> {
match self.unalias() {
RsTypeKind::Primitive(Primitive::Void) => None,
_ => Some(self.to_token_stream_replacing_by_self(db, self_record)),
}
}
/// Formats this RsTypeKind as the `self` parameter: usually, `&'a self` or
/// `&'a mut self`.
///
/// If this is !Unpin, however, it uses `self: Pin<&mut Self>` instead.
///
/// If `self` is formatted as RvalueReference or ConstRvalueReference, then
/// `arbitrary_self_types` feature flag is returned in the feature flags.
pub fn format_as_self_param(&self) -> Result<RsSnippet> {
match self {
RsTypeKind::Pointer { .. } => {
// TODO(jeanpierreda): provide end-user-facing docs, and insert a link to e.g.
// something like <internal link>
bail!(
"`self` has no lifetime. Use lifetime annotations or `#pragma clang lifetime_elision` to create bindings for this function."
)
}
RsTypeKind::Reference { referent, lifetime, mutability } => {
let mut_ = mutability.format_for_reference();
let lifetime = lifetime.format_for_reference();
if mutability == &Mutability::Mut && !referent.is_unpin() {
// TODO(b/239661934): Add a `use ::core::pin::Pin` to the crate, and use
// `Pin`.
Ok(RsSnippet::new(quote! {self: ::core::pin::Pin< & #lifetime #mut_ Self>}))
} else {
Ok(RsSnippet::new(quote! { & #lifetime #mut_ self }))
}
}
RsTypeKind::RvalueReference { referent: _, lifetime, mutability } => {
let lifetime = lifetime.format_for_reference();
// TODO(b/239661934): Add `use ::ctor::{RvalueReference, ConstRvalueReference}`.
match mutability {
Mutability::Mut => Ok(RsSnippet {
tokens: quote! {self: ::ctor::RvalueReference<#lifetime, Self>},
features: Feature::arbitrary_self_types.into(),
}),
Mutability::Const => Ok(RsSnippet {
tokens: quote! {self: ::ctor::ConstRvalueReference<#lifetime, Self>},
features: Feature::arbitrary_self_types.into(),
}),
}
}
RsTypeKind::Record { .. } => {
// This case doesn't happen for methods, but is needed for free functions mapped
// to a trait impl that take the first argument by value.
Ok(RsSnippet::new(quote! { self }))
}
_ => bail!("Unexpected type of `self` parameter: {:?}", self),
}
}
/// Returns whether the type represented by `self` implements the `Copy`
/// trait.
pub fn implements_copy(&self) -> bool {
match self {
RsTypeKind::Error { .. } => false,
RsTypeKind::Primitive { .. } => true,
RsTypeKind::Pointer { .. } => true,
RsTypeKind::FuncPtr { .. } => true,
RsTypeKind::Reference { mutability: Mutability::Const, .. } => true,
RsTypeKind::Reference { mutability: Mutability::Mut, .. } => false,
RsTypeKind::RvalueReference { .. } => false,
RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => should_derive_copy(record),
RsTypeKind::Enum { .. } => true,
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.implements_copy(),
RsTypeKind::BridgeType { bridge_type, .. } => match bridge_type {
// We cannot get the information of the Rust type so we assume it is not Copy.
BridgeRsTypeKind::BridgeVoidConverters { .. }
| BridgeRsTypeKind::Bridge { .. }
| BridgeRsTypeKind::ProtoMessageBridge { .. } => false,
BridgeRsTypeKind::StdOptional(t) => t.implements_copy(),
BridgeRsTypeKind::StdPair(t1, t2) => t1.implements_copy() && t2.implements_copy(),
BridgeRsTypeKind::StdString { .. } => false,
},
RsTypeKind::ExistingRustType(_) => true,
RsTypeKind::C9Co { .. } => false,
}
}
pub fn is_pointer(&self) -> bool {
matches!(self.unalias(), RsTypeKind::Pointer { .. })
}
pub fn is_pointer_to(&self, expected_record: &Record) -> bool {
match self.unalias() {
RsTypeKind::Pointer { pointee, .. } => pointee.is_record(expected_record),
_ => false,
}
}
pub fn is_ref_to(&self, expected_record: &Record) -> bool {
match self.unalias() {
RsTypeKind::Reference { referent, .. } => referent.is_record(expected_record),
RsTypeKind::RvalueReference { referent, .. } => referent.is_record(expected_record),
_ => false,
}
}
pub fn is_rvalue_ref_to(&self, expected_record: &Record) -> bool {
match self.unalias() {
RsTypeKind::RvalueReference { referent, .. } => referent.is_record(expected_record),
_ => false,
}
}
pub fn is_shared_ref_to(&self, expected_record: &Record) -> bool {
match self.unalias() {
RsTypeKind::Reference { referent, mutability: Mutability::Const, .. } => {
referent.is_record(expected_record)
}
_ => false,
}
}
pub fn is_record(&self, expected_record: &Record) -> bool {
match self.unalias() {
RsTypeKind::Record { record: actual_record, .. } => {
actual_record.id == expected_record.id
}
_ => false,
}
}
pub fn is_bool(&self) -> bool {
matches!(self.unalias(), RsTypeKind::Primitive(Primitive::Bool))
}
pub fn is_void(&self) -> bool {
matches!(self.unalias(), RsTypeKind::Primitive(Primitive::Void))
}
pub fn is_complete(&self) -> bool {
!matches!(self.unalias(), RsTypeKind::IncompleteRecord { .. })
}
pub fn is_destructible(&self) -> bool {
match self.unalias() {
RsTypeKind::Record { record, .. } => {
record.destructor != SpecialMemberFunc::Unavailable
}
RsTypeKind::IncompleteRecord { .. } => false,
_ => true,
}
}
/// Iterates over `self` and all the nested types (e.g. pointees, generic
/// type args, etc.) in DFS order.
pub fn dfs_iter(&self) -> impl Iterator<Item = &RsTypeKind> + '_ {
RsTypeKindIter::new(self)
}
/// Iterates over all `LifetimeId`s in `self` and in all the nested types.
/// Note that the results might contain duplicate LifetimeId values (e.g.
/// if the same LifetimeId is used in two `type_args`).
pub fn lifetimes(&self) -> impl Iterator<Item = Lifetime> + use<'_> {
self.dfs_iter().filter_map(Self::lifetime)
}
/// Returns the pointer or reference target.
pub fn referent(&self) -> Option<&RsTypeKind> {
match self.unalias() {
Self::Pointer { pointee: p, .. }
| Self::Reference { referent: p, .. }
| Self::RvalueReference { referent: p, .. } => Some(&**p),
_ => None,
}
}
/// Returns the reference lifetime, or None if this is not a reference.
pub fn lifetime(&self) -> Option<Lifetime> {
match self.unalias() {
Self::Reference { lifetime, .. } | Self::RvalueReference { lifetime, .. } => {
Some(lifetime.clone())
}
_ => None,
}
}
/// Similar to to_token_stream, but replacing RsTypeKind:Record with Self
/// when the underlying Record matches the given one.
pub fn to_token_stream_replacing_by_self(
&self,
db: &dyn BindingsGenerator,
self_record: Option<&Record>,
) -> TokenStream {
match self {
RsTypeKind::Pointer { pointee, kind, mutability } => {
let mutability = mutability.format_for_pointer();
let pointee_ = pointee.to_token_stream_replacing_by_self(db, self_record);
if let RustPtrKind::Slice = kind {
quote! {* #mutability [#pointee_] }
} else {
quote! {* #mutability #pointee_ }
}
}
RsTypeKind::Reference { referent, mutability, lifetime } => {
let mut_ = mutability.format_for_reference();
let lifetime = lifetime.format_for_reference();
let referent_ = referent.to_token_stream_replacing_by_self(db, self_record);
let mut tokens = quote! {& #lifetime #mut_ #referent_};
if mutability == &Mutability::Mut && !referent.is_unpin() {
// TODO(b/239661934): Add a `use ::core::pin::Pin` to the crate, and use
// `Pin`. This either requires deciding how to qualify pin at
// RsTypeKind-creation time, or returning a non-TokenStream type from here (and
// not implementing ToTokens, but instead some other interface.)
tokens = quote! {::core::pin::Pin< #tokens >};
}
tokens
}
RsTypeKind::RvalueReference { referent, mutability, lifetime } => {
let referent_ = referent.to_token_stream_replacing_by_self(db, self_record);
// TODO(b/239661934): Add a `use ::ctor::RvalueReference` (etc.) to the crate.
if mutability == &Mutability::Mut {
quote! {::ctor::RvalueReference<#lifetime, #referent_>}
} else {
quote! {::ctor::ConstRvalueReference<#lifetime, #referent_>}
}
}
RsTypeKind::FuncPtr { option, cc_calling_conv, return_type, param_types } => {
let param_types_ = param_types
.iter()
.map(|type_| type_.to_token_stream_replacing_by_self(db, self_record));
let abi = cc_calling_conv.rs_extern_abi();
let mut tokens = quote! { extern #abi fn( #( #param_types_ ),* ) };
if let Some(return_frag) =
return_type.format_as_return_type_fragment(db, self_record)
{
quote! { -> #return_frag }.to_tokens(&mut tokens);
}
if param_types.iter().any(|p| db.is_rs_type_kind_unsafe(p.clone())) {
tokens = quote! { unsafe #tokens }
}
if *option {
tokens = quote! {Option< #tokens >}
}
tokens
}
RsTypeKind::Record { record, .. } => {
if self_record == Some(record) {
quote! { Self }
} else {
self.to_token_stream(db)
}
}
RsTypeKind::BridgeType { .. } => self.to_token_stream(db),
RsTypeKind::ExistingRustType(_) => self.to_token_stream(db),
_ => self.to_token_stream(db),
}
}
/// Returns a `Display`able type for this `RsTypeKind`.
pub fn display<'a, 'db>(
&'a self,
db: &'a dyn BindingsGenerator<'db>,
) -> impl std::fmt::Display + use<'a, 'db> {
DisplayRsTypeKind { rs_type_kind: self, db }
}
pub fn overloads_operator_delete(&self) -> bool {
match self.unalias() {
RsTypeKind::Record { record, .. } => record.overloads_operator_delete,
// Unlikely to come up (usually a compilation error to even consider it), but
// we should imagine that an incomplete type _might_ implement operator delete?
// This is going to go poorly either way.
RsTypeKind::IncompleteRecord { .. } => true,
_ => false,
}
}
}
/// A type that implements [`std::fmt::Display`] for [`RsTypeKind`], which
/// requires a [`BindingsGenerator`] to be able to format the type.
pub struct DisplayRsTypeKind<'a, 'db> {
rs_type_kind: &'a RsTypeKind,
db: &'a dyn BindingsGenerator<'db>,
}
impl std::fmt::Display for DisplayRsTypeKind<'_, '_> {
// Formats the token stream of the RsTypeKind to a string. Note that this can
// include extra whitespace, where we'd ideally remove it, but it is hard to
// remove whitespace without invoking rustfmt.
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match write_unformatted_tokens(f, self.rs_type_kind.to_token_stream(self.db)) {
Ok(_) => Ok(()),
Err(e) => {
// Honestly this should never happen, but we should spit out something.
write!(f, "<error: {e}>")
}
}
}
}
impl RsTypeKind {
pub fn to_token_stream(&self, db: &dyn BindingsGenerator) -> TokenStream {
match self {
// errors become opaque blobs
RsTypeKind::Error { symbol, .. } => {
// We use `()` as the crate identifier for convenience, and because
// the only types using `()` like this are all pub(crate) (generated here.)
quote! { ::forward_declare::Incomplete<::forward_declare::symbol!(#symbol), ()> }
}
RsTypeKind::Pointer { pointee, kind, mutability } => {
let mutability = mutability.format_for_pointer();
let pointee_tokens = pointee.to_token_stream(db);
if let RustPtrKind::Slice = kind {
quote! {* #mutability [#pointee_tokens] }
} else {
quote! {* #mutability #pointee_tokens }
}
}
RsTypeKind::Reference { referent, mutability, lifetime } => {
let mut_ = mutability.format_for_reference();
let lifetime = lifetime.format_for_reference();
let referent_tokens = referent.to_token_stream(db);
let mut tokens = quote! {& #lifetime #mut_ #referent_tokens};
if mutability == &Mutability::Mut && !referent.is_unpin() {
// TODO(b/239661934): Add a `use ::core::pin::Pin` to the crate, and use
// `Pin`. This either requires deciding how to qualify pin at
// RsTypeKind-creation time, or returning a non-TokenStream type from here (and
// not implementing ToTokens, but instead some other interface.)
tokens = quote! { ::core::pin::Pin< #tokens > };
}
tokens
}
RsTypeKind::RvalueReference { referent, mutability, lifetime } => {
// TODO(b/239661934): Add a `use ::ctor::RvalueReference` (etc.) to the crate.
let referent_tokens = referent.to_token_stream(db);
if mutability == &Mutability::Mut {
quote! {::ctor::RvalueReference<#lifetime, #referent_tokens>}
} else {
quote! {::ctor::ConstRvalueReference<#lifetime, #referent_tokens>}
}
}
RsTypeKind::FuncPtr { option, cc_calling_conv, return_type, param_types } => {
let param_types_tokens = param_types.iter().map(|ty| ty.to_token_stream(db));
let abi = cc_calling_conv.rs_extern_abi();
let mut tokens = quote! { extern #abi fn( #( #param_types_tokens ),* ) };
if let Some(return_frag) = return_type.format_as_return_type_fragment(db, None) {
quote! { -> #return_frag }.to_tokens(&mut tokens);
}
if param_types.iter().any(|p| db.is_rs_type_kind_unsafe(p.clone())) {
tokens = quote! { unsafe #tokens }
}
if *option {
tokens = quote! {Option< #tokens >}
}
tokens
}
RsTypeKind::IncompleteRecord { incomplete_record, crate_path } => {
let record_ident = make_rs_ident(incomplete_record.rs_name.identifier.as_ref());
quote! { #crate_path #record_ident }
}
RsTypeKind::Record { record, crate_path, uniform_repr_template_type } => {
if let Some(generic_monomorphization) = uniform_repr_template_type {
return generic_monomorphization.to_token_stream(db);
}
let ident = make_rs_ident(record.rs_name.identifier.as_ref());
quote! { #crate_path #ident }
}
RsTypeKind::Enum { enum_, crate_path } => {
let ident = make_rs_ident(&enum_.rs_name.identifier);
quote! { #crate_path #ident }
}
RsTypeKind::TypeAlias { type_alias, crate_path, .. } => {
let ident = make_rs_ident(&type_alias.rs_name.identifier);
quote! { #crate_path #ident }
}
RsTypeKind::Primitive(primitive) => match primitive {
Primitive::Bool => quote! { bool },
Primitive::Void => quote! { ::core::ffi::c_void },
Primitive::Float => quote! { f32 },
Primitive::Double => quote! { f64 },
Primitive::Char => quote! { ::core::ffi::c_char },
Primitive::SignedChar => quote! { ::core::ffi::c_schar },
Primitive::UnsignedChar => quote! { ::core::ffi::c_uchar },
Primitive::Short => quote! { ::core::ffi::c_short },
Primitive::Int => quote! { ::core::ffi::c_int },
Primitive::Long => quote! { ::core::ffi::c_long },
Primitive::LongLong => quote! { ::core::ffi::c_longlong },
Primitive::UnsignedShort => quote! { ::core::ffi::c_ushort },
Primitive::UnsignedInt => quote! { ::core::ffi::c_uint },
Primitive::UnsignedLong => quote! { ::core::ffi::c_ulong },
Primitive::UnsignedLongLong => quote! { ::core::ffi::c_ulonglong },
Primitive::Char16T => quote! { u16 },
Primitive::Char32T => quote! { u32 },
Primitive::PtrdiffT
| Primitive::StdPtrdiffT
| Primitive::IntptrT
| Primitive::StdIntptrT => quote! { isize },
Primitive::SizeT
| Primitive::StdSizeT
| Primitive::UintptrT
| Primitive::StdUintptrT => quote! { usize },
Primitive::Int8T | Primitive::StdInt8T => quote! { i8 },
Primitive::Int16T | Primitive::StdInt16T => quote! { i16 },
Primitive::Int32T | Primitive::StdInt32T => quote! { i32 },
Primitive::Int64T | Primitive::StdInt64T => quote! { i64 },
Primitive::Uint8T | Primitive::StdUint8T => quote! { u8 },
Primitive::Uint16T | Primitive::StdUint16T => quote! { u16 },
Primitive::Uint32T | Primitive::StdUint32T => quote! { u32 },
Primitive::Uint64T | Primitive::StdUint64T => quote! { u64 },
},
RsTypeKind::BridgeType { bridge_type, original_type } => {
match bridge_type {
BridgeRsTypeKind::BridgeVoidConverters { rust_name, .. } => {
fully_qualify_type(db, ir::Item::Record(original_type.clone()), rust_name)
}
BridgeRsTypeKind::Bridge { rust_name, generic_types, .. } => {
let path = fully_qualify_type(
db,
ir::Item::Record(original_type.clone()),
rust_name,
);
// If there are no generic types, then we're done.
if generic_types.is_empty() {
return path;
}
let generic_types_tokens =
generic_types.iter().map(|t| t.to_token_stream(db));
quote! { #path < #(#generic_types_tokens),* > }
}
BridgeRsTypeKind::ProtoMessageBridge { rust_name, .. } => {
fully_qualify_type(db, ir::Item::Record(original_type.clone()), rust_name)
}
BridgeRsTypeKind::StdOptional(inner) => {
let inner = inner.to_token_stream(db);
quote! { ::core::option::Option< #inner > }
}
BridgeRsTypeKind::StdPair(first, second) => {
let first = first.to_token_stream(db);
let second = second.to_token_stream(db);
quote! { (#first, #second) }
}
BridgeRsTypeKind::StdString { in_cc_std } => {
if *in_cc_std {
quote! { crate::std::string }
} else {
quote! { ::cc_std::std::string }
}
}
}
}
RsTypeKind::ExistingRustType(existing_rust_type) => fully_qualify_type(
db,
ir::Item::ExistingRustType(existing_rust_type.clone()),
&existing_rust_type.rs_name,
),
RsTypeKind::C9Co { have_reference_param, result_type, .. } => {
let result_type_tokens = if result_type.is_void() {
quote! { () }
} else {
result_type.to_token_stream(db)
};
// When there are reference parameters, the coroutine must finish before they are
// invalidated (http://shortn/_XPma06AwZh).
match have_reference_param {
false => quote! { ::co::Co<'static, #result_type_tokens> },
true => quote! { ::co::Co<'_, #result_type_tokens> },
}
}
}
}
}
/// Take a user defined path, like `foo` or `::bar`, and convert it to
/// an absolute path, like `crate::foo` or `::bar` respectively.
///
/// The path is taken to be relative to crate defining the item.
///
/// This has _very_ limited support for other type expressions, like `&T`,
/// and special-cases well known builtin types like `char`.
fn fully_qualify_type(
db: &dyn BindingsGenerator,
item: ir::Item,
type_expression: &str,
) -> TokenStream {
let root_crate = || {
let target = item.defining_target().cloned().or_else(|| item.owning_target()).unwrap();
if db.ir().is_current_target(&target) {
quote! { crate }
} else {
let ident = make_rs_ident(target.target_name());
quote! { :: #ident }
}
};
fully_qualify_type_impl(type_expression, root_crate)
}
/// Broken out for testing :/
fn fully_qualify_type_impl(
type_expression: &str,
root_crate: impl Fn() -> TokenStream,
) -> TokenStream {
let mut type_expression_suffix = type_expression;
'fix: loop {
type_expression_suffix = type_expression_suffix.trim_start();
for prefix in ["&", "*", "const", "mut"] {
if let Some(suffix) = type_expression_suffix.strip_prefix(prefix) {
type_expression_suffix = suffix;
continue 'fix;
}
}
break;
}
let prefix = &type_expression[..type_expression.len() - type_expression_suffix.len()];
let prefix: TokenStream = prefix.parse().unwrap();
// Primitive types are special-cased.
if matches!(
type_expression_suffix.trim(),
"char"
| "bool"
| "i8"
| "u8"
| "i16"
| "u16"
| "i32"
| "u32"
| "i64"
| "u64"
| "i128"
| "u128"
| "isize"
| "usize"
| "str"
| "f32"
| "f64"
) {
let suffix: TokenStream = type_expression_suffix.parse().unwrap();
return quote! { #prefix #suffix };
}
// Otherwise, we assume it's a path.
let is_absolute_path = type_expression_suffix.starts_with("::");
// If the name starts with "::", then it is an absolute path. In this case, we
// need to skip the first part of the split, since it returns the empty string.
// Note: Crubit can generate poorly formatted names, like `:: foo :: bar`, so we also
// need to trim whitespace to create valid identifiers.
let name_parts = type_expression_suffix
.split("::")
.skip(is_absolute_path as usize)
.map(str::trim)
.map(make_rs_ident);
let top_level_crate = if is_absolute_path {
quote! {}
} else {
root_crate()
};
quote! { #prefix #top_level_crate :: #(#name_parts)::* }
}
struct RsTypeKindIter<'ty> {
todo: Vec<&'ty RsTypeKind>,
}
impl<'ty> RsTypeKindIter<'ty> {
pub fn new(ty: &'ty RsTypeKind) -> Self {
Self { todo: vec![ty] }
}
}
impl<'ty> Iterator for RsTypeKindIter<'ty> {
type Item = &'ty RsTypeKind;
fn next(&mut self) -> Option<Self::Item> {
match self.todo.pop() {
None => None,
Some(curr) => {
match curr {
RsTypeKind::Error { .. }
| RsTypeKind::Primitive { .. }
| RsTypeKind::IncompleteRecord { .. }
| RsTypeKind::Record { .. }
| RsTypeKind::Enum { .. } => {}
RsTypeKind::Pointer { pointee, .. } => self.todo.push(pointee),
RsTypeKind::Reference { referent, .. } => self.todo.push(referent),
RsTypeKind::RvalueReference { referent, .. } => self.todo.push(referent),
RsTypeKind::TypeAlias { underlying_type: t, .. } => self.todo.push(t),
RsTypeKind::FuncPtr { return_type, param_types, .. } => {
self.todo.push(return_type);
self.todo.extend(param_types.iter().rev());
}
RsTypeKind::BridgeType { bridge_type, .. } => match bridge_type {
BridgeRsTypeKind::BridgeVoidConverters { .. }
| BridgeRsTypeKind::ProtoMessageBridge { .. }
| BridgeRsTypeKind::Bridge { .. } => {}
BridgeRsTypeKind::StdOptional(t) => self.todo.push(t),
BridgeRsTypeKind::StdPair(t1, t2) => {
self.todo.push(t2);
self.todo.push(t1);
}
BridgeRsTypeKind::StdString { .. } => {}
},
RsTypeKind::ExistingRustType(_) => {}
RsTypeKind::C9Co { result_type, .. } => {
self.todo.push(result_type);
}
};
Some(curr)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use arc_anyhow::Result;
use googletest::prelude::*;
use token_stream_matchers::assert_rs_matches;
fn make_existing_rust_type(name: Rc<str>, is_same_abi: bool) -> RsTypeKind {
RsTypeKind::ExistingRustType(Rc::new(ExistingRustType {
rs_name: name,
cc_name: "".into(),
type_parameters: Vec::new(),
owning_target: BazelLabel("//new/for/testing".into()),
size_align: None,
is_same_abi,
id: ItemId::new_for_testing(0),
must_bind: false,
}))
}
#[gtest]
fn test_dfs_iter_ordering_for_func_ptr() {
// Set up a test input representing: fn(A, B) -> C
let f = {
let a = make_existing_rust_type("::A".into(), true);
let b = make_existing_rust_type("::B".into(), true);
let c = make_existing_rust_type("::C".into(), true);
RsTypeKind::FuncPtr {
option: false,
cc_calling_conv: CcCallingConv::C,
param_types: Rc::from([a, b]),
return_type: Rc::new(c),
}
};
let dfs_names = f
.dfs_iter()
.map(|t| match t {
RsTypeKind::FuncPtr { .. } => "fn".to_string(),
RsTypeKind::ExistingRustType(existing_rust_type) => {
existing_rust_type.rs_name.to_string()
}
_ => unreachable!("Only FuncPtr and ExistingRustType kinds are used in this test"),
})
.collect_vec();
assert_eq!(vec!["fn", "::A", "::B", "::C"], dfs_names);
}
struct EmptyDatabase;
impl<'db> BindingsGenerator<'db> for EmptyDatabase {}
#[gtest]
fn test_lifetime_elision_for_references() {
let referent = Rc::new(make_existing_rust_type("::T".into(), true));
let reference = RsTypeKind::Reference {
referent,
mutability: Mutability::Const,
lifetime: Lifetime::new("_"),
};
assert_rs_matches!(reference.to_token_stream(&EmptyDatabase), quote! {&::T});
}
#[gtest]
fn test_lifetime_elision_for_rvalue_references() {
let referent = Rc::new(make_existing_rust_type("::T".into(), true));
let reference = RsTypeKind::RvalueReference {
referent,
mutability: Mutability::Mut,
lifetime: Lifetime::new("_"),
};
assert_rs_matches!(
reference.to_token_stream(&EmptyDatabase),
quote! {RvalueReference<'_, ::T>}
);
}
#[gtest]
fn test_format_as_self_param_rvalue_reference() -> Result<()> {
let referent = Rc::new(make_existing_rust_type("::T".into(), true));
let result = RsTypeKind::RvalueReference {
referent,
mutability: Mutability::Mut,
lifetime: Lifetime::new("a"),
}
.format_as_self_param()?;
assert_rs_matches!(result.tokens, quote! {self: ::ctor::RvalueReference<'a, Self>});
assert_eq!(result.features, Feature::arbitrary_self_types);
Ok(())
}
#[gtest]
fn test_format_as_self_param_const_rvalue_reference() -> Result<()> {
let referent = Rc::new(make_existing_rust_type("::T".into(), true));
let result = RsTypeKind::RvalueReference {
referent,
mutability: Mutability::Const,
lifetime: Lifetime::new("a"),
}
.format_as_self_param()?;
assert_rs_matches!(result.tokens, quote! {self: ::ctor::ConstRvalueReference<'a, Self>});
assert_eq!(result.features, Feature::arbitrary_self_types);
Ok(())
}
/// Basic unit test of required_crubit_features on a compound data type.
///
/// If a nested type within it requires a feature, then the whole feature
/// does. This is done automatically via dfs_iter().
#[gtest]
fn test_required_crubit_features() {
let no_types: &[RsTypeKind] = &[];
let int = RsTypeKind::Primitive(Primitive::Int32T);
let reference = RsTypeKind::Reference {
referent: Rc::new(int.clone()),
mutability: Mutability::Const,
lifetime: Lifetime::new("_"),
};
for func_ptr in [
RsTypeKind::FuncPtr {
option: false,
cc_calling_conv: CcCallingConv::C,
return_type: Rc::new(reference.clone()),
param_types: no_types.into(),
},
RsTypeKind::FuncPtr {
option: false,
cc_calling_conv: CcCallingConv::C,
return_type: Rc::new(int),
param_types: Rc::from([reference]),
},
] {
let (all_required_features, reason) = func_ptr.required_crubit_features(
&EmptyDatabase,
<flagset::FlagSet<CrubitFeature>>::default(),
);
assert_eq!(
all_required_features,
CrubitFeature::Experimental | CrubitFeature::Supported
);
assert_eq!(reason, "references are not supported");
}
}
fn make_crate_path() -> Rc<CratePath> {
let ns = NamespaceQualifier { namespaces: vec![], nested_records: vec![] };
Rc::new(CratePath {
crate_ident: None,
crate_root_path: ns.clone(),
namespace_qualifier: ns,
})
}
#[gtest]
fn test_simple_types_allowed_behind_single_and_multi_element_ptr() {
let error = RsTypeKind::Error {
symbol: "some error".into(),
error: anyhow!("some error happened!"),
visibility_override: None,
};
expect_that!(error.allowed_behind_single_element_ptr(), eq(false));
expect_that!(error.allowed_behind_multi_element_ptr(), eq(false));
let prim = RsTypeKind::Primitive(Primitive::Int32T);
expect_that!(prim.allowed_behind_single_element_ptr(), eq(true));
expect_that!(prim.allowed_behind_multi_element_ptr(), eq(true));
let enum_ = RsTypeKind::Enum {
enum_: Rc::new(Enum {
cc_name: Identifier { identifier: "MyEnum".into() },
rs_name: Identifier { identifier: "MyEnum".into() },
id: ItemId::new_for_testing(0),
owning_target: BazelLabel("//foo/bar".into()),
source_loc: "some_file.h:123".into(),
underlying_type: CcType {
variant: CcTypeVariant::Primitive(Primitive::Int32T),
is_const: false,
unknown_attr: "".into(),
},
enumerators: Some(vec![]),
unknown_attr: None,
enclosing_item_id: None,
must_bind: false,
}),
crate_path: make_crate_path(),
};
expect_that!(enum_.allowed_behind_single_element_ptr(), eq(true));
expect_that!(enum_.allowed_behind_multi_element_ptr(), eq(true));
}
fn make_incomplete_record() -> RsTypeKind {
RsTypeKind::IncompleteRecord {
incomplete_record: Rc::new(IncompleteRecord {
cc_name: Identifier { identifier: "MyStruct".into() },
rs_name: Identifier { identifier: "MyStruct".into() },
id: ItemId::new_for_testing(0),
owning_target: BazelLabel("//foo/bar".into()),
unknown_attr: None,
record_type: RecordType::Class,
enclosing_item_id: None,
must_bind: false,
}),
crate_path: make_crate_path(),
}
}
#[gtest]
fn test_incomplete_record_only_allowed_behind_single_element_ptr() {
let incomplete_record = make_incomplete_record();
expect_that!(incomplete_record.allowed_behind_single_element_ptr(), eq(true));
expect_that!(incomplete_record.allowed_behind_multi_element_ptr(), eq(false));
}
#[gtest]
fn test_alias_incomplete_record_only_allowed_behind_single_element_ptr() {
let alias_incomplete_record = RsTypeKind::TypeAlias {
type_alias: Rc::new(TypeAlias {
cc_name: Identifier { identifier: "MyAlias".into() },
rs_name: Identifier { identifier: "MyAlias".into() },
id: ItemId::new_for_testing(1),
owning_target: BazelLabel("//foo/bar".into()),
doc_comment: None,
unknown_attr: None,
underlying_type: CcType {
variant: CcTypeVariant::Decl(ItemId::new_for_testing(0)),
is_const: false,
unknown_attr: "".into(),
},
source_loc: "some_file.h:123".into(),
enclosing_item_id: None,
must_bind: false,
}),
underlying_type: Rc::new(make_incomplete_record()),
crate_path: make_crate_path(),
};
expect_that!(alias_incomplete_record.allowed_behind_single_element_ptr(), eq(true));
expect_that!(alias_incomplete_record.allowed_behind_multi_element_ptr(), eq(false));
}
#[gtest]
fn test_void_ptr_only_allowed_behind_single_element_ptr() {
let void_ptr = RsTypeKind::Primitive(Primitive::Void);
expect_that!(void_ptr.allowed_behind_single_element_ptr(), eq(true));
expect_that!(void_ptr.allowed_behind_multi_element_ptr(), eq(false));
}
#[gtest]
fn test_fully_qualify_type() {
assert_rs_matches!(
fully_qualify_type_impl("A", || {
quote! {crate}
}),
quote! {crate::A},
);
}
#[gtest]
fn test_fully_qualify_i32() {
assert_rs_matches!(
fully_qualify_type_impl("i32", || {
quote! {crate}
}),
quote! {i32},
);
}
#[gtest]
fn test_fully_qualify_ref() {
assert_rs_matches!(
fully_qualify_type_impl("&mut *const X", || {
quote! {crate}
}),
quote! {&mut *const crate::X},
);
}
}