Try caching macro calls more aggressively

This commit is contained in:
Lukas Wirth 2024-04-03 15:23:56 +02:00
parent f28f15ac6e
commit 97b58f2846
11 changed files with 382 additions and 300 deletions

View file

@ -19,8 +19,8 @@ use hir_def::{
AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId,
};
use hir_expand::{
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo,
InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, InMacroFile,
MacroCallId, MacroFileId, MacroFileIdExt,
};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
@ -129,12 +129,9 @@ pub struct Semantics<'db, DB> {
pub struct SemanticsImpl<'db> {
pub db: &'db dyn HirDatabase,
s2d_cache: RefCell<SourceToDefCache>,
s2d_cache: RefCell<(SourceToDefCache, FxHashMap<MacroFileId, hir_expand::ExpansionInfo>)>,
/// Rootnode to HirFileId cache
root_to_file_cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
// These 2 caches are mainly useful for semantic highlighting as nothing else descends a lot of tokens
// So we might wanna move them out into something specific for semantic highlighting
expansion_info_cache: RefCell<FxHashMap<MacroFileId, ExpansionInfo>>,
/// MacroCall to its expansion's MacroFileId cache
macro_call_cache: RefCell<FxHashMap<InFile<ast::MacroCall>, MacroFileId>>,
}
@ -295,7 +292,6 @@ impl<'db> SemanticsImpl<'db> {
db,
s2d_cache: Default::default(),
root_to_file_cache: Default::default(),
expansion_info_cache: Default::default(),
macro_call_cache: Default::default(),
}
}
@ -314,7 +310,16 @@ impl<'db> SemanticsImpl<'db> {
pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
let sa = self.analyze_no_infer(macro_call.syntax())?;
let file_id = sa.expand(self.db, InFile::new(sa.file_id, macro_call))?;
let macro_call = InFile::new(sa.file_id, macro_call);
let file_id = if let Some(call) =
<ast::MacroCall as crate::semantics::ToDef>::to_def(self, macro_call)
{
call.as_macro_file()
} else {
sa.expand(self.db, macro_call)?
};
let node = self.parse_or_expand(file_id.into());
Some(node)
}
@ -322,7 +327,7 @@ impl<'db> SemanticsImpl<'db> {
/// If `item` has an attribute macro attached to it, expands it.
pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
let src = self.wrap_node_infile(item.clone());
let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?;
let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src.as_ref()))?;
Some(self.parse_or_expand(macro_call_id.as_file()))
}
@ -341,9 +346,7 @@ impl<'db> SemanticsImpl<'db> {
Some(
calls
.into_iter()
.map(|call| {
macro_call_to_macro_id(ctx, self.db.upcast(), call?).map(|id| Macro { id })
})
.map(|call| macro_call_to_macro_id(ctx, call?).map(|id| Macro { id }))
.collect(),
)
})
@ -403,7 +406,7 @@ impl<'db> SemanticsImpl<'db> {
pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
let file_id = self.find_file(item.syntax()).file_id;
let src = InFile::new(file_id, item.clone());
let src = InFile::new(file_id, item);
self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some())
}
@ -453,7 +456,7 @@ impl<'db> SemanticsImpl<'db> {
token_to_map: SyntaxToken,
) -> Option<(SyntaxNode, SyntaxToken)> {
let macro_call = self.wrap_node_infile(actual_macro_call.clone());
let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(macro_call))?;
let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(macro_call.as_ref()))?;
hir_expand::db::expand_speculative(
self.db.upcast(),
macro_call_id,
@ -705,8 +708,6 @@ impl<'db> SemanticsImpl<'db> {
let parent = token.parent()?;
let file_id = self.find_file(&parent).file_id.file_id()?;
let mut cache = self.expansion_info_cache.borrow_mut();
// iterate related crates and find all include! invocations that include_file_id matches
for (invoc, _) in self
.db
@ -716,18 +717,31 @@ impl<'db> SemanticsImpl<'db> {
.filter(|&(_, include_file_id)| include_file_id == file_id)
{
let macro_file = invoc.as_macro_file();
let expansion_info = cache.entry(macro_file).or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());
let expansion_info = {
self.with_ctx(|ctx| {
ctx.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());
let InMacroFile { file_id, value } = exp_info.expanded();
self.cache(value, file_id.into());
let InMacroFile { file_id, value } = exp_info.expanded();
if let InFile { file_id, value: Some(value) } = exp_info.call_node() {
self.cache(value.ancestors().last().unwrap(), file_id);
}
self.cache(value, file_id.into());
exp_info
});
exp_info
})
.clone()
})
};
// FIXME: uncached parse
// Create the source analyzer for the macro call scope
let Some(sa) = self.analyze_no_infer(&self.parse_or_expand(expansion_info.call_file()))
let Some(sa) = expansion_info
.call_node()
.value
.and_then(|it| self.analyze_no_infer(&it.ancestors().last().unwrap()))
else {
continue;
};
@ -785,23 +799,27 @@ impl<'db> SemanticsImpl<'db> {
}
};
let mut cache = self.expansion_info_cache.borrow_mut();
let mut mcache = self.macro_call_cache.borrow_mut();
let mut m_cache = self.macro_call_cache.borrow_mut();
let def_map = sa.resolver.def_map();
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
let exp_info = cache.entry(macro_file).or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| {
Some(
ctx.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());
let InMacroFile { file_id, value } = exp_info.expanded();
self.cache(value, file_id.into());
let InMacroFile { file_id, value } = exp_info.expanded();
self.cache(value, file_id.into());
exp_info
});
let InMacroFile { file_id, value: mapped_tokens } = exp_info.map_range_down(span)?;
let mapped_tokens: SmallVec<[_; 2]> = mapped_tokens.collect();
exp_info
})
.map_range_down(span)?
.map(SmallVec::<[_; 2]>::from_iter),
)
})?;
// we have found a mapping for the token if the vec is non-empty
let res = mapped_tokens.is_empty().not().then_some(());
@ -818,10 +836,7 @@ impl<'db> SemanticsImpl<'db> {
token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
// Don't force populate the dyn cache for items that don't have an attribute anyways
item.attrs().next()?;
Some((
ctx.item_to_macro_call(InFile::new(file_id, item.clone()))?,
item,
))
Some((ctx.item_to_macro_call(InFile::new(file_id, &item))?, item))
})
});
if let Some((call_id, item)) = containing_attribute_macro_call {
@ -874,13 +889,20 @@ impl<'db> SemanticsImpl<'db> {
return None;
}
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
InFile::new(file_id, macro_call);
let file_id = match mcache.get(&mcall) {
let mcall = InFile::new(file_id, macro_call);
let file_id = match m_cache.get(&mcall) {
Some(&it) => it,
None => {
let it = sa.expand(self.db, mcall.as_ref())?;
mcache.insert(mcall, it);
let it = if let Some(call) =
<ast::MacroCall as crate::semantics::ToDef>::to_def(
self,
mcall.as_ref(),
) {
call.as_macro_file()
} else {
sa.expand(self.db, mcall.as_ref())?
};
m_cache.insert(mcall, it);
it
}
};
@ -1056,16 +1078,19 @@ impl<'db> SemanticsImpl<'db> {
node: SyntaxNode,
) -> impl Iterator<Item = SyntaxNode> + Clone + '_ {
let node = self.find_file(&node);
let db = self.db.upcast();
iter::successors(Some(node.cloned()), move |&InFile { file_id, ref value }| {
match value.parent() {
Some(parent) => Some(InFile::new(file_id, parent)),
None => {
let call_node = file_id.macro_file()?.call_node(db);
// cache the node
// FIXME: uncached parse
self.parse_or_expand(call_node.file_id);
Some(call_node)
let macro_file = file_id.macro_file()?;
self.with_ctx(|ctx| {
let expansion_info = ctx
.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
expansion_info.call_node().transpose()
})
}
}
})
@ -1090,7 +1115,7 @@ impl<'db> SemanticsImpl<'db> {
.find(|tp| tp.lifetime().as_ref().map(|lt| lt.text()).as_ref() == Some(&text))
})?;
let src = self.wrap_node_infile(lifetime_param);
ToDef::to_def(self, src)
ToDef::to_def(self, src.as_ref())
}
pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> {
@ -1112,7 +1137,7 @@ impl<'db> SemanticsImpl<'db> {
})
})?;
let src = self.wrap_node_infile(label);
ToDef::to_def(self, src)
ToDef::to_def(self, src.as_ref())
}
pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {
@ -1308,8 +1333,8 @@ impl<'db> SemanticsImpl<'db> {
pub fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
let item_in_file = self.wrap_node_infile(item.clone());
let id = self.with_ctx(|ctx| {
let macro_call_id = ctx.item_to_macro_call(item_in_file)?;
macro_call_to_macro_id(ctx, self.db.upcast(), macro_call_id)
let macro_call_id = ctx.item_to_macro_call(item_in_file.as_ref())?;
macro_call_to_macro_id(ctx, macro_call_id)
})?;
Some(Macro { id })
}
@ -1339,13 +1364,13 @@ impl<'db> SemanticsImpl<'db> {
}
fn with_ctx<F: FnOnce(&mut SourceToDefCtx<'_, '_>) -> T, T>(&self, f: F) -> T {
let mut cache = self.s2d_cache.borrow_mut();
let mut ctx = SourceToDefCtx { db: self.db, dynmap_cache: &mut cache };
let (dynmap_cache, expansion_info_cache) = &mut *self.s2d_cache.borrow_mut();
let mut ctx = SourceToDefCtx { db: self.db, dynmap_cache, expansion_info_cache };
f(&mut ctx)
}
pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> {
let src = self.find_file(src.syntax()).with_value(src).cloned();
let src = self.find_file(src.syntax()).with_value(src);
T::to_def(self, src)
}
@ -1613,27 +1638,57 @@ impl<'db> SemanticsImpl<'db> {
fn macro_call_to_macro_id(
ctx: &mut SourceToDefCtx<'_, '_>,
db: &dyn ExpandDatabase,
macro_call_id: MacroCallId,
) -> Option<MacroId> {
use span::HirFileIdRepr;
let db: &dyn ExpandDatabase = ctx.db.upcast();
let loc = db.lookup_intern_macro_call(macro_call_id);
match loc.def.ast_id() {
Either::Left(it) => ctx.macro_to_def(InFile::new(it.file_id, it.to_node(db))),
Either::Right(it) => ctx.proc_macro_to_def(InFile::new(it.file_id, it.to_node(db))),
Either::Left(it) => {
let node = match it.file_id.repr() {
HirFileIdRepr::FileId(file_id) => {
it.to_ptr(db).to_node(&db.parse(file_id).syntax_node())
}
HirFileIdRepr::MacroFile(macro_file) => {
let expansion_info = ctx
.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| macro_file.expansion_info(ctx.db.upcast()));
it.to_ptr(db).to_node(&expansion_info.expanded().value)
}
};
ctx.macro_to_def(InFile::new(it.file_id, &node))
}
Either::Right(it) => {
let node = match it.file_id.repr() {
HirFileIdRepr::FileId(file_id) => {
it.to_ptr(db).to_node(&db.parse(file_id).syntax_node())
}
HirFileIdRepr::MacroFile(macro_file) => {
let expansion_info = ctx
.expansion_info_cache
.entry(macro_file)
.or_insert_with(|| macro_file.expansion_info(ctx.db.upcast()));
it.to_ptr(db).to_node(&expansion_info.expanded().value)
}
};
ctx.proc_macro_to_def(InFile::new(it.file_id, &node))
}
}
}
pub trait ToDef: AstNode + Clone {
type Def;
fn to_def(sema: &SemanticsImpl<'_>, src: InFile<Self>) -> Option<Self::Def>;
fn to_def(sema: &SemanticsImpl<'_>, src: InFile<&Self>) -> Option<Self::Def>;
}
macro_rules! to_def_impls {
($(($def:path, $ast:path, $meth:ident)),* ,) => {$(
impl ToDef for $ast {
type Def = $def;
fn to_def(sema: &SemanticsImpl<'_>, src: InFile<Self>) -> Option<Self::Def> {
fn to_def(sema: &SemanticsImpl<'_>, src: InFile<&Self>) -> Option<Self::Def> {
sema.with_ctx(|ctx| ctx.$meth(src)).map(<$def>::from)
}
}
@ -1666,6 +1721,7 @@ to_def_impls![
(crate::Label, ast::Label, label_to_def),
(crate::Adt, ast::Adt, adt_to_def),
(crate::ExternCrateDecl, ast::ExternCrate, extern_crate_to_def),
(MacroCallId, ast::MacroCall, macro_call_to_macro_call),
];
fn find_root(node: &SyntaxNode) -> SyntaxNode {