blob: a2536499d98c015b290bfec2b768deded2ccde50 [file] [log] [blame]
// 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());
});
}
}