blob: 3f5ad7aaffe5d461308249b3cc5d42ab7a139f28 [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)]
//! Vocabulary types and code generation functions for generating Rust code.
use crate::BindingsGenerator;
use arc_anyhow::Result;
use code_gen_utils::make_rs_ident;
use code_gen_utils::NamespaceQualifier;
use error_report::bail;
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: HashSet<Ident>,
}
impl RsSnippet {
/// Convenience function to initialize RsSnippet with empty `features`.
pub fn new(tokens: TokenStream) -> RsSnippet {
RsSnippet { tokens, features: HashSet::<Ident>::new() }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Mutability {
Const,
Mut,
}
impl Mutability {
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))
}
/// 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 }
}
}
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 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 lifetimes.peek().is_none() && types.peek().is_none() {
quote! {}
} else {
quote! { < #( #lifetimes ),* #( #types ),*> }
}
}
pub fn format_generic_params_replacing_by_self<'a>(
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(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 {
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 {
// TODO(b/202258760): Make `Copy` inclusion configurable.
record.is_unpin()
&& record.copy_constructor == SpecialMemberFunc::Trivial
&& record.destructor == ir::SpecialMemberFunc::Trivial
&& check_by_value(record).is_ok()
}
pub fn check_by_value(record: &Record) -> Result<()> {
if record.destructor == SpecialMemberFunc::Unavailable {
bail!(
"Can't directly construct values of type `{}` as it has a non-public or deleted destructor",
record.cc_name.as_ref()
)
}
if record.is_abstract {
bail!(
"Can't directly construct values of type `{}`: it is abstract",
record.cc_name.as_ref()
);
}
Ok(())
}
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum PrimitiveType {
/// (), void
Unit,
bool,
u8,
i8,
u16,
i16,
u32,
i32,
u64,
i64,
usize,
isize,
f32,
f64,
c_char,
c_uchar,
c_schar,
c_ushort,
c_short,
c_uint,
c_int,
c_ulong,
c_long,
c_ulonglong,
c_longlong,
}
impl PrimitiveType {
pub fn from_str(s: &str) -> Option<Self> {
Some(match s {
"()" => Self::Unit,
"bool" => Self::bool,
"u8" => Self::u8,
"i8" => Self::i8,
"u16" => Self::u16,
"i16" => Self::i16,
"u32" => Self::u32,
"i32" => Self::i32,
"u64" => Self::u64,
"i64" => Self::i64,
"usize" => Self::usize,
"isize" => Self::isize,
"f32" => Self::f32,
"f64" => Self::f64,
"::core::ffi::c_char" => Self::c_char,
"::core::ffi::c_uchar" => Self::c_uchar,
"::core::ffi::c_schar" => Self::c_schar,
"::core::ffi::c_ushort" => Self::c_ushort,
"::core::ffi::c_short" => Self::c_short,
"::core::ffi::c_uint" => Self::c_uint,
"::core::ffi::c_int" => Self::c_int,
"::core::ffi::c_ulong" => Self::c_ulong,
"::core::ffi::c_long" => Self::c_long,
"::core::ffi::c_ulonglong" => Self::c_ulonglong,
"::core::ffi::c_longlong" => Self::c_longlong,
_ => return None,
})
}
}
impl ToTokens for PrimitiveType {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
// This doesn't affect void in function return values, as those are special-cased to be
// omitted.
Self::Unit => quote! {::core::ffi::c_void},
Self::bool => quote! {bool},
Self::u8 => quote! {u8},
Self::i8 => quote! {i8},
Self::u16 => quote! {u16},
Self::i16 => quote! {i16},
Self::u32 => quote! {u32},
Self::i32 => quote! {i32},
Self::u64 => quote! {u64},
Self::i64 => quote! {i64},
Self::usize => quote! {usize},
Self::isize => quote! {isize},
Self::f32 => quote! {f32},
Self::f64 => quote! {f64},
Self::c_char => quote! {::core::ffi::c_char},
Self::c_uchar => quote! {::core::ffi::c_uchar},
Self::c_schar => quote! {::core::ffi::c_schar},
Self::c_ushort => quote! {::core::ffi::c_ushort},
Self::c_short => quote! {::core::ffi::c_short},
Self::c_uint => quote! {::core::ffi::c_uint},
Self::c_int => quote! {::core::ffi::c_int},
Self::c_ulong => quote! {::core::ffi::c_ulong},
Self::c_long => quote! {::core::ffi::c_long},
Self::c_ulonglong => quote! {::core::ffi::c_ulonglong},
Self::c_longlong => quote! {::core::ffi::c_longlong},
}
.to_tokens(tokens)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum RsTypeKind {
Pointer {
pointee: Rc<RsTypeKind>,
mutability: Mutability,
},
Reference {
referent: Rc<RsTypeKind>,
mutability: Mutability,
lifetime: Lifetime,
},
RvalueReference {
referent: Rc<RsTypeKind>,
mutability: Mutability,
lifetime: Lifetime,
},
FuncPtr {
abi: Rc<str>,
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>,
},
Enum {
enum_: Rc<Enum>,
crate_path: Rc<CratePath>,
},
TypeAlias {
type_alias: Rc<TypeAlias>,
underlying_type: Rc<RsTypeKind>,
crate_path: Rc<CratePath>,
},
Primitive(PrimitiveType),
Slice(Rc<RsTypeKind>),
/// Nullable T, using the rust Option type.
Option(Rc<RsTypeKind>),
Other {
name: Rc<str>,
type_args: Rc<[RsTypeKind]>,
is_same_abi: bool,
},
}
impl RsTypeKind {
pub fn new_record(record: Rc<Record>, ir: &IR) -> Result<Self> {
let crate_path = Rc::new(CratePath::new(
ir,
ir.namespace_qualifier(&record)?,
rs_imported_crate_name(&record.owning_target, ir),
));
Ok(RsTypeKind::Record { record, crate_path })
}
pub fn new_enum(enum_: Rc<Enum>, ir: &IR) -> Result<Self> {
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 })
}
pub fn new_type_map_override(
db: &dyn BindingsGenerator,
type_map_override: &TypeMapOverride,
) -> Result<Self> {
if type_map_override.rs_name.as_ref() == SLICE_REF_NAME_RS {
if type_map_override.type_parameters.len() != 1 {
bail!(
"SliceRef has {} type parameters, expected 1",
type_map_override.type_parameters.len()
);
}
let mapped_slice_type = type_map_override.type_parameters.first().unwrap();
let mapped_slice_type_rs = db.rs_type_kind(mapped_slice_type.rs_type.clone())?;
return Ok(RsTypeKind::Pointer {
pointee: Rc::new(RsTypeKind::Slice(Rc::new(mapped_slice_type_rs))),
mutability: if mapped_slice_type.cpp_type.is_const {
Mutability::Const
} else {
Mutability::Mut
},
});
}
Ok(RsTypeKind::Other {
name: type_map_override.rs_name.clone(),
type_args: Rc::from([]),
is_same_abi: type_map_override.is_same_abi,
})
}
/// Returns true if the type is known to be `Unpin`, false otherwise.
pub fn is_unpin(&self) -> bool {
match self {
RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => record.is_unpin(),
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.is_unpin(),
_ => true,
}
}
/// Returns true if this type is unsafe to pass across function boundaries.
///
/// In particular, anything representing a pointer with unknown lifetime is
/// unsafe.
pub fn is_unsafe(&self) -> bool {
// TODO(b/315346467): also include string_view, etc. here.
matches!(self, RsTypeKind::Pointer { .. })
}
/// Returns the features required to use this type which are not already
/// enabled.
///
/// 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,
enabled_features: flagset::FlagSet<ir::CrubitFeature>,
) -> (flagset::FlagSet<ir::CrubitFeature>, String) {
// TODO(b/318006909): Explain why a given feature is required, don't just return
// a FlagSet.
let mut missing_features = <flagset::FlagSet<ir::CrubitFeature>>::default();
let mut reasons = <std::collections::BTreeSet<std::borrow::Cow<'static, str>>>::new();
let mut require_feature =
|required_feature: ir::CrubitFeature,
reason: Option<&dyn Fn() -> std::borrow::Cow<'static, str>>| {
let required_features =
<flagset::FlagSet<ir::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::Pointer { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::Reference { .. } | RsTypeKind::RvalueReference { .. } => {
require_feature(
CrubitFeature::Experimental,
Some(&|| "references are not supported".into()),
);
}
RsTypeKind::FuncPtr { abi, .. } => {
if &**abi == "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::Experimental,
Some(&|| format!("{rs_type_kind} is not a complete type)").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 template instantiations, are
// only supported experimentally.
if rs_type_kind.is_unpin() && record.defining_target.is_none() {
require_feature(CrubitFeature::Supported, None)
} else {
require_feature(
CrubitFeature::Experimental,
Some(&|| {
format!("<internal link>_relocatable_error: {rs_type_kind} is not rust-movable").into()
}),
)
}
}
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::Slice { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::Option { .. } => require_feature(CrubitFeature::Supported, None),
// Fallback case, we can't really give a good error message here.
RsTypeKind::Other { .. } => require_feature(CrubitFeature::Experimental, 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 {
RsTypeKind::TypeAlias { underlying_type, .. } => {
underlying_type.is_c_abi_compatible_by_value()
}
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::Other { is_same_abi, .. } => *is_same_abi,
_ => true,
}
}
/// 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 {
RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => {
record.move_constructor != ir::SpecialMemberFunc::Unavailable
}
RsTypeKind::TypeAlias { underlying_type, .. } => {
underlying_type.is_move_constructible()
}
_ => 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 {
RsTypeKind::Record { record, .. } => check_by_value(record),
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.check_by_value(),
_ => Ok(()),
}
}
pub fn format_as_return_type_fragment(&self, self_record: Option<&Record>) -> TokenStream {
match self {
RsTypeKind::Primitive(PrimitiveType::Unit) => quote! {},
other_type => {
let other_type_ = other_type.to_token_stream_replacing_by_self(self_record);
quote! { -> #other_type_ }
}
}
}
/// Formats this RsTypeKind as `&'a mut MaybeUninit<SomeStruct>`. This is
/// used to format `__this` parameter in a constructor thunk.
pub fn format_mut_ref_as_uninitialized(&self) -> Result<TokenStream> {
match self {
RsTypeKind::Reference { referent, lifetime, mutability: Mutability::Mut } => {
let lifetime = lifetime.format_for_reference();
Ok(quote! { & #lifetime mut ::core::mem::MaybeUninit< #referent > })
}
_ => bail!("Expected reference to format as MaybeUninit, got: {:?}", self),
}
}
/// 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();
let arbitrary_self_types = make_rs_ident("arbitrary_self_types");
// TODO(b/239661934): Add `use ::ctor::{RvalueReference, ConstRvalueReference}`.
match mutability {
Mutability::Mut => Ok(RsSnippet {
tokens: quote! {self: ::ctor::RvalueReference<#lifetime, Self>},
features: [arbitrary_self_types].into_iter().collect(),
}),
Mutability::Const => Ok(RsSnippet {
tokens: quote! {self: ::ctor::ConstRvalueReference<#lifetime, Self>},
features: [arbitrary_self_types].into_iter().collect(),
}),
}
}
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::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::Option(t) => t.implements_copy(),
RsTypeKind::Slice(t) => t.implements_copy(),
RsTypeKind::Other { type_args, .. } => {
// All types that may appear here without `type_args` (e.g.
// primitive types like `i32`) implement `Copy`. Generic types
// that may be present here (e.g. Option<...>) are `Copy` if all
// of their `type_args` are `Copy`.
type_args.iter().all(|t| t.implements_copy())
}
}
}
pub fn is_ref_to(&self, expected_record: &Record) -> bool {
match self {
RsTypeKind::Reference { referent, .. } => referent.is_record(expected_record),
RsTypeKind::RvalueReference { referent, .. } => referent.is_record(expected_record),
_ => false,
}
}
pub fn is_shared_ref_to(&self, expected_record: &Record) -> bool {
match self {
RsTypeKind::Reference { referent, mutability: Mutability::Const, .. } => {
referent.is_record(expected_record)
}
_ => false,
}
}
pub fn is_record(&self, expected_record: &Record) -> bool {
match self {
RsTypeKind::Record { record: actual_record, .. } => {
actual_record.id == expected_record.id
}
_ => false,
}
}
pub fn is_bool(&self) -> bool {
match self {
RsTypeKind::Primitive(PrimitiveType::bool) => true,
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.is_bool(),
_ => false,
}
}
/// 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> + '_ {
self.dfs_iter().filter_map(Self::lifetime)
}
/// Returns the pointer or reference target.
pub fn referent(&self) -> Option<&RsTypeKind> {
match self {
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 {
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, self_record: Option<&Record>) -> TokenStream {
match self {
RsTypeKind::Pointer { pointee, mutability } => {
let mutability = mutability.format_for_pointer();
let pointee_ = pointee.to_token_stream_replacing_by_self(self_record);
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(self_record);
let reference = 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.)
quote! {::core::pin::Pin< #reference >}
} else {
reference
}
}
RsTypeKind::RvalueReference { referent, mutability, lifetime } => {
let referent_ = referent.to_token_stream_replacing_by_self(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 { abi, return_type, param_types } => {
let param_types_: Vec<TokenStream> = param_types
.iter()
.map(|type_| type_.to_token_stream_replacing_by_self(self_record))
.collect();
let return_frag = return_type.format_as_return_type_fragment(self_record);
let unsafe_ = if param_types.iter().any(|p| p.is_unsafe()) {
quote! {unsafe}
} else {
quote! {}
};
quote! { #unsafe_ extern #abi fn( #( #param_types_ ),* ) #return_frag }
}
RsTypeKind::Record { record, crate_path } => {
if self_record == Some(record) {
quote! { Self }
} else {
let ident = make_rs_ident(record.rs_name.as_ref());
quote! { #crate_path #ident }
}
}
RsTypeKind::Slice(t) => {
let type_arg = t.to_token_stream_replacing_by_self(self_record);
quote! {[#type_arg]}
}
RsTypeKind::Option(t) => {
let type_arg = t.to_token_stream_replacing_by_self(self_record);
// TODO(jeanpierreda): This should likely be `::core::option::Option`.
quote! {Option<#type_arg>}
}
RsTypeKind::Other { name, type_args, .. } => {
let name: TokenStream = name.parse().expect("Invalid RsType::name in the IR");
let generic_params =
format_generic_params_replacing_by_self(type_args.iter(), self_record);
quote! {#name #generic_params}
}
_ => self.to_token_stream(),
}
}
}
impl std::fmt::Display for RsTypeKind {
// 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) -> Result<(), std::fmt::Error> {
match write_unformatted_tokens(f, self.to_token_stream()) {
Ok(_) => Ok(()),
Err(e) => {
// Honestly this should never happen, but we should spit out something.
write!(f, "<error: {e}>")
}
}
}
}
impl ToTokens for RsTypeKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.to_token_stream().to_tokens(tokens)
}
fn to_token_stream(&self) -> TokenStream {
match self {
RsTypeKind::Pointer { pointee, mutability } => {
let mutability = mutability.format_for_pointer();
quote! {* #mutability #pointee}
}
RsTypeKind::Reference { referent, mutability, lifetime } => {
let mut_ = mutability.format_for_reference();
let lifetime = lifetime.format_for_reference();
let reference = 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.)
quote! {::core::pin::Pin< #reference >}
} else {
reference
}
}
RsTypeKind::RvalueReference { referent, mutability, lifetime } => {
// 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 { abi, return_type, param_types } => {
let return_frag = return_type.format_as_return_type_fragment(None);
let unsafe_ = if param_types.iter().any(|p| p.is_unsafe()) {
quote! {unsafe}
} else {
quote! {}
};
quote! { #unsafe_ extern #abi fn( #( #param_types ),* ) #return_frag }
}
RsTypeKind::IncompleteRecord { incomplete_record, crate_path } => {
let record_ident = make_rs_ident(incomplete_record.rs_name.as_ref());
quote! { #crate_path #record_ident }
}
RsTypeKind::Record { record, crate_path } => {
let ident = make_rs_ident(record.rs_name.as_ref());
quote! { #crate_path #ident }
}
RsTypeKind::Enum { enum_, crate_path } => {
let ident = make_rs_ident(&enum_.identifier.identifier);
quote! { #crate_path #ident }
}
RsTypeKind::TypeAlias { type_alias, crate_path, .. } => {
let ident = make_rs_ident(&type_alias.identifier.identifier);
quote! { #crate_path #ident }
}
RsTypeKind::Primitive(primitive) => quote! {#primitive},
RsTypeKind::Slice(t) => {
let type_arg = t.to_token_stream();
quote! {[#type_arg]}
}
RsTypeKind::Option(t) => {
// TODO(jeanpierreda): This should likely be `::core::option::Option`.
quote! {Option<#t>}
}
RsTypeKind::Other { name, type_args, .. } => {
let name: TokenStream = name.parse().expect("Invalid RsType::name in the IR");
let generic_params =
format_generic_params(/* lifetimes= */ &[], type_args.iter());
quote! {#name #generic_params}
}
}
}
}
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::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::Slice(t) => self.todo.push(t),
RsTypeKind::Option(t) => self.todo.push(t),
RsTypeKind::Other { type_args, .. } => self.todo.extend(type_args.iter().rev()),
};
Some(curr)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use token_stream_matchers::assert_rs_matches;
#[test]
fn test_dfs_iter_ordering() {
// Set up a test input representing: A<B<C>, D<E>>.
let a = {
let b = {
let c = RsTypeKind::Other {
name: "C".into(),
type_args: Rc::from([]),
is_same_abi: true,
};
RsTypeKind::Other { name: "B".into(), type_args: Rc::from([c]), is_same_abi: true }
};
let d = {
let e = RsTypeKind::Other {
name: "E".into(),
type_args: Rc::from([]),
is_same_abi: true,
};
RsTypeKind::Other { name: "D".into(), type_args: Rc::from([e]), is_same_abi: true }
};
RsTypeKind::Other { name: "A".into(), type_args: Rc::from([b, d]), is_same_abi: true }
};
let dfs_names = a
.dfs_iter()
.map(|t| match t {
RsTypeKind::Other { name, .. } => &**name,
_ => unreachable!("Only 'other' types are used in this test"),
})
.collect_vec();
assert_eq!(vec!["A", "B", "C", "D", "E"], dfs_names);
}
#[test]
fn test_dfs_iter_ordering_for_func_ptr() {
// Set up a test input representing: fn(A, B) -> C
let f = {
let a = RsTypeKind::Other {
name: "A".into(),
type_args: Rc::from(&[][..]),
is_same_abi: true,
};
let b = RsTypeKind::Other {
name: "B".into(),
type_args: Rc::from(&[][..]),
is_same_abi: true,
};
let c = RsTypeKind::Other {
name: "C".into(),
type_args: Rc::from(&[][..]),
is_same_abi: true,
};
RsTypeKind::FuncPtr {
abi: "blah".into(),
param_types: Rc::from([a, b]),
return_type: Rc::new(c),
}
};
let dfs_names = f
.dfs_iter()
.map(|t| match t {
RsTypeKind::FuncPtr { .. } => "fn",
RsTypeKind::Other { name, .. } => &**name,
_ => unreachable!("Only FuncPtr and Other kinds are used in this test"),
})
.collect_vec();
assert_eq!(vec!["fn", "A", "B", "C"], dfs_names);
}
#[test]
fn test_lifetime_elision_for_references() {
let type_args: &[RsTypeKind] = &[];
let referent = Rc::new(RsTypeKind::Other {
name: "T".into(),
type_args: type_args.into(),
is_same_abi: true,
});
let reference = RsTypeKind::Reference {
referent,
mutability: Mutability::Const,
lifetime: Lifetime::new("_"),
};
assert_rs_matches!(quote! {#reference}, quote! {&T});
}
#[test]
fn test_lifetime_elision_for_rvalue_references() {
let type_args: &[RsTypeKind] = &[];
let referent = Rc::new(RsTypeKind::Other {
name: "T".into(),
type_args: type_args.into(),
is_same_abi: true,
});
let reference = RsTypeKind::RvalueReference {
referent,
mutability: Mutability::Mut,
lifetime: Lifetime::new("_"),
};
assert_rs_matches!(quote! {#reference}, quote! {RvalueReference<'_, T>});
}
#[test]
fn test_format_as_self_param_rvalue_reference() -> Result<()> {
let type_args: &[RsTypeKind] = &[];
let referent = Rc::new(RsTypeKind::Other {
name: "T".into(),
type_args: type_args.into(),
is_same_abi: 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, [make_rs_ident("arbitrary_self_types")].into_iter().collect());
Ok(())
}
#[test]
fn test_format_as_self_param_const_rvalue_reference() -> Result<()> {
let type_args: &[RsTypeKind] = &[];
let referent = Rc::new(RsTypeKind::Other {
name: "T".into(),
type_args: type_args.into(),
is_same_abi: 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, [make_rs_ident("arbitrary_self_types")].into_iter().collect());
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().
#[test]
fn test_required_crubit_features() {
let no_types: &[RsTypeKind] = &[];
let int = RsTypeKind::Primitive(PrimitiveType::i32);
let reference = RsTypeKind::Reference {
referent: Rc::new(int.clone()),
mutability: Mutability::Const,
lifetime: Lifetime::new("_"),
};
for func_ptr in [
RsTypeKind::FuncPtr {
abi: "C".into(),
return_type: Rc::new(reference.clone()),
param_types: no_types.into(),
},
RsTypeKind::FuncPtr {
abi: "C".into(),
return_type: Rc::new(int),
param_types: Rc::from([reference]),
},
] {
let (missing_features, reason) =
func_ptr.required_crubit_features(<flagset::FlagSet<ir::CrubitFeature>>::default());
assert_eq!(
missing_features,
ir::CrubitFeature::Experimental | ir::CrubitFeature::Supported
);
assert_eq!(reason, "references are not supported");
}
}
}