| // 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; |
| use rustc_ast::ast::{MetaItemKind, NestedMetaItem}; |
| use rustc_middle::ty::TyCtxt; |
| use rustc_span::def_id::DefId; |
| use rustc_span::symbol::Symbol; |
| |
| /// A `#[__crubit::annotate(...)]` attribute. |
| #[derive(Debug, Default, Clone, PartialEq)] |
| pub struct CrubitAttr { |
| /// The C++ type of this is spelled as so. |
| /// For instance, |
| /// `#[__crubit::annotate(internal_cpp_type="std::basic_string<char>")]` |
| pub cpp_type: 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, |
| // |
| // ``` |
| // #[__crubit::annotate(cpp_name="Create")] |
| // pub fn new() -> i32 {...} |
| // ``` |
| // |
| // will rename `new` in Rust to `Create` in C++. |
| pub cpp_name: Option<Symbol>, |
| } |
| |
| /// Gets the `#[__crubit::annotate(...)]` attribute(s) applied to a definition. |
| /// |
| /// If the definition has no crubit attributes, then an empty (default) |
| /// `CrubitAttr` is returned. |
| pub fn get(tcx: TyCtxt, did: impl Into<DefId>) -> Result<CrubitAttr> { |
| // 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 cpp_type = Symbol::intern("cpp_type"); |
| let cpp_name = Symbol::intern("cpp_name"); |
| |
| let mut crubit_attr = CrubitAttr::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 NestedMetaItem::MetaItem(arg) = arg else { |
| bail!( |
| "Invalid #[__crubit::annotate(...)] attribute (expected nested meta item, not a literal)" |
| ); |
| }; |
| if arg.path == cpp_type { |
| let MetaItemKind::NameValue(value) = &arg.kind else { |
| bail!("Invalid #[__crubit::annotate(cpp_type=...)] attribute (expected =...)"); |
| }; |
| let LitKind::Str(s, _raw) = value.kind else { |
| bail!( |
| "Invalid #[__crubit::annotate(cpp_type=...)] attribute (expected =\"...\")" |
| ); |
| }; |
| ensure!( |
| crubit_attr.cpp_type.is_none(), |
| "Unexpected duplicate #[__crubit::annotate(cpp_type=...)]" |
| ); |
| crubit_attr.cpp_type = Some(s) |
| } else if arg.path == cpp_name { |
| let MetaItemKind::NameValue(value) = &arg.kind else { |
| bail!("Invalid #[__crubit::annotate(cpp_name=...)] attribute (expected =...)"); |
| }; |
| let LitKind::Str(s, _raw) = value.kind else { |
| bail!( |
| "Invalid #[__crubit::annotate(cpp_name=...)] attribute (expected =\"...\")" |
| ); |
| }; |
| ensure!( |
| crubit_attr.cpp_name.is_none(), |
| "Unexpected duplicate #[__crubit::annotate(cpp_name=...)]" |
| ); |
| crubit_attr.cpp_name = Some(s); |
| } |
| } |
| } |
| Ok(crubit_attr) |
| } |
| |
| #[cfg(test)] |
| pub mod tests { |
| use super::*; |
| use run_compiler_test_support::{find_def_id_by_name, run_compiler_for_testing}; |
| |
| #[test] |
| fn test_missing() { |
| let test_src = r#" |
| pub struct SomeStruct; |
| "#; |
| run_compiler_for_testing(test_src, |tcx| { |
| let attr = get(tcx, find_def_id_by_name(tcx, "SomeStruct")).unwrap(); |
| assert_eq!(attr, CrubitAttr::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(tcx, find_def_id_by_name(tcx, "SomeStruct")).unwrap(); |
| assert_eq!(attr, CrubitAttr::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(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(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(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(tcx, find_def_id_by_name(tcx, "SomeStruct")); |
| assert!(attr.is_err()); |
| }); |
| } |
| } |