Skip to content

Commit c50c4f8

Browse files
committed
internal: Use assoc items as anchors for spans
1 parent d085ade commit c50c4f8

File tree

4 files changed

+74
-17
lines changed

4 files changed

+74
-17
lines changed

crates/hir-expand/src/span_map.rs

+51-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
//! Span maps for real files and macro expansions.
22
33
use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId};
4-
use syntax::{AstNode, TextRange};
4+
use stdx::TupleExt;
5+
use syntax::{ast, AstNode, TextRange};
56
use triomphe::Arc;
67

78
pub use span::RealSpanMap;
89

9-
use crate::db::ExpandDatabase;
10+
use crate::{attrs::collect_attrs, db::ExpandDatabase};
1011

1112
pub type ExpansionSpanMap = span::SpanMap<SyntaxContextId>;
1213

@@ -83,13 +84,54 @@ pub(crate) fn real_span_map(db: &dyn ExpandDatabase, file_id: FileId) -> Arc<Rea
8384
let mut pairs = vec![(syntax::TextSize::new(0), span::ROOT_ERASED_FILE_AST_ID)];
8485
let ast_id_map = db.ast_id_map(file_id.into());
8586
let tree = db.parse(file_id).tree();
86-
// FIXME: Descend into modules and other item containing items that are not annotated with attributes
87-
// and allocate pairs for those as well. This gives us finer grained span anchors resulting in
88-
// better incrementality
89-
pairs.extend(
90-
tree.items()
91-
.map(|item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase())),
92-
);
87+
// This is an incrementality layer. Basically we can't use absolute ranges for our spans as that
88+
// would mean we'd invalidate everything whenever we type. So instead we make the text ranges
89+
// relative to some AstIds reducing the risk of invalidation as typing somewhere no longer
90+
// affects all following spans in the file.
91+
// There is some stuff to bear in mind here though, for one, the more "anchors" we create, the
92+
// easier it gets to invalidate things again as spans are as stable as their anchor's ID.
93+
// The other problem is proc-macros. Proc-macros have a `Span::join` api that allows them
94+
// to join two spans that come from the same file. rust-analyzer's proc-macro server
95+
// can only join two spans if they belong to the same anchor though, as the spans are relative
96+
// to that anchor. To do cross anchor joining we'd need to access to the ast id map to resolve
97+
// them again, something we might get access to in the future. But even then, proc-macros doing
98+
// this kind of joining makes them as stable as the AstIdMap (which is basically changing on
99+
// every input of the file)…
100+
101+
let item_to_entry =
102+
|item: ast::Item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase());
103+
// Top level items make for great anchors as they are the most stable and a decent boundary
104+
pairs.extend(tree.items().map(item_to_entry));
105+
// Unfortunately, assoc items are very common in Rust, so descend into those as well and make
106+
// them anchors too, but only if they have no attributes attached, as those might be proc-macros
107+
// and using different anchors inside of them will prevent spans from being joinable.
108+
tree.items().for_each(|item| match &item {
109+
ast::Item::ExternBlock(it)
110+
if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) =>
111+
{
112+
if let Some(extern_item_list) = it.extern_item_list() {
113+
pairs.extend(
114+
extern_item_list.extern_items().map(ast::Item::from).map(item_to_entry),
115+
);
116+
}
117+
}
118+
ast::Item::Impl(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => {
119+
if let Some(assoc_item_list) = it.assoc_item_list() {
120+
pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry));
121+
}
122+
}
123+
ast::Item::Module(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => {
124+
if let Some(item_list) = it.item_list() {
125+
pairs.extend(item_list.items().map(item_to_entry));
126+
}
127+
}
128+
ast::Item::Trait(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => {
129+
if let Some(assoc_item_list) = it.assoc_item_list() {
130+
pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry));
131+
}
132+
}
133+
_ => (),
134+
});
93135

