use base_db::CrateId;
use hir_expand::{
attrs::{Attr, AttrId, AttrInput},
inert_attr_macro::find_builtin_attr_idx,
MacroCallId, MacroCallKind, MacroDefId,
};
use span::SyntaxContextId;
use syntax::ast;
use triomphe::Arc;
use crate::{
db::DefDatabase,
item_scope::BuiltinShadowMode,
nameres::path_resolution::ResolveMode,
path::{self, ModPath, PathKind},
AstIdWithPath, LocalModuleId, MacroId, UnresolvedMacro,
};
use super::{DefMap, MacroSubNs};
pub enum ResolvedAttr {
Macro(MacroCallId),
Other,
}
impl DefMap {
pub(crate) fn resolve_attr_macro(
&self,
db: &dyn DefDatabase,
original_module: LocalModuleId,
ast_id: AstIdWithPath<ast::Item>,
attr: &Attr,
) -> Result<ResolvedAttr, UnresolvedMacro> {
if self.is_builtin_or_registered_attr(&ast_id.path) {
return Ok(ResolvedAttr::Other);
}
let resolved_res = self.resolve_path_fp_with_macro(
db,
ResolveMode::Other,
original_module,
&ast_id.path,
BuiltinShadowMode::Module,
Some(MacroSubNs::Attr),
);
let def = match resolved_res.resolved_def.take_macros() {
Some(def) => {
if def.is_attribute(db) {
def
} else {
return Ok(ResolvedAttr::Other);
}
}
None => return Err(UnresolvedMacro { path: ast_id.path.as_ref().clone() }),
};
Ok(ResolvedAttr::Macro(attr_macro_as_call_id(
db,
&ast_id,
attr,
self.krate,
db.macro_def(def),
)))
}
pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool {
if path.kind != PathKind::Plain {
return false;
}
let segments = path.segments();
if let Some(name) = segments.first() {
let name = name.symbol();
let pred = |n: &_| *n == *name;
let is_tool = self.data.registered_tools.iter().any(pred);
if is_tool {
return true;
}
if segments.len() == 1 {
if find_builtin_attr_idx(name).is_some() {
return true;
}
if self.data.registered_attrs.iter().any(pred) {
return true;
}
}
}
false
}
}
pub(super) fn attr_macro_as_call_id(
db: &dyn DefDatabase,
item_attr: &AstIdWithPath<ast::Item>,
macro_attr: &Attr,
krate: CrateId,
def: MacroDefId,
) -> MacroCallId {
let arg = match macro_attr.input.as_deref() {
Some(AttrInput::TokenTree(tt)) => {
let mut tt = tt.as_ref().clone();
tt.delimiter.kind = tt::DelimiterKind::Invisible;
Some(tt)
}
_ => None,
};
def.make_call(
db.upcast(),
krate,
MacroCallKind::Attr {
ast_id: item_attr.ast_id,
attr_args: arg.map(Arc::new),
invoc_attr_index: macro_attr.id,
},
macro_attr.ctxt,
)
}
pub(super) fn derive_macro_as_call_id(
db: &dyn DefDatabase,
item_attr: &AstIdWithPath<ast::Adt>,
derive_attr_index: AttrId,
derive_pos: u32,
call_site: SyntaxContextId,
krate: CrateId,
resolver: impl Fn(&path::ModPath) -> Option<(MacroId, MacroDefId)>,
derive_macro_id: MacroCallId,
) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> {
let (macro_id, def_id) = resolver(&item_attr.path)
.filter(|(_, def_id)| def_id.is_derive())
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.as_ref().clone() })?;
let call_id = def_id.make_call(
db.upcast(),
krate,
MacroCallKind::Derive {
ast_id: item_attr.ast_id,
derive_index: derive_pos,
derive_attr_index,
derive_macro_id,
},
call_site,
);
Ok((macro_id, def_id, call_id))
}