| // 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 |
| |
| use arc_anyhow::{ensure, Context, Result}; |
| use code_gen_utils::make_rs_ident; |
| use crubit_abi_type::CrubitAbiTypeToRustTokens; |
| use database::code_snippet::{ApiSnippets, Visibility}; |
| use database::function_types::{FunctionId, GeneratedFunction, ImplFor, ImplKind, TraitName}; |
| use database::rs_snippet::{ |
| check_by_value, format_generic_params, format_generic_params_replacing_by_self, |
| should_derive_clone, unique_lifetimes, Lifetime, Mutability, RsTypeKind, |
| }; |
| use database::BindingsGenerator; |
| use error_report::{anyhow, bail, ErrorList}; |
| use errors::{bail_to_errors, Errors, ErrorsOr}; |
| use generate_comment::generate_doc_comment; |
| use generate_function_thunk::{ |
| generate_function_thunk, generate_function_thunk_impl, thunk_ident, |
| thunk_ident_for_derived_member_function, |
| }; |
| use ir::*; |
| use itertools::Itertools; |
| use proc_macro2::{Ident, TokenStream}; |
| use quote::{format_ident, quote, ToTokens}; |
| use std::collections::{BTreeSet, HashMap, HashSet}; |
| use std::fmt::Write as _; |
| use std::ptr; |
| use std::rc::Rc; |
| use std::sync::LazyLock; |
| |
| /// Similar to to_tokens but removing a given record type from the list of |
| /// generic args |
| /// |
| /// This is used to remove the record whose trait implementation is being |
| /// generated. |
| fn trait_name_to_token_stream_removing_trait_record( |
| db: &dyn BindingsGenerator, |
| trait_name: &TraitName, |
| trait_record: Option<&Record>, |
| ) -> TokenStream { |
| use TraitName::*; |
| match trait_name { |
| UnpinConstructor { name, params } | Other { name, params, .. } => { |
| let name_as_token_stream = name.parse::<TokenStream>().unwrap(); |
| let formatted_params = |
| format_generic_params_replacing_by_self(db, &**params, trait_record); |
| quote! {#name_as_token_stream #formatted_params} |
| } |
| PartialEq { param } => { |
| if trait_record.is_some() && param.is_record(trait_record.unwrap()) { |
| quote! {PartialEq} |
| } else { |
| let formatted_params = format_generic_params_replacing_by_self( |
| db, |
| core::slice::from_ref(&**param), |
| trait_record, |
| ); |
| quote! {PartialEq #formatted_params} |
| } |
| } |
| PartialOrd { param } => { |
| if trait_record.is_some() && param.is_record(trait_record.unwrap()) { |
| quote! {PartialOrd} |
| } else { |
| let formatted_params = format_generic_params_replacing_by_self( |
| db, |
| core::slice::from_ref(&**param), |
| trait_record, |
| ); |
| quote! {PartialOrd #formatted_params} |
| } |
| } |
| CtorNew(arg_types) => { |
| let formatted_arg_types = |
| format_tuple_except_singleton_replacing_by_self(db, arg_types, trait_record); |
| quote! { ::ctor::CtorNew < #formatted_arg_types > } |
| } |
| } |
| } |
| |
| fn trait_name_to_token_stream(db: &dyn BindingsGenerator, trait_name: &TraitName) -> TokenStream { |
| trait_name_to_token_stream_removing_trait_record(db, trait_name, None) |
| } |
| |
| /// Returns whether an argument of this type causes ADL to include the `record`. |
| fn adl_expands_to(record: &Record, rs_type_kind: &RsTypeKind) -> bool { |
| match rs_type_kind { |
| RsTypeKind::Record { record: nested_record, .. } => ptr::eq(record, &**nested_record), |
| RsTypeKind::Reference { referent, .. } => adl_expands_to(record, referent), |
| RsTypeKind::RvalueReference { referent, .. } => adl_expands_to(record, referent), |
| _ => false, |
| } |
| } |
| |
| /// Returns whether any type in `param_types` causes ADL to include `record`. |
| /// |
| /// This is an under-approximation. Things not considered include class template |
| /// arguments and the parameters and return type of function types. |
| /// |
| /// See https://en.cppreference.com/w/cpp/language/adl |
| fn is_visible_by_adl(enclosing_record: &Record, param_types: &[RsTypeKind]) -> bool { |
| param_types.iter().any(|param_type| adl_expands_to(enclosing_record, param_type)) |
| } |
| |
| #[derive(Debug)] |
| struct OperatorMetadata { |
| by_cc_name_and_params: HashMap<(&'static str, usize), OperatorMetadataEntry>, |
| } |
| |
| #[derive(Clone, Copy, Debug)] |
| struct OperatorMetadataEntry { |
| cc_name: &'static str, |
| cc_params: usize, |
| trait_name: &'static str, |
| method_name: &'static str, |
| is_compound_assignment: bool, |
| } |
| |
| impl OperatorMetadataEntry { |
| const fn unary( |
| cc_name: &'static str, |
| trait_name: &'static str, |
| method_name: &'static str, |
| ) -> Self { |
| Self { cc_name, cc_params: 1, trait_name, method_name, is_compound_assignment: false } |
| } |
| |
| const fn binary( |
| cc_name: &'static str, |
| trait_name: &'static str, |
| method_name: &'static str, |
| ) -> Self { |
| Self { cc_name, cc_params: 2, trait_name, method_name, is_compound_assignment: false } |
| } |
| |
| const fn assign( |
| cc_name: &'static str, |
| trait_name: &'static str, |
| method_name: &'static str, |
| ) -> Self { |
| Self { cc_name, cc_params: 2, trait_name, method_name, is_compound_assignment: true } |
| } |
| } |
| |
| static OPERATOR_METADATA: LazyLock<OperatorMetadata> = LazyLock::new(|| { |
| const ENTRIES: &[OperatorMetadataEntry] = &[ |
| OperatorMetadataEntry::unary("-", "Neg", "neg"), |
| // The Rust `Not` trait matches with both the C++ `!` and `~` operators to some extent. The |
| // two operators appear with similar frequency in our target codebase so it's not clear |
| // which is better to map here. Mapping `operator!` to `Not` as chosen here means that a |
| // C++ `!` matches up with a Rust `!`. |
| OperatorMetadataEntry::unary("!", "Not", "not"), |
| OperatorMetadataEntry::binary("+", "Add", "add"), |
| OperatorMetadataEntry::binary("-", "Sub", "sub"), |
| OperatorMetadataEntry::binary("*", "Mul", "mul"), |
| OperatorMetadataEntry::binary("/", "Div", "div"), |
| OperatorMetadataEntry::binary("%", "Rem", "rem"), |
| OperatorMetadataEntry::binary("&", "BitAnd", "bitand"), |
| OperatorMetadataEntry::binary("|", "BitOr", "bitor"), |
| OperatorMetadataEntry::binary("^", "BitXor", "bitxor"), |
| OperatorMetadataEntry::binary("<<", "Shl", "shl"), |
| OperatorMetadataEntry::binary(">>", "Shr", "shr"), |
| OperatorMetadataEntry::assign("+=", "AddAssign", "add_assign"), |
| OperatorMetadataEntry::assign("-=", "SubAssign", "sub_assign"), |
| OperatorMetadataEntry::assign("*=", "MulAssign", "mul_assign"), |
| OperatorMetadataEntry::assign("/=", "DivAssign", "div_assign"), |
| OperatorMetadataEntry::assign("%=", "RemAssign", "rem_assign"), |
| OperatorMetadataEntry::assign("&=", "BitAndAssign", "bitand_assign"), |
| OperatorMetadataEntry::assign("|=", "BitOrAssign", "bitor_assign"), |
| OperatorMetadataEntry::assign("^=", "BitXorAssign", "bitxor_assign"), |
| OperatorMetadataEntry::assign("<<=", "ShlAssign", "shl_assign"), |
| OperatorMetadataEntry::assign(">>=", "ShrAssign", "shr_assign"), |
| ]; |
| OperatorMetadata { |
| by_cc_name_and_params: ENTRIES.iter().map(|e| ((e.cc_name, e.cc_params), *e)).collect(), |
| } |
| }); |
| |
| /// Whether the function is a friend of a record, but the record is not visible |
| /// by ADL. |
| /// |
| /// This is a heuristic to avoid generating bindings for functions that are not |
| /// visible by ADL. |
| /// |
| /// This is necessary because ADL is needed in order to find friend functions. |
| fn is_friend_of_record_not_visible_by_adl( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| param_types: &[RsTypeKind], |
| ) -> bool { |
| let Some(decl_id) = func.adl_enclosing_record else { return false }; |
| let ir = db.ir(); |
| let adl_enclosing_record = ir |
| .find_decl::<Rc<Record>>(decl_id) |
| .with_context(|| format!("Failed to look up `adl_enclosing_record` of {:?}", func)) |
| .unwrap(); |
| !is_visible_by_adl(adl_enclosing_record, param_types) |
| } |
| |
| /// Ensures that `kind` is a record or a const reference to a record. |
| /// |
| /// Returns the `RsTypeKind` and `Record` of the underlying record type. |
| fn type_by_value_or_under_const_ref<'a>( |
| db: &dyn BindingsGenerator, |
| kind: &'a mut RsTypeKind, |
| value_desc: &str, |
| errors: &Errors, |
| ) -> ErrorsOr<(&'a RsTypeKind, &'a Rc<Record>)> { |
| // Pre-record `kind_string` before any adjustments occur. |
| let kind_string = kind.display(db).to_string(); |
| match *kind { |
| RsTypeKind::Reference { referent: ref lhs, ref mut mutability, .. } => { |
| if !mutability.is_const() { |
| // NOTE: bindings will never actually rely on this immutability, as we |
| // the resulting generated code will always result in a compiler error if used |
| // because of the `errors.add`. |
| errors.add(anyhow!("Expected {value_desc} reference to be immutable, but found mutable reference: {kind_string}")); |
| *mutability = Mutability::Const; |
| } |
| if let RsTypeKind::Record { record: lhs_record, .. } = &**lhs { |
| Ok((lhs, lhs_record)) |
| } else { |
| bail_to_errors!(errors, "Expected {value_desc} to be a record or a const reference to a record, found a reference that doesn't refer to a record: {kind_string}"); |
| } |
| } |
| RsTypeKind::Record { record: ref lhs_record, .. } => Ok((kind, lhs_record)), |
| _ => bail_to_errors!( |
| errors, |
| "Expected {value_desc} to be a record or const reference to a record, found {kind_string}" |
| ), |
| } |
| } |
| |
| fn api_func_shape_for_operator_eq( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| param_types: &mut [RsTypeKind], |
| errors: &Errors, |
| ) -> ErrorsOr<(Ident, ImplKind)> { |
| // C++ requires that operator== is binary. |
| let [param_1, param_2] = param_types else { |
| panic!("Expected operator== to have exactly two parameters. Found: {func:?}"); |
| }; |
| |
| let lhs_ty = type_by_value_or_under_const_ref(db, param_1, "first operator== param", errors); |
| let rhs_ty = type_by_value_or_under_const_ref(db, param_2, "second operator== param", errors); |
| let ((_, lhs_record), (param, _)) = (lhs_ty?, rhs_ty?); |
| let param = Rc::new(param.clone()); |
| let func_name = make_rs_ident("eq"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::PartialEq { param }, |
| lhs_record.clone(), |
| /* format_first_param_as_self= */ true, |
| /* force_const_reference_params= */ true, |
| ); |
| Ok((func_name, impl_kind)) |
| } |
| |
| fn api_func_shape_for_operator_lt( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| param_types: &mut [RsTypeKind], |
| errors: &Errors, |
| ) -> ErrorsOr<(Ident, ImplKind)> { |
| let [param_1, param_2] = param_types else { |
| panic!("Expected operator< to have exactly two parameters. Found: {func:?}") |
| }; |
| let lhs_ty = type_by_value_or_under_const_ref(db, param_1, "first operator< param", errors); |
| let rhs_ty = type_by_value_or_under_const_ref(db, param_2, "second operator< param", errors); |
| let ((_, lhs_record), (param, rhs_record)) = (lhs_ty?, rhs_ty?); |
| // Even though Rust and C++ allow operator< to be implemented on different |
| // types, we don't generate bindings for them at this moment. The |
| // issue is that our canonical implementation of partial_cmp relies |
| // on transitivity. This would require checking that both lt(&T1, |
| // &T2) and lt(&T2, &T1) are implemented. In other words, both lt |
| // implementations would need to query for the existence of the other, which |
| // would create a cyclic dependency. |
| if lhs_record != rhs_record { |
| bail_to_errors!( |
| errors, |
| "operator< where lhs and rhs are not the same type. This is not yet supported." |
| ); |
| } |
| let param = param.clone(); |
| let lhs_record = lhs_record.clone(); |
| // PartialOrd requires PartialEq, so we need to make sure operator== is |
| // implemented for this Record type. |
| let partialeq_binding = get_binding( |
| db, |
| UnqualifiedIdentifier::Operator(Operator { name: Rc::from("==") }), |
| param_types.to_vec(), |
| ); |
| match partialeq_binding { |
| Some((_, ImplKind::Trait { trait_name: TraitName::PartialEq { .. }, .. })) => {} |
| _ => errors.add(anyhow!("operator< where operator== is missing.")), |
| } |
| let func_name = make_rs_ident("lt"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::PartialOrd { param: Rc::new(param) }, |
| lhs_record.clone(), |
| /* format_first_param_as_self= */ true, |
| /* force_const_reference_params= */ true, |
| ); |
| Ok((func_name, impl_kind)) |
| } |
| |
| fn api_func_shape_for_operator_assign( |
| func: &Func, |
| maybe_record: Option<&Rc<Record>>, |
| param_types: &mut [RsTypeKind], |
| errors: &Errors, |
| ) -> ErrorsOr<(Ident, ImplKind)> { |
| assert_eq!(param_types.len(), 2, "Unexpected number of parameters in operator=: {func:?}"); |
| let Some(record) = maybe_record else { |
| bail_to_errors!(errors, "operator= must be a member function") |
| }; |
| materialize_ctor_in_caller(func, param_types); |
| |
| let rhs = ¶m_types[1]; |
| |
| // TODO(b/219963671): consolidate UnpinAssign and Assign in ctor.rs |
| let trait_name; |
| let func_name; |
| if record.is_unpin() { |
| trait_name = Rc::from("::ctor::UnpinAssign"); |
| func_name = make_rs_ident("unpin_assign"); |
| } else { |
| trait_name = Rc::from("::ctor::Assign"); |
| func_name = make_rs_ident("assign") |
| }; |
| |
| let impl_kind = ImplKind::Trait { |
| record: record.clone(), |
| trait_name: TraitName::Other { |
| name: trait_name, |
| params: Rc::new([rhs.clone()]), |
| is_unsafe_fn: false, |
| }, |
| impl_for: ImplFor::T, |
| trait_generic_params: Rc::new([]), |
| format_first_param_as_self: true, |
| drop_return: true, |
| associated_return_type: None, |
| force_const_reference_params: false, |
| }; |
| Ok((func_name, impl_kind)) |
| } |
| |
| fn api_func_shape_for_operator_unary_plus( |
| db: &dyn BindingsGenerator, |
| param_type: &RsTypeKind, |
| errors: &Errors, |
| ) -> ErrorsOr<(Ident, ImplKind)> { |
| let (record, _) = extract_first_operator_parameter(db, param_type, errors)?; |
| Ok(( |
| make_rs_ident("unary_plus"), |
| ImplKind::Struct { is_unsafe: false, record, format_first_param_as_self: true }, |
| )) |
| } |
| |
| fn extract_first_operator_parameter( |
| db: &dyn BindingsGenerator, |
| param_types: &RsTypeKind, |
| errors: &Errors, |
| ) -> ErrorsOr<(Rc<Record>, ImplFor)> { |
| match param_types { |
| RsTypeKind::Record { record, .. } => Ok((record.clone(), ImplFor::T)), |
| RsTypeKind::IncompleteRecord { incomplete_record, .. } => { |
| bail_to_errors!( |
| errors, |
| "Incomplete record types are not yet supported as first parameter of operator, found {cc_name}", cc_name=incomplete_record.cc_name, |
| ) |
| } |
| RsTypeKind::Reference { referent, .. } => Ok(( |
| expect_possibly_incomplete_record(db, referent, "first operator parameter", errors)? |
| .clone(), |
| ImplFor::RefT, |
| )), |
| RsTypeKind::RvalueReference { .. } => { |
| bail_to_errors!( |
| errors, |
| "Rvalue reference types are not yet supported as first parameter of operators (b/219826128)", |
| ) |
| } |
| _ => bail_to_errors!( |
| errors, |
| "Non-record-nor-reference operator parameters are not yet supported, found {}", |
| param_types.display(db) |
| ), |
| } |
| } |
| |
| fn expect_possibly_incomplete_record<'a>( |
| db: &dyn BindingsGenerator, |
| type_kind: &'a RsTypeKind, |
| value_desc: &str, |
| errors: &Errors, |
| ) -> ErrorsOr<&'a Rc<Record>> { |
| match type_kind { |
| RsTypeKind::Record { record, .. } => Ok(record), |
| RsTypeKind::IncompleteRecord { .. } => bail_to_errors!( |
| errors, |
| "Incomplete record types are not yet supported as {value_desc}, found {}", |
| type_kind.display(db), |
| ), |
| _ => bail_to_errors!( |
| errors, |
| "Expected {value_desc} to be a record or incomplete record, found {}", |
| type_kind.display(db), |
| ), |
| } |
| } |
| |
| fn record_type_of_compound_assignment<'a>( |
| db: &dyn BindingsGenerator, |
| lhs_type: &'a mut RsTypeKind, |
| errors: &Errors, |
| ) -> ErrorsOr<&'a Rc<Record>> { |
| let lhs_str = lhs_type.display(db).to_string(); |
| let fix_mutability = |mutability: &mut Mutability| { |
| if mutability.is_const() { |
| errors.add(anyhow!( |
| "Compound assignment with const left-hand side is not supported, found {lhs_str}" |
| )); |
| *mutability = Mutability::Mut; |
| } |
| }; |
| |
| let lhs_record_type: &Rc<Record> = match lhs_type { |
| RsTypeKind::Record { record, .. } => { |
| errors.add(anyhow!("Compound assignment with by-value left-hand side is not yet supported, found {lhs_str}")); |
| record |
| } |
| RsTypeKind::IncompleteRecord { .. } => { |
| bail_to_errors!(errors, "Compound assignment with incomplete record left-hand side is not yet supported, found {lhs_str}") |
| } |
| RsTypeKind::Reference { referent, mutability, .. } => { |
| fix_mutability(mutability); |
| expect_possibly_incomplete_record(db, referent, "compound assignment first parameter", errors)? |
| } |
| RsTypeKind::RvalueReference { referent, mutability, .. } => { |
| errors.add(anyhow!("Compound assignment with rvalue reference is not yet supported (b/219826128), found {lhs_str}")); |
| fix_mutability(mutability); |
| expect_possibly_incomplete_record(db, referent, "compound assignment first parameter", errors)? |
| } |
| RsTypeKind::Pointer { pointee, mutability } => { |
| errors.add(anyhow!("Compound assignment operators are not yet supported for pointers with unknown lifetime (b/219826128), found {lhs_str}")); |
| fix_mutability(mutability); |
| expect_possibly_incomplete_record(db, pointee, "compound assignment first parameter", errors)? |
| } |
| _ => panic!("Compound assignment operator defined, but first parameter is not a record or reference: {lhs_str}"), |
| }; |
| if !lhs_record_type.is_unpin() { |
| // Note: we bail here to avoid generating an impl with `self: Pin<&mut Self>` |
| // which will fail to compile. This could be relaxed in the future. |
| bail_to_errors!( |
| errors, |
| "Compound assignment operators are not supported for non-Unpin types, found {lhs_str}" |
| ); |
| } |
| Ok(lhs_record_type) |
| } |
| |
| /// Reports a fatal error generating bindings for a function. |
| /// Fatal errors should only be reported |
| fn report_fatal_func_error(db: &dyn BindingsGenerator, func: &Func, msg: &str) { |
| db.fatal_errors().report(&format!("{}: {}", func.source_loc, msg)); |
| } |
| |
| fn api_func_shape_for_operator( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| maybe_record: Option<&Rc<Record>>, |
| param_types: &mut [RsTypeKind], |
| op: &Operator, |
| errors: &Errors, |
| ) -> ErrorsOr<(Ident, ImplKind)> { |
| if let SafetyAnnotation::Unsafe = func.safety_annotation { |
| report_fatal_func_error(db, func, "Unsafe annotations on operators are not supported"); |
| } |
| match op.name.as_ref() { |
| "==" => api_func_shape_for_operator_eq(db, func, param_types, errors), |
| "<=>" => { |
| bail_to_errors!(errors, "Three-way comparison operator not yet supported (b/219827738)") |
| } |
| "<" => api_func_shape_for_operator_lt(db, func, param_types, errors), |
| "=" => api_func_shape_for_operator_assign(func, maybe_record, param_types, errors), |
| "+" if param_types.len() == 1 => { |
| api_func_shape_for_operator_unary_plus(db, ¶m_types[0], errors) |
| } |
| _ => { |
| let Some(op_metadata) = |
| OPERATOR_METADATA.by_cc_name_and_params.get(&(&op.name, param_types.len())) |
| else { |
| bail_to_errors!( |
| errors, |
| "Bindings for this kind of operator (operator {op} with {n} parameter(s)) are not supported", |
| op = &op.name, |
| n = param_types.len(), |
| ); |
| }; |
| materialize_ctor_in_caller(func, param_types); |
| let trait_name = op_metadata.trait_name; |
| if op_metadata.is_compound_assignment { |
| let record = record_type_of_compound_assignment(db, &mut param_types[0], errors)?; |
| let func_name = make_rs_ident(op_metadata.method_name); |
| let impl_kind = ImplKind::Trait { |
| record: record.clone(), |
| trait_name: TraitName::Other { |
| name: Rc::from(format!("::core::ops::{trait_name}")), |
| params: Rc::from(¶m_types[1..]), |
| is_unsafe_fn: false, |
| }, |
| impl_for: ImplFor::T, |
| trait_generic_params: Rc::new([]), |
| format_first_param_as_self: true, |
| drop_return: true, |
| associated_return_type: None, |
| force_const_reference_params: false, |
| }; |
| Ok((func_name, impl_kind)) |
| } else { |
| let (record, impl_for) = |
| extract_first_operator_parameter(db, ¶m_types[0], errors)?; |
| let func_name = make_rs_ident(op_metadata.method_name); |
| let impl_kind = ImplKind::Trait { |
| record, |
| trait_name: TraitName::Other { |
| name: Rc::from(format!("::core::ops::{trait_name}")), |
| params: Rc::from(¶m_types[1..]), |
| is_unsafe_fn: false, |
| }, |
| impl_for, |
| 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, |
| }; |
| Ok((func_name, impl_kind)) |
| } |
| } |
| } |
| } |
| |
| fn api_func_shape_for_identifier( |
| func: &Func, |
| maybe_record: Option<&Rc<Record>>, |
| param_types: &mut [RsTypeKind], |
| id: &Identifier, |
| is_unsafe: bool, |
| ) -> (Ident, ImplKind) { |
| let func_name = make_rs_ident(&id.identifier); |
| let Some(record) = maybe_record else { return (func_name, ImplKind::None { is_unsafe }) }; |
| let format_first_param_as_self = if func.is_instance_method() { |
| let Some(first_param) = param_types.first() else { |
| panic!("Missing `__this` parameter in an instance method: {:?}", func); |
| }; |
| first_param.is_ref_to(record) |
| } else { |
| false |
| }; |
| (func_name, ImplKind::Struct { record: record.clone(), format_first_param_as_self, is_unsafe }) |
| } |
| |
| fn api_func_shape_for_destructor( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| maybe_record: Option<&Rc<Record>>, |
| param_types: &mut [RsTypeKind], |
| ) -> Option<(Ident, ImplKind)> { |
| if let SafetyAnnotation::Unsafe = func.safety_annotation { |
| report_fatal_func_error(db, func, "Unsafe annotations on destructors are not supported"); |
| } |
| let Some(record) = maybe_record else { |
| panic!("Destructors are always member functions, but found: {func:?}"); |
| }; |
| // Note: to avoid double-destruction of the fields, they are all wrapped in |
| // ManuallyDrop in this case. See `generate_record`. |
| if !record.should_implement_drop() { |
| return None; |
| } |
| if record.is_unpin() { |
| let func_name = make_rs_ident("drop"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::Other { name: Rc::from("Drop"), params: Rc::from([]), is_unsafe_fn: false }, |
| record.clone(), |
| /* format_first_param_as_self= */ true, |
| /* force_const_reference_params= */ |
| false, |
| ); |
| Some((func_name, impl_kind)) |
| } else { |
| materialize_ctor_in_caller(func, param_types); |
| let func_name = make_rs_ident("pinned_drop"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::Other { |
| name: Rc::from("::ctor::PinnedDrop"), |
| params: Rc::from([]), |
| is_unsafe_fn: true, |
| }, |
| record.clone(), |
| /* format_first_param_as_self= */ true, |
| /* force_const_reference_params= */ false, |
| ); |
| Some((func_name, impl_kind)) |
| } |
| } |
| |
| fn api_func_shape_for_constructor( |
| func: &Func, |
| maybe_record: Option<&Rc<Record>>, |
| param_types: &mut [RsTypeKind], |
| is_unsafe: bool, |
| errors: &Errors, |
| ) -> Option<(Ident, ImplKind)> { |
| let Some(record) = maybe_record else { |
| panic!("Constructors must be associated with a record."); |
| }; |
| if is_unsafe { |
| // TODO(b/216648347): Allow this outside of traits (e.g. after supporting |
| // translating C++ constructors into static methods in Rust). |
| errors.add(anyhow!( |
| "Unsafe constructors (e.g. with no elided or explicit lifetimes) \ |
| are intentionally not supported. See b/216648347.", |
| )); |
| } |
| if let Err(err) = check_by_value(record) { |
| errors.add(err); |
| } |
| materialize_ctor_in_caller(func, param_types); |
| if !record.is_unpin() { |
| let func_name = make_rs_ident("ctor_new"); |
| let [_this, params @ ..] = param_types else { |
| panic!("Missing `__this` parameter in a constructor: {:?}", func) |
| }; |
| let impl_kind = ImplKind::Trait { |
| record: record.clone(), |
| trait_name: TraitName::CtorNew(params.iter().cloned().collect()), |
| impl_for: ImplFor::T, |
| trait_generic_params: Rc::new([]), |
| format_first_param_as_self: false, |
| drop_return: false, |
| associated_return_type: Some(make_rs_ident("CtorType")), |
| force_const_reference_params: false, |
| }; |
| return Some((func_name, impl_kind)); |
| } |
| match func.params.len() { |
| 0 => panic!("Missing `__this` parameter in a constructor: {:?}", func), |
| 1 => { |
| let func_name = make_rs_ident("default"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::UnpinConstructor { name: Rc::from("Default"), params: Rc::from([]) }, |
| record.clone(), |
| /* format_first_param_as_self= */ false, |
| /* force_const_reference_params= */ false, |
| ); |
| Some((func_name, impl_kind)) |
| } |
| 2 if param_types[1].is_shared_ref_to(record) => { |
| // Copy constructor |
| if should_derive_clone(record) { |
| None |
| } else { |
| let func_name = make_rs_ident("clone"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::UnpinConstructor { name: Rc::from("Clone"), params: Rc::from([]) }, |
| record.clone(), |
| /* format_first_param_as_self= */ true, |
| /* force_const_reference_params= */ false, |
| ); |
| Some((func_name, impl_kind)) |
| } |
| } |
| 2 => { |
| let param_type = ¶m_types[1]; |
| let func_name = make_rs_ident("from"); |
| let impl_kind = ImplKind::new_trait( |
| TraitName::UnpinConstructor { |
| name: Rc::from("From"), |
| params: Rc::from([param_type.clone()]), |
| }, |
| record.clone(), |
| /* format_first_param_as_self= */ false, |
| /* force_const_reference_params= */ |
| false, |
| ); |
| Some((func_name, impl_kind)) |
| } |
| _ => { |
| // TODO(b/216648347): Support bindings for other constructors. |
| errors.add(anyhow!( |
| "Constructors with more than one parameter are not yet supported. See b/216648347." |
| )); |
| None |
| } |
| } |
| } |
| |
| /// Returns the shape of the generated Rust API for a given function definition. |
| /// |
| /// If the shape is a trait, this also mutates the parameter types to be |
| /// trait-compatible. In particular, types which would be `impl Ctor<Output=T>` |
| /// become a `RvalueReference<'_, T>`. |
| /// |
| /// Returns: |
| /// |
| /// * `Err(_)`: something went wrong importing this function. |
| /// * `Ok(None)`: the function imported as "nothing". (For example, a defaulted |
| /// destructor might be mapped to no `Drop` impl at all.) |
| /// * `Ok((func_name, impl_kind))`: The function name and ImplKind. |
| fn api_func_shape( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| param_types: &mut [RsTypeKind], |
| errors: &Errors, |
| ) -> Option<(Ident, ImplKind)> { |
| let ir = db.ir(); |
| let maybe_record = match ir.record_for_member_func(func).map(<&Rc<Record>>::try_from) { |
| None => None, |
| Some(Ok(record)) => Some(record), |
| // Functions whose record was replaced with some other IR Item type are ignored. |
| // This occurs for instance if you use crubit_internal_rust_type: member functions defined |
| // out-of-line, such as implicitly generated constructors, will still be present in the IR, |
| // but should be ignored. |
| Some(Err(_)) => return None, |
| }; |
| |
| if is_friend_of_record_not_visible_by_adl(db, func, param_types) { |
| return None; |
| } |
| |
| let is_unsafe = match func.safety_annotation { |
| SafetyAnnotation::Unannotated => { |
| param_types.iter().any(|p| db.is_rs_type_kind_unsafe(p.clone())) |
| } |
| SafetyAnnotation::Unsafe => true, |
| SafetyAnnotation::DisableUnsafe => false, |
| }; |
| |
| match &func.rs_name { |
| UnqualifiedIdentifier::Operator(op) => { |
| api_func_shape_for_operator(db, func, maybe_record, param_types, op, errors).ok() |
| } |
| UnqualifiedIdentifier::Identifier(id) => { |
| Some(api_func_shape_for_identifier(func, maybe_record, param_types, id, is_unsafe)) |
| } |
| UnqualifiedIdentifier::Destructor => { |
| api_func_shape_for_destructor(db, func, maybe_record, param_types) |
| } |
| UnqualifiedIdentifier::Constructor => { |
| api_func_shape_for_constructor(func, maybe_record, param_types, is_unsafe, errors) |
| } |
| } |
| } |
| |
| /// Returns the shape of the generated Rust API for a given function definition |
| /// or `None` if no function will be generated. |
| fn api_func_shape_if_some( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| param_types: &mut [RsTypeKind], |
| ) -> Option<(Ident, ImplKind)> { |
| let errors = Errors::new(); |
| let shape = api_func_shape(db, func, param_types, &errors); |
| if !errors.is_empty() { |
| errors.discard(); |
| return None; |
| } |
| shape |
| } |
| |
| /// Implementation of `BindingsGenerator::get_binding`. |
| pub fn get_binding( |
| db: &dyn BindingsGenerator, |
| expected_function_name: UnqualifiedIdentifier, |
| expected_param_types: Vec<RsTypeKind>, |
| ) -> Option<(Ident, ImplKind)> { |
| db.ir() |
| .get_functions_by_name(&expected_function_name) |
| .filter(|function| { |
| generate_function(db, (*function).clone(), None).ok().flatten().is_some() |
| }) |
| .find_map(|function| { |
| let mut function_param_types = function |
| .params |
| .iter() |
| .map(|param| db.rs_type_kind(param.type_.clone())) |
| .collect::<Result<Vec<_>>>() |
| .ok()?; |
| if !function_param_types.iter().eq(expected_param_types.iter()) { |
| return None; |
| } |
| api_func_shape_if_some(db, function, &mut function_param_types) |
| }) |
| } |
| |
| /// Implementation of `BindingsGenerator::is_record_clonable`. |
| pub fn is_record_clonable(db: &dyn BindingsGenerator, record: Rc<Record>) -> bool { |
| if !record.is_unpin() { |
| return false; |
| } |
| should_derive_clone(&record) |
| || db |
| .ir() |
| .get_functions_by_name(&UnqualifiedIdentifier::Constructor) |
| .filter(|function| { |
| // __this is always the first parameter of constructors |
| function.params.len() == 2 |
| }) |
| .any(|function| { |
| let mut function_param_types = function |
| .params |
| .iter() |
| .map(|param| db.rs_type_kind(param.type_.clone())) |
| .collect::<Result<Vec<_>>>() |
| .unwrap_or_default(); |
| if function.params.len() != 2 || !function_param_types[1].is_shared_ref_to(&record) |
| { |
| return false; |
| } |
| api_func_shape_if_some(db, function, &mut function_param_types) |
| .is_some_and(|(func_name, _)| func_name == *"clone") |
| }) |
| } |
| |
| /// Mutates the provided parameters so that nontrivial by-value parameters are, |
| /// instead, materialized in the caller and passed by rvalue reference. |
| fn materialize_ctor_in_caller(func: &Func, params: &mut [RsTypeKind]) { |
| let mut existing_lifetime_params: HashSet<Rc<str>> = |
| params.iter().flat_map(|param| param.lifetimes().map(|lifetime| lifetime.0)).collect(); |
| let mut new_lifetime_param = |mut lifetime_name: String| { |
| let suffix_start = lifetime_name.len(); |
| let mut next_suffix = 2; |
| loop { |
| if !existing_lifetime_params.contains(&*lifetime_name) { |
| let lifetime_name = <Rc<str>>::from(lifetime_name); |
| existing_lifetime_params.insert(lifetime_name.clone()); |
| return Lifetime(lifetime_name); |
| } |
| lifetime_name.truncate(suffix_start); |
| write!(lifetime_name, "_{next_suffix}").unwrap(); |
| next_suffix += 1; |
| } |
| }; |
| for (func_param, param) in func.params.iter().zip(params.iter_mut()) { |
| if param.is_unpin() { |
| continue; |
| } |
| let value = std::mem::replace(param, RsTypeKind::Primitive(Primitive::Void)); // Temporarily swap in a garbage value. |
| *param = RsTypeKind::RvalueReference { |
| referent: Rc::new(value), |
| mutability: Mutability::Mut, |
| lifetime: new_lifetime_param(func_param.identifier.identifier.to_string()), |
| }; |
| } |
| } |
| |
| /// A series of adjustments to apply to parameter values to make them compatible |
| /// with the Rust trait signature (e.g. adding `&mut ...` or `.clone()`). |
| struct ParamValueAdjustments { |
| clone_prefixes: Vec<TokenStream>, |
| clone_suffixes: Vec<TokenStream>, |
| } |
| |
| /// Applies adjustments to the `param_types` of a function to make it compatible |
| /// with the Rust trait signature. |
| /// |
| /// If the Rust trait requires a function to take the params by const reference |
| /// and the thunk takes some of its params by value then we should add a const |
| /// reference around these Rust func params and clone the records when calling |
| /// the thunk. |
| /// |
| /// This function mutates `param_types` in-place and returns a |
| /// `param_types`-length list containing any necessary adjustments to the |
| /// parameter values. |
| fn adjust_param_types_for_trait_impl( |
| db: &dyn BindingsGenerator, |
| impl_kind: &ImplKind, |
| param_types: &mut [RsTypeKind], |
| errors: &Errors, |
| ) -> ParamValueAdjustments { |
| let ImplKind::Trait { trait_name, force_const_reference_params: true, .. } = impl_kind else { |
| return ParamValueAdjustments { |
| clone_prefixes: vec![TokenStream::new(); param_types.len()], |
| clone_suffixes: vec![TokenStream::new(); param_types.len()], |
| }; |
| }; |
| let (clone_prefixes, clone_suffixes) = param_types |
| .iter_mut() |
| .enumerate() |
| .map(|(i, param_type)| { |
| let RsTypeKind::Record { record: param_record, .. } = ¶m_type else { |
| return Default::default(); |
| }; |
| if !is_record_clonable(db, param_record.clone()) { |
| errors.add(anyhow!( |
| "Argument {i} of Rust trait `{trait_name:?}` requires a const reference, but the C++ implementation takes non-cloneable record `{}` by value", |
| param_type.display(db), |
| )); |
| return Default::default(); |
| } |
| *param_type = RsTypeKind::Reference { |
| option: false, |
| referent: Rc::new(param_type.clone()), |
| mutability: Mutability::Const, |
| lifetime: Lifetime::new("_"), |
| }; |
| (quote! {&mut }, quote! {.clone()}) |
| }) |
| .unzip(); |
| ParamValueAdjustments { clone_prefixes, clone_suffixes } |
| } |
| |
| #[allow(clippy::too_many_arguments)] |
| fn generate_func_body( |
| db: &dyn BindingsGenerator, |
| impl_kind: &ImplKind, |
| crate_root_path: TokenStream, |
| return_type: &RsTypeKind, |
| param_value_adjustments: &ParamValueAdjustments, |
| thunk_ident: Ident, |
| thunk_prepare: TokenStream, |
| thunk_args: Vec<TokenStream>, |
| ) -> Result<TokenStream> { |
| let ParamValueAdjustments { clone_prefixes, clone_suffixes } = param_value_adjustments; |
| |
| if let ImplKind::Trait { trait_name: TraitName::UnpinConstructor { .. }, .. } = &impl_kind { |
| // SAFETY: A user-defined constructor is not guaranteed to |
| // initialize all the fields. To make the `assume_init()` call |
| // below safe, the memory is zero-initialized first. This is a |
| // bit safer, because zero-initialized memory represents a valid |
| // value for the currently supported field types (this may |
| // change once the bindings generator starts supporting |
| // reference fields). TODO(b/213243309): Double-check if |
| // zero-initialization is desirable here. |
| Ok(quote! { |
| let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed(); |
| unsafe { |
| #crate_root_path::detail::#thunk_ident( &raw mut tmp as *mut ::core::ffi::c_void #( , #thunk_args )* ); |
| tmp.assume_init() |
| } |
| }) |
| } else { |
| // Note: for the time being, all !Unpin values are treated as if they were not |
| // trivially relocatable. We could, in the special case of trivial !Unpin types, |
| // not generate the thunk at all, but this would be a bit of extra work. |
| // |
| // TODO(jeanpierreda): separately handle non-Unpin and non-trivial types. |
| let mut body = if return_type.is_c_abi_compatible_by_value() { |
| quote! { |
| #crate_root_path::detail::#thunk_ident( |
| #( #clone_prefixes #thunk_args #clone_suffixes ),* |
| ) |
| } |
| } else { |
| let return_type_or_self = { |
| let record = match impl_kind { |
| ImplKind::Struct { ref record, .. } |
| | ImplKind::Trait { ref record, impl_for: ImplFor::T, .. } => Some(&**record), |
| _ => None, |
| }; |
| return_type.to_token_stream_replacing_by_self(db, record) |
| }; |
| if return_type.is_crubit_abi_bridge_type() { |
| let crubit_abi_type = db.crubit_abi_type(return_type.clone())?; |
| let crubit_abi_type_tokens = CrubitAbiTypeToRustTokens(&crubit_abi_type); |
| quote! { |
| ::bridge_rust::unstable_return!(#crubit_abi_type_tokens, |__return_abi_buffer| { |
| #crate_root_path::detail::#thunk_ident( |
| __return_abi_buffer, |
| #(#clone_prefixes #thunk_args #clone_suffixes ),* |
| ); |
| }) |
| } |
| } else if return_type.is_unpin() { |
| quote! { |
| let mut __return = ::core::mem::MaybeUninit::<#return_type_or_self>::uninit(); |
| #crate_root_path::detail::#thunk_ident( |
| &raw mut __return as *mut ::core::ffi::c_void |
| #( , #clone_prefixes #thunk_args #clone_suffixes )* |
| ); |
| __return.assume_init() |
| } |
| } else { |
| // TODO(b/200067242): the Pin-wrapping code doesn't know to wrap &mut |
| // MaybeUninit<T> in Pin if T is !Unpin. It should understand |
| // 'structural pinning', so that we do not need into_inner_unchecked() |
| // here. |
| quote! { |
| ::ctor::FnCtor::new( |
| move |dest: ::core::pin::Pin<&mut ::core::mem::MaybeUninit<#return_type_or_self>>| { |
| #crate_root_path::detail::#thunk_ident( |
| ::core::pin::Pin::into_inner_unchecked(dest) as *mut _ as *mut ::core::ffi::c_void |
| #( , #thunk_args )* |
| ); |
| }) |
| } |
| } |
| }; |
| // Discard the return value if requested (for example, when calling a C++ |
| // operator that returns a value from a Rust trait that returns |
| // unit). |
| if let ImplKind::Trait { drop_return: true, .. } = impl_kind { |
| if return_type.is_unpin() { |
| // If it's unpin, just discard it: |
| body = quote! { #body; }; |
| } else { |
| // Otherwise, in order to discard the return value and return void, we |
| // need to run the constructor. |
| body = quote! {let _ = ::ctor::emplace!(#body);}; |
| } |
| |
| // We would need to do this, but it's no longer used: |
| // return_type = RsTypeKind::Primitive(PrimitiveType::Unit); |
| let _ = return_type; // proof that we don't need to update it. |
| } |
| // Only need to wrap everything in an `unsafe { ... }` block if |
| // the *whole* api function is safe. |
| if !impl_kind.is_unsafe() { |
| body = quote! { unsafe { #body } }; |
| } |
| Ok(quote! { |
| #thunk_prepare |
| #body |
| }) |
| } |
| } |
| |
| /// A structure describing how to represent any errors that occured as an |
| /// unsatisfied trait bound. See `errors_as_unsatisfied_trait_bound` for more |
| /// details. |
| /// |
| /// Note: a default value of this structure represents no errors. |
| #[derive(Default)] |
| struct ErrorsAsUnsatisfiedTraitBound { |
| // like `'error` |
| lifetime_param: Option<Lifetime>, |
| // like "where &'error (): BindingGenerationFailure" |
| unsatisfied_where_clause: TokenStream, |
| // like `#[diagnostic::on_unimplemented(...)] trait BindingGenerationFailure {}` |
| unimplemented_trait_def: TokenStream, |
| } |
| |
| /// Represents `reportable_errors` as an unsatisfied trait bound which will |
| /// report the errors when a user attempts to compile a usage of the generated |
| /// function or trait that the `unsatisfied_where_clause` is attached to. |
| /// |
| /// This generates code like: |
| /// |
| /// ``` |
| /// #[diagnostic::on_unimplemented(message = "binding generation for function failed\n...")] |
| /// pub trait BindingFailedFor{unique_id} {} |
| /// |
| /// fn generated_api_func<'a>() where &'error (): BindingFailedFor{unique_id} { unreachable!() } |
| /// ``` |
| /// |
| /// Note: the `lifetime_param` `'error` is only needed until the |
| /// `trivial_bounds` feature is stable, see: https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956 |
| fn errors_as_unsatisfied_trait_bound( |
| reportable_errors: &Result<(), ErrorList>, |
| unique_id: &str, |
| ) -> ErrorsAsUnsatisfiedTraitBound { |
| let Err(reportable_errors) = reportable_errors else { |
| return ErrorsAsUnsatisfiedTraitBound::default(); |
| }; |
| let lt = Lifetime::new("error"); |
| let trait_name = format_ident!("BindingFailedFor{}", unique_id); |
| let unsatisfied_where_clause = quote! { where & #lt (): #trait_name }; |
| let message = format!("binding generation for function failed\n{reportable_errors}"); |
| let unimplemented_trait_def = quote! { |
| #[diagnostic::on_unimplemented(message = #message)] |
| pub trait #trait_name {} |
| }; |
| ErrorsAsUnsatisfiedTraitBound { |
| lifetime_param: Some(lt), |
| unsatisfied_where_clause, |
| unimplemented_trait_def, |
| } |
| } |
| |
| /// Implementation of `BindingsGenerator::generate_function`. |
| pub fn generate_function( |
| db: &dyn BindingsGenerator, |
| func: Rc<Func>, |
| derived_record: Option<Rc<Record>>, |
| ) -> Result<Option<GeneratedFunction>> { |
| let ir = db.ir(); |
| let crate_root_path = ir.crate_root_path_tokens(); |
| let mut features = BTreeSet::new(); |
| let param_errors = Errors::new(); |
| let mut param_types: Vec<RsTypeKind> = func |
| .params |
| .iter() |
| .enumerate() |
| .filter_map(|(i, p)| { |
| db.rs_type_kind(p.type_.clone()) |
| .map_err(|err| { |
| param_errors.add(anyhow!("Failed to format type of parameter {i}: {err}")) |
| }) |
| .ok() |
| }) |
| .collect(); |
| param_errors.consolidate()?; |
| |
| let errors = Errors::new(); |
| let (func_name, mut impl_kind) = |
| if let Some(values) = api_func_shape(db, &func, &mut param_types, &errors) { |
| values |
| } else { |
| errors.consolidate()?; |
| return Ok(None); |
| }; |
| let namespace_qualifier = ir.namespace_qualifier(&func).format_for_rs(); |
| |
| let mut return_type = errors.consolidate_on_err( |
| db.rs_type_kind(func.return_type.clone()).with_context(|| "Failed to format return type"), |
| )?; |
| if let Err(err) = return_type.check_by_value() { |
| errors.add(err); |
| } |
| let param_idents = |
| func.params.iter().map(|p| make_rs_ident(&p.identifier.identifier)).collect_vec(); |
| let thunk = generate_function_thunk( |
| db, |
| &func, |
| ¶m_idents, |
| ¶m_types, |
| &return_type, |
| derived_record.clone(), |
| ) |
| .unwrap_or_else(|err| { |
| errors.add(err); |
| TokenStream::new() |
| }); |
| |
| let param_value_adjustments = |
| adjust_param_types_for_trait_impl(db, &impl_kind, &mut param_types, &errors); |
| |
| let BindingsSignature { |
| mut lifetimes, |
| params: api_params, |
| return_type_fragment: mut quoted_return_type, |
| thunk_prepare, |
| thunk_args, |
| } = errors.consolidate_on_err(function_signature( |
| db, |
| &mut features, |
| &func, |
| &impl_kind, |
| ¶m_idents, |
| &mut param_types, |
| &mut return_type, |
| derived_record.clone(), |
| &errors, |
| ))?; |
| |
| if let ImplKind::Trait { drop_return: true, .. } = impl_kind { |
| quoted_return_type = quote! {}; |
| } |
| |
| if !errors.is_empty() { |
| if let ImplKind::Trait { trait_name: TraitName::CtorNew(_), .. } = impl_kind { |
| // Generated CtorNew functions return an `impl Trait` type which can't use |
| // the `errors_as_unsatisfied_trait_bound` reporting system because |
| // the `'error` lifetime causes an error when combined with `impl Trait due to |
| // https://github.com/rust-lang/rust/issues/134804 |
| errors.consolidate()?; |
| } |
| } |
| |
| let reportable_status: Result<(), ErrorList> = errors.consolidate(); |
| let failed = reportable_status.is_err(); |
| |
| let ErrorsAsUnsatisfiedTraitBound { |
| lifetime_param: error_lifetime_param, |
| mut unsatisfied_where_clause, |
| unimplemented_trait_def, |
| } = errors_as_unsatisfied_trait_bound(&reportable_status, &func.mangled_name); |
| |
| let api_func_def = { |
| let thunk_ident = if let Some(ref derived_record) = derived_record { |
| thunk_ident_for_derived_member_function(&func, derived_record.clone()) |
| } else { |
| thunk_ident(&func) |
| }; |
| |
| let func_body = if reportable_status.is_ok() { |
| generate_func_body( |
| db, |
| &impl_kind, |
| crate_root_path, |
| &return_type, |
| ¶m_value_adjustments, |
| thunk_ident, |
| thunk_prepare, |
| thunk_args, |
| )? |
| } else { |
| quote! { |
| #![allow(unused_variables)] |
| unreachable!( |
| "This impl can never be instantiated. \ |
| If this message appears at runtime, please report a <internal link>." |
| ) |
| } |
| }; |
| |
| // If there are no bindings, use `Public` for the sake of "keeping on going" when |
| // collecting errors for items that will not actually be generated. |
| let visibility = |
| db.has_bindings(ir::Item::Func(func.clone())).unwrap_or_default().visibility; |
| let pub_ = match (&impl_kind, visibility) { |
| (ImplKind::Trait { .. }, Visibility::Public) => quote! {}, |
| (ImplKind::Trait { .. }, Visibility::PubCrate) => { |
| bail!("Trait implementations cannot be restricted to wrappers with pub(crate)") |
| } |
| (_, visibility) => quote! {#visibility}, |
| }; |
| let unsafe_ = if impl_kind.is_unsafe() { |
| quote! { unsafe } |
| } else { |
| quote! {} |
| }; |
| |
| // If we don't have an outer `impl ... { ... }` block, we have to introduce the |
| // lifetimes and bounds inside this one. |
| let has_wrapping_impl = match impl_kind { |
| ImplKind::None { .. } => false, |
| ImplKind::Struct { .. } | ImplKind::Trait { .. } => true, |
| }; |
| let where_clause = if has_wrapping_impl { |
| None |
| } else { |
| if let Some(lt) = &error_lifetime_param { |
| lifetimes.insert(0, lt.clone()); |
| } |
| Some(core::mem::take(&mut unsatisfied_where_clause)) |
| }; |
| |
| let fn_generic_params: TokenStream; |
| if let ImplKind::Trait { trait_name, trait_generic_params, impl_for, .. } = &mut impl_kind { |
| // When the impl block is for some kind of reference to T, consider the lifetime |
| // parameters on the self parameter to be trait lifetimes so they can be |
| // introduced before they are used. |
| let first_param_lifetimes = match (impl_for, param_types.first()) { |
| (ImplFor::RefT, Some(first_param)) => Some(first_param.lifetimes()), |
| _ => None, |
| }; |
| |
| let trait_lifetimes: HashSet<Lifetime> = |
| trait_name.lifetimes().chain(first_param_lifetimes.into_iter().flatten()).collect(); |
| fn_generic_params = format_generic_params( |
| lifetimes.iter().filter(|lifetime| !trait_lifetimes.contains(lifetime)), |
| std::iter::empty::<syn::Ident>(), |
| ); |
| *trait_generic_params = Rc::from( |
| lifetimes |
| .iter() |
| .filter_map(|lifetime| { |
| if trait_lifetimes.contains(lifetime) { |
| Some(lifetime.clone()) |
| } else { |
| None |
| } |
| }) |
| .collect::<Vec<Lifetime>>(), |
| ); |
| } else { |
| fn_generic_params = format_generic_params(&lifetimes, std::iter::empty::<syn::Ident>()); |
| } |
| |
| let function_return_type = match &impl_kind { |
| ImplKind::Trait { associated_return_type: Some(ident), .. } => quote! {Self::#ident}, |
| _ => quoted_return_type.clone(), |
| }; |
| let arrow = if !function_return_type.is_empty() { |
| quote! {->} |
| } else { |
| quote! {} |
| }; |
| |
| quote! { |
| #[inline(always)] |
| #pub_ #unsafe_ fn #func_name #fn_generic_params( |
| #( #api_params ),* ) #arrow #function_return_type #where_clause { |
| #func_body |
| } |
| } |
| }; |
| |
| let doc_comment = |
| generate_doc_comment(func.doc_comment.as_deref(), Some(&func.source_loc), db.environment()); |
| let api_func: TokenStream; |
| let function_id: FunctionId; |
| match impl_kind { |
| ImplKind::None { .. } => { |
| api_func = quote! { #unimplemented_trait_def #doc_comment #api_func_def }; |
| function_id = FunctionId { |
| self_type: None, |
| function_path: syn::parse2(quote! { #namespace_qualifier #func_name }).unwrap(), |
| }; |
| } |
| ImplKind::Struct { record, .. } => { |
| let record_name; |
| if let Some(ref derived_record) = derived_record { |
| // Generate the bindings for the derived record. |
| record_name = make_rs_ident(derived_record.rs_name.identifier.as_ref()); |
| } else { |
| record_name = make_rs_ident(record.rs_name.identifier.as_ref()); |
| }; |
| let error_lifetime_generic = match &error_lifetime_param { |
| Some(lifetime) => quote! { <#lifetime> }, |
| None => quote! {}, |
| }; |
| api_func = quote! { #unimplemented_trait_def impl #error_lifetime_generic #record_name #unsatisfied_where_clause { #doc_comment #api_func_def } }; |
| function_id = FunctionId { |
| self_type: None, |
| function_path: syn::parse2(quote! { |
| #namespace_qualifier #record_name :: #func_name |
| }) |
| .unwrap(), |
| }; |
| } |
| ImplKind::Trait { |
| record: trait_record, |
| trait_name, |
| impl_for, |
| trait_generic_params, |
| associated_return_type, |
| .. |
| } => { |
| let extra_body = if let Some(name) = associated_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 { |
| ImplFor::T => param.to_token_stream_replacing_by_self(db, Some(&trait_record)), |
| ImplFor::RefT => param.to_token_stream(db), |
| }; |
| quote! { |
| #[inline(always)] |
| fn partial_cmp(&self, other: & #quoted_param_or_self) -> Option<core::cmp::Ordering> { |
| if self == other { |
| return Some(core::cmp::Ordering::Equal); |
| } |
| if self < other { |
| return Some(core::cmp::Ordering::Less); |
| } |
| if other < self { |
| return Some(core::cmp::Ordering::Greater); |
| } |
| None |
| } |
| } |
| } else { |
| quote! {} |
| }; |
| |
| let record_name = make_rs_ident(trait_record.rs_name.identifier.as_ref()); |
| let trait_lifetime_params = error_lifetime_param.as_slice(); |
| // NOTE: `trait_generic_params` may include lifetimes! |
| let formatted_trait_generic_params = |
| format_generic_params(trait_lifetime_params, &*trait_generic_params); |
| let extra_items = match &trait_name { |
| TraitName::CtorNew(params) if params.len() == 1 => { |
| let single_param_ = format_tuple_except_singleton_replacing_by_self( |
| db, |
| params, |
| Some(&trait_record), |
| ); |
| quote! { |
| impl #formatted_trait_generic_params ::ctor::CtorNew<(#single_param_,)> for #record_name #unsatisfied_where_clause { |
| #extra_body |
| |
| #[inline (always)] |
| fn ctor_new(args: (#single_param_,)) -> Self::CtorType { |
| let (arg,) = args; |
| <Self as ::ctor::CtorNew<#single_param_>>::ctor_new(arg) |
| } |
| } |
| } |
| } |
| TraitName::UnpinConstructor { name, params } |
| if *name == Rc::from("From") && reportable_status.is_ok() => |
| { |
| let single_param_ = format_tuple_except_singleton_replacing_by_self( |
| db, |
| params, |
| Some(&trait_record), |
| ); |
| quote! { |
| impl #formatted_trait_generic_params ::ctor::CtorNew<#single_param_> for #record_name #unsatisfied_where_clause { |
| type CtorType = Self; |
| |
| #[inline (always)] |
| fn ctor_new(args: #single_param_) -> Self::CtorType { |
| <Self as From<#single_param_>>::from(args) |
| } |
| } |
| } |
| } |
| _ => { |
| quote! {} |
| } |
| }; |
| let record_qualifier = ir.namespace_qualifier(&trait_record).format_for_rs(); |
| let full_record_qualifier = if Some(trait_record.id) == func.enclosing_item_id { |
| // If the method is defined in the record, then the record qualifier is not |
| // needed for better readability. |
| quote! {} |
| } else { |
| quote! { crate :: #record_qualifier } |
| }; |
| let (trait_name_without_trait_record, impl_for) = match impl_for { |
| ImplFor::T => ( |
| trait_name_to_token_stream_removing_trait_record( |
| db, |
| &trait_name, |
| Some(&trait_record), |
| ), |
| quote! { #full_record_qualifier #record_name }, |
| ), |
| ImplFor::RefT => ( |
| trait_name_to_token_stream(db, &trait_name), |
| param_types[0].to_token_stream(db), |
| ), |
| }; |
| api_func = quote! { |
| #unimplemented_trait_def |
| #doc_comment |
| impl #formatted_trait_generic_params #trait_name_without_trait_record for #impl_for #unsatisfied_where_clause { |
| #extra_body |
| #api_func_def |
| } |
| #extra_items |
| }; |
| function_id = FunctionId { |
| self_type: Some(syn::parse2(quote! { #record_qualifier #record_name }).unwrap()), |
| function_path: { |
| let trait_name_tokens = trait_name_to_token_stream(db, &trait_name); |
| syn::parse2(quote! { #trait_name_tokens :: #func_name }).unwrap() |
| }, |
| }; |
| } |
| } |
| |
| // If we are generating bindings for a derived record, we reuse the base |
| // record's thunks, so we don't need to generate thunks. |
| let cc_details = if derived_record.is_some() || failed { |
| quote! {} |
| } else { |
| generate_function_thunk_impl(db, &func)? |
| }; |
| |
| let generated_item = ApiSnippets { |
| main_api: api_func, |
| thunks: if failed { TokenStream::new() } else { thunk }, |
| features, |
| cc_details, |
| ..Default::default() |
| }; |
| Ok(Some(GeneratedFunction { |
| snippets: Rc::new(generated_item), |
| id: Rc::new(function_id), |
| status: reportable_status.map_err(arc_anyhow::Error::from), |
| })) |
| } |
| |
| /// The function signature for a function's bindings. |
| struct BindingsSignature { |
| /// The lifetime parameters for the Rust function. |
| lifetimes: Vec<Lifetime>, |
| |
| /// The parameter list for the Rust function. |
| /// |
| /// For example, `vec![quote!{self}, quote!{x: &i32}]`. |
| params: Vec<TokenStream>, |
| |
| /// The return type fragment of the Rust function, as a token stream. |
| /// |
| /// This is the same as the actual return type, except that () is the empty |
| /// tokens, non-Unpin by-value types are `impl Ctor<Output=#return_type> + |
| /// ...`, and wherever the type is the type of `Self`, it gets replaced by |
| /// literal `Self`. |
| return_type_fragment: TokenStream, |
| |
| /// Any preparation code to define the arguments in `thunk_args`. |
| thunk_prepare: TokenStream, |
| |
| /// The arguments passed to the thunk, expressed in terms of `params`. |
| thunk_args: Vec<TokenStream>, |
| } |
| |
| /// Reformats API parameters and return values to match Rust conventions and the |
| /// trait requirements. |
| /// |
| /// For example: |
| /// |
| /// * Use the `self` keyword for the this pointer. Upcast to base classed as |
| /// needed. |
| /// * Use `Self` for the return value of constructor traits. |
| /// * For C++ constructors, remove `self` from the Rust side (as it becomes the |
| /// return value), retaining it on the C++ side / thunk args. |
| /// * serialize a `()` as the empty string. |
| #[allow(clippy::too_many_arguments)] |
| fn function_signature( |
| db: &dyn BindingsGenerator, |
| features: &mut BTreeSet<Ident>, |
| func: &Func, |
| impl_kind: &ImplKind, |
| param_idents: &[Ident], |
| param_types: &mut Vec<RsTypeKind>, |
| return_type: &mut RsTypeKind, |
| derived_record: Option<Rc<Record>>, |
| errors: &Errors, |
| ) -> Result<BindingsSignature> { |
| if let Some(derived_record) = derived_record.as_deref() { |
| ensure!( |
| db.ir() |
| .target_crubit_features(&derived_record.owning_target) |
| .contains(crubit_feature::CrubitFeature::Experimental), |
| "upcasting is currently experimental, see b/216195042" |
| ); |
| } |
| |
| let mut api_params = Vec::with_capacity(func.params.len()); |
| let mut thunk_args = Vec::with_capacity(func.params.len()); |
| let mut thunk_prepare = quote! {}; |
| let impl_kind_record = match impl_kind { |
| ImplKind::Struct { record, .. } | ImplKind::Trait { record, impl_for: ImplFor::T, .. } => { |
| Some(record) |
| } |
| _ => None, |
| }; |
| for (i, (ident, type_)) in param_idents.iter().zip(param_types.iter()).enumerate() { |
| // If we are generating bindings for a derived record, parameter types should be |
| // kept the same because `Self` will refer to the derived record type. |
| // One exception is the first parameter, as it points to the derived |
| // record. |
| let should_replace_by_self = derived_record.is_none() || i == 0; |
| if let Err(err) = type_.check_by_value() { |
| errors.add(err); |
| } |
| if !type_.is_unpin() { |
| // `impl Ctor` will fail to compile in a trait. |
| // This will only be hit if there was a bug in api_func_shape. |
| if let ImplKind::Trait { .. } = &impl_kind { |
| panic!( |
| "non-Unpin types cannot work by value in traits; this should have instead \ |
| become an rvalue reference to force the caller to materialize the Ctor." |
| ); |
| } |
| // The generated bindings require a move constructor. |
| if !type_.is_move_constructible() { |
| errors.add(anyhow!("Non-movable, non-trivial_abi type '{}' is not supported by value as parameter #{i}", type_.display(db))); |
| } |
| let quoted_type_or_self = if let Some(impl_record) = impl_kind_record { |
| if should_replace_by_self { |
| type_.to_token_stream_replacing_by_self(db, Some(impl_record)) |
| } else { |
| type_.to_token_stream(db) |
| } |
| } else { |
| type_.to_token_stream(db) |
| }; |
| features.insert(make_rs_ident("impl_trait_in_assoc_type")); |
| api_params.push(quote! {#ident: impl ::ctor::Ctor<Output=#quoted_type_or_self>}); |
| thunk_args |
| .push(quote! {::core::pin::Pin::into_inner_unchecked(::ctor::emplace!(#ident))}); |
| } else { |
| let quoted_type_or_self = if let Some(impl_record) = impl_kind_record { |
| if should_replace_by_self { |
| type_.to_token_stream_replacing_by_self(db, Some(impl_record)) |
| } else { |
| type_.to_token_stream(db) |
| } |
| } else { |
| type_.to_token_stream(db) |
| }; |
| if type_.is_crubit_abi_bridge_type() { |
| let crubit_abi_type = db |
| .crubit_abi_type(type_.clone()) |
| .with_context(|| format!("while generating bridge param '{ident}'"))?; |
| let crubit_abi_type_tokens = CrubitAbiTypeToRustTokens(&crubit_abi_type); |
| |
| api_params.push(quote! {#ident: #quoted_type_or_self}); |
| thunk_args.push(quote! {::bridge_rust::unstable_encode!(#crubit_abi_type_tokens, #ident).as_ptr() as *const u8}); |
| } else if type_.is_c_abi_compatible_by_value() { |
| api_params.push(quote! {#ident: #quoted_type_or_self}); |
| thunk_args.push(quote! {#ident}); |
| } else { |
| api_params.push(quote! {mut #ident: #quoted_type_or_self}); |
| thunk_args.push(quote! {&mut #ident}); |
| } |
| } |
| } |
| |
| let mut lifetimes: Vec<Lifetime> = unique_lifetimes(&*param_types).collect(); |
| |
| let mut quoted_return_type = None; |
| // TODO: b/389131731 - Unify adjustment of return and parameter types. |
| let trait_name = match &impl_kind { |
| ImplKind::Trait { trait_name, .. } => Some(trait_name), |
| _ => None, |
| }; |
| match trait_name { |
| Some(TraitName::PartialOrd { .. } | TraitName::PartialEq { .. }) => { |
| if *return_type != RsTypeKind::Primitive(Primitive::Bool) { |
| errors.add(anyhow!( |
| "comparison operator return type must be `bool`, found: {}", |
| return_type.display(db), |
| )); |
| *return_type = RsTypeKind::Primitive(Primitive::Bool); |
| } |
| } |
| Some(TraitName::UnpinConstructor { .. } | TraitName::CtorNew(..)) => { |
| // For constructors, we move the output parameter to be the return value. |
| // The return value is "really" void. |
| assert!( |
| func.return_type.is_unit_type(), |
| "Unexpectedly non-void return type of a constructor: {func:?}" |
| ); |
| |
| // Presence of element #0 is indirectly verified by a `Constructor`-related |
| // `match` branch a little bit above. |
| *return_type = param_types[0] |
| .referent() |
| .ok_or_else(|| { |
| anyhow!( |
| "Expected pointer/reference for `__this` parameter, found {}", |
| param_types[0].display(db) |
| ) |
| })? |
| .clone(); |
| quoted_return_type = Some(quote! {Self}); |
| |
| // Grab the `__this` lifetime to remove it from the lifetime parameters. |
| let this_lifetime = param_types[0].lifetime().ok_or_else(|| { |
| anyhow!( |
| "Missing lifetime for `__this` parameter type: {}", |
| param_types[0].display(db) |
| ) |
| })?; |
| |
| // Drop `__this` parameter from the public Rust API. |
| api_params.remove(0); |
| thunk_args.remove(0); |
| param_types.remove(0); |
| |
| // Remove the lifetime associated with `__this`. |
| lifetimes.retain(|l| l != &this_lifetime); |
| if let Some(type_still_dependent_on_removed_lifetime) = |
| param_types.iter().find(|t| t.lifetimes().any(|lt| lt == this_lifetime)) |
| { |
| bail!( |
| "The lifetime of `__this` is unexpectedly also used by another \ |
| parameter: {}", |
| type_still_dependent_on_removed_lifetime.display(db) |
| ); |
| } |
| |
| // CtorNew groups parameters into a tuple. |
| if let Some(TraitName::CtorNew(args_type)) = trait_name { |
| let args_type = if let Some(impl_record) = impl_kind_record { |
| format_tuple_except_singleton_replacing_by_self( |
| db, |
| args_type, |
| Some(impl_record), |
| ) |
| } else { |
| format_tuple_except_singleton( |
| args_type.iter().map(|rs_type_kind| rs_type_kind.to_token_stream(db)), |
| ) |
| }; |
| api_params = vec![quote! {args: #args_type}]; |
| let thunk_vars = format_tuple_except_singleton(thunk_args.iter().cloned()); |
| thunk_prepare.extend(quote! {let #thunk_vars = args;}); |
| } |
| } |
| Some(TraitName::Other { .. }) | None => {} |
| } |
| |
| let return_type_fragment = |
| if matches!(return_type.unalias(), RsTypeKind::Primitive(Primitive::Void)) { |
| quote! {} |
| } else { |
| let ty = quoted_return_type.unwrap_or_else(|| return_type.to_token_stream(db)); |
| if return_type.is_unpin() { |
| ty |
| } else { |
| // TODO(jeanpierreda): use `-> impl Ctor` instead of `-> Self::X` where `X = impl |
| // Ctor`. The latter requires `impl_trait_in_assoc_type`, the former |
| // was stabilized in 1.75. Directly returning an unnameable `impl |
| // Ctor` is sufficient for us, and makes traits like `CtorNew` more |
| // similar to top-level functions.) |
| |
| // The returned lazy FnCtor depends on all inputs. |
| let extra_lifetimes = if lifetimes.is_empty() { |
| quote! {} |
| } else { |
| quote! {+ use<#(#lifetimes),*> } |
| }; |
| features.insert(make_rs_ident("impl_trait_in_assoc_type")); |
| quote! {impl ::ctor::Ctor<Output=#ty> #extra_lifetimes } |
| } |
| }; |
| |
| // Change `__this: &'a SomeStruct` into `&'a self` if needed. |
| if impl_kind.format_first_param_as_self() { |
| let Some(first_api_param) = param_types.first() else { |
| panic!( |
| "`format_first_param_self` to function with no parameter types:\n\ |
| Func: {func:?}\nParameter types: {param_types:?}" |
| ) |
| }; |
| // If param_types[0] exists, so do api_params[0] and thunk_args[0]. |
| 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()?; |
| api_params[0] = rs_snippet.tokens; |
| features.extend(rs_snippet.features); |
| if derived_record.is_some() { |
| thunk_args[0] = quote! { oops::Upcast::<_>::upcast(self) }; |
| } else { |
| thunk_args[0] = quote! { self }; |
| } |
| } else { |
| 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 }; |
| } |
| } |
| } |
| ImplKind::Trait { impl_for: ImplFor::RefT, .. } => { |
| // In the ImplFor::RefT reference style the impl block is for a reference type |
| // referring to T (`&T`, `&mut T`, or `Pin<&mut T>` so a bare `self` parameter |
| // has that type and can be passed to a thunk via the expression `self`. |
| api_params[0] = quote! { self }; |
| if derived_record.is_some() { |
| thunk_args[0] = quote! { oops::Upcast::<_>::upcast(self) }; |
| } else { |
| thunk_args[0] = quote! { self }; |
| } |
| } |
| } |
| } else if derived_record.is_some() |
| && !thunk_args.is_empty() |
| && thunk_args[0].to_string() == "__this" |
| { |
| let arg_this = thunk_args[0].clone(); |
| thunk_args[0] = quote! { oops::UnsafeUpcast::<_>::unsafe_upcast(#arg_this) }; |
| } |
| |
| Ok(BindingsSignature { |
| lifetimes, |
| params: api_params, |
| return_type_fragment, |
| thunk_prepare, |
| thunk_args, |
| }) |
| } |
| |
| /// Formats singletons as themselves, and collections of n!=1 items as a tuple. |
| /// |
| /// In other words, this formats a collection of things as if via `#(#items),*`, |
| /// but without lint warnings. |
| /// |
| /// For example: |
| /// |
| /// * [] => () |
| /// * [x] => x // equivalent to (x), but lint-free. |
| /// * [x, y] => (x, y) |
| fn format_tuple_except_singleton(iter: impl IntoIterator<Item = TokenStream>) -> TokenStream { |
| let mut items = iter.into_iter(); |
| let Some(first) = items.next() else { |
| return quote! { () }; |
| }; |
| |
| let Some(second) = items.next() else { |
| // If there's no second item, then return the first _without_ parens. |
| return first; |
| }; |
| |
| quote! { (#first, #second #(, #items)*) } |
| } |
| |
| fn format_tuple_except_singleton_replacing_by_self( |
| db: &dyn BindingsGenerator, |
| items: &[RsTypeKind], |
| trait_record: Option<&Record>, |
| ) -> TokenStream { |
| match items { |
| [singleton] => singleton.to_token_stream_replacing_by_self(db, trait_record), |
| items => { |
| let mut elements_of_tuple = quote! {}; |
| for (type_index, type_) in items.iter().enumerate() { |
| let quoted_type_or_self = type_.to_token_stream_replacing_by_self(db, trait_record); |
| if type_index > 0 { |
| (quote! {, #quoted_type_or_self }).to_tokens(&mut elements_of_tuple); |
| } else { |
| quoted_type_or_self.to_tokens(&mut elements_of_tuple); |
| } |
| } |
| quote! { ( #elements_of_tuple ) } |
| } |
| } |
| } |
| |
| /// Implementation of `BindingsGenerator::overloaded_funcs`. |
| pub fn overloaded_funcs(db: &dyn BindingsGenerator) -> Rc<HashSet<Rc<FunctionId>>> { |
| let mut seen_funcs = HashSet::new(); |
| let mut overloaded_funcs = HashSet::new(); |
| for func in db.ir().functions() { |
| // TODO(b/251045039) This check shouldn't fail so eagerly. |
| // Functions that fail to receive bindings may still |
| // participate in a C++ overload set, and we must still detect the |
| // overload. |
| if let Ok(Some(f)) = db.generate_function(func.clone(), None) { |
| let function_id = &f.id; |
| if !seen_funcs.insert(function_id.clone()) { |
| overloaded_funcs.insert(function_id.clone()); |
| } |
| } |
| } |
| Rc::new(overloaded_funcs) |
| } |