blob: f6f8cec8e6bf8e96bcfbb1de3e1342b8cbdf5742 [file] [log] [blame] [edit]
// 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)
}