blob: 2a27303be46fb1cbfdf57ac2208d46eefde5bb05 [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>,
/// 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)
}