| // 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 |
| #![feature(rustc_private)] |
| #![feature(cfg_accessible)] |
| #![deny(rustc::internal)] |
| #![feature(stmt_expr_attributes)] |
| #![feature(proc_macro_hygiene)] |
| |
| extern crate rustc_abi; |
| extern crate rustc_ast; |
| extern crate rustc_attr_parsing; |
| extern crate rustc_hir; |
| extern crate rustc_infer; |
| extern crate rustc_middle; |
| extern crate rustc_span; |
| extern crate rustc_target; |
| extern crate rustc_trait_selection; |
| extern crate rustc_type_ir; |
| |
| pub mod format_type; |
| pub mod generate_function; |
| mod generate_function_thunk; |
| mod generate_struct_and_union; |
| |
| use crate::format_type::{ |
| create_canonical_name_from_foreign_path, crubit_abi_type_from_ty, ensure_ty_is_pointer_like, |
| format_cc_ident, format_cc_ident_symbol, format_param_types_for_cc, |
| format_region_as_cc_lifetime, format_ret_ty_for_cc, format_top_level_ns_for_crate, |
| is_bridged_type, BridgedBuiltin, BridgedType, BridgedTypeConversionInfo, |
| }; |
| use crate::generate_function::{generate_function, must_use_attr_of}; |
| use crate::generate_function_thunk::{generate_trait_thunks, TraitThunks}; |
| use crate::generate_struct_and_union::{ |
| from_trait_impls_by_argument, generate_adt, generate_adt_core, scalar_value_to_string, |
| }; |
| use arc_anyhow::{Context, Error, Result}; |
| use code_gen_utils::{format_cc_includes, CcConstQualifier, CcInclude, NamespaceQualifier}; |
| use database::code_snippet::{ApiSnippets, CcPrerequisites, CcSnippet, ExternCDecl, RsSnippet}; |
| use database::{ |
| AdtCoreBindings, BindingsGenerator, FineGrainedFeature, FullyQualifiedName, SugaredTy, |
| TypeLocation, |
| }; |
| pub use database::{Database, IncludeGuard}; |
| use error_report::{anyhow, bail, ErrorReporting, ReportFatalError}; |
| use itertools::Itertools; |
| use proc_macro2::TokenStream; |
| use query_compiler::{ |
| does_type_implement_trait, get_layout, get_scalar_int_type, get_tag_size_with_padding, |
| is_c_abi_compatible_by_value, is_copy, is_directly_public, is_exported, |
| liberate_and_deanonymize_late_bound_regions, post_analysis_typing_env, repr_attrs, |
| }; |
| use quote::{format_ident, quote}; |
| use rustc_abi::{AddressSpace, BackendRepr, Integer, Primitive, Scalar}; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::{self as hir, Item, ItemKind, Node, UseKind, UsePath}; |
| use rustc_middle::dep_graph::DepContext; |
| use rustc_middle::mir::ConstValue; |
| use rustc_middle::ty::{self, Ty, TyCtxt}; |
| use rustc_span::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; |
| use rustc_span::symbol::{sym, Ident, Symbol}; |
| use std::cmp::Ordering; |
| use std::collections::{BTreeSet, HashMap, HashSet}; |
| use std::iter::once; |
| use std::rc::Rc; |
| |
| /// Implementation of `BindingsGenerator::support_header`. |
| fn support_header<'tcx>(db: &dyn BindingsGenerator<'tcx>, suffix: &'tcx str) -> CcInclude { |
| CcInclude::support_lib_header(db.crubit_support_path_format(), suffix.into()) |
| } |
| |
| pub struct BindingsTokens { |
| pub cc_api: TokenStream, |
| pub cc_api_impl: TokenStream, |
| } |
| |
| fn add_include_guard(db: &dyn BindingsGenerator<'_>, cc_api: TokenStream) -> Result<TokenStream> { |
| match db.h_out_include_guard() { |
| IncludeGuard::PragmaOnce => Ok(quote! { |
| __HASH_TOKEN__ pragma once __NEWLINE__ |
| __NEWLINE__ |
| |
| #cc_api |
| }), |
| IncludeGuard::Guard(include_guard_str) => { |
| let include_guard = format_cc_ident(db, include_guard_str.as_str())?; |
| Ok(quote! { |
| __HASH_TOKEN__ ifndef #include_guard __NEWLINE__ |
| __HASH_TOKEN__ define #include_guard __NEWLINE__ |
| __NEWLINE__ |
| |
| #cc_api |
| |
| __HASH_TOKEN__ endif __COMMENT__ #include_guard_str __NEWLINE__ |
| }) |
| } |
| } |
| } |
| |
| /// Wrap `repr_attrs` for use as a database function. |
| fn repr_attrs_from_db( |
| db: &dyn BindingsGenerator<'_>, |
| def_id: DefId, |
| ) -> Rc<[rustc_hir::attrs::ReprAttr]> { |
| repr_attrs(db.tcx(), def_id) |
| } |
| |
| fn source_crate_num(db: &dyn BindingsGenerator<'_>) -> CrateNum { |
| let Some(source_crate_name) = db.source_crate_name() else { |
| return LOCAL_CRATE; |
| }; |
| let source_crate_name = Symbol::intern(&*source_crate_name); |
| let tcx = db.tcx(); |
| let Some(crate_num) = tcx |
| .used_crates(()) |
| .iter() |
| .copied() |
| .find(|&crate_num| tcx.crate_name(crate_num) == source_crate_name) |
| else { |
| db.fatal_errors() |
| .report(&format!("Failed to resolve source crate name: `{source_crate_name}`")); |
| return LOCAL_CRATE; |
| }; |
| crate_num |
| } |
| |
| pub fn new_database<'db>( |
| tcx: TyCtxt<'db>, |
| source_crate_name: Option<Rc<str>>, |
| crubit_support_path_format: dyn_format::Format<1>, |
| crubit_debug_path_format: Option<dyn_format::Format<2>>, |
| default_features: flagset::FlagSet<crubit_feature::CrubitFeature>, |
| crate_name_to_include_paths: Rc<HashMap<Rc<str>, Vec<CcInclude>>>, |
| crate_name_to_features: Rc<HashMap<Rc<str>, flagset::FlagSet<crubit_feature::CrubitFeature>>>, |
| crate_name_to_namespace: Rc<HashMap<Rc<str>, Rc<str>>>, |
| crate_renames: Rc<HashMap<Rc<str>, Rc<str>>>, |
| errors: Rc<dyn ErrorReporting>, |
| fatal_errors: Rc<dyn ReportFatalError>, |
| no_thunk_name_mangling: bool, |
| h_out_include_guard: IncludeGuard, |
| ) -> Database<'db> { |
| Database::new( |
| tcx, |
| source_crate_name, |
| crubit_support_path_format, |
| crubit_debug_path_format, |
| default_features, |
| crate_name_to_include_paths, |
| crate_name_to_features, |
| crate_name_to_namespace, |
| crate_renames, |
| errors, |
| fatal_errors, |
| no_thunk_name_mangling, |
| h_out_include_guard, |
| source_crate_num, |
| support_header, |
| repr_attrs_from_db, |
| reexported_symbol_canonical_name_mapping, |
| format_cc_ident_symbol, |
| format_top_level_ns_for_crate, |
| format_type::format_ty_for_cc, |
| format_type::format_ty_for_rs, |
| generate_default_ctor, |
| generate_copy_ctor_and_assignment_operator, |
| generate_move_ctor_and_assignment_operator, |
| generate_item, |
| generate_function, |
| generate_adt_core, |
| crubit_abi_type_from_ty, |
| from_trait_impls_by_argument, |
| ) |
| } |
| |
| pub fn generate_bindings(db: &Database) -> Result<BindingsTokens> { |
| let tcx = db.tcx(); |
| |
| let top_comment = { |
| let source_crate_num = db.source_crate_num(); |
| let crate_name = tcx.crate_name(source_crate_num); |
| let crubit_features = { |
| let mut crubit_features: Vec<&str> = crate_features(db, source_crate_num) |
| .into_iter() |
| .map(|feature| feature.short_name()) |
| .collect(); |
| crubit_features.sort(); |
| if crubit_features.is_empty() { |
| "<none>".to_string() |
| } else { |
| crubit_features.join(", ") |
| } |
| }; |
| let txt = format!( |
| "Automatically @generated C++ bindings for the following Rust crate:\n\ |
| {crate_name}\n\ |
| Features: {crubit_features}" |
| ); |
| quote! { __COMMENT__ #txt __NEWLINE__ } |
| }; |
| |
| let BindingsTokens { cc_api, cc_api_impl } = generate_crate(db).unwrap_or_else(|err| { |
| let txt = format!("Failed to generate bindings for the crate: {err}"); |
| let src = quote! { __COMMENT__ #txt }; |
| BindingsTokens { cc_api: src.clone(), cc_api_impl: src } |
| }); |
| let cc_api = add_include_guard(db, cc_api)?; |
| let cc_api = quote! { |
| #top_comment |
| |
| #cc_api |
| }; |
| |
| let mut extern_crate_decls: Vec<TokenStream> = vec![]; |
| for (name, renamed) in db.crate_renames().iter() { |
| let name = format_ident!("{}", name.to_string()); |
| let renamed = format_ident!("{}", renamed.to_string()); |
| |
| extern_crate_decls.push(quote! { |
| extern crate #name as #renamed; |
| }); |
| } |
| |
| let cc_api_impl = quote! { |
| #top_comment |
| |
| #![allow(unused_unsafe, deprecated, non_snake_case, unreachable_code)] __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_definitions)] __NEWLINE__ |
| #![deny(warnings)] __NEWLINE__ |
| |
| __NEWLINE__ |
| |
| #(#extern_crate_decls)* |
| |
| __NEWLINE__ |
| |
| #cc_api_impl |
| }; |
| |
| Ok(BindingsTokens { cc_api, cc_api_impl }) |
| } |
| |
| fn crate_features( |
| db: &dyn BindingsGenerator, |
| krate: CrateNum, |
| ) -> flagset::FlagSet<crubit_feature::CrubitFeature> { |
| let crate_features = db.crate_name_to_features(); |
| let features = if krate == LOCAL_CRATE { |
| crate_features.get("self") |
| } else { |
| crate_features.get(db.tcx().crate_name(krate).as_str()) |
| }; |
| features.copied().unwrap_or_else(|| db.default_features()) |
| } |
| |
| fn check_feature_enabled_on_self_and_all_deps( |
| db: &dyn BindingsGenerator, |
| feature: FineGrainedFeature, |
| ) -> bool { |
| for (_, crate_features) in db.crate_name_to_features().iter() { |
| if feature.ensure_crubit_feature(*crate_features).is_err() { |
| return false; |
| } |
| } |
| true |
| } |
| |
| fn format_with_cc_body( |
| db: &dyn BindingsGenerator, |
| ns: &NamespaceQualifier, |
| mut tokens: TokenStream, |
| attributes: Vec<TokenStream>, |
| ) -> Result<TokenStream> { |
| let mut namespaces = ns.parts().map(|s| format_cc_ident(db, s)).collect::<Result<Vec<_>>>()?; |
| |
| // Nested namespace syntax does not accept attributes (see b/445613694), so we have to split out |
| // the with-attribute decl to contain only the trailing namespace. |
| if !attributes.is_empty() { |
| let innermost_namespace = namespaces |
| .pop() |
| .expect("there should be at least one namespace if there are attributes"); |
| tokens = quote! { |
| __NEWLINE__ |
| namespace #(#attributes)* #innermost_namespace { __NEWLINE__ |
| #tokens __NEWLINE__ |
| } __NEWLINE__ |
| }; |
| } |
| |
| if !namespaces.is_empty() { |
| tokens = quote! { |
| __NEWLINE__ |
| namespace #(#namespaces)::* { __NEWLINE__ |
| #tokens __NEWLINE__ |
| } __NEWLINE__ |
| }; |
| } |
| Ok(tokens) |
| } |
| |
| fn symbols_from_extern_crate(db: &dyn BindingsGenerator<'_>) -> Vec<(DefId, FullyQualifiedName)> { |
| use rustc_hir::intravisit::Visitor; |
| let tcx = db.tcx(); |
| struct ForeignSymbols<'a, 'tcx> { |
| pub symbols: Vec<(DefId, FullyQualifiedName)>, |
| pub db: &'a dyn BindingsGenerator<'tcx>, |
| } |
| |
| impl<'tcx> Visitor<'tcx> for ForeignSymbols<'_, '_> { |
| fn visit_path(&mut self, path: &rustc_hir::Path<'tcx>, _id: rustc_hir::HirId) { |
| let db = self.db; |
| if let Some((def_id, fully_qualified_name)) = |
| create_canonical_name_from_foreign_path(db, path.segments, &path.res) |
| { |
| self.symbols.push((def_id, fully_qualified_name)); |
| } |
| } |
| } |
| |
| let mut visitor = ForeignSymbols { symbols: Vec::new(), db }; |
| // TODO: b/433286909 - Support adding aliases to the name map even if they aren't used by the |
| // local crate. |
| tcx.hir_visit_all_item_likes_in_crate(&mut visitor); |
| |
| visitor.symbols |
| } |
| |
| #[derive(Debug)] |
| struct AliasInfo { |
| using_name: String, |
| local_def_id: LocalDefId, |
| type_def_id: DefId, |
| def_kind: DefKind, |
| } |
| |
| fn create_canonical_name( |
| db: &dyn BindingsGenerator<'_>, |
| name_map: &HashMap<DefId, FullyQualifiedName>, |
| alias_info: &AliasInfo, |
| ) -> Option<FullyQualifiedName> { |
| let alias_name = &alias_info.using_name; |
| let alias_local_def_id = alias_info.local_def_id; |
| let aliased_entity_def_id = alias_info.type_def_id; |
| let rs_name = Symbol::intern(alias_name); |
| if let Some(canonical_name) = name_map.get(&aliased_entity_def_id) { |
| // We keep the lexicographically smallest name. |
| if canonical_name.rs_name.unwrap().as_str() < rs_name.as_str() { |
| return None; |
| } |
| } |
| let def_id = alias_local_def_id.to_def_id(); |
| let tcx = db.tcx(); |
| |
| // We only handle local reexported private symbols for `pub use`. |
| if !tcx.effective_visibilities(()).is_directly_public(alias_local_def_id) // not pub use |
| || !aliased_entity_def_id.is_local() // symbols from other crates |
| || tcx.effective_visibilities(()).is_directly_public(aliased_entity_def_id.expect_local()) |
| { |
| return None; |
| } |
| let item_name = tcx.opt_item_name(aliased_entity_def_id)?; |
| let krate = tcx.crate_name(def_id.krate); |
| let cpp_top_level_ns = format_top_level_ns_for_crate(db, def_id.krate); |
| let parent_def_key = tcx.def_key(def_id).parent?; |
| let parent_def_id = DefId::local(parent_def_key); |
| |
| // If the parent is being aliased, we use its canonical name and we always |
| // process parents before their children. |
| let full_path_strs: Vec<Rc<str>> = if let Some(con_name) = name_map.get(&parent_def_id) { |
| con_name.rs_mod_path.parts().cloned().collect() |
| } else { |
| let mut full_path = tcx.def_path(def_id).data; // mod_path + name |
| full_path.pop().expect("At least the use exists"); |
| full_path |
| .into_iter() |
| .filter_map(|p| p.data.get_opt_name()) |
| .map(|s| Rc::<str>::from(s.as_str())) |
| .collect() |
| }; |
| |
| if krate.as_str() == "polars_plan" && matches!(rs_name.as_str(), "date_range" | "time_range") { |
| // Short-circuit `polars_plan::dsl::{date_range, time_range}`. |
| // |
| // These two paths are the result of a pathological chain of ambiguous reexports that is |
| // not supported by the compiler and must not be considered. |
| // |
| // See https://github.com/rust-lang/rust/issues/144333 for details. |
| let path_strs: Vec<&str> = full_path_strs.iter().map(|x| &**x).collect(); |
| if matches!(&*path_strs, ["dsl"]) { |
| return None; |
| } |
| } |
| |
| let rs_mod_path = NamespaceQualifier::new(full_path_strs.clone()); |
| |
| let cpp_ns_path = |
| NamespaceQualifier::new(full_path_strs.into_iter().chain([Rc::from("__crubit_internal")])); |
| let attributes = crubit_attr::get_attrs(tcx, aliased_entity_def_id).unwrap(); |
| let cpp_type = attributes.cpp_type; |
| Some(FullyQualifiedName { |
| cpp_name: Some(item_name), |
| cpp_ns_path, |
| rs_name: Some(rs_name), |
| krate, |
| cpp_top_level_ns, |
| rs_mod_path, |
| cpp_type, |
| }) |
| } |
| |
| /// Implementation of `BindingsGenerator::reexported_symbol_canonical_name_mapping`. |
| // TODO(b/350772554): Don't generate bindings for ambiguous symbols. |
| fn reexported_symbol_canonical_name_mapping( |
| db: &dyn BindingsGenerator<'_>, |
| ) -> HashMap<DefId, FullyQualifiedName> { |
| let tcx = db.tcx(); |
| let mut name_map: HashMap<DefId, FullyQualifiedName> = |
| symbols_from_extern_crate(db).into_iter().collect(); |
| |
| let aliases = tcx |
| .hir_free_items() |
| .filter_map(|item_id| { |
| let local_def_id: LocalDefId = item_id.owner_id.def_id; |
| let Item { kind: kind @ ItemKind::Use(use_path, use_kind), .. } = |
| tcx.hir_expect_item(local_def_id) |
| else { |
| return None; |
| }; |
| let ident_str = &kind.ident().map_or("".to_owned(), |ident| ident.as_str().to_owned()); |
| // TODO(b/350772554): Preserve the errors. |
| collect_alias_from_use(db, ident_str, use_path, use_kind).ok().map(|aliases| { |
| aliases.into_iter().map(move |(using_name, type_def_id, def_kind)| AliasInfo { |
| using_name, |
| local_def_id, |
| type_def_id, |
| def_kind, |
| }) |
| }) |
| }) |
| .flatten() |
| .collect::<Vec<AliasInfo>>(); |
| |
| // TODO(b/350772554): Support mod. |
| // We should process the aliases in the path order: mod -> struct -> |
| // function/etc. Otherwise, for example, the function will still use the |
| // private fully qualified name as it doesn't know the canonical struct name |
| // yet. |
| let (struct_like_aliases, other_aliases): (Vec<AliasInfo>, Vec<AliasInfo>) = |
| aliases.into_iter().partition(|AliasInfo { def_kind, .. }| { |
| matches!(*def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union) |
| }); |
| for alias_info in struct_like_aliases.into_iter().chain(other_aliases.into_iter()) { |
| if let Some(canonical_name) = create_canonical_name(db, &name_map, &alias_info) { |
| name_map.insert(alias_info.type_def_id, canonical_name); |
| } |
| } |
| |
| name_map |
| } |
| |
| /// Checks whether a definition matches a specific qualified name. |
| fn matches_qualified_name( |
| db: &dyn BindingsGenerator<'_>, |
| item_did: DefId, |
| name_to_compare: &str, |
| ) -> bool { |
| // TODO(b/372153103): Compare the name via `tcx.def_path(adt.did())`. |
| let type_name = FullyQualifiedName::new(db, item_did); |
| type_name.format_for_rs().to_string() == name_to_compare |
| } |
| |
| /// Checks that `ty` has the same ABI as `rs_std::SliceRef`. |
| fn check_slice_layout<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) { |
| // Check the assumption from `rust_builtin_type_abi_assumptions.md` that Rust's |
| // slice has the same ABI as `rs_std::SliceRef`. |
| let layout = tcx |
| .layout_of( |
| ty::TypingEnv { |
| typing_mode: ty::TypingMode::PostAnalysis, |
| param_env: ty::ParamEnv::empty(), |
| } |
| .as_query_input(ty), |
| ) |
| .expect("`layout_of` is expected to succeed for `{ty}` type") |
| .layout; |
| assert_eq!(8, layout.align().abi.bytes()); |
| assert_eq!(16, layout.size().bytes()); |
| assert!(matches!( |
| layout.backend_repr(), |
| BackendRepr::ScalarPair( |
| Scalar::Initialized { value: Primitive::Pointer(AddressSpace(_)), .. }, |
| Scalar::Initialized { |
| value: Primitive::Int(Integer::I64, /* signedness = */ false), |
| .. |
| } |
| ) |
| )); |
| } |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum CcType { |
| Pointer { cpp_type: Symbol, cv: CcConstQualifier }, |
| Other(Symbol), |
| } |
| |
| impl AsRef<str> for CcType { |
| fn as_ref(&self) -> &str { |
| match self { |
| CcType::Other(cpp_type) => cpp_type.as_str(), |
| CcType::Pointer { cpp_type, .. } => cpp_type.as_str(), |
| } |
| } |
| } |
| |
| /// Returns the C++ must_use tag for the item identified by `def_id`, or None if there is no such |
| /// tag. |
| fn generate_must_use_tag(tcx: TyCtxt, def_id: DefId) -> Option<TokenStream> { |
| if let Some(must_use_attr) = must_use_attr_of(tcx, def_id) { |
| let cc_must_use_tag = match must_use_attr.reason { |
| None => quote! {[[nodiscard]]}, |
| Some(reason) => { |
| let reason = reason.as_str(); |
| quote! {[[nodiscard(#reason)]]} |
| } |
| }; |
| return Some(cc_must_use_tag); |
| } |
| None |
| } |
| |
| /// Returns the C++ deprecated tag for the item identified by `def_id`, if it is |
| /// deprecated. Otherwise, returns None. |
| fn generate_deprecated_tag(tcx: TyCtxt, def_id: DefId) -> Option<TokenStream> { |
| use rustc_hir::attrs::AttributeKind; |
| use rustc_hir::find_attr; |
| |
| if let Some((deprecation, _span)) = find_attr!(tcx.get_all_attrs(def_id), AttributeKind::Deprecation{deprecation, span} => (*deprecation, *span)) |
| { |
| let cc_deprecated_tag = match deprecation.note { |
| None => quote! {[[deprecated]]}, |
| Some(note_symbol) => { |
| let note = note_symbol.as_str(); |
| quote! {[[deprecated(#note)]]} |
| } |
| }; |
| return Some(cc_deprecated_tag); |
| } |
| None |
| } |
| |
| fn generate_using_statement( |
| db: &dyn BindingsGenerator<'_>, |
| using_name: &str, |
| def_id: DefId, |
| def_kind: DefKind, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| |
| match def_kind { |
| DefKind::Fn => { |
| let mut prereqs; |
| if !def_id.is_local() { |
| bail!("`use` of external functions is not yet supported"); |
| } |
| // TODO(b/350772554): Support exporting private functions. |
| match db.generate_function(def_id) { |
| Ok(snippet) => { |
| prereqs = snippet.main_api.prereqs; |
| } |
| Err(err) => { |
| bail!("Unable to `use` function whose bindings failed: {err:?}"); |
| } |
| } |
| let fully_qualified_fn_name = FullyQualifiedName::new(db, def_id); |
| let formatted_fully_qualified_fn_name = fully_qualified_fn_name.format_for_cc(db)?; |
| let main_api_fn_name = |
| format_cc_ident(db, fully_qualified_fn_name.cpp_name.unwrap().as_str()) |
| .context("Error formatting function name")?; |
| let using_name = |
| format_cc_ident(db, using_name).context("Error formatting using name")?; |
| |
| prereqs.defs.insert(def_id); |
| let tokens = if format!("{}", using_name) == format!("{}", main_api_fn_name) { |
| quote! {using #formatted_fully_qualified_fn_name;} |
| } else { |
| // TODO(b/350772554): Support function alias. |
| bail!("Unsupported function alias"); |
| }; |
| Ok(ApiSnippets { |
| main_api: CcSnippet { prereqs, tokens }, |
| cc_details: CcSnippet::default(), |
| rs_details: RsSnippet::default(), |
| }) |
| } |
| DefKind::Struct | DefKind::Enum => { |
| // This points directly to a type definition, not an alias or compound data |
| // type, so we can drop the hir type. |
| let use_type = SugaredTy::new(tcx.type_of(def_id).instantiate_identity(), None); |
| create_type_alias(db, def_id, using_name, use_type) |
| } |
| DefKind::TyAlias => generate_type_alias(db, def_id, using_name), |
| _ => { |
| bail!("Unsupported use statement that refers to this type of the entity: {:#?}", def_id) |
| } |
| } |
| } |
| |
| fn debug_print_use_path(use_path: &UsePath) -> String { |
| use_path.segments.iter().map(|segment| segment.ident.as_str()).collect::<Vec<&str>>().join("::") |
| } |
| |
| fn use_path_as_single_res(use_path: &UsePath) -> Result<Res> { |
| // TODO(b/350772554): Support multiple items with the same name in `use` |
| // statements.` |
| let mut present_items: Vec<Res> = |
| use_path.res.present_items().filter(|res| !matches!(res, Res::Err)).collect(); |
| |
| // Filter `Ctor` functions with the same names as their types. |
| if present_items.len() > 1 { |
| present_items.retain(|item| !matches!(item, Res::Def(DefKind::Ctor(..), _))) |
| } |
| |
| if present_items.is_empty() { |
| bail!("`use` path `{}` resolved to no non-error items", debug_print_use_path(use_path)) |
| } |
| |
| if present_items.len() > 1 { |
| bail!( |
| "`use` path `{}` resolved to multiple items with the same name: {:?}", |
| debug_print_use_path(use_path), |
| use_path.res.present_items().collect::<Vec<_>>() |
| ); |
| } |
| |
| Ok(present_items.into_iter().next().unwrap()) |
| } |
| |
| struct DefInfo { |
| ident: Ident, |
| def_id: DefId, |
| def_kind: DefKind, |
| } |
| |
| enum PublicOnly { |
| Yes, |
| No, |
| } |
| |
| /// Returns all public definitions in the given module. |
| fn defs_in_mod<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| def_id: DefId, |
| public_only: PublicOnly, |
| ) -> impl Iterator<Item = DefInfo> + use<'tcx> { |
| let module_children = match def_id.as_local() { |
| None => tcx.module_children(def_id), |
| // Local `module_children` does not use the query due to perf impacts. |
| Some(local_def_id) => tcx.module_children_local(local_def_id), |
| }; |
| module_children.iter().filter_map(move |mod_child| { |
| if matches!(public_only, PublicOnly::Yes) && !mod_child.vis.is_public() { |
| return None; |
| } |
| |
| let hir::def::Res::Def(mut item_def_kind, mut item_def_id) = mod_child.res else { |
| // TODO(b/350772554): Support PrimTy. |
| return None; |
| }; |
| |
| // For re-exported items, we want to return the ID of the re-export itself, not the ID of |
| // the re-export target. |
| if let Some(&reexport) = mod_child.reexport_chain.first() { |
| if let Some(reexport_id) = reexport.id() { |
| item_def_id = reexport_id; |
| item_def_kind = tcx.def_kind(reexport_id); |
| } |
| } |
| |
| if let Some(stability) = tcx.lookup_stability(item_def_id) { |
| if stability.is_unstable() { |
| return None; |
| } |
| } |
| |
| // Omit tuple Ctors functions as top-level C++ items. They are instead emitted only as part |
| // of the type with the same name. |
| if matches!(item_def_kind, DefKind::Ctor(..)) { |
| return None; |
| } |
| |
| Some(DefInfo { ident: mod_child.ident, def_id: item_def_id, def_kind: item_def_kind }) |
| }) |
| } |
| |
| /// Returns all public items of a bindable kind (fns, structs, enums, using statements) in the given |
| /// module and its nested modules. |
| fn defs_in_mod_recursive(tcx: TyCtxt, def_id: DefId) -> Vec<DefInfo> { |
| let mut items = vec![]; |
| // List of child modules to visit. |
| let mut mods_to_visit = vec![def_id]; |
| let mut visited = HashSet::new(); |
| while let Some(mod_id) = mods_to_visit.pop() { |
| items.extend( |
| defs_in_mod(tcx, mod_id, PublicOnly::No) |
| .filter(|info| visited.insert(info.def_id)) |
| .inspect(|def_info| { |
| if matches!(def_info.def_kind, DefKind::Mod) { |
| mods_to_visit.push(def_info.def_id); |
| } |
| }), |
| ); |
| } |
| items |
| } |
| |
| // Collect all the aliases (alias_name, underlying_type_def_id, |
| // underlying_type_def_kind) created by the `use` statement. For example, `pub |
| // use some_mod::*` will return all the free items that are exported. |
| fn collect_alias_from_use( |
| db: &dyn BindingsGenerator<'_>, |
| using_name: &str, |
| use_path: &UsePath, |
| use_kind: &UseKind, |
| ) -> Result<Vec<(String, DefId, DefKind)>> { |
| let res = use_path_as_single_res(use_path)?; |
| // TODO(b/350772554): Support PrimTy. |
| let Res::Def(def_kind, def_id) = res else { |
| bail!( |
| "`use` statement `{}` refers to unsupported definition kind: {:#?}", |
| debug_print_use_path(use_path), |
| res |
| ); |
| }; |
| // TODO(b/350772554): Support `use some_module`. |
| if def_kind == DefKind::Mod && !matches!(use_kind, UseKind::Glob) { |
| bail!("`use` of a module (`{}`) is not yet supported", debug_print_use_path(use_path)) |
| } |
| |
| let mut aliases = vec![]; |
| if def_kind == DefKind::Mod { |
| for DefInfo { ident, def_id: item_def_id, def_kind: item_def_kind } in |
| defs_in_mod(db.tcx(), def_id, PublicOnly::Yes) |
| { |
| // TODO(b/350772554): Support export Enum fields. |
| if !ident.name.is_empty() { |
| aliases.push((ident.name.to_string(), item_def_id, item_def_kind)); |
| } |
| } |
| } else { |
| // TODO(b/350772554): Support export Enum fields. |
| if !using_name.is_empty() { |
| aliases.push((using_name.to_string(), def_id, def_kind)); |
| } |
| } |
| Ok(aliases) |
| } |
| |
| fn generate_use( |
| db: &dyn BindingsGenerator<'_>, |
| using_name: &str, |
| use_path: &UsePath, |
| use_kind: &UseKind, |
| ) -> Result<ApiSnippets> { |
| let aliases = collect_alias_from_use(db, using_name, use_path, use_kind)?; |
| // TODO(b/350772554): Expose the errors. If any of the types in the `use` |
| // statement is not supported, we currently ignore it and discard the |
| // errors. |
| Ok(aliases |
| .into_iter() |
| .filter_map(|(using_name, def_id, def_kind)| { |
| if is_public_or_supported_export(db, def_id) { |
| Some(generate_using_statement(db, &using_name, def_id, def_kind)) |
| } else { |
| None |
| } |
| }) |
| .filter_map(Result::ok) |
| .collect()) |
| } |
| |
| fn generate_const(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let unsupported_node_item_msg = "Called `generate_const` with a `rustc_hir::Node` that is not a `Node::Item` or `Node::ImplItem`"; |
| let ty = tcx.type_of(def_id).instantiate_identity(); |
| let hir_ty = def_id.as_local().map(|local_def_id| { |
| let hir_node = tcx.hir_node_by_def_id(local_def_id); |
| match hir_node { |
| Node::Item(item) => item.expect_const().2, |
| Node::ImplItem(item) => item.expect_const().0, |
| _ => panic!("{}", unsupported_node_item_msg), |
| } |
| }); |
| let rust_type = SugaredTy::new(ty, hir_ty); |
| let cc_type_snippet = db.format_ty_for_cc(rust_type, TypeLocation::Const)?; |
| |
| let cc_type = cc_type_snippet.tokens; |
| let cc_name = format_cc_ident(db, tcx.item_name(def_id).as_str())?; |
| |
| // Note that `&str` constants may appear as either `ConstValue::Slice` or |
| // `ConstValue::Indirect`. |
| let const_value: ConstValue = tcx.const_eval_poly(def_id).unwrap(); |
| let cc_value = match const_value { |
| ConstValue::Scalar(scalar) => scalar_value_to_string(tcx, scalar, *ty.kind()), |
| ConstValue::ZeroSized => bail!("const of type `{rust_type}` cannot be generated as zero-sized consts are not supported in C++."), |
| ConstValue::Slice { .. } | ConstValue::Indirect { .. } => { |
| let string_literal = match ty.kind() { |
| ty::TyKind::Ref(_region, referent_ty, mutability) |
| if matches!(referent_ty.kind(), ty::TyKind::Str) => |
| { |
| if mutability.is_mut() { |
| panic!("Unexpected mutable reference in a constant of type `{rust_type}`") |
| } |
| if let Some(slice) = const_value.try_get_slice_bytes_for_diagnostics(tcx) { |
| let str_data = std::str::from_utf8(slice).unwrap(); |
| Some(quote! { rs_std::StrRef(#str_data) }.to_string()) |
| } else { None } |
| } |
| _ => None |
| }; |
| string_literal.ok_or_else(|| { |
| anyhow!("const of type `{rust_type}` cannot be generated as only scalar consts are supported.") |
| }) |
| } |
| }? |
| .parse::<TokenStream>() |
| .unwrap(); |
| |
| Ok(ApiSnippets { |
| main_api: CcSnippet { |
| tokens: quote! { |
| static constexpr #cc_type #cc_name = #cc_value; |
| }, |
| ..cc_type_snippet |
| }, |
| cc_details: CcSnippet::default(), |
| rs_details: RsSnippet::default(), |
| }) |
| } |
| |
| fn generate_type_alias( |
| db: &dyn BindingsGenerator<'_>, |
| def_id: DefId, |
| using_name: &str, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let hir_ty = def_id.as_local().map(|local_def_id| { |
| let Item { kind: ItemKind::TyAlias(_, _, hir_ty, ..), .. } = |
| tcx.hir_expect_item(local_def_id) |
| else { |
| panic!("called generate_type_alias on a non-type-alias"); |
| }; |
| *hir_ty |
| }); |
| let alias_type = SugaredTy::new(tcx.type_of(def_id).instantiate_identity(), hir_ty); |
| create_type_alias(db, def_id, using_name, alias_type) |
| } |
| |
| fn create_type_alias<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| def_id: DefId, |
| alias_name: &str, |
| alias_type: SugaredTy<'tcx>, |
| ) -> Result<ApiSnippets> { |
| let cc_bindings = db.format_ty_for_cc(alias_type, TypeLocation::Other)?; |
| let mut main_api_prereqs = CcPrerequisites::default(); |
| let actual_type_name = cc_bindings.into_tokens(&mut main_api_prereqs); |
| |
| let alias_name = format_cc_ident(db, alias_name).context("Error formatting type alias name")?; |
| |
| let fully_qualified_name = database::FullyQualifiedName::new(db, def_id); |
| let rs_type = format!("{}", fully_qualified_name.format_for_rs()); |
| |
| main_api_prereqs.includes.insert(db.support_header("annotations_internal.h")); |
| let mut attributes = vec![quote! {CRUBIT_INTERNAL_RUST_TYPE(#rs_type)}]; |
| if let Some(cc_deprecated_tag) = generate_deprecated_tag(db.tcx(), def_id) { |
| attributes.push(cc_deprecated_tag); |
| } |
| |
| let tokens = quote! {using #alias_name #(#attributes)* = #actual_type_name;}; |
| |
| Ok(ApiSnippets { |
| main_api: CcSnippet { prereqs: main_api_prereqs, tokens }, |
| cc_details: CcSnippet::default(), |
| rs_details: RsSnippet::default(), |
| }) |
| } |
| |
| fn is_public_or_supported_export(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> bool { |
| is_directly_public(db.tcx(), def_id) |
| || ((is_exported(db.tcx(), def_id) || !def_id.is_local()) |
| && db.reexported_symbol_canonical_name_mapping().contains_key(&def_id)) |
| } |
| |
| /// Implementation of `BindingsGenerator::generate_default_ctor`. |
| fn generate_default_ctor<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets> { |
| fn fallible_format_default_ctor<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let trait_id = tcx |
| .get_diagnostic_item(sym::Default) |
| .ok_or(anyhow!("Couldn't find `core::default::Default`"))?; |
| let TraitThunks { |
| method_name_to_cc_thunk_name, |
| cc_thunk_decls, |
| rs_thunk_impls: rs_details, |
| } = generate_trait_thunks(db, trait_id, &[], &core)?; |
| |
| let cc_struct_name = &core.cc_short_name; |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ "Default::default" |
| #cc_struct_name(); __NEWLINE__ __NEWLINE__ |
| }); |
| let cc_details = { |
| let thunk_name = method_name_to_cc_thunk_name |
| .into_values() |
| .exactly_one() |
| .expect("Expecting a single `default` method"); |
| |
| let mut prereqs = CcPrerequisites::default(); |
| let cc_thunk_decls = cc_thunk_decls.into_tokens(&mut prereqs); |
| |
| let tokens = quote! { |
| #cc_thunk_decls |
| inline #cc_struct_name::#cc_struct_name() { |
| __crubit_internal::#thunk_name(this); |
| } |
| }; |
| CcSnippet { tokens, prereqs } |
| }; |
| Ok(ApiSnippets { main_api, cc_details, rs_details }) |
| } |
| fallible_format_default_ctor(db, core.clone()).map_err(|err| { |
| let msg = format!("{err:#}"); |
| let adt_cc_name = &core.cc_short_name; |
| ApiSnippets { |
| main_api: CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #adt_cc_name() = delete; __NEWLINE__ |
| }), |
| ..Default::default() |
| } |
| }) |
| } |
| |
| /// Implementation of `BindingsGenerator::generate_copy_ctor_and_assignment_operator`. |
| fn generate_copy_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets> { |
| fn fallible_format_copy_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let cc_struct_name = &core.cc_short_name; |
| |
| if is_copy(tcx, core.def_id, core.self_ty) { |
| let msg = "Rust types that are `Copy` get trivial, `default` C++ copy constructor \ |
| and assignment operator."; |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #cc_struct_name(const #cc_struct_name&) = default; __NEWLINE__ |
| #cc_struct_name& operator=(const #cc_struct_name&) = default; |
| }); |
| let cc_details = CcSnippet::with_include( |
| quote! { |
| static_assert(std::is_trivially_copy_constructible_v<#cc_struct_name>); |
| static_assert(std::is_trivially_copy_assignable_v<#cc_struct_name>); |
| }, |
| CcInclude::type_traits(), |
| ); |
| |
| return Ok(ApiSnippets { main_api, cc_details, rs_details: RsSnippet::default() }); |
| } |
| |
| let trait_id = tcx |
| .lang_items() |
| .clone_trait() |
| .ok_or_else(|| anyhow!("Can't find the `Clone` trait"))?; |
| let TraitThunks { |
| method_name_to_cc_thunk_name, |
| cc_thunk_decls, |
| rs_thunk_impls: rs_details, |
| } = generate_trait_thunks(db, trait_id, &[], &core)?; |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ "Clone::clone" |
| #cc_struct_name(const #cc_struct_name&); __NEWLINE__ |
| __NEWLINE__ __COMMENT__ "Clone::clone_from" |
| #cc_struct_name& operator=(const #cc_struct_name&); __NEWLINE__ __NEWLINE__ |
| }); |
| let cc_details = { |
| // `unwrap` calls are okay because `Clone` trait always has these methods. |
| let clone_thunk_name = method_name_to_cc_thunk_name.get(&sym::clone).unwrap(); |
| let clone_from_thunk_name = method_name_to_cc_thunk_name.get(&sym::clone_from).unwrap(); |
| |
| let mut prereqs = CcPrerequisites::default(); |
| let cc_thunk_decls = cc_thunk_decls.into_tokens(&mut prereqs); |
| |
| let tokens = quote! { |
| #cc_thunk_decls |
| inline #cc_struct_name::#cc_struct_name(const #cc_struct_name& other) { |
| __crubit_internal::#clone_thunk_name(other, this); |
| } |
| inline #cc_struct_name& #cc_struct_name::operator=(const #cc_struct_name& other) { |
| if (this != &other) { |
| __crubit_internal::#clone_from_thunk_name(*this, other); |
| } |
| return *this; |
| } |
| }; |
| CcSnippet { tokens, prereqs } |
| }; |
| Ok(ApiSnippets { main_api, cc_details, rs_details }) |
| } |
| fallible_format_copy_ctor_and_assignment_operator(db, core.clone()).map_err(|err| { |
| let msg = format!("{err:#}"); |
| let adt_cc_name = &core.cc_short_name; |
| ApiSnippets { |
| main_api: CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #adt_cc_name(const #adt_cc_name&) = delete; __NEWLINE__ |
| #adt_cc_name& operator=(const #adt_cc_name&) = delete; |
| }), |
| ..Default::default() |
| } |
| }) |
| } |
| |
| /// Implementation of `BindingsGenerator::generate_move_ctor_and_assignment_operator`. |
| fn generate_move_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets> { |
| fn fallible_format_move_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let adt_cc_name = &core.cc_short_name; |
| if generate_struct_and_union::adt_core_bindings_needs_drop(&core, tcx) { |
| let has_default_ctor = db.generate_default_ctor(core.clone()).is_ok(); |
| let is_unpin = core.self_ty.is_unpin(tcx, post_analysis_typing_env(tcx, core.def_id)); |
| if has_default_ctor && is_unpin { |
| let main_api = CcSnippet::new(quote! { |
| #adt_cc_name(#adt_cc_name&&); __NEWLINE__ |
| #adt_cc_name& operator=(#adt_cc_name&&); __NEWLINE__ |
| }); |
| let mut prereqs = CcPrerequisites::default(); |
| prereqs.includes.insert(db.support_header("internal/memswap.h")); |
| prereqs.includes.insert(CcInclude::utility()); // for `std::move` |
| let tokens = quote! { |
| inline #adt_cc_name::#adt_cc_name(#adt_cc_name&& other) |
| : #adt_cc_name() { |
| *this = std::move(other); |
| } |
| inline #adt_cc_name& #adt_cc_name::operator=(#adt_cc_name&& other) { |
| crubit::MemSwap(*this, other); |
| return *this; |
| } |
| }; |
| let cc_details = CcSnippet { tokens, prereqs }; |
| Ok(ApiSnippets { main_api, cc_details, ..Default::default() }) |
| } else if db.generate_copy_ctor_and_assignment_operator(core).is_ok() { |
| // The class will have a custom copy constructor and copy assignment operator |
| // and *no* move constructor nor move assignment operator. This |
| // way, when a move is requested, a copy is performed instead |
| // (this is okay, this is what happens if a copyable pre-C++11 |
| // class is compiled in C++11 mode and moved). |
| // |
| // We can't use the `=default` move constructor, because it is elementwise and |
| // semantically incorrect. We can't `=delete` the move constructor because it |
| // would make `SomeStruct(MakeSomeStruct())` select the deleted move constructor |
| // and fail to compile. |
| Ok(ApiSnippets::default()) |
| } else { |
| bail!( |
| "C++ moves are deleted \ |
| because there's no non-destructive implementation available." |
| ); |
| } |
| } else { |
| let main_api = CcSnippet::new(quote! { |
| // The generated bindings have to follow Rust move semantics: |
| // * All Rust types are memcpy-movable (e.g. <internal link>/constructors.html says |
| // that "Every type must be ready for it to be blindly memcopied to somewhere |
| // else in memory") |
| // * The only valid operation on a moved-from non-`Copy` Rust struct is to assign to |
| // it. |
| // |
| // The generated C++ bindings below match the required semantics because they: |
| // * Generate trivial` C++ move constructor and move assignment operator. Per |
| // <internal link>/cpp/language/move_constructor#Trivial_move_constructor: "A trivial |
| // move constructor is a constructor that performs the same action as the trivial |
| // copy constructor, that is, makes a copy of the object representation as if by |
| // std::memmove." |
| // * Generate trivial C++ destructor. |
| // |
| // In particular, note that the following C++ code and Rust code are exactly |
| // equivalent (except that in Rust, reuse of `y` is forbidden at compile time, |
| // whereas in C++, it's only prohibited by convention): |
| // * C++, assumming trivial move constructor and trivial destructor: |
| // `auto x = std::move(y);` |
| // * Rust, assumming non-`Copy`, no custom `Drop` or drop glue: |
| // `let x = y;` |
| // |
| // TODO(b/258251148): If the ADT provides a custom `Drop` impls or requires drop |
| // glue, then extra care should be taken to ensure the C++ destructor can handle |
| // the moved-from object in a way that meets Rust move semantics. For example, the |
| // generated C++ move constructor might need to assign `Default::default()` to the |
| // moved-from object. |
| #adt_cc_name(#adt_cc_name&&) = default; __NEWLINE__ |
| #adt_cc_name& operator=(#adt_cc_name&&) = default; __NEWLINE__ |
| __NEWLINE__ |
| }); |
| let cc_details = CcSnippet::with_include( |
| quote! { |
| static_assert(std::is_trivially_move_constructible_v<#adt_cc_name>); |
| static_assert(std::is_trivially_move_assignable_v<#adt_cc_name>); |
| }, |
| CcInclude::type_traits(), |
| ); |
| Ok(ApiSnippets { main_api, cc_details, ..Default::default() }) |
| } |
| } |
| fallible_format_move_ctor_and_assignment_operator(db, core.clone()).map_err(|err| { |
| let msg = format!("{err:#}"); |
| let adt_cc_name = &core.cc_short_name; |
| ApiSnippets { |
| main_api: CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #adt_cc_name(#adt_cc_name&&) = delete; __NEWLINE__ |
| #adt_cc_name& operator=(#adt_cc_name&&) = delete; |
| }), |
| ..Default::default() |
| } |
| }) |
| } |
| |
| /// Formats the forward declaration of an algebraic data type (an ADT - a |
| /// struct, an enum, or a union), returning something like |
| /// `quote!{ struct SomeStruct; }`. |
| /// |
| /// Will panic if `def_id` doesn't identify an ADT that can be successfully |
| /// handled by `generate_adt_core`. |
| fn generate_fwd_decl(db: &Database<'_>, def_id: DefId) -> TokenStream { |
| // `generate_fwd_decl` should only be called for items from |
| // `CcPrerequisites::fwd_decls` and `fwd_decls` should only contain ADTs |
| // that `generate_adt_core` succeeds for. |
| let core_bindings = db |
| .generate_adt_core(def_id) |
| .expect("`generate_fwd_decl` should only be called if `generate_adt_core` succeeded"); |
| let AdtCoreBindings { keyword, cc_short_name, .. } = &*core_bindings; |
| |
| quote! { #keyword #cc_short_name; } |
| } |
| |
| fn generate_source_location(db: &dyn BindingsGenerator, def_id: DefId) -> String { |
| let tcx = db.tcx(); |
| let def_span = tcx.def_span(def_id); |
| let rustc_span::FileLines { file, lines } = |
| match tcx.sess().source_map().span_to_lines(def_span) { |
| Ok(filelines) => filelines, |
| Err(_) => return "unknown location".to_string(), |
| }; |
| let file_name = file.name.prefer_local().to_string(); |
| // Note: line_index starts at 0, while most everything else starts indexing at 1. |
| let line_number = (lines[0].line_index + 1).to_string(); |
| if let Some(path_format) = db.crubit_debug_path_format() { |
| if file.name.is_real() { |
| return path_format.format(&[file_name.as_str(), line_number.as_str()]); |
| } |
| } |
| format!("{file_name};l={line_number}") |
| } |
| |
| /// Formats the doc comment (if any) associated with the item identified by |
| /// `local_def_id`, and appends the source location at which the item is |
| /// defined. |
| fn generate_doc_comment(db: &dyn BindingsGenerator, def_id: DefId) -> TokenStream { |
| let doc_comment = db |
| .tcx() |
| .get_all_attrs(def_id) |
| .iter() |
| .filter_map(|attr| attr.doc_str()) |
| .map(|symbol| symbol.to_string()) |
| .chain(once(format!("Generated from: {}", generate_source_location(db, def_id)))) |
| .join("\n\n"); |
| quote! { __COMMENT__ #doc_comment} |
| } |
| |
| /// Returns the name of the item identified by `def_id`, or "<unknown>" if |
| /// the item can't be identified. |
| fn item_name(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> Symbol { |
| db.tcx().opt_item_name(def_id).unwrap_or_else(|| Symbol::intern("<unknown>")) |
| } |
| |
| /// Implementation of `BindingsGenerator::generate_item`. |
| fn generate_item(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> Result<Option<ApiSnippets>> { |
| let tcx = db.tcx(); |
| let generated = generate_item_impl(db, def_id); |
| let attributes = crubit_attr::get_attrs(tcx, def_id).unwrap(); |
| if attributes.must_bind { |
| if let Err(e) = &generated { |
| let item_name = item_name(db, def_id); |
| let must_bind_message = format!( |
| "Failed to generate bindings for `{item_name}`:\n\ |
| {e:?}\n\ |
| This is a hard error because `{item_name}` was annotated with \ |
| `#[crubit_annotate::must_bind]`" |
| ); |
| db.fatal_errors().report(&must_bind_message); |
| } |
| } |
| generated |
| } |
| |
| // A helper for `generate_item`. |
| // The wrapper is used to ensure that the `must_bind` annotation is enforced. |
| fn generate_item_impl( |
| db: &dyn BindingsGenerator<'_>, |
| def_id: DefId, |
| ) -> Result<Option<ApiSnippets>> { |
| let tcx = db.tcx(); |
| |
| // TODO(b/350772554): Support `use` mod. |
| if !is_public_or_supported_export(db, def_id) { |
| return Ok(None); |
| } |
| |
| let item = match tcx.def_kind(def_id) { |
| DefKind::Struct | DefKind::Enum | DefKind::Union => { |
| let attributes = crubit_attr::get_attrs(tcx, def_id).unwrap(); |
| |
| let has_composable_bridging_attrs = matches!( |
| attributes.get_bridging_attrs()?, |
| Some(crubit_attr::BridgingAttrs::Composable { .. }) |
| ); |
| |
| if !has_composable_bridging_attrs |
| && BridgedBuiltin::new(db, tcx.adt_def(def_id)).is_none() |
| && query_compiler::has_non_lifetime_generics(tcx, def_id) |
| { |
| bail!("Generic types are not supported yet (b/259749095)"); |
| } |
| |
| if let Some(cpp_type) = attributes.cpp_type { |
| let item_name = tcx.def_path_str(def_id); |
| bail!( |
| "Type bindings for {item_name} suppressed due to being mapped to \ |
| an existing C++ type ({cpp_type})" |
| ); |
| } |
| db.generate_adt_core(def_id).map(|core| Some(generate_adt(db, core))) |
| } |
| DefKind::Fn => db.generate_function(def_id).map(Some), |
| DefKind::TyAlias => { |
| generate_type_alias(db, def_id, tcx.item_name(def_id).as_str()).map(Some) |
| } |
| DefKind::Use => { |
| let Some(local_def_id) = def_id.as_local() else { |
| bail!("Generated bindings for `use` from remote crate not yet supported."); |
| }; |
| let Item { kind: kind @ ItemKind::Use(use_path, use_kind), .. } = |
| tcx.hir_expect_item(local_def_id) |
| else { |
| panic!("Use item not found"); |
| }; |
| let ident_str = &kind.ident().map_or("".to_owned(), |ident| ident.as_str().to_owned()); |
| generate_use(db, ident_str, use_path, use_kind).map(Some) |
| } |
| DefKind::Const => generate_const(db, def_id).map(Some), |
| DefKind::Impl { .. } => Ok(None), // Handled by `generate_adt` |
| DefKind::Mod => Ok(None), // Handled by `generate_crate` |
| kind => bail!("Unsupported rustc_hir::hir::DefKind: {kind:?}"), |
| }; |
| |
| if let Ok(Some(item)) = item { |
| Ok(Some(item.resolve_feature_requirements(crate_features(db, db.source_crate_num()))?)) |
| } else { |
| item |
| } |
| } |
| |
| /// Formats a C++ comment explaining why no bindings have been generated for |
| /// `local_def_id`. |
| fn generate_unsupported_def( |
| db: &dyn BindingsGenerator<'_>, |
| def_id: DefId, |
| err: Error, |
| ) -> ApiSnippets { |
| let tcx = db.tcx(); |
| db.errors().report(&err); |
| let source_loc = generate_source_location(db, def_id); |
| let name = tcx.def_path_str(def_id); |
| |
| // https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations |
| // says: To print causes as well [...], use the alternate selector “{:#}”. |
| let msg = format!("Error generating bindings for `{name}` defined at {source_loc}: {err:#}"); |
| let main_api = CcSnippet::new(quote! { __NEWLINE__ __NEWLINE__ __COMMENT__ #msg __NEWLINE__ }); |
| |
| ApiSnippets { main_api, cc_details: CcSnippet::default(), rs_details: RsSnippet::default() } |
| } |
| |
| /// Formats namespace-bound snippets, given an iterator over (namespace_def_id, |
| /// namespace_qualifier, tokens) and the TyCtxt. |
| /// |
| /// (The namespace_def_id is optional, where None corresponds to the top-level |
| /// namespace.) |
| /// |
| /// For example, `[(id, ns, tokens)]` will be formatted as: |
| /// |
| /// ``` |
| /// namespace ns { |
| /// #tokens |
| /// } |
| /// ``` |
| /// |
| /// `format_namespace_bound_cc_tokens` tries to give a nice-looking output - for |
| /// example it combines consecutive items that belong to the same namespace, |
| /// when given `[(id, ns, tokens1), (id, ns, tokens2)]` as input: |
| /// |
| /// ``` |
| /// namespace ns { |
| /// #tokens1 |
| /// #tokens2 |
| /// } |
| /// ``` |
| /// |
| /// `format_namespace_bound_cc_tokens` also knows that top-level items (e.g. |
| /// ones where `NamespaceQualifier` doesn't contain any namespace names) should |
| /// be emitted at the top-level (not nesting them under a `namespace` keyword). |
| /// For example, `[(None, toplevel_ns, tokens)]` will be formatted as just: |
| /// |
| /// ``` |
| /// #tokens |
| /// ``` |
| pub fn format_namespace_bound_cc_tokens( |
| db: &dyn BindingsGenerator<'_>, |
| iter: impl IntoIterator<Item = (Option<DefId>, NamespaceQualifier, TokenStream)>, |
| tcx: TyCtxt, |
| ) -> TokenStream { |
| let iter = iter |
| .into_iter() |
| .coalesce(|(id1, ns1, mut tokens1), (id2, ns2, tokens2)| { |
| // Coalesce tokens if consecutive items belong to the same namespace. |
| if (id1 == id2) && (ns1 == ns2) { |
| tokens1.extend(tokens2); |
| Ok((id1, ns1, tokens1)) |
| } else { |
| Err(((id1, ns1, tokens1), (id2, ns2, tokens2))) |
| } |
| }) |
| .map(|(ns_def_id_opt, ns, tokens)| { |
| let mut ns_attributes = vec![]; |
| if let Some(ns_def_id) = ns_def_id_opt { |
| if let Some(cc_deprecated_tag) = generate_deprecated_tag(tcx, ns_def_id) { |
| ns_attributes.push(cc_deprecated_tag); |
| } |
| } |
| format_with_cc_body(db, &ns, tokens, ns_attributes).unwrap_or_else(|err| { |
| let name = ns.parts().join("::"); |
| let err = format!("Failed to format namespace name `{name}`: {err}"); |
| quote! { __COMMENT__ #err } |
| }) |
| }); |
| |
| // Using fully-qualified syntax to avoid the warning that `intersperse` |
| // may be added to the standard library in the future. |
| // |
| // TODO(https://github.com/rust-lang/rust/issues/79524): Use `.intersperse(...)` syntax once |
| // 1) this stdlib feature gets stabilized and |
| // 2) the method with conflicting name gets removed from `itertools`. |
| let iter = itertools::Itertools::intersperse(iter, quote! { __NEWLINE__ __NEWLINE__ }); |
| |
| iter.collect() |
| } |
| |
| /// Compares two `DefId` s |
| pub(crate) fn stable_def_id_cmp<'tcx>(tcx: TyCtxt<'tcx>, lhs_id: DefId, rhs_id: DefId) -> Ordering { |
| let lhs_span = tcx.def_span(lhs_id); |
| let rhs_span = tcx.def_span(rhs_id); |
| if lhs_span.source_equal(rhs_span) { |
| let lhs_def_path_hash = tcx.def_path_hash(lhs_id); |
| let rhs_def_path_hash = tcx.def_path_hash(rhs_id); |
| lhs_def_path_hash.cmp(&rhs_def_path_hash) |
| } else { |
| lhs_span.cmp(&rhs_span) |
| } |
| } |
| |
| pub(crate) trait SortedByDef: Iterator + Sized { |
| fn sorted_by_def<'tcx>(self, tcx: TyCtxt<'tcx>) -> std::vec::IntoIter<Self::Item> |
| where |
| Self::Item: Copy + Into<DefId>, |
| { |
| self.sorted_by_def_with(tcx, |item| (*item).into()) |
| } |
| |
| fn sorted_by_def_with<'tcx>( |
| self, |
| tcx: TyCtxt<'tcx>, |
| mut item_to_def_id: impl FnMut(&Self::Item) -> DefId, |
| ) -> std::vec::IntoIter<Self::Item> { |
| self.sorted_unstable_by(|lhs, rhs| { |
| stable_def_id_cmp(tcx, item_to_def_id(lhs), item_to_def_id(rhs)) |
| }) |
| } |
| } |
| impl<T: Iterator + Sized> SortedByDef for T {} |
| |
| /// Formats all public items from the Rust crate being compiled. |
| fn generate_crate(db: &Database) -> Result<BindingsTokens> { |
| let tcx = db.tcx(); |
| let mut cc_details_prereqs = CcPrerequisites::default(); |
| let mut cc_details: Vec<(DefId, TokenStream)> = vec![]; |
| let mut cc_api_impl = TokenStream::default(); |
| let mut extern_c_decls = BTreeSet::new(); |
| let mut main_apis = HashMap::<DefId, CcSnippet>::new(); |
| |
| let defs_in_crate = defs_in_mod_recursive(tcx, db.source_crate_num().as_def_id()); |
| let formatted_items = defs_in_crate |
| .into_iter() |
| .filter_map(|def_info| { |
| let def_id = def_info.def_id; |
| db.generate_item(def_id) |
| .unwrap_or_else(|err| Some(generate_unsupported_def(db, def_id, err))) |
| .map(|api_snippets| (def_id, api_snippets)) |
| }) |
| .sorted_by_def_with(tcx, |&(id, _)| id); |
| |
| for (def_id, api_snippets) in formatted_items { |
| let old_item = main_apis.insert(def_id, api_snippets.main_api); |
| assert!(old_item.is_none(), "Duplicated key: {def_id:?}"); |
| |
| // `cc_details` don't participate in the toposort, because |
| // `CcPrerequisites::defs` always use `main_api` as the predecessor |
| // - `chain`ing `cc_details` after `ordered_main_apis` trivially |
| // meets the prerequisites. |
| cc_details.push((def_id, api_snippets.cc_details.into_tokens(&mut cc_details_prereqs))); |
| cc_api_impl.extend(api_snippets.rs_details.into_tokens(&mut extern_c_decls)); |
| } |
| |
| // Find the order of `main_apis` that 1) meets the requirements of |
| // `CcPrerequisites::defs` and 2) makes a best effort attempt to keep the |
| // `main_apis` in the same order as the source order of the Rust APIs. |
| let ordered_ids = { |
| let toposort::TopoSortResult { ordered: ordered_ids, failed: failed_ids } = { |
| let nodes = main_apis.keys().copied(); |
| let deps = main_apis.iter().flat_map(|(&successor, main_api)| { |
| let predecessors = main_api.prereqs.defs.iter().copied().filter(|pre| |
| // Only consider `pre`s that we're currently generating APIs for. |
| main_apis.contains_key(pre)); |
| predecessors.map(move |predecessor| toposort::Dependency { predecessor, successor }) |
| }); |
| toposort::toposort(nodes, deps, move |&lhs_id, &rhs_id| { |
| stable_def_id_cmp(tcx, lhs_id, rhs_id) |
| }) |
| }; |
| assert_eq!( |
| 0, |
| failed_ids.len(), |
| "There are no known scenarios where CcPrerequisites::defs can form \ |
| a dependency cycle. These `LocalDefId`s form an unexpected cycle: {}", |
| failed_ids.into_iter().map(|id| format!("{:?}", id)).join(",") |
| ); |
| ordered_ids |
| }; |
| |
| // Destructure/rebuild `main_apis` (in the same order as `ordered_ids`) into |
| // `includes`, and `ordered_cc` (mixing in `fwd_decls` and `cc_details`). |
| let (includes, ordered_cc) = { |
| let mut already_declared: HashSet<DefId> = HashSet::new(); |
| let mut fwd_decls: HashSet<DefId> = HashSet::new(); |
| let mut includes = cc_details_prereqs.includes; |
| let mut ordered_main_apis: Vec<(DefId, TokenStream)> = Vec::new(); |
| for def_id in ordered_ids.into_iter() { |
| let CcSnippet { |
| tokens: cc_tokens, |
| prereqs: CcPrerequisites { |
| includes: mut inner_includes, |
| fwd_decls: inner_fwd_decls, |
| .. // `defs` have already been utilized by `toposort` above |
| } |
| } = main_apis.remove(&def_id).unwrap(); |
| |
| fwd_decls.extend(inner_fwd_decls.difference(&already_declared).copied()); |
| already_declared.insert(def_id); |
| already_declared.extend(inner_fwd_decls.into_iter()); |
| |
| includes.append(&mut inner_includes); |
| ordered_main_apis.push((def_id, cc_tokens)); |
| } |
| |
| let fwd_decls = fwd_decls |
| .into_iter() |
| .sorted_by_def(tcx) |
| .map(|local_def_id| (local_def_id, generate_fwd_decl(db, local_def_id))); |
| |
| // The first item of the tuple here is the DefId of the namespace. |
| let ordered_cc: Vec<(Option<DefId>, NamespaceQualifier, TokenStream)> = fwd_decls |
| .into_iter() |
| .chain(ordered_main_apis) |
| .chain(cc_details) |
| .map(|(def_id, tokens)| { |
| let ns_def_id = tcx.opt_parent(def_id); |
| let mod_path = FullyQualifiedName::new(db, def_id).cpp_ns_path; |
| (ns_def_id, mod_path, tokens) |
| }) |
| .collect_vec(); |
| |
| (includes, ordered_cc) |
| }; |
| |
| // Generate top-level elements of the C++ header file. |
| let cc_api = { |
| let cpp_top_level_ns = format_top_level_ns_for_crate(db, db.source_crate_num()) |
| .iter() |
| .map(|ns| db.format_cc_ident(*ns)) |
| .collect::<Result<Vec<_>>>()?; |
| |
| let includes = format_cc_includes(&includes); |
| let ordered_cc = format_namespace_bound_cc_tokens(db, ordered_cc, tcx); |
| quote! { |
| #includes |
| __NEWLINE__ __NEWLINE__ |
| namespace #(#cpp_top_level_ns)::* { |
| __NEWLINE__ |
| #ordered_cc |
| __NEWLINE__ |
| } |
| __NEWLINE__ |
| } |
| }; |
| |
| let mut decls = quote! {}; |
| for ExternCDecl { decl, .. } in extern_c_decls.into_iter() { |
| decls.extend(decl); |
| } |
| |
| if !decls.is_empty() { |
| cc_api_impl = quote! { |
| #cc_api_impl |
| |
| extern "C" { |
| #decls |
| } |
| }; |
| } |
| |
| Ok(BindingsTokens { cc_api, cc_api_impl }) |
| } |