94136
Arc::new(RealSpanMap::from_file(
95137
file_id,

crates/ide-db/src/defs.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ impl NameClass {
407407
}
408408

409409
pub fn classify(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<NameClass> {
410-
let _p = tracing::span!(tracing::Level::INFO, "classify_name").entered();
410+
let _p = tracing::span!(tracing::Level::INFO, "NameClass::classify").entered();
411411

412412
let parent = name.syntax().parent()?;
413413

@@ -499,7 +499,8 @@ impl NameClass {
499499
sema: &Semantics<'_, RootDatabase>,
500500
lifetime: &ast::Lifetime,
501501
) -> Option<NameClass> {
502-
let _p = tracing::span!(tracing::Level::INFO, "classify_lifetime", ?lifetime).entered();
502+
let _p = tracing::span!(tracing::Level::INFO, "NameClass::classify_lifetime", ?lifetime)
503+
.entered();
503504
let parent = lifetime.syntax().parent()?;
504505

505506
if let Some(it) = ast::LifetimeParam::cast(parent.clone()) {
@@ -590,7 +591,8 @@ impl NameRefClass {
590591
sema: &Semantics<'_, RootDatabase>,
591592
name_ref: &ast::NameRef,
592593
) -> Option<NameRefClass> {
593-
let _p = tracing::span!(tracing::Level::INFO, "classify_name_ref", ?name_ref).entered();
594+
let _p =
595+
tracing::span!(tracing::Level::INFO, "NameRefClass::classify", ?name_ref).entered();
594596

595597
let parent = name_ref.syntax().parent()?;
596598

@@ -689,7 +691,8 @@ impl NameRefClass {
689691
sema: &Semantics<'_, RootDatabase>,
690692
lifetime: &ast::Lifetime,
691693
) -> Option<NameRefClass> {
692-
let _p = tracing::span!(tracing::Level::INFO, "classify_lifetime_ref", ?lifetime).entered();
694+
let _p = tracing::span!(tracing::Level::INFO, "NameRefClass::classify_lifetime", ?lifetime)
695+
.entered();
693696
let parent = lifetime.syntax().parent()?;
694697
match parent.kind() {
695698
SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {

crates/rust-analyzer/src/integrated_benchmarks.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ fn integrated_highlighting_benchmark() {
5656
vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
5757
};
5858

59-
let _g = crate::tracing::hprof::init("*>150");
60-
6159
{
6260
let _it = stdx::timeit("initial");
6361
let analysis = host.analysis();
@@ -67,13 +65,16 @@ fn integrated_highlighting_benchmark() {
6765
{
6866
let _it = stdx::timeit("change");
6967
let mut text = host.analysis().file_text(file_id).unwrap().to_string();
70-
text.push_str("\npub fn _dummy() {}\n");
68+
text = text.replace(
69+
"self.data.cargo_buildScripts_rebuildOnSave",
70+
"self. data. cargo_buildScripts_rebuildOnSave",
71+
);
7172
let mut change = ChangeWithProcMacros::new();
7273
change.change_file(file_id, Some(text));
7374
host.apply_change(change);
7475
}
7576

76-
let _g = crate::tracing::hprof::init("*>50");
77+
let _g = crate::tracing::hprof::init("*>20");
7778

7879
{
7980
let _it = stdx::timeit("after change");

crates/syntax/src/ast/node_ext.rs

+11
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ impl From<ast::AssocItem> for ast::Item {
139139
}
140140
}
141141

142+
impl From<ast::ExternItem> for ast::Item {
143+
fn from(extern_item: ast::ExternItem) -> Self {
144+
match extern_item {
145+
ast::ExternItem::Static(it) => ast::Item::Static(it),
146+
ast::ExternItem::Fn(it) => ast::Item::Fn(it),
147+
ast::ExternItem::MacroCall(it) => ast::Item::MacroCall(it),
148+
ast::ExternItem::TypeAlias(it) => ast::Item::TypeAlias(it),
149+
}
150+
}
151+
}
152+
142153
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
143154
pub enum AttrKind {
144155
Inner,

0 commit comments

Comments
 (0)