Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 9c3de09

Browse files
committed
Auto merge of rust-lang#16054 - Veykril:fix-downmapping, r=Veykril
fix: Fix token downmapping being quadratic Fixes rust-lang/rust-analyzer#16050
2 parents b03a0bd + 5d951a6 commit 9c3de09

File tree

2 files changed

+164
-131
lines changed

2 files changed

+164
-131
lines changed

crates/hir-expand/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,8 @@ pub struct ExpansionInfo {
605605
}
606606

607607
impl ExpansionInfo {
608-
pub fn expanded(&self) -> InFile<SyntaxNode> {
609-
self.expanded.clone().into()
608+
pub fn expanded(&self) -> InMacroFile<SyntaxNode> {
609+
self.expanded.clone()
610610
}
611611

612612
pub fn call_node(&self) -> Option<InFile<SyntaxNode>> {
@@ -617,13 +617,13 @@ impl ExpansionInfo {
617617
pub fn map_range_down<'a>(
618618
&'a self,
619619
span: SpanData,
620-
) -> Option<impl Iterator<Item = InMacroFile<SyntaxToken>> + 'a> {
620+
) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + 'a>> {
621621
let tokens = self
622622
.exp_map
623623
.ranges_with_span(span)
624624
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
625625

626-
Some(tokens.map(move |token| InMacroFile::new(self.expanded.file_id, token)))
626+
Some(InMacroFile::new(self.expanded.file_id, tokens))
627627
}
628628

629629
/// Looks up the span at the given offset.

crates/hir/src/semantics.rs

Lines changed: 160 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod source_to_def;
55
use std::{
66
cell::RefCell,
77
fmt, iter, mem,
8-
ops::{self, ControlFlow},
8+
ops::{self, ControlFlow, Not},
99
};
1010

1111
use base_db::{FileId, FileRange};
@@ -20,16 +20,16 @@ use hir_def::{
2020
AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId,
2121
};
2222
use hir_expand::{
23-
db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo, MacroCallId, MacroFileId,
24-
MacroFileIdExt,
23+
db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo, InMacroFile, MacroCallId,
24+
MacroFileId, MacroFileIdExt,
2525
};
2626
use itertools::Itertools;
2727
use rustc_hash::{FxHashMap, FxHashSet};
2828
use smallvec::{smallvec, SmallVec};
2929
use stdx::TupleExt;
3030
use syntax::{
3131
algo::skip_trivia_token,
32-
ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _},
32+
ast::{self, HasAttrs as _, HasDocComments, HasGenericParams, HasLoopBody, IsString as _},
3333
match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
3434
TextRange, TextSize,
3535
};
@@ -129,9 +129,10 @@ pub struct Semantics<'db, DB> {
129129
pub struct SemanticsImpl<'db> {
130130
pub db: &'db dyn HirDatabase,
131131
s2d_cache: RefCell<SourceToDefCache>,
132-
expansion_info_cache: RefCell<FxHashMap<MacroFileId, ExpansionInfo>>,
133132
/// Rootnode to HirFileId cache
134133
cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
134+
// These 2 caches are mainly useful for semantic highlighting as nothing else descends a lot of tokens
135+
expansion_info_cache: RefCell<FxHashMap<MacroFileId, ExpansionInfo>>,
135136
/// MacroCall to its expansion's MacroFileId cache
136137
macro_call_cache: RefCell<FxHashMap<InFile<ast::MacroCall>, MacroFileId>>,
137138
}
@@ -616,164 +617,196 @@ impl<'db> SemanticsImpl<'db> {
616617
res
617618
}
618619

619-
// FIXME: should only take real file inputs for simplicity
620620
fn descend_into_macros_impl(
621621
&self,
622622
token: SyntaxToken,
623623
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
624624
) {
625-
// FIXME: Clean this up
626625
let _p = profile::span("descend_into_macros");
627626
let sa = match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
628627
Some(it) => it,
629628
None => return,
630629
};
631630

