| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| #![allow(clippy::collapsible_else_if)] |
| |
| use arc_anyhow::{anyhow, ensure, Context, Result}; |
| use code_gen_utils::{format_cc_includes, is_cpp_reserved_keyword, make_rs_ident, CcInclude}; |
| use cpp_type_name::format_cpp_type_with_references; |
| use crubit_abi_type::{CrubitAbiType, FullyQualifiedPath}; |
| use database::code_snippet::{ |
| self, ApiSnippets, Bindings, BindingsTokens, CppDetails, CppIncludes, Feature, GeneratedItem, |
| }; |
| use database::db::{self, BindingsGenerator, CodegenFunctions, Database}; |
| use database::rs_snippet::{BridgeRsTypeKind, Mutability, RsTypeKind, RustPtrKind}; |
| use error_report::{bail, ErrorReporting, ReportFatalError}; |
| use ffi_types::Environment; |
| use generate_comment::generate_top_level_comment; |
| use generate_comment::{generate_comment, generate_doc_comment, generate_unsupported}; |
| use generate_struct_and_union::generate_incomplete_record; |
| use ir::*; |
| use itertools::Itertools; |
| use proc_macro2::Ident; |
| use quote::{quote, ToTokens}; |
| use rs_type_kind::rs_type_kind_with_lifetime_elision; |
| use std::collections::{BTreeSet, HashMap}; |
| use std::ffi::OsStr; |
| use std::path::Path; |
| use std::rc::Rc; |
| use token_stream_printer::{ |
| cc_tokens_to_formatted_string, rs_tokens_to_formatted_string, RustfmtConfig, |
| }; |
| |
| /// Deserializes IR from `json` and generates bindings source code. |
| pub fn generate_bindings( |
| json: &[u8], |
| crubit_support_path_format: &str, |
| clang_format_exe_path: &OsStr, |
| rustfmt_exe_path: &OsStr, |
| rustfmt_config_path: &OsStr, |
| errors: &dyn ErrorReporting, |
| fatal_errors: &dyn ReportFatalError, |
| environment: Environment, |
| ) -> Result<Bindings> { |
| let ir = deserialize_ir(json).with_context(|| { |
| let ir_string = String::from_utf8_lossy(json); |
| format!("Failed to deserialize IR:\n{}", ir_string) |
| })?; |
| |
| let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens( |
| &ir, |
| crubit_support_path_format, |
| errors, |
| fatal_errors, |
| environment, |
| )?; |
| let rs_api = { |
| let rustfmt_exe_path = Path::new(rustfmt_exe_path); |
| let rustfmt_config_path = if rustfmt_config_path.is_empty() { |
| None |
| } else { |
| Some(Path::new(rustfmt_config_path)) |
| }; |
| let rustfmt_config = RustfmtConfig::new(rustfmt_exe_path, rustfmt_config_path); |
| rs_tokens_to_formatted_string(rs_api, &rustfmt_config)? |
| }; |
| let rs_api_impl = cc_tokens_to_formatted_string(rs_api_impl, Path::new(clang_format_exe_path))?; |
| |
| let top_level_comment = generate_top_level_comment(&ir, environment); |
| // TODO(lukasza): Try to remove `#![rustfmt:skip]` - in theory it shouldn't |
| // be needed when `@generated` comment/keyword is present... |
| let rs_api = format!( |
| "{top_level_comment}\n\ |
| #![rustfmt::skip]\n\ |
| {rs_api}" |
| ); |
| |
| let rs_api_impl = format!( |
| "{top_level_comment}\n\ |
| {rs_api_impl}" |
| ); |
| |
| Ok(Bindings { rs_api, rs_api_impl }) |
| } |
| |
| fn generate_type_alias( |
| db: &dyn BindingsGenerator, |
| type_alias: Rc<TypeAlias>, |
| ) -> Result<ApiSnippets> { |
| // Skip the type alias if it maps to a bridge type. |
| let rs_type_kind = db.rs_type_kind((&*type_alias).into())?; |
| let generated_item = if rs_type_kind.unalias().is_bridge_type() { |
| let disable_comment = format!( |
| "Type alias for {cpp_type} suppressed due to being a bridge type", |
| cpp_type = type_alias.debug_name(db.ir()), |
| ); |
| GeneratedItem::Comment { message: disable_comment.into() } |
| } else { |
| let underlying_type = db |
| .rs_type_kind(type_alias.underlying_type.clone()) |
| .with_context(|| format!("Failed to format underlying type for {type_alias}"))?; |
| |
| GeneratedItem::TypeAlias { |
| doc_comment: generate_doc_comment( |
| type_alias.doc_comment.as_deref(), |
| Some(&type_alias.source_loc), |
| db.environment(), |
| ), |
| visibility: db::type_visibility(db, &type_alias.owning_target, rs_type_kind) |
| .unwrap_or_default(), |
| ident: make_rs_ident(&type_alias.rs_name.identifier), |
| underlying_type: underlying_type.to_token_stream(db), |
| } |
| }; |
| Ok(ApiSnippets { |
| generated_items: HashMap::from([(type_alias.id, generated_item)]), |
| ..Default::default() |
| }) |
| } |
| |
| fn generate_global_var(db: &dyn BindingsGenerator, var: Rc<GlobalVar>) -> Result<ApiSnippets> { |
| let type_ = db.rs_type_kind(var.type_.clone())?; |
| |
| Ok(ApiSnippets { |
| generated_items: HashMap::from([( |
| var.id, |
| GeneratedItem::GlobalVar { |
| link_name: var.mangled_name.clone(), |
| is_mut: !var.type_.is_const, |
| ident: make_rs_ident(&var.rs_name.identifier), |
| type_tokens: type_.to_token_stream(db), |
| visibility: db::type_visibility(db, &var.owning_target, type_).unwrap_or_default(), |
| }, |
| )]), |
| ..Default::default() |
| }) |
| } |
| |
| fn generate_namespace(db: &dyn BindingsGenerator, namespace: Rc<Namespace>) -> Result<ApiSnippets> { |
| let ir = db.ir(); |
| let mut api_snippets = ApiSnippets::default(); |
| |
| for &item_id in &namespace.child_item_ids { |
| let item = ir.find_untyped_decl(item_id); |
| api_snippets.append(db.generate_item(item.clone())?); |
| } |
| |
| api_snippets.generated_items.insert(namespace.id, GeneratedItem::NonCanonicalNamespace); |
| api_snippets.generated_items.insert( |
| namespace.canonical_namespace_id, |
| GeneratedItem::CanonicalNamespace { items: namespace.child_item_ids.to_vec() }, |
| ); |
| Ok(api_snippets) |
| } |
| |
| /// Implementation of `BindingsGenerator::generate_item`. |
| fn generate_item(db: &dyn BindingsGenerator, item: Item) -> Result<ApiSnippets> { |
| let err = match generate_item_impl(db, &item) { |
| Ok(generated) => return Ok(generated), |
| Err(err) => err, |
| }; |
| |
| if db.has_bindings(item.clone()).is_ok() && !matches!(item, Item::Func(_)) { |
| return Err(err); |
| } |
| // We didn't guarantee that bindings would exist, so it is not invalid to |
| // write down the error but continue. |
| |
| // FIXME(cramertj): get paths here in more cases. It may be that |
| // `generate_item_impl` failed in such a way that the path is still available. |
| let unsupported_item = |
| UnsupportedItem::new_with_cause(db.ir(), &item, /* path= */ None, err); |
| |
| Ok(generate_unsupported(db, unsupported_item.into())) |
| } |
| |
| /// The implementation of generate_item, without the error recovery logic. |
| /// |
| /// Returns Err if bindings could not be generated for this item. |
| fn generate_item_impl(db: &dyn BindingsGenerator, item: &Item) -> Result<ApiSnippets> { |
| let ir = db.ir(); |
| if let Some(owning_target) = item.owning_target() { |
| if !ir.is_current_target(&owning_target) { |
| return Ok(ApiSnippets::default()); |
| } |
| } |
| let generated_item = match item { |
| Item::Func(func) => match db.generate_function(func.clone(), None)? { |
| None => ApiSnippets::default(), |
| Some(generated_function) => { |
| if let Err(e) = &generated_function.status { |
| // Add any non-fatal errors to the error report. |
| // These won't result in an UnsupportedItem since we *did* generate an |
| // uncallable function item. |
| db.errors().report(e); |
| } |
| if db.is_ambiguous_function(&generated_function.id, func.id) { |
| bail!("Cannot generate bindings for overloaded function") |
| } else { |
| (*generated_function.snippets).clone() |
| } |
| } |
| }, |
| Item::IncompleteRecord(incomplete_record) => { |
| generate_incomplete_record(db, incomplete_record.clone())? |
| } |
| Item::Record(record) => db.generate_record(record.clone())?, |
| Item::Enum(enum_) => db.generate_enum(enum_.clone())?, |
| Item::GlobalVar(var) => generate_global_var(db, var.clone())?, |
| Item::TypeAlias(type_alias) => generate_type_alias(db, type_alias.clone())?, |
| Item::UnsupportedItem(unsupported) => generate_unsupported(db, unsupported.clone()), |
| Item::Comment(comment) => generate_comment(comment.clone()), |
| Item::Namespace(namespace) => generate_namespace(db, namespace.clone())?, |
| Item::UseMod(use_mod) => { |
| let UseMod { path, mod_name, .. } = &**use_mod; |
| let mod_name = make_rs_ident(&mod_name.identifier); |
| // TODO(b/308949532): Skip re-export if the module being used is empty |
| // (transitively). |
| ApiSnippets { |
| generated_items: HashMap::from([( |
| use_mod.id, |
| GeneratedItem::UseMod { path: path.clone(), mod_name }, |
| )]), |
| ..Default::default() |
| } |
| } |
| Item::ExistingRustType(existing_rust_type) => { |
| let rs_type_kind = db.rs_type_kind((&**existing_rust_type).into())?; |
| let disable_comment = format!( |
| "Type bindings for {cpp_type} suppressed due to being mapped to \ |
| an existing Rust type ({rs_type_kind})", |
| cpp_type = existing_rust_type.debug_name(&ir), |
| rs_type_kind = rs_type_kind.display(db), |
| ); |
| let assertions = existing_rust_type |
| .size_align |
| .as_ref() |
| .map(|size_align| { |
| generate_struct_and_union::rs_size_align_assertions( |
| rs_type_kind.to_token_stream(db), |
| size_align, |
| ) |
| }) |
| .into_iter() |
| .collect_vec(); |
| |
| ApiSnippets { |
| generated_items: HashMap::from([( |
| existing_rust_type.id, |
| GeneratedItem::Comment { message: disable_comment.into() }, |
| )]), |
| assertions, |
| ..Default::default() |
| } |
| } |
| }; |
| |
| // Suppress bindings at the last minute, to collect other errors first. |
| let _ = db.has_bindings(item.clone())?; |
| |
| Ok(generated_item) |
| } |
| |
| /// Creates a new database. Public for testing. |
| pub fn new_database<'db>( |
| ir: &'db IR, |
| errors: &'db dyn ErrorReporting, |
| fatal_errors: &'db dyn ReportFatalError, |
| environment: Environment, |
| ) -> Database<'db> { |
| Database::new( |
| ir, |
| errors, |
| fatal_errors, |
| environment, |
| CodegenFunctions { |
| generate_enum: generate_enum::generate_enum, |
| generate_item, |
| generate_record: generate_struct_and_union::generate_record, |
| }, |
| is_rs_type_kind_unsafe, |
| has_bindings::has_bindings, |
| rs_type_kind_with_lifetime_elision, |
| generate_function::generate_function, |
| generate_function::overload_sets, |
| generate_function::is_record_clonable, |
| generate_function::get_binding, |
| generate_struct_and_union::collect_unqualified_member_functions, |
| crubit_abi_type, |
| has_bindings::type_target_restriction, |
| has_bindings::resolve_type_names, |
| ) |
| } |
| |
| /// Returns the Rust code implementing bindings, plus any auxiliary C++ code |
| /// needed to support it. |
| // |
| /// Public for use in `generate_bindings_tokens_for_test`. |
| pub fn generate_bindings_tokens( |
| ir: &IR, |
| crubit_support_path_format: &str, |
| errors: &dyn ErrorReporting, |
| fatal_errors: &dyn ReportFatalError, |
| environment: Environment, |
| ) -> Result<BindingsTokens> { |
| let db = new_database(ir, errors, fatal_errors, environment); |
| let mut snippets = ApiSnippets::default(); |
| |
| // For #![rustfmt::skip]. |
| snippets.features |= Feature::custom_inner_attributes; |
| // For the `vector` in `cc_std`. |
| snippets.features |= Feature::allocator_api; |
| snippets.features |= Feature::cfg_sanitize; |
| |
| for top_level_item_id in ir.top_level_item_ids() { |
| let item = ir.find_untyped_decl(*top_level_item_id); |
| snippets.append(db.generate_item(item.clone())?); |
| } |
| |
| // when we go through the main_api, we want to go through one at a time. |
| // if the parent is none, we're responsible. |
| // each thing needs to go through all its children. |
| let ApiSnippets { generated_items, thunks, assertions, cc_details, features } = snippets; |
| let main_api = code_snippet::generated_items_to_token_stream( |
| &generated_items, |
| ir, |
| ir.top_level_item_ids(), |
| ); |
| |
| let cc_details = |
| CppDetails::new(generate_rs_api_impl_includes(&db, crubit_support_path_format), cc_details); |
| |
| let mod_detail = if thunks.is_empty() { |
| quote! {} |
| } else { |
| quote! { |
| mod detail { |
| #[allow(unused_imports)] |
| use super::*; |
| unsafe extern "C" { |
| #( #thunks )* |
| } |
| } |
| } |
| }; |
| |
| let features = if features.is_empty() { |
| quote! {} |
| } else { |
| let feature_iter = features.into_iter(); |
| quote! { |
| #![feature( #(#feature_iter),* )] __NEWLINE__ |
| #![allow(stable_features)] |
| } |
| }; |
| |
| let assertions = if assertions.is_empty() { |
| quote! {} |
| } else { |
| quote! { |
| const _: () = { __NEWLINE__ |
| #( #assertions __NEWLINE__ )* |
| }; __NEWLINE__ |
| } |
| }; |
| |
| Ok(BindingsTokens { |
| rs_api: quote! { |
| #features __NEWLINE__ |
| #![no_std] __NEWLINE__ |
| |
| // `rust_builtin_type_abi_assumptions.md` documents why the generated |
| // bindings need to relax the `improper_ctypes_definitions` warning |
| // for `char` (and possibly for other built-in types in the future). |
| #![allow(improper_ctypes)] __NEWLINE__ |
| |
| // C++ names don't follow Rust guidelines: |
| #![allow(nonstandard_style)] __NEWLINE__ |
| |
| // Parts of our generated code are sometimes considered dead |
| // (b/349776381). |
| #![allow(dead_code, unused_mut)] __NEWLINE__ |
| #![deny(warnings)] __NEWLINE__ __NEWLINE__ |
| |
| #main_api |
| |
| #mod_detail __NEWLINE__ __NEWLINE__ |
| |
| #assertions |
| }, |
| rs_api_impl: cc_details.into_token_stream(), |
| }) |
| } |
| |
| /// Implementation of `BindingsGenerator::is_rs_type_kind_unsafe`. |
| fn is_rs_type_kind_unsafe(db: &dyn BindingsGenerator, rs_type_kind: RsTypeKind) -> bool { |
| match rs_type_kind { |
| RsTypeKind::Error { .. } => true, |
| RsTypeKind::Pointer { .. } => true, |
| RsTypeKind::Reference { referent: t, .. } |
| | RsTypeKind::RvalueReference { referent: t, .. } |
| | RsTypeKind::TypeAlias { underlying_type: t, .. } => { |
| db.is_rs_type_kind_unsafe(t.as_ref().clone()) |
| } |
| RsTypeKind::FuncPtr { return_type, param_types, .. } => { |
| db.is_rs_type_kind_unsafe(return_type.as_ref().clone()) |
| || param_types |
| .iter() |
| .cloned() |
| .any(|param_type| db.is_rs_type_kind_unsafe(param_type)) |
| } |
| RsTypeKind::IncompleteRecord { .. } => { |
| // TODO(b/390474240): Add a way to mark a forward declaration as being an unsafe |
| // type. |
| false |
| } |
| RsTypeKind::Enum { .. } |
| | RsTypeKind::Primitive(..) |
| | RsTypeKind::ExistingRustType { .. } => false, |
| RsTypeKind::BridgeType { bridge_type, original_type } => match bridge_type { |
| // TODO(b/390621592): Should bridge types just delegate to the underlying type? |
| BridgeRsTypeKind::BridgeVoidConverters { .. } |
| | BridgeRsTypeKind::Bridge { .. } |
| | BridgeRsTypeKind::ProtoMessageBridge { .. } => is_record_unsafe(db, &original_type), |
| BridgeRsTypeKind::StdOptional(t) => db.is_rs_type_kind_unsafe(t.as_ref().clone()), |
| BridgeRsTypeKind::StdPair(t1, t2) => { |
| db.is_rs_type_kind_unsafe(t1.as_ref().clone()) |
| || db.is_rs_type_kind_unsafe(t2.as_ref().clone()) |
| } |
| BridgeRsTypeKind::StdString { .. } => false, |
| }, |
| RsTypeKind::Record { record, .. } => is_record_unsafe(db, &record), |
| } |
| } |
| |
| /// Helper function for `is_rs_type_kind_unsafe`. |
| /// Returns true if the record is unsafe, or if it transitively contains a public field of |
| /// an unsafe type. |
| fn is_record_unsafe(db: &dyn BindingsGenerator, record: &Record) -> bool { |
| if record.is_unsafe_type { |
| return true; |
| } |
| |
| if record.record_type == RecordType::Union { |
| return true; |
| } |
| for field in &record.fields { |
| if field.access != AccessSpecifier::Public { |
| continue; |
| } |
| let Ok(cpp_type) = &field.type_ else { |
| // If we can't get the CcType for a public field, we assume it's unsafe. |
| return true; |
| }; |
| let Ok(field_rs_type_kind) = db.rs_type_kind(cpp_type.clone()) else { |
| // If we can't get the RsTypeKind for a public field, we assume it's unsafe. |
| return true; |
| }; |
| if db.is_rs_type_kind_unsafe(field_rs_type_kind) { |
| return true; |
| } |
| } |
| false |
| } |
| |
| fn generate_rs_api_impl_includes(db: &Database, crubit_support_path_format: &str) -> CppIncludes { |
| let ir = db.ir(); |
| |
| let mut internal_includes = BTreeSet::new(); |
| internal_includes.insert(CcInclude::memory()); // ubiquitous. |
| if ir.records().next().is_some() { |
| internal_includes.insert(CcInclude::cstddef()); |
| internal_includes.insert(CcInclude::SupportLibHeader( |
| crubit_support_path_format.into(), |
| "internal/sizeof.h".into(), |
| )); |
| }; |
| |
| for record in ir.records() { |
| // Err means that this bridge type has some issues. For the purpose of generating includes, |
| // we can ignore it. |
| if let Ok(Some(bridge_type)) = BridgeRsTypeKind::new(record, db) { |
| internal_includes.insert(CcInclude::SupportLibHeader( |
| crubit_support_path_format.into(), |
| if bridge_type.is_void_converters_bridge_type() { |
| "internal/lazy_init.h".into() |
| } else { |
| "bridge.h".into() |
| }, |
| )); |
| |
| // TODO(b/436862191): Remove this once the migration is complete. |
| if !db |
| .ir() |
| .target_crubit_features(&record.owning_target) |
| .contains(crubit_feature::CrubitFeature::DoNotHardcodeStatusBridge) |
| && (record.owning_target == "@abseil-cpp//absl/status".into() |
| || record.owning_target == "@abseil-cpp//absl/status:statusor".into()) |
| { |
| internal_includes.insert(CcInclude::SupportLibHeader( |
| crubit_support_path_format.into(), |
| "status_bridge.h".into(), |
| )); |
| } |
| } |
| } |
| |
| for type_alias in ir.type_aliases() { |
| let Ok(rs_type_kind) = db.rs_type_kind((&**type_alias).into()) else { |
| continue; |
| }; |
| |
| if let RsTypeKind::BridgeType { bridge_type, .. } = rs_type_kind.unalias() { |
| internal_includes.insert(CcInclude::SupportLibHeader( |
| crubit_support_path_format.into(), |
| if bridge_type.is_void_converters_bridge_type() { |
| "internal/lazy_init.h".into() |
| } else { |
| "bridge.h".into() |
| }, |
| )); |
| } |
| } |
| |
| for crubit_header in ["internal/cxx20_backports.h", "internal/offsetof.h"] { |
| internal_includes.insert(CcInclude::SupportLibHeader( |
| crubit_support_path_format.into(), |
| crubit_header.into(), |
| )); |
| } |
| let internal_includes = format_cc_includes(&internal_includes); |
| |
| // In order to generate C++ thunk in all the cases Clang needs to be able to |
| // access declarations from public headers of the C++ library. We don't |
| // process these includes via `format_cc_includes` to preserve their |
| // original order (some libraries require certain headers to be included |
| // first - e.g. `config.h`). |
| let ir_includes = |
| ir.public_headers().map(|hdr| CcInclude::user_header(hdr.name.clone())).collect_vec(); |
| |
| CppIncludes { internal_includes, ir_includes } |
| } |
| |
| /// Implementation of `BindingsGenerator::crubit_abi_type`. |
| fn crubit_abi_type(db: &dyn BindingsGenerator, rs_type_kind: RsTypeKind) -> Result<CrubitAbiType> { |
| match rs_type_kind { |
| RsTypeKind::Error { error, .. } => { |
| bail!("Type has an error and cannot be bridged: {error}") |
| } |
| RsTypeKind::TypeAlias { underlying_type, .. } => { |
| // We don't actually _have_ to expand the type alias here |
| db.crubit_abi_type(underlying_type.as_ref().clone()) |
| } |
| RsTypeKind::Pointer { pointee, kind, mutability } => { |
| let rust_tokens = pointee.to_token_stream(db); |
| let cpp_tokens = format_cpp_type_with_references(&pointee, db.ir())?; |
| |
| Ok(CrubitAbiType::Ptr { |
| is_const: mutability == Mutability::Const, |
| is_rust_slice: kind == RustPtrKind::Slice, |
| rust_type: rust_tokens, |
| cpp_type: cpp_tokens, |
| }) |
| } |
| RsTypeKind::Enum { .. } => bail!("RsTypeKind::Enum is not supported yet"), |
| RsTypeKind::ExistingRustType(existing_rust_type) => { |
| // Rust names are of the form ":: tuples_golden :: NontrivialDrop" |
| let mut rust_path = existing_rust_type.rs_name.as_ref(); |
| let mut start_with_colon2 = false; |
| if let Some(strip_universal_qualifier) = rust_path.strip_prefix(":: ") { |
| start_with_colon2 = true; |
| rust_path = strip_universal_qualifier; |
| } |
| let rust_type = FullyQualifiedPath { |
| start_with_colon2, |
| parts: rust_path |
| .split("::") |
| .map(|ident| { |
| syn::parse_str::<Ident>(ident.trim()).map_err(|_| { |
| anyhow!( |
| "The type `{ident}` does not parse as an identifier. \ |
| This may be because it contains template parameters, and \ |
| bridging such types by value is not yet supported." |
| ) |
| }) |
| }) |
| .collect::<Result<Rc<[Ident]>>>()?, |
| }; |
| |
| let cpp_type = make_cpp_type_from_item( |
| existing_rust_type.as_ref(), |
| &existing_rust_type.cc_name.split("::").collect::<Vec<&str>>(), |
| db, |
| )?; |
| |
| Ok(CrubitAbiType::Transmute { rust_type, cpp_type }) |
| } |
| RsTypeKind::Primitive(primitive) => Ok(match primitive { |
| Primitive::Bool => CrubitAbiType::transmute("bool", "bool"), |
| Primitive::Void => bail!("values of type `void` cannot be bridged by value"), |
| Primitive::Float => CrubitAbiType::transmute("f32", "float"), |
| Primitive::Double => CrubitAbiType::transmute("f64", "double"), |
| Primitive::Char => CrubitAbiType::transmute("::core::ffi::c_char", "char"), |
| Primitive::SignedChar => CrubitAbiType::SignedChar, |
| Primitive::UnsignedChar => CrubitAbiType::UnsignedChar, |
| Primitive::Short => CrubitAbiType::transmute("::core::ffi::c_short", "short"), |
| Primitive::Int => CrubitAbiType::transmute("::core::ffi::c_int", "int"), |
| Primitive::Long => CrubitAbiType::transmute("::core::ffi::c_long", "long"), |
| Primitive::LongLong => CrubitAbiType::LongLong, |
| Primitive::UnsignedShort => CrubitAbiType::UnsignedShort, |
| Primitive::UnsignedInt => CrubitAbiType::UnsignedInt, |
| Primitive::UnsignedLong => CrubitAbiType::UnsignedLong, |
| Primitive::UnsignedLongLong => CrubitAbiType::UnsignedLongLong, |
| Primitive::Char16T => CrubitAbiType::transmute("u16", "char16_t"), |
| Primitive::Char32T => CrubitAbiType::transmute("u32", "char32_t"), |
| Primitive::PtrdiffT => CrubitAbiType::transmute("isize", "ptrdiff_t"), |
| Primitive::IntptrT => CrubitAbiType::transmute("isize", "intptr_t"), |
| Primitive::StdPtrdiffT => CrubitAbiType::transmute("isize", "std::ptrdiff_t"), |
| Primitive::StdIntptrT => CrubitAbiType::transmute("isize", "std::intptr_t"), |
| Primitive::SizeT => CrubitAbiType::transmute("usize", "size_t"), |
| Primitive::UintptrT => CrubitAbiType::transmute("usize", "uintptr_t"), |
| Primitive::StdSizeT => CrubitAbiType::transmute("usize", "std::size_t"), |
| Primitive::StdUintptrT => CrubitAbiType::transmute("usize", "std::uintptr_t"), |
| Primitive::Int8T => CrubitAbiType::transmute("i8", "int8_t"), |
| Primitive::Int16T => CrubitAbiType::transmute("i16", "int16_t"), |
| Primitive::Int32T => CrubitAbiType::transmute("i32", "int32_t"), |
| Primitive::Int64T => CrubitAbiType::transmute("i64", "int64_t"), |
| Primitive::StdInt8T => CrubitAbiType::transmute("i8", "std::int8_t"), |
| Primitive::StdInt16T => CrubitAbiType::transmute("i16", "std::int16_t"), |
| Primitive::StdInt32T => CrubitAbiType::transmute("i32", "std::int32_t"), |
| Primitive::StdInt64T => CrubitAbiType::transmute("i64", "std::int64_t"), |
| Primitive::Uint8T => CrubitAbiType::transmute("u8", "uint8_t"), |
| Primitive::Uint16T => CrubitAbiType::transmute("u16", "uint16_t"), |
| Primitive::Uint32T => CrubitAbiType::transmute("u32", "uint32_t"), |
| Primitive::Uint64T => CrubitAbiType::transmute("u64", "uint64_t"), |
| Primitive::StdUint8T => CrubitAbiType::transmute("u8", "std::uint8_t"), |
| Primitive::StdUint16T => CrubitAbiType::transmute("u16", "std::uint16_t"), |
| Primitive::StdUint32T => CrubitAbiType::transmute("u32", "std::uint32_t"), |
| Primitive::StdUint64T => CrubitAbiType::transmute("u64", "std::uint64_t"), |
| }), |
| RsTypeKind::BridgeType { bridge_type, original_type } => match bridge_type { |
| BridgeRsTypeKind::BridgeVoidConverters { .. } => { |
| bail!("Void pointer bridge types are not allowed within composable bridging") |
| } |
| BridgeRsTypeKind::ProtoMessageBridge { abi_rust, abi_cpp, .. } => { |
| let target = |
| original_type.defining_target().unwrap_or(&original_type.owning_target); |
| let rust_abi_path = make_rust_abi_path_from_str(&abi_rust, db.ir(), target); |
| let cpp_abi_path = make_cpp_abi_path_from_str(&abi_cpp)?; |
| |
| let cpp_namespace_qualifier = db.ir().namespace_qualifier(original_type.as_ref()); |
| |
| // Rust message types are exported to crate root, but we need the full namespace for the C++ ABI. |
| let merged_cpp_abi_path = cpp_namespace_qualifier.parts().join("::") |
| + "::" |
| + original_type.cc_name.identifier.as_ref(); |
| |
| let type_args = Rc::from([CrubitAbiType::Type { |
| rust_abi_path: make_rust_abi_path_from_str( |
| original_type.rs_name.identifier.as_ref(), |
| db.ir(), |
| target, |
| ), |
| cpp_abi_path: make_cpp_abi_path_from_str(&merged_cpp_abi_path)?, |
| type_args: Rc::default(), |
| }]); |
| |
| Ok(CrubitAbiType::Type { rust_abi_path, cpp_abi_path, type_args }) |
| } |
| BridgeRsTypeKind::Bridge { abi_rust, abi_cpp, generic_types, .. } => { |
| let target = |
| original_type.defining_target().unwrap_or(&original_type.owning_target); |
| let rust_abi_path = make_rust_abi_path_from_str(&abi_rust, db.ir(), target); |
| |
| let cpp_abi_path = make_cpp_abi_path_from_str(&abi_cpp)?; |
| |
| let type_args = generic_types |
| .iter() |
| .map(|t: &RsTypeKind| db.crubit_abi_type(t.clone())) |
| .collect::<Result<Rc<[CrubitAbiType]>>>()?; |
| |
| Ok(CrubitAbiType::Type { rust_abi_path, cpp_abi_path, type_args }) |
| } |
| BridgeRsTypeKind::StdOptional(inner) => { |
| let inner_abi = db.crubit_abi_type(inner.as_ref().clone())?; |
| Ok(CrubitAbiType::option(inner_abi)) |
| } |
| BridgeRsTypeKind::StdPair(first, second) => { |
| let first_abi = db.crubit_abi_type(first.as_ref().clone())?; |
| let second_abi = db.crubit_abi_type(second.as_ref().clone())?; |
| Ok(CrubitAbiType::Pair(Rc::from(first_abi), Rc::from(second_abi))) |
| } |
| BridgeRsTypeKind::StdString { in_cc_std } => Ok(CrubitAbiType::StdString { in_cc_std }), |
| }, |
| RsTypeKind::Record { record, crate_path, .. } => { |
| database::rs_snippet::check_by_value(record.as_ref())?; |
| |
| let rust_type = crate_path |
| .to_fully_qualified_path(make_rs_ident(record.rs_name.identifier.as_ref())); |
| |
| // This inlines the logic of code_gen_utils::format_cc_ident and joins the namespace parts, |
| // except that it creates an Ident instead of a TokenStream. |
| code_gen_utils::check_valid_cc_name(&record.cc_name.identifier) |
| .expect("IR should only contain valid C++ types"); |
| |
| // TODO(okabayashi): File a bug for generalizing "canonical insts". |
| let cc_name = record.cc_name.identifier.as_ref(); |
| let cc_name_parts = if cc_name == "std::basic_string_view<char, std::char_traits<char>>" |
| { |
| // In the C++ TransmuteAbi, we spell string_view as `std::string_view`. |
| // In theory we should let Crubit spell the C++ type as |
| // `std::basic_string_view<char, std::char_traits<char>>`, but `FullyQualifiedPath` |
| // does not support template arguments right now. It's also the case that since |
| // Crubit doesn't support templates in general right now, it doesn't make sense to |
| // support template arguments in `FullyQualifiedPath` yet. |
| &["std", "string_view"][..] |
| } else { |
| &[cc_name][..] |
| }; |
| let cpp_type = make_cpp_type_from_item(record.as_ref(), cc_name_parts, db)?; |
| |
| Ok(CrubitAbiType::Transmute { rust_type, cpp_type }) |
| } |
| _ => bail!("Unsupported RsTypeKind: {}", rs_type_kind.display(db)), |
| } |
| } |
| |
| /// Parses the given Rust path into a [`FullyQualifiedPath`]. |
| /// * if the path is fully qualified, it stays unchanged. |
| /// * else, if it is the current target, it is prepended with "crate". |
| /// * else, it is prepended with the "::" and the crate name. |
| fn make_rust_abi_path_from_str( |
| mut rust_path: &str, |
| ir: &IR, |
| target: &BazelLabel, |
| ) -> FullyQualifiedPath { |
| let mut start_with_colon2 = strip_leading_colon2(&mut rust_path); |
| |
| let prefix = if start_with_colon2 { |
| None |
| } else if ir.is_current_target(target) { |
| Some(Ident::new("crate", proc_macro2::Span::call_site())) |
| } else { |
| start_with_colon2 = true; |
| Some(make_rs_ident(target.target_name())) |
| }; |
| |
| FullyQualifiedPath { |
| start_with_colon2, |
| parts: prefix |
| .into_iter() |
| .chain(rust_path.split("::").map(make_rs_ident)) |
| .collect::<Rc<[Ident]>>(), |
| } |
| } |
| |
| /// Parses the given C++ path into a [`FullyQualifiedPath`]. |
| fn make_cpp_abi_path_from_str(mut cpp_path: &str) -> Result<FullyQualifiedPath> { |
| let start_with_colon2 = strip_leading_colon2(&mut cpp_path); |
| Ok(FullyQualifiedPath { |
| start_with_colon2, |
| parts: cpp_path |
| .split("::") |
| .map(|part| { |
| ensure!(!part.is_empty(), "cpp path has an empty part: {cpp_path}"); |
| ensure!( |
| !is_cpp_reserved_keyword(part), |
| "cpp path has a reserved keyword: {cpp_path}" |
| ); |
| // Can't reuse machinery in code_gen_utils because that returns |
| // a TokenStream. We _need_ an Ident because it implements Hash. |
| syn::parse_str::<Ident>(part) |
| .map_err(|err| anyhow!("Can't format `{part}` as a C++ identifier: {err}")) |
| }) |
| .collect::<Result<Rc<[Ident]>>>()?, |
| }) |
| } |
| |
| /// Strips the leading `::` from the given path if it exists. |
| /// |
| /// Returns true if the path was modified, false otherwise. |
| fn strip_leading_colon2(path: &mut &str) -> bool { |
| if let Some(stripped) = path.strip_prefix("::") { |
| *path = stripped; |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Only to be used in a `CrubitAbiType::Transmute` context. |
| fn make_cpp_type_from_item( |
| item: &impl GenericItem, |
| cc_name_parts: &[&str], |
| db: &dyn BindingsGenerator, |
| ) -> Result<FullyQualifiedPath> { |
| let namespace_qualifier = db.ir().namespace_qualifier(item); |
| let parts = namespace_qualifier |
| .parts() |
| .map(AsRef::as_ref) |
| .chain(cc_name_parts.iter().copied()) |
| .map(|ident| { |
| syn::parse_str::<Ident>(ident).map_err(|_| { |
| anyhow!( |
| "The type `{ident}` does not parse as an identifier. \ |
| This may be because it contains template parameters, and \ |
| bridging such types by value is not yet supported." |
| ) |
| }) |
| }) |
| .collect::<Result<Rc<[Ident]>>>()?; |
| |
| Ok(FullyQualifiedPath { start_with_colon2: true, parts }) |
| } |