| // 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_ast; |
| extern crate rustc_middle; |
| extern crate rustc_span; |
| |
| use anyhow::{bail, ensure, Result}; |
| |
| use rustc_ast::ast::{LitKind, MetaItemKind}; |
| use rustc_middle::ty::TyCtxt; |
| use rustc_span::def_id::DefId; |
| use rustc_span::symbol::Symbol; |
| |
| /// A collection of `#[__crubit::annotate(...)]` attributes. |
| #[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>`: |
| /// `#[__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. |
| pub cpp_type_include: 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++: |
| /// |
| /// ``` |
| /// #[__crubit::annotate(cpp_name="Create")] |
| /// pub fn new() -> i32 {...} |
| /// ``` |
| pub cpp_name: 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>, |
| } |
| |
| 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 = "cpp_type_include"; |
| 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 fn get_attr(&self, name: &str) -> Option<Symbol> { |
| match name { |
| CrubitAttrs::CPP_TYPE => self.cpp_type, |
| CrubitAttrs::CPP_NAME => self.cpp_name, |
| CrubitAttrs::CPP_TYPE_INCLUDE => self.cpp_type_include, |
| CrubitAttrs::RUST_TO_CPP_CONVERTER => self.rust_to_cpp_converter, |
| CrubitAttrs::CPP_TO_RUST_CONVERTER => self.cpp_to_rust_converter, |
| _ => panic!("Invalid attribute name: \"{name}\""), |
| } |
| } |
| |
| pub fn set_attr(&mut self, name: &str, symbol: Option<Symbol>) { |
| match name { |
| CrubitAttrs::CPP_TYPE => self.cpp_type = symbol, |
| CrubitAttrs::CPP_NAME => self.cpp_name = symbol, |
| CrubitAttrs::CPP_TYPE_INCLUDE => self.cpp_type_include = symbol, |
| CrubitAttrs::RUST_TO_CPP_CONVERTER => self.rust_to_cpp_converter = symbol, |
| CrubitAttrs::CPP_TO_RUST_CONVERTER => self.cpp_to_rust_converter = symbol, |
| _ => panic!("Invalid attribute name: \"{name}\""), |
| } |
| } |
| } |
| |
| /// Returns a CrubitAttrs object containing all the `#[__crubit::annotate(...)]` |
| /// attributes of the specified definition. |
| pub fn get_attrs(tcx: TyCtxt, did: impl Into<DefId>) -> Result<CrubitAttrs> { |
| // NB: do not make these lazy globals, symbols are per-session and sessions are |
| // reset in tests. The resulting test failures are very difficult. |
| let crubit_annotate = &[Symbol::intern("__crubit"), Symbol::intern("annotate")]; |
| let attr_name_symbol_pairs = [ |
| CrubitAttrs::CPP_TYPE, |
| CrubitAttrs::CPP_NAME, |
| CrubitAttrs::CPP_TYPE_INCLUDE, |
| CrubitAttrs::RUST_TO_CPP_CONVERTER, |
| CrubitAttrs::CPP_TO_RUST_CONVERTER, |
| ] |
| .into_iter() |
| .map(|name| (name, Symbol::intern(name))) |
| .collect::<Vec<_>>(); |
| |
| let mut crubit_attrs = CrubitAttrs::default(); |
| // A quick note: the parsing logic is unfortunate, but such is life. We don't |
| // put extra special effort into making the error messages maximally |
| // helpful, because they "should never happen": `__crubit::annotate` calls |
| // are introduced automatically by Crubit itself, so these errors are only |
| // going to be read by Crubit developers when we mess up, not Crubit |
| // _users_. |
| for attr in tcx.get_attrs_by_path(did.into(), crubit_annotate) { |
| let Some(meta) = attr.meta() else { |
| bail!("Invalid #[__crubit::annotate(...)] attribute (not a rustc_ast::ast::MetaItem)"); |
| }; |
| let MetaItemKind::List(args) = &meta.kind else { |
| bail!("Invalid #[__crubit::annotate(...)] attribute (expected __crubit::annotate())"); |
| }; |
| for arg in args { |
| let Some(arg) = arg.meta_item() else { |
| bail!( |
| "Invalid #[__crubit::annotate(...)] attribute (expected nested meta item, not a literal)" |
| ); |
| }; |
| for (attr_name, attr_symbol) in attr_name_symbol_pairs.iter() { |
| if arg.path == *attr_symbol { |
| let bail_message = format!( |
| "Invalid #[__crubit::annotate({attr_name}=...) attribute (expected =\"...\")" |
| ); |
| |
| let MetaItemKind::NameValue(value) = &arg.kind else { |
| bail!(bail_message); |
| }; |
| let LitKind::Str(s, _raw) = value.kind else { |
| bail!(bail_message); |
| }; |
| ensure!( |
| crubit_attrs.get_attr(attr_name).is_none(), |
| format!("Unexpected duplicate #[__crubit::annotate({attr_name}=...)]") |
| ); |
| crubit_attrs.set_attr(attr_name, Some(s)); |
| } |
| } |
| } |
| } |
| |
| Ok(crubit_attrs) |
| } |
| |
| #[cfg(test)] |
| pub mod tests { |
| use super::*; |
| use run_compiler_test_support::{find_def_id_by_name, run_compiler_for_testing}; |
| |
| #[test] |
| fn test_bridged_type() { |
| let test_src = r#" |
| #![feature(register_tool)] |
| #![register_tool(__crubit)] |
| |
| #[__crubit::annotate( |
| cpp_type = "CppType", |
| cpp_type_include = "crubit/cpp_type.h", |
| cpp_to_rust_converter = "cpp_to_rust", |
| rust_to_cpp_converter = "rust_to_cpp") |
| ] |
| pub struct SomeStruct; |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attrs = get_attrs(tcx, find_def_id_by_name(tcx, "SomeStruct")).unwrap(); |
| |
| let mut expected_attrs = CrubitAttrs::default(); |
| expected_attrs.cpp_type = Some(Symbol::intern("CppType")); |
| expected_attrs.cpp_type_include = Some(Symbol::intern("crubit/cpp_type.h")); |
| expected_attrs.cpp_to_rust_converter = Some(Symbol::intern("cpp_to_rust")); |
| expected_attrs.rust_to_cpp_converter = Some(Symbol::intern("rust_to_cpp")); |
| |
| assert_eq!(attrs, expected_attrs); |
| }); |
| } |
| |
| #[test] |
| fn test_missing() { |
| let test_src = r#" |
| pub struct SomeStruct; |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get_attrs(tcx, find_def_id_by_name(tcx, "SomeStruct")).unwrap(); |
| assert_eq!(attr, CrubitAttrs::default()); |
| }); |
| } |
| |
| #[test] |
| fn test_empty() { |
| let test_src = r#" |
| #![feature(register_tool)] |
| #![register_tool(__crubit)] |
| #[__crubit::annotate()] |
| pub struct SomeStruct; |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get_attrs(tcx, find_def_id_by_name(tcx, "SomeStruct")).unwrap(); |
| assert_eq!(attr, CrubitAttrs::default()); |
| }); |
| } |
| |
| #[test] |
| fn test_cpp_type() { |
| let test_src = r#" |
| #![feature(register_tool)] |
| #![register_tool(__crubit)] |
| #[__crubit::annotate(cpp_type = "A C++ Type")] |
| pub struct SomeStruct; |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get_attrs(tcx, find_def_id_by_name(tcx, "SomeStruct")).unwrap(); |
| assert_eq!(attr.cpp_type.unwrap(), Symbol::intern("A C++ Type")); |
| }); |
| } |
| |
| #[test] |
| fn test_cpp_name() { |
| let test_src = r#" |
| #![feature(register_tool)] |
| #![register_tool(__crubit)] |
| #[__crubit::annotate(cpp_name = "Create")] |
| pub fn new() -> i32 { 0 } |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get_attrs(tcx, find_def_id_by_name(tcx, "new")).unwrap(); |
| assert_eq!(attr.cpp_name.unwrap(), Symbol::intern("Create")); |
| }); |
| } |
| |
| #[test] |
| fn test_cpp_name_duplicated() { |
| let test_src = r#" |
| #![feature(register_tool)] |
| #![register_tool(__crubit)] |
| #[__crubit::annotate(cpp_name = "Create", cpp_name = "Create2")] |
| pub fn new() -> i32 { 0 } |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get_attrs(tcx, find_def_id_by_name(tcx, "new")); |
| assert!(attr.is_err()); |
| }); |
| } |
| |
| #[test] |
| fn test_cpp_type_multi() { |
| let test_src = r#" |
| #![feature(register_tool)] |
| #![register_tool(__crubit)] |
| #[__crubit::annotate(cpp_type = "X", cpp_type = "X")] |
| pub struct SomeStruct; |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get_attrs(tcx, find_def_id_by_name(tcx, "SomeStruct")); |
| assert!(attr.is_err()); |
| }); |
| } |
| } |