632-
let mut cache = self.expansion_info_cache.borrow_mut();
633-
let mut mcache = self.macro_call_cache.borrow_mut();
634-
let span = match sa.file_id.repr() {
635-
base_db::span::HirFileIdRepr::FileId(file_id) => {
636-
self.db.real_span_map(file_id).span_for_range(token.text_range())
631+
let span = match sa.file_id.file_id() {
632+
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
633+
None => {
634+
stdx::never!();
635+
return;
637636
}
638-
base_db::span::HirFileIdRepr::MacroFile(macro_file) => cache
639-
.entry(macro_file)
640-
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()))
641-
.exp_map
642-
.span_at(token.text_range().start()),
643637
};
644638

639+
let mut cache = self.expansion_info_cache.borrow_mut();
640+
let mut mcache = self.macro_call_cache.borrow_mut();
645641
let def_map = sa.resolver.def_map();
646-
let mut stack: SmallVec<[_; 4]> = smallvec![InFile::new(sa.file_id, token)];
647642

648-
let mut process_expansion_for_token = |stack: &mut SmallVec<_>, macro_file| {
643+
let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
649644
let expansion_info = cache
650645
.entry(macro_file)
651646
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
652647

653648
{
654-
let InFile { file_id, value } = expansion_info.expanded();
655-
self.cache(value, file_id);
649+
let InMacroFile { file_id, value } = expansion_info.expanded();
650+
self.cache(value, file_id.into());
656651
}
657652

658-
let mapped_tokens = expansion_info.map_range_down(span)?;
659-
let len = stack.len();
653+
let InMacroFile { file_id, value: mapped_tokens } =
654+
expansion_info.map_range_down(span)?;
655+
let mapped_tokens: SmallVec<[_; 2]> = mapped_tokens.collect();
660656

661-
// requeue the tokens we got from mapping our current token down
662-
stack.extend(mapped_tokens.map(Into::into));
663657
// if the length changed we have found a mapping for the token
664-
(stack.len() != len).then_some(())
658+
let res = mapped_tokens.is_empty().not().then_some(());
659+
// requeue the tokens we got from mapping our current token down
660+
stack.push((HirFileId::from(file_id), mapped_tokens));
661+
res
665662
};
666663

667-
// Remap the next token in the queue into a macro call its in, if it is not being remapped
668-
// either due to not being in a macro-call or because its unused push it into the result vec,
669-
// otherwise push the remapped tokens back into the queue as they can potentially be remapped again.
670-
while let Some(token) = stack.pop() {
671-
let was_not_remapped = (|| {
672-
// First expand into attribute invocations
673-
674-
let containing_attribute_macro_call = self.with_ctx(|ctx| {
675-
token.value.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
676-
if item.attrs().next().is_none() {
677-
// Don't force populate the dyn cache for items that don't have an attribute anyways
678-
return None;
679-
}
680-
Some(ctx.item_to_macro_call(token.with_value(item.clone()))?)
681-
})
682-
});
683-
if let Some(call_id) = containing_attribute_macro_call {
684-
let file_id = call_id.as_macro_file();
685-
return process_expansion_for_token(&mut stack, file_id);
686-
}
664+
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(sa.file_id, smallvec![token])];
665+
666+
while let Some((file_id, mut tokens)) = stack.pop() {
667+
while let Some(token) = tokens.pop() {
668+
let was_not_remapped = (|| {
669+
// First expand into attribute invocations
670+
let containing_attribute_macro_call = self.with_ctx(|ctx| {
671+
token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
672+
if item.attrs().next().is_none() {
673+
// Don't force populate the dyn cache for items that don't have an attribute anyways
674+
return None;
675+
}
676+
Some((
677+
ctx.item_to_macro_call(InFile::new(file_id, item.clone()))?,
678+
item,
679+
))
680+
})
681+
});
682+
if let Some((call_id, item)) = containing_attribute_macro_call {
683+
let file_id = call_id.as_macro_file();
684+
let attr_id = match self.db.lookup_intern_macro_call(call_id).kind {
685+
hir_expand::MacroCallKind::Attr { invoc_attr_index, .. } => {
686+
invoc_attr_index.ast_index()
687+
}
688+
_ => 0,
689+
};
690+
let text_range = item.syntax().text_range();
691+
let start = item
692+
.doc_comments_and_attrs()
693+
.nth(attr_id)
694+
.map(|attr| match attr {
695+
Either::Left(it) => it.syntax().text_range().start(),
696+
Either::Right(it) => it.syntax().text_range().start(),
697+
})
698+
.unwrap_or_else(|| text_range.start());
699+
let text_range = TextRange::new(start, text_range.end());
700+
// remove any other token in this macro input, all their mappings are the
701+
// same as this one
702+
tokens.retain(|t| !text_range.contains_range(t.text_range()));
703+
return process_expansion_for_token(&mut stack, file_id);
704+
}
687705

688-
// Then check for token trees, that means we are either in a function-like macro or
689-
// secondary attribute inputs
690-
let tt = token.value.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
691-
let parent = tt.syntax().parent()?;
706+
// Then check for token trees, that means we are either in a function-like macro or
707+
// secondary attribute inputs
708+
let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
709+
let parent = tt.syntax().parent()?;
692710

693-
if tt.left_delimiter_token().map_or(false, |it| it == token.value) {
694-
return None;
695-
}
696-
if tt.right_delimiter_token().map_or(false, |it| it == token.value) {
697-
return None;
698-
}
711+
if tt.left_delimiter_token().map_or(false, |it| it == token) {
712+
return None;
713+
}
714+
if tt.right_delimiter_token().map_or(false, |it| it == token) {
715+
return None;
716+
}
699717

700-
if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
701-
let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
702-
token.with_value(macro_call);
703-
let file_id = match mcache.get(&mcall) {
704-
Some(&it) => it,
705-
None => {
706-
let it = sa.expand(self.db, mcall.as_ref())?;
707-
mcache.insert(mcall, it);
708-
it
709-
}
710-
};
711-
process_expansion_for_token(&mut stack, file_id)
712-
} else if let Some(meta) = ast::Meta::cast(parent) {
713-
// attribute we failed expansion for earlier, this might be a derive invocation
714-
// or derive helper attribute
715-
let attr = meta.parent_attr()?;
716-
717-
let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast) {
718-
// this might be a derive, or a derive helper on an ADT
719-
let derive_call = self.with_ctx(|ctx| {
720-
// so try downmapping the token into the pseudo derive expansion
721-
// see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
722-
ctx.attr_to_derive_macro_call(
723-
token.with_value(&adt),
724-
token.with_value(attr.clone()),
725-
)
726-
.map(|(_, call_id, _)| call_id)
727-
});
728-
729-
match derive_call {
730-
Some(call_id) => {
731-
// resolved to a derive
732-
let file_id = call_id.as_macro_file();
733-
return process_expansion_for_token(&mut stack, file_id);
718+
if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
719+
let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
720+
InFile::new(file_id, macro_call);
721+
let file_id = match mcache.get(&mcall) {
722+
Some(&it) => it,
723+
None => {
724+
let it = sa.expand(self.db, mcall.as_ref())?;
725+
mcache.insert(mcall, it);
726+
it
734727
}
735-
None => Some(adt),
736-
}
737-
} else {
738-
// Otherwise this could be a derive helper on a variant or field
739-
if let Some(field) = attr.syntax().parent().and_then(ast::RecordField::cast)
740-
{
741-
field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
742-
} else if let Some(field) =
743-
attr.syntax().parent().and_then(ast::TupleField::cast)
744-
{
745-
field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
746-
} else if let Some(variant) =
747-
attr.syntax().parent().and_then(ast::Variant::cast)
728+
};
729+
let text_range = tt.syntax().text_range();
730+
// remove any other token in this macro input, all their mappings are the
731+
// same as this one
732+
tokens.retain(|t| !text_range.contains_range(t.text_range()));
733+
process_expansion_for_token(&mut stack, file_id)
734+
} else if let Some(meta) = ast::Meta::cast(parent) {
735+
// attribute we failed expansion for earlier, this might be a derive invocation
736+
// or derive helper attribute
737+
let attr = meta.parent_attr()?;
738+
739+
let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
748740
{
749-
variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
741+
// this might be a derive, or a derive helper on an ADT
742+
let derive_call = self.with_ctx(|ctx| {
743+
// so try downmapping the token into the pseudo derive expansion
744+
// see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
745+
ctx.attr_to_derive_macro_call(
746+
InFile::new(file_id, &adt),
747+
InFile::new(file_id, attr.clone()),
748+
)
749+
.map(|(_, call_id, _)| call_id)
750+
});
751+
752+
match derive_call {
753+
Some(call_id) => {
754+
// resolved to a derive
755+
let file_id = call_id.as_macro_file();
756+
let text_range = attr.syntax().text_range();
757+
// remove any other token in this macro input, all their mappings are the
758+
// same as this one
759+
tokens.retain(|t| !text_range.contains_range(t.text_range()));
760+
return process_expansion_for_token(&mut stack, file_id);
761+
}
762+
None => Some(adt),
763+
}
750764
} else {
751-
None
765+
// Otherwise this could be a derive helper on a variant or field
766+
if let Some(field) =
767+
attr.syntax().parent().and_then(ast::RecordField::cast)
768+
{
769+
field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
770+
} else if let Some(field) =
771+
attr.syntax().parent().and_then(ast::TupleField::cast)
772+
{
773+
field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
774+
} else if let Some(variant) =
775+
attr.syntax().parent().and_then(ast::Variant::cast)
776+
{
777+
variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
778+
} else {
779+
None
780+
}
781+
}?;
782+
if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
783+
return None;
752784
}
753-
}?;
754-
if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(token.file_id, &adt))) {
755-
return None;
756-
}
757-
// Not an attribute, nor a derive, so it's either a builtin or a derive helper
758-
// Try to resolve to a derive helper and downmap
759-
let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
760-
let id = self.db.ast_id_map(token.file_id).ast_id(&adt);
761-
let helpers =
762-
def_map.derive_helpers_in_scope(InFile::new(token.file_id, id))?;
763-
let mut res = None;
764-
for (.., derive) in helpers.iter().filter(|(helper, ..)| *helper == attr_name) {
765-
res =
766-
res.or(process_expansion_for_token(&mut stack, derive.as_macro_file()));
785+
// Not an attribute, nor a derive, so it's either a builtin or a derive helper
786+
// Try to resolve to a derive helper and downmap
787+
let attr_name =
788+
attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
789+
let id = self.db.ast_id_map(file_id).ast_id(&adt);
790+
let helpers = def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
791+
let mut res = None;
792+
for (.., derive) in
793+
helpers.iter().filter(|(helper, ..)| *helper == attr_name)
794+
{
795+
res = res.or(process_expansion_for_token(
796+
&mut stack,
797+
derive.as_macro_file(),
798+
));
799+
}
800+
res
801+
} else {
802+
None
767803
}
768-
res
769-
} else {
770-
None
771-
}
772-
})()
773-
.is_none();
804+
})()
805+
.is_none();
774806

775-
if was_not_remapped && f(token).is_break() {
776-
break;
807+
if was_not_remapped && f(InFile::new(file_id, token)).is_break() {
808+
break;
809+
}
777810
}
778811
}
779812
}

0 commit comments

Comments
 (0)