| // 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>, |
| |
| /// 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 { |
| const CPP_TYPE: &'static str = "cpp_type"; |
| const CPP_NAME: &'static str = "cpp_name"; |
| const CPP_TYPE_INCLUDE: &'static str = "include_path"; |
| const CPP_ENUM: &'static str = "cpp_enum"; |
| const RUST_TO_CPP_CONVERTER: &'static str = "rust_to_cpp_converter"; |
| const CPP_TO_RUST_CONVERTER: &'static str = "cpp_to_rust_converter"; |
| 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, |
| // 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::MUST_BIND => self.must_bind = true, |
| _ => bail!("Invalid CRUBIT_ANNOTATE key: \"{name}\""), |
| } |
| Ok(()) |
| } |
| } |
| |
| /// 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) |
| } |