Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions rs_bindings_from_cc/generate_bindings/database/function_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ pub enum TraitName {
PartialOrd {
param: Rc<RsTypeKind>,
},
/// The trait for the const C++ operator[] overload.
CcIndex {
index_type: Rc<RsTypeKind>,
output_type: Rc<RsTypeKind>,
},
/// The trait for the mutable C++ operator[] overload.
CcIndexMut {
index_type: Rc<RsTypeKind>,
output_type: Rc<RsTypeKind>,
},
/// Any other trait, e.g. Eq.
/// The operator::Delete trait.
Delete,
/// Any other trait, e.g. Eq.
Expand All @@ -80,6 +91,8 @@ impl TraitName {
TraitName::From { .. } => "From",
TraitName::PartialEq { .. } => "PartialEq",
TraitName::PartialOrd { .. } => "PartialOrd",
TraitName::CcIndex { .. } => "CcIndex",
TraitName::CcIndexMut { .. } => "CcIndexMut",
TraitName::Delete => "::operator::Delete",
TraitName::Other { name, .. } => name,
}
Expand All @@ -93,6 +106,9 @@ impl TraitName {
Self::PartialEq { param, .. } | Self::PartialOrd { param } => {
core::slice::from_ref(param)
}
Self::CcIndex { index_type, .. } | Self::CcIndexMut { index_type, .. } => {
core::slice::from_ref(index_type)
}
}
}

Expand Down
298 changes: 274 additions & 24 deletions rs_bindings_from_cc/generate_bindings/generate_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ fn trait_name_to_token_stream_removing_trait_record(
quote! {PartialOrd #formatted_params}
}
}
CcIndex { index_type, .. } | CcIndexMut { index_type, .. } => {
let trait_path = match trait_name {
CcIndex { .. } => quote! { ::operator::CcIndex },
CcIndexMut { .. } => quote! { ::operator::CcIndexMut },
_ => unreachable!(),
};
if trait_record.is_some() && index_type.is_record(trait_record.unwrap()) {
quote! {#trait_path}
} else {
let formatted_params = format_generic_params_replacing_by_self(
db,
core::slice::from_ref(&**index_type),
trait_record,
);
quote! {#trait_path #formatted_params}
}
}
Other { name, params, .. } => {
let name_as_token_stream = name.parse::<TokenStream>().unwrap();
let formatted_params =
Expand Down Expand Up @@ -316,6 +333,195 @@ fn api_func_shape_for_operator_eq(
Ok((func_name, impl_kind))
}

/// TODO: b/242938276 - For now, just emit one impl per overload, either CcIndex or CcIndexMut.
/// Do not generate native core::ops::Index or core::ops::IndexMut impls yet. This means that Rust
/// callers must call .cc_index and .cc_index_mut rather than use bracket syntax.
///
/// Other current limitations.
/// - Does not support C++ overloads that return items by value.
/// - Does not support C++ overloads that use multiple indices.
/// - Does not support C++ explicit object parameters, i.e. decltype(auto).
fn api_func_shape_for_operator_index(
db: &BindingsGenerator,
func: &Func,
param_types: &mut [RsTypeKind],
errors: &Errors,
) -> ErrorsOr<(Ident, ImplKind)> {
let CcTypeVariant::Pointer(pointee) = &func.return_type.variant else {
bail_to_errors!(
errors,
"operator[] should return a reference, found {:?}",
&func.return_type.variant
)
};
let return_val_is_const = pointee.pointee_type.is_const;

let Some(instance_method_metadata) = &func.instance_method_metadata else {
panic!("cannot tell whether operator[] is const or not, shouldn't happen")
};
let method_is_const = instance_method_metadata.is_const;

let [container_type, index_type] = param_types else {
bail_to_errors!(
errors,
"Expected operator[] to have exactly two parameters. Found: {}.",
param_types.len(),
);
};

let (container_record, _is_ref) = match container_type {
RsTypeKind::Reference { referent, .. } => {
let RsTypeKind::Record { record, .. } = &**referent else {
bail_to_errors!(
errors,
"Expected the 'this' parameter to refer to a record type, but found {:?}.",
pointee
);
};
(record.clone(), true)
}
_ => {
bail_to_errors!(
errors,
"Unexpected type for 'this' parameter of operator[]: {:?}.",
container_type
);
}
};

let index_type_rc = Rc::new(index_type.clone());
if return_val_is_const && method_is_const {
generate_cc_operator_index_nonmut_impls(db, func, container_record, index_type_rc, errors)
} else if !return_val_is_const && !method_is_const {
generate_cc_operator_index_mut_impls(db, func, container_record, index_type_rc, errors)
} else {
bail_to_errors!(
errors,
"operator[] must either:\n\
(a) be a const method that returns a const reference, or,\n\
(b) be a non-const method that returns a non-const reference.\n\
Instead found a method: (which is const?)={}, and (whose return value is const?)={}",
method_is_const,
return_val_is_const
)
}
}

fn generate_cc_operator_index_nonmut_impls(
db: &BindingsGenerator,
func: &Func,
container_record: Rc<Record>,
index_type: Rc<RsTypeKind>,
errors: &Errors,
) -> ErrorsOr<(Ident, ImplKind)> {
let func_name = make_rs_ident("cc_index");
let output_pointee_cc_type = match &func.return_type.variant {
CcTypeVariant::Pointer(pointer_data) => {
if !matches!(pointer_data.kind, PointerTypeKind::LValueRef) {
errors.add(anyhow!(
"operator[] must return an lvalue reference (e.g. const T&), but found {:?}",
pointer_data.kind
));
}
if !pointer_data.pointee_type.is_const {
errors.add(anyhow!("operator[] must return a const value"));
}

(*pointer_data.pointee_type).clone()
}

other_variant => {
bail_to_errors!(
errors,
"operator[] should return a reference (values are not yet supported), found {:?}",
other_variant
)
}
};

let output_type: Rc<RsTypeKind> = match db.rs_type_kind(output_pointee_cc_type) {
Ok(rs_kind) => Rc::new(rs_kind),
Err(err) => {
bail_to_errors!(
errors,
"In the return value of operator[], could not convert C++ pointee to a Rust equivalent: {}",
err
)
}
};

let impl_kind = ImplKind::Trait {
record: container_record,
trait_name: TraitName::CcIndex { index_type, output_type },
impl_for: ImplFor::T,
trait_generic_params: Rc::new([]),
format_first_param_as_self: true,
drop_return: false,
associated_return_type: Some(make_rs_ident("Output")),
force_const_reference_params: false,
always_public: false,
};
Ok((func_name, impl_kind))
}

fn generate_cc_operator_index_mut_impls(
db: &BindingsGenerator,
func: &Func,
container_record: Rc<Record>,
index_type: Rc<RsTypeKind>,
errors: &Errors,
) -> ErrorsOr<(Ident, ImplKind)> {
let func_name = make_rs_ident("cc_index_mut");

let output_pointee_cc_type = match &func.return_type.variant {
CcTypeVariant::Pointer(pointer_data) => {
if !matches!(pointer_data.kind, PointerTypeKind::LValueRef) {
errors.add(anyhow!(
"operator[] must return an lvalue reference (e.g. const T&), but found {:?}",
pointer_data.kind
));
}
if pointer_data.pointee_type.is_const {
errors.add(anyhow!("(mutable) operator[] must return a non-const value"));
}

(*pointer_data.pointee_type).clone()
}

other_variant => {
bail_to_errors!(
errors,
"(mutable) operator[] should return a reference (values are not yet supported), found {:?}",
other_variant
)
}
};

let output_type: Rc<RsTypeKind> = match db.rs_type_kind(output_pointee_cc_type) {
Ok(rs_kind) => Rc::new(rs_kind),
Err(err) => {
bail_to_errors!(
errors,
"In the return value of operator[], could not convert C++ pointee to a Rust equivalent: {}",
err
)
}
};

let impl_kind = ImplKind::Trait {
record: container_record,
trait_name: TraitName::CcIndexMut { index_type, output_type },
impl_for: ImplFor::T,
trait_generic_params: Rc::new([]),
format_first_param_as_self: true,
drop_return: false,
associated_return_type: Some(make_rs_ident("Output")),
force_const_reference_params: false,
always_public: false,
};
Ok((func_name, impl_kind))
}

fn api_func_shape_for_operator_lt(
db: &BindingsGenerator,
func: &Func,
Expand Down Expand Up @@ -567,6 +773,7 @@ fn api_func_shape_for_operator(
"+" if param_types.len() == 1 => {
api_func_shape_for_operator_unary_plus(db, &param_types[0], errors).ok()
}
"[]" => api_func_shape_for_operator_index(db, func, param_types, errors).ok(),
_ => {
let Some(op_metadata) =
OPERATOR_METADATA.by_cc_name_and_params.get(&(&op.name, param_types.len()))
Expand Down Expand Up @@ -1638,7 +1845,15 @@ pub fn generate_function(
}

let function_return_type = match &impl_kind {
ImplKind::Trait { associated_return_type: Some(ident), .. } => quote! {Self::#ident},
ImplKind::Trait { trait_name, associated_return_type: Some(ident), .. } => {
if matches!(trait_name, TraitName::CcIndex { .. } | TraitName::CcIndexMut { .. }) {
let gat_lifetime =
lifetimes.first().map(|l| quote! {#l}).unwrap_or_else(|| quote! {'_});
quote! {Self::#ident<#gat_lifetime>}
} else {
quote! {Self::#ident}
}
}
_ => quoted_return_type.clone(),
};
let arrow = if !function_return_type.is_empty() {
Expand Down Expand Up @@ -1730,13 +1945,21 @@ pub fn generate_function(
};

let mut extra_body = if let Some(name) = associated_return_type {
let quoted_return_type = if quoted_return_type.is_empty() {
quote! {()}
if let TraitName::CcIndex { .. } | TraitName::CcIndexMut { .. } = trait_name {
let gat_lifetime =
lifetimes.first().map(|l| quote! {#l}).unwrap_or_else(|| quote! {'a});
quote! {
type #name<#gat_lifetime> = #quoted_return_type;
}
} else {
quoted_return_type
};
quote! {
type #name = #quoted_return_type;
let quoted_return_type = if quoted_return_type.is_empty() {
quote! {()}
} else {
quoted_return_type
};
quote! {
type #name = #quoted_return_type;
}
}
} else if let TraitName::PartialOrd { param } = &trait_name {
let quoted_param_or_self = match impl_for {
Expand Down Expand Up @@ -2141,7 +2364,13 @@ fn function_signature(
};
}
}
Some(TraitName::Other { .. } | TraitName::Delete) | None => {}
Some(
TraitName::CcIndex { .. }
| TraitName::CcIndexMut { .. }
| TraitName::Other { .. }
| TraitName::Delete,
)
| None => {}
}

let return_type_fragment = if matches!(
Expand Down Expand Up @@ -2188,25 +2417,46 @@ fn function_signature(
match impl_kind {
ImplKind::None { .. } => unreachable!(),
ImplKind::Struct { .. } | ImplKind::Trait { impl_for: ImplFor::T, .. } => {
// In the ImplFor::T reference style (which is implied for ImplKind::Struct) the
// impl block is for `T`. The `self` parameter has a type determined by the
// first parameter (typically a reference of some kind) and can be passed to a
// thunk via the expression `self`.
if first_api_param.is_c_abi_compatible_by_value() {
let rs_snippet = first_api_param.format_as_self_param()?;
thunk_args[0] = if derived_record.is_some() {
quote! { oops::Upcast::<_>::upcast(self) }
} else {
quote! { self }
let is_cc_index_mut_method = matches!(
impl_kind,
ImplKind::Trait { trait_name: TraitName::CcIndexMut { .. }, .. }
);
if is_cc_index_mut_method {
// Always use Pin<&mut Self> for CcIndexMut::cc_index_mut's receiver.
match first_api_param {
RsTypeKind::Reference { lifetime, referent, .. } => {
let lifetime_token = lifetime.format_for_reference();
api_params[0] =
quote! { self: ::core::pin::Pin< & #lifetime_token mut Self > };
if referent.is_unpin() {
thunk_args[0] = quote! { self.get_unchecked_mut() };
} else {
thunk_args[0] = quote! { self };
}
}
_ => unreachable!(),
};
api_params[0] = rs_snippet.tokens;
*features |= rs_snippet.features;
} else {
api_params[0] = quote! { mut self };
if derived_record.is_some() {
thunk_args[0] = quote! { oops::Upcast::<_>::upcast(&mut self) };
// In the ImplFor::T reference style (which is implied for ImplKind::Struct) the
// impl block is for `T`. The `self` parameter has a type determined by the
// first parameter (typically a reference of some kind) and can be passed to a
// thunk via the expression `self`.
if first_api_param.is_c_abi_compatible_by_value() {
let rs_snippet = first_api_param.format_as_self_param()?;
thunk_args[0] = if derived_record.is_some() {
quote! { oops::Upcast::<_>::upcast(self) }
} else {
quote! { self }
};
api_params[0] = rs_snippet.tokens;
*features |= rs_snippet.features;
} else {
thunk_args[0] = quote! { &mut self };
api_params[0] = quote! { mut self };
if derived_record.is_some() {
thunk_args[0] = quote! { oops::Upcast::<_>::upcast(&mut self) };
} else {
thunk_args[0] = quote! { &mut self };
}
}
}
}
Expand Down
Loading