mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-19 11:35:16 +00:00
354 lines
12 KiB
Rust
354 lines
12 KiB
Rust
//! Proc Macro Expander stuff
|
|
|
|
use core::fmt;
|
|
use std::any::Any;
|
|
use std::{panic::RefUnwindSafe, sync};
|
|
|
|
use base_db::{Crate, CrateBuilderId, CratesIdMap, Env, ProcMacroLoadingError};
|
|
use intern::Symbol;
|
|
use rustc_hash::FxHashMap;
|
|
use span::Span;
|
|
use triomphe::Arc;
|
|
|
|
use crate::{ExpandError, ExpandErrorKind, ExpandResult, db::ExpandDatabase, tt};
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
|
|
pub enum ProcMacroKind {
|
|
CustomDerive,
|
|
Bang,
|
|
Attr,
|
|
}
|
|
|
|
/// A proc-macro expander implementation.
|
|
pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe + Any {
|
|
/// Run the expander with the given input subtree, optional attribute input subtree (for
|
|
/// [`ProcMacroKind::Attr`]), environment variables, and span information.
|
|
fn expand(
|
|
&self,
|
|
subtree: &tt::TopSubtree,
|
|
attrs: Option<&tt::TopSubtree>,
|
|
env: &Env,
|
|
def_site: Span,
|
|
call_site: Span,
|
|
mixed_site: Span,
|
|
current_dir: String,
|
|
) -> Result<tt::TopSubtree, ProcMacroExpansionError>;
|
|
|
|
fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool;
|
|
}
|
|
|
|
impl PartialEq for dyn ProcMacroExpander {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.eq_dyn(other)
|
|
}
|
|
}
|
|
|
|
impl Eq for dyn ProcMacroExpander {}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ProcMacroExpansionError {
|
|
/// The proc-macro panicked.
|
|
Panic(String),
|
|
/// The server itself errored out.
|
|
System(String),
|
|
}
|
|
|
|
pub type ProcMacroLoadResult = Result<Vec<ProcMacro>, ProcMacroLoadingError>;
|
|
type StoredProcMacroLoadResult = Result<Box<[ProcMacro]>, ProcMacroLoadingError>;
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct ProcMacrosBuilder(FxHashMap<CrateBuilderId, Arc<CrateProcMacros>>);
|
|
|
|
impl ProcMacrosBuilder {
|
|
pub fn insert(
|
|
&mut self,
|
|
proc_macros_crate: CrateBuilderId,
|
|
mut proc_macro: ProcMacroLoadResult,
|
|
) {
|
|
if let Ok(proc_macros) = &mut proc_macro {
|
|
// Sort proc macros to improve incrementality when only their order has changed (ideally the build system
|
|
// will not change their order, but just to be sure).
|
|
proc_macros.sort_unstable_by(|proc_macro, proc_macro2| {
|
|
(proc_macro.name.as_str(), proc_macro.kind)
|
|
.cmp(&(proc_macro2.name.as_str(), proc_macro2.kind))
|
|
});
|
|
}
|
|
self.0.insert(
|
|
proc_macros_crate,
|
|
match proc_macro {
|
|
Ok(it) => Arc::new(CrateProcMacros(Ok(it.into_boxed_slice()))),
|
|
Err(e) => Arc::new(CrateProcMacros(Err(e))),
|
|
},
|
|
);
|
|
}
|
|
|
|
pub(crate) fn build(self, crates_id_map: &CratesIdMap) -> ProcMacros {
|
|
let mut map = self
|
|
.0
|
|
.into_iter()
|
|
.map(|(krate, proc_macro)| (crates_id_map[&krate], proc_macro))
|
|
.collect::<FxHashMap<_, _>>();
|
|
map.shrink_to_fit();
|
|
ProcMacros(map)
|
|
}
|
|
}
|
|
|
|
impl FromIterator<(CrateBuilderId, ProcMacroLoadResult)> for ProcMacrosBuilder {
|
|
fn from_iter<T: IntoIterator<Item = (CrateBuilderId, ProcMacroLoadResult)>>(iter: T) -> Self {
|
|
let mut builder = ProcMacrosBuilder::default();
|
|
for (k, v) in iter {
|
|
builder.insert(k, v);
|
|
}
|
|
builder
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct CrateProcMacros(StoredProcMacroLoadResult);
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct ProcMacros(FxHashMap<Crate, Arc<CrateProcMacros>>);
|
|
impl ProcMacros {
|
|
fn get(&self, krate: Crate) -> Option<Arc<CrateProcMacros>> {
|
|
self.0.get(&krate).cloned()
|
|
}
|
|
}
|
|
|
|
impl CrateProcMacros {
|
|
fn get(&self, idx: u32, err_span: Span) -> Result<&ProcMacro, ExpandError> {
|
|
let proc_macros = match &self.0 {
|
|
Ok(proc_macros) => proc_macros,
|
|
Err(_) => {
|
|
return Err(ExpandError::other(
|
|
err_span,
|
|
"internal error: no proc macros for crate",
|
|
));
|
|
}
|
|
};
|
|
proc_macros.get(idx as usize).ok_or_else(|| {
|
|
ExpandError::other(err_span,
|
|
format!(
|
|
"internal error: proc-macro index out of bounds: the length is {} but the index is {}",
|
|
proc_macros.len(),
|
|
idx
|
|
)
|
|
)
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn get_error(&self) -> Option<&ProcMacroLoadingError> {
|
|
self.0.as_ref().err()
|
|
}
|
|
|
|
/// Fetch the [`CustomProcMacroExpander`]s and their corresponding names for the given crate.
|
|
pub fn list(
|
|
&self,
|
|
def_site_ctx: span::SyntaxContext,
|
|
) -> Option<Box<[(crate::name::Name, CustomProcMacroExpander, bool)]>> {
|
|
match &self.0 {
|
|
Ok(proc_macros) => Some(
|
|
proc_macros
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(idx, it)| {
|
|
let name = crate::name::Name::new_symbol(it.name.clone(), def_site_ctx);
|
|
(name, CustomProcMacroExpander::new(idx as u32), it.disabled)
|
|
})
|
|
.collect(),
|
|
),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A loaded proc-macro.
|
|
#[derive(Debug, Clone, Eq)]
|
|
pub struct ProcMacro {
|
|
/// The name of the proc macro.
|
|
pub name: Symbol,
|
|
pub kind: ProcMacroKind,
|
|
/// The expander handle for this proc macro.
|
|
pub expander: sync::Arc<dyn ProcMacroExpander>,
|
|
/// Whether this proc-macro is disabled for early name resolution. Notably, the
|
|
/// [`Self::expander`] is still usable.
|
|
pub disabled: bool,
|
|
}
|
|
|
|
// `#[derive(PartialEq)]` generates a strange "cannot move" error.
|
|
impl PartialEq for ProcMacro {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
let Self { name, kind, expander, disabled } = self;
|
|
let Self {
|
|
name: other_name,
|
|
kind: other_kind,
|
|
expander: other_expander,
|
|
disabled: other_disabled,
|
|
} = other;
|
|
name == other_name
|
|
&& kind == other_kind
|
|
&& expander == other_expander
|
|
&& disabled == other_disabled
|
|
}
|
|
}
|
|
|
|
/// A custom proc-macro expander handle. This handle together with its crate resolves to a [`ProcMacro`]
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
|
pub struct CustomProcMacroExpander {
|
|
proc_macro_id: u32,
|
|
}
|
|
|
|
impl CustomProcMacroExpander {
|
|
const MISSING_EXPANDER: u32 = !0;
|
|
const DISABLED_ID: u32 = !1;
|
|
const PROC_MACRO_ATTR_DISABLED: u32 = !2;
|
|
|
|
pub fn new(proc_macro_id: u32) -> Self {
|
|
assert_ne!(proc_macro_id, Self::MISSING_EXPANDER);
|
|
assert_ne!(proc_macro_id, Self::DISABLED_ID);
|
|
assert_ne!(proc_macro_id, Self::PROC_MACRO_ATTR_DISABLED);
|
|
Self { proc_macro_id }
|
|
}
|
|
|
|
/// An expander that always errors due to the actual proc-macro expander missing.
|
|
pub const fn missing_expander() -> Self {
|
|
Self { proc_macro_id: Self::MISSING_EXPANDER }
|
|
}
|
|
|
|
/// A dummy expander that always errors. This expander is used for macros that have been disabled.
|
|
pub const fn disabled() -> Self {
|
|
Self { proc_macro_id: Self::DISABLED_ID }
|
|
}
|
|
|
|
/// A dummy expander that always errors. This expander is used for attribute macros when
|
|
/// proc-macro attribute expansion is disabled.
|
|
pub const fn disabled_proc_attr() -> Self {
|
|
Self { proc_macro_id: Self::PROC_MACRO_ATTR_DISABLED }
|
|
}
|
|
|
|
/// The macro-expander is missing or has yet to be build.
|
|
pub const fn is_missing(&self) -> bool {
|
|
self.proc_macro_id == Self::MISSING_EXPANDER
|
|
}
|
|
|
|
/// The macro is explicitly disabled and cannot be expanded.
|
|
pub const fn is_disabled(&self) -> bool {
|
|
self.proc_macro_id == Self::DISABLED_ID
|
|
}
|
|
|
|
/// The macro is explicitly disabled due to proc-macro attribute expansion being disabled.
|
|
pub const fn is_disabled_proc_attr(&self) -> bool {
|
|
self.proc_macro_id == Self::PROC_MACRO_ATTR_DISABLED
|
|
}
|
|
|
|
pub fn as_expand_error(&self, def_crate: Crate) -> Option<ExpandErrorKind> {
|
|
match self.proc_macro_id {
|
|
Self::PROC_MACRO_ATTR_DISABLED => Some(ExpandErrorKind::ProcMacroAttrExpansionDisabled),
|
|
Self::DISABLED_ID => Some(ExpandErrorKind::MacroDisabled),
|
|
Self::MISSING_EXPANDER => Some(ExpandErrorKind::MissingProcMacroExpander(def_crate)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn expand(
|
|
self,
|
|
db: &dyn ExpandDatabase,
|
|
def_crate: Crate,
|
|
calling_crate: Crate,
|
|
tt: &tt::TopSubtree,
|
|
attr_arg: Option<&tt::TopSubtree>,
|
|
def_site: Span,
|
|
call_site: Span,
|
|
mixed_site: Span,
|
|
) -> ExpandResult<tt::TopSubtree> {
|
|
match self.proc_macro_id {
|
|
Self::PROC_MACRO_ATTR_DISABLED => ExpandResult::new(
|
|
tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
|
ExpandError::new(call_site, ExpandErrorKind::ProcMacroAttrExpansionDisabled),
|
|
),
|
|
Self::MISSING_EXPANDER => ExpandResult::new(
|
|
tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
|
ExpandError::new(call_site, ExpandErrorKind::MissingProcMacroExpander(def_crate)),
|
|
),
|
|
Self::DISABLED_ID => ExpandResult::new(
|
|
tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
|
ExpandError::new(call_site, ExpandErrorKind::MacroDisabled),
|
|
),
|
|
id => {
|
|
let proc_macros = match db.proc_macros_for_crate(def_crate) {
|
|
Some(it) => it,
|
|
None => {
|
|
return ExpandResult::new(
|
|
tt::TopSubtree::empty(tt::DelimSpan {
|
|
open: call_site,
|
|
close: call_site,
|
|
}),
|
|
ExpandError::other(
|
|
call_site,
|
|
"internal error: no proc macros for crate",
|
|
),
|
|
);
|
|
}
|
|
};
|
|
let proc_macro = match proc_macros.get(id, call_site) {
|
|
Ok(proc_macro) => proc_macro,
|
|
Err(e) => {
|
|
return ExpandResult::new(
|
|
tt::TopSubtree::empty(tt::DelimSpan {
|
|
open: call_site,
|
|
close: call_site,
|
|
}),
|
|
e,
|
|
);
|
|
}
|
|
};
|
|
|
|
// Proc macros have access to the environment variables of the invoking crate.
|
|
let env = calling_crate.env(db);
|
|
// FIXME: Can we avoid the string allocation here?
|
|
let current_dir = calling_crate.data(db).proc_macro_cwd.to_string();
|
|
|
|
match proc_macro.expander.expand(
|
|
tt,
|
|
attr_arg,
|
|
env,
|
|
def_site,
|
|
call_site,
|
|
mixed_site,
|
|
current_dir,
|
|
) {
|
|
Ok(t) => ExpandResult::ok(t),
|
|
Err(err) => match err {
|
|
// Don't discard the item in case something unexpected happened while expanding attributes
|
|
ProcMacroExpansionError::System(text)
|
|
if proc_macro.kind == ProcMacroKind::Attr =>
|
|
{
|
|
ExpandResult {
|
|
value: tt.clone(),
|
|
err: Some(ExpandError::other(call_site, text)),
|
|
}
|
|
}
|
|
ProcMacroExpansionError::System(text)
|
|
| ProcMacroExpansionError::Panic(text) => ExpandResult::new(
|
|
tt::TopSubtree::empty(tt::DelimSpan {
|
|
open: call_site,
|
|
close: call_site,
|
|
}),
|
|
ExpandError::new(
|
|
call_site,
|
|
ExpandErrorKind::ProcMacroPanic(text.into_boxed_str()),
|
|
),
|
|
),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn proc_macros_for_crate(
|
|
db: &dyn ExpandDatabase,
|
|
krate: Crate,
|
|
) -> Option<Arc<CrateProcMacros>> {
|
|
db.proc_macros().get(krate)
|
|
}
|