| // 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 |
| |
| //! Support for retrieving Crubit control attributes. These attributes override |
| //! how Crubit handles a given AST entry. |
| //! |
| //! These should never be written directly by users, but rather generated by |
| //! Crubit itself. (Potentially in a procedural macro.) |
| #![feature(rustc_private)] |
| #![deny(rustc::internal)] |
| |
| extern crate rustc_middle; |
| extern crate rustc_span; |
| |
| use anyhow::{bail, ensure, Result}; |
| use rustc_middle::ty::TyCtxt; |
| use rustc_span::def_id::DefId; |
| use rustc_span::symbol::Symbol; |
| |
| #[rustversion::before(2025-05-26)] |
| use rustc_span::symbol::kw; |
| |
| #[rustversion::since(2025-05-26)] |
| use rustc_span::symbol::sym; |
| |
| /// A collection of attributes applied via `#[crubit_annotate::...]`. |
| /// |
| /// Note that these attributes are procedural macros that generate doc comment attributes of the |
| /// form `#[doc="CRUBIT_ANNOTATE: ..."]`. |
| #[derive(Debug, Default, Clone, PartialEq)] |
| pub struct CrubitAttrs { |
| /// The spelling of the C++ type of the item. |
| /// |
| /// For example, the following annotation indicates that the C++ type of |
| /// this item should be `std::basic_string<char>`: |
| /// |
| /// #[doc="CRUBIT_ANNOTATE: cpp_type=std::basic_string<char>"] |
| pub cpp_type: Option<Symbol>, |
| |
| /// Path to the header file that declares the type specified in `cpp_type`. |
| /// |
| /// If specified, the header file will be added as an #include directive to |
| /// the generated bindings. |
| /// |
| /// For example, the following annotation indicates that the C++ type of |
| /// this item is `std::basic_string<char>` and that the header for that type |
| /// is `<string>`: |
| /// |
| /// #[doc="CRUBIT_ANNOTATE: cpp_type=std::basic_string<char>"] |
| /// #[doc="CRUBIT_ANNOTATE: include_path=<string>]` |
| pub include_path: Option<Symbol>, |
| |
| /// The C++ name of the item. This allows us to rename Rust function names |
| /// that are not C++-compatible like `new`. |
| /// |
| /// For instance, the following annotation indicates that the Rust function |
| /// `new` should be renamed to `Create` in C++: |
| /// |
| /// ``` |
| /// #[doc="CRUBIT_ANNOTATE: cpp_name=Create"] |
| /// pub fn new() -> i32 {...} |
| /// ``` |
| pub cpp_name: Option<Symbol>, |
| |
| pub cpp_enum: Option<Symbol>, |
| |
| /// The name of a function that can convert the annotated Rust |
| /// type into the C++ type specifed in the `cpp_type` attribute. |
| /// |
| /// The generated bindings assume the function to be an `extern C` function |
| /// with the following type signature: |
| /// ``` |
| /// fn #rust_to_cpp_converter(rs_in: *const std::ffi::c_void, cpp_out: *mut std::ffi::c_void) |
| /// ``` |
| /// `rs_in` is a valid pointer to an instance of the annotated Rust type. |
| /// `cpp_out` is a valid pointer to uninitialized instance of |
| /// `cpp_type`. |
| pub rust_to_cpp_converter: Option<Symbol>, |
| |
| /// The name of a function that can convert the C++ type specifed in the |
| /// `cpp_type` attribute into the annotated Rust type. |
| /// |
| /// The generated bindings assume the function to be an `extern C` function |
| /// with the following type signature: |
| /// ``` |
| /// fn #cpp_to_rust_converter(cpp_in: *const std::ffi::c_void, rs_out: *mut std::ffi::c_void) |
| /// ``` |
| /// `cpp_in` is a valid pointer to an instance of the C++ type specified in |
| /// `cpp_type`. `rs_out` is a valid pointer to uninitialized instance of the |
| /// annotated Rust Type. |
| pub cpp_to_rust_converter: Option<Symbol>, |
| |
| /// The name of the Rust Crubit ABI that the type should be bridged as. |
| pub bridge_abi_rust: Option<Symbol>, |
| /// The name of the C++ Crubit ABI that the type should be bridged as. |
| pub bridge_abi_cpp: Option<Symbol>, |
| |
| /// Whether the annotated item must be bound to C++. |
| /// |
| /// By default, when a given item does not receive bindings, it is replaced |
| /// by a failure marker, such as a comment, which explains that bindings |
| /// were not generated, and why. If this is true, then instead of replacing |
| /// with a failure marker, bindings generation will fail outright at build time. |
| /// |
| /// This can be used to make failures more obvious, and occur earlier in the |
| /// development process. |
| /// |
| /// For example, the following annotation indicates that the Rust function |
| /// `new` must be bound to C++: |
| /// |
| /// ``` |
| /// #[crubit_annotate::must_bind] |
| /// pub fn new() -> i32 {...} |
| /// ``` |
| pub must_bind: bool, |
| } |
| |
| #[rustversion::before(2025-05-26)] |
| const EMPTY_SYMBOL: Symbol = kw::Empty; |
| |
| #[rustversion::since(2025-05-26)] |
| const EMPTY_SYMBOL: Symbol = sym::empty; |
| |
| impl CrubitAttrs { |
| pub const CPP_TYPE: &'static str = "cpp_type"; |
| pub const CPP_NAME: &'static str = "cpp_name"; |
| pub const CPP_TYPE_INCLUDE: &'static str = "include_path"; |
| pub const CPP_ENUM: &'static str = "cpp_enum"; |
| pub const RUST_TO_CPP_CONVERTER: &'static str = "rust_to_cpp_converter"; |
| pub const CPP_TO_RUST_CONVERTER: &'static str = "cpp_to_rust_converter"; |
| pub const BRIDGE_ABI_RUST: &'static str = "bridge_abi_rust"; |
| pub const BRIDGE_ABI_CPP: &'static str = "bridge_abi_cpp"; |
| pub const MUST_BIND: &'static str = "must_bind"; |
| |
| fn get_attr(&self, name: &str) -> Result<Option<Symbol>> { |
| Ok(match name { |
| CrubitAttrs::CPP_TYPE => self.cpp_type, |
| CrubitAttrs::CPP_NAME => self.cpp_name, |
| CrubitAttrs::CPP_TYPE_INCLUDE => self.include_path, |
| CrubitAttrs::CPP_ENUM => self.cpp_enum, |
| CrubitAttrs::RUST_TO_CPP_CONVERTER => self.rust_to_cpp_converter, |
| CrubitAttrs::CPP_TO_RUST_CONVERTER => self.cpp_to_rust_converter, |
| CrubitAttrs::BRIDGE_ABI_RUST => self.bridge_abi_rust, |
| CrubitAttrs::BRIDGE_ABI_CPP => self.bridge_abi_cpp, |
| // MUST_BIND is a boolean attribute, so it does not have a Symbol value. |
| CrubitAttrs::MUST_BIND => self.must_bind.then_some(EMPTY_SYMBOL), |
| _ => bail!("Invalid attribute name: \"{name}\""), |
| }) |
| } |
| |
| fn set_attr(&mut self, name: &str, symbol: Option<Symbol>) -> Result<()> { |
| match name { |
| CrubitAttrs::CPP_TYPE => self.cpp_type = symbol, |
| CrubitAttrs::CPP_NAME => self.cpp_name = symbol, |
| CrubitAttrs::CPP_TYPE_INCLUDE => self.include_path = symbol, |
| CrubitAttrs::CPP_ENUM => self.cpp_enum = symbol, |
| CrubitAttrs::RUST_TO_CPP_CONVERTER => self.rust_to_cpp_converter = symbol, |
| CrubitAttrs::CPP_TO_RUST_CONVERTER => self.cpp_to_rust_converter = symbol, |
| CrubitAttrs::BRIDGE_ABI_RUST => self.bridge_abi_rust = symbol, |
| CrubitAttrs::BRIDGE_ABI_CPP => self.bridge_abi_cpp = symbol, |
| CrubitAttrs::MUST_BIND => self.must_bind = true, |
| _ => bail!("Invalid CRUBIT_ANNOTATE key: \"{name}\""), |
| } |
| Ok(()) |
| } |
| |
| /// Returns the Crubit attributes that specify a bridging strategy, |
| /// either composable, extern C function converters, or just a pointer case. |
| /// |
| /// Returns None if the attributes are not present. |
| /// Returns an error if the attributes are present but malformed. |
| pub fn get_bridging_attrs(&self) -> Result<Option<BridgingAttrs>> { |
| match self { |
| Self { |
| cpp_type: Some(cpp_type), |
| include_path, |
| cpp_to_rust_converter, |
| rust_to_cpp_converter, |
| bridge_abi_rust: None, |
| bridge_abi_cpp: None, |
| .. |
| } => match (cpp_to_rust_converter, rust_to_cpp_converter) { |
| (Some(cpp_to_rust_converter), Some(rust_to_cpp_converter)) => { |
| Ok(Some(BridgingAttrs::ExternCFuncConverters { |
| include_path: *include_path, |
| cpp_type: *cpp_type, |
| cpp_to_rust_converter: *cpp_to_rust_converter, |
| rust_to_cpp_converter: *rust_to_cpp_converter, |
| })) |
| } |
| (None, None) => Ok(Some(BridgingAttrs::JustCppType { |
| include_path: *include_path, |
| cpp_type: *cpp_type, |
| })), |
| _ => bail!( |
| "Invalid state of #[crubit_annotate::...] attribute. \ |
| Some, but not all, of cpp_to_rust_converter and rust_to_cpp_converter \ |
| are set." |
| ), |
| }, |
| Self { |
| cpp_type, |
| cpp_to_rust_converter: None, |
| rust_to_cpp_converter: None, |
| bridge_abi_rust, |
| bridge_abi_cpp, |
| .. |
| } => match (cpp_type, bridge_abi_rust, bridge_abi_cpp) { |
| (Some(cpp_type), Some(abi_rust), Some(abi_cpp)) => { |
| Ok(Some(BridgingAttrs::Composable { |
| cpp_type: *cpp_type, |
| abi_rust: *abi_rust, |
| abi_cpp: *abi_cpp, |
| })) |
| } |
| (None, None, None) => Ok(None), |
| _ => bail!( |
| "Invalid state of #[crubit_annotate::...] attribute. \ |
| Some, but not all, of cpp_type, bridge_abi_rust, and bridge_abi_cpp \ |
| are set." |
| ), |
| }, |
| Self { |
| cpp_type, |
| cpp_to_rust_converter, |
| rust_to_cpp_converter, |
| bridge_abi_rust, |
| bridge_abi_cpp, |
| .. |
| } => { |
| let mut attrs = Vec::with_capacity(6); |
| if cpp_type.is_some() { |
| attrs.push(CrubitAttrs::CPP_TYPE); |
| } |
| if cpp_to_rust_converter.is_some() { |
| attrs.push(CrubitAttrs::CPP_TO_RUST_CONVERTER); |
| } |
| if rust_to_cpp_converter.is_some() { |
| attrs.push(CrubitAttrs::RUST_TO_CPP_CONVERTER); |
| } |
| if bridge_abi_rust.is_some() { |
| attrs.push(CrubitAttrs::BRIDGE_ABI_RUST); |
| } |
| if bridge_abi_cpp.is_some() { |
| attrs.push(CrubitAttrs::BRIDGE_ABI_CPP); |
| } |
| ensure!( |
| attrs.is_empty(), |
| "Invalid state of #[crubit_annotate::...] attribute. \ |
| contains conflicting attributes: {attrs:?}" |
| ); |
| Ok(None) |
| } |
| } |
| } |
| } |
| |
| /// An enum that separates out bridging attributes. Successful construction of |
| /// this type from `CrubitAttrs::get_bridging_attrs` indicates that the |
| /// attributes are valid and there are no conflicting attributes. |
| #[derive(Debug)] |
| pub enum BridgingAttrs { |
| JustCppType { |
| include_path: Option<Symbol>, |
| cpp_type: Symbol, |
| }, |
| ExternCFuncConverters { |
| include_path: Option<Symbol>, |
| cpp_type: Symbol, |
| cpp_to_rust_converter: Symbol, |
| rust_to_cpp_converter: Symbol, |
| }, |
| Composable { |
| /// The name of the C++ type that the annotated Rust type should be bridged |
| /// to. |
| cpp_type: Symbol, |
| /// The name of the Rust Crubit ABI that the type should be bridged as. |
| abi_rust: Symbol, |
| /// The name of the C++ Crubit ABI that the type should be bridged as. |
| abi_cpp: Symbol, |
| }, |
| } |
| |
| /// Returns a CrubitAttrs object containing all the `#[doc="CRUBIT_ANNOTATE: key=value"]` |
| /// attributes of the specified definition. |
| pub fn get_attrs(tcx: TyCtxt, did: DefId) -> Result<CrubitAttrs> { |
| let mut crubit_attrs = CrubitAttrs::default(); |
| if !did.is_local() && tcx.crate_name(did.krate).as_str() == "core" { |
| // TODO: cramertj - calling `get_all_attrs` on `did` values from `core` crashes like: |
| // |
| // compiler/rustc_metadata/src/rmeta/decoder.rs:1396:17: |
| // assertion `left == right` failed |
| // left: TypeNs("core") |
| // right: Ctor |
| return Ok(crubit_attrs); |
| } |
| for attr in tcx.get_all_attrs(did) { |
| let Some(comment) = attr.doc_str() else { continue }; |
| let Some((_, key_value)) = comment.as_str().split_once("CRUBIT_ANNOTATE:") else { |
| continue; |
| }; |
| let Some((key, value)) = key_value.split_once("=") else { |
| bail!("Invalid CRUBIT_ANNOTATE attribute: `{comment}`. Expected `key=value`") |
| }; |
| // Remove optional whitespace (e.g. `key = "value"` vs. `key="value"`). |
| let key = key.trim(); |
| let value = value.trim(); |
| ensure!( |
| crubit_attrs.get_attr(key)?.is_none(), |
| format!("Unexpected duplicate Crubit attribute: {key}=...") |
| ); |
| crubit_attrs.set_attr(key, Some(Symbol::intern(value)))?; |
| } |
| Ok(crubit_attrs) |
| } |