about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-03-15 09:28:39 +0100
committerLukas Wirth <lukastw97@gmail.com>2024-03-15 09:28:39 +0100
commitc50c4f8bbb8da046c19765f0bd909e634bdd9cdb (patch)
treebc09236b7e37ebc3f191c066ee1c63f587a8210d
parentd085ade631ac8e042c62398aa425e9813d91dd7c (diff)
downloadrust-c50c4f8bbb8da046c19765f0bd909e634bdd9cdb.tar.gz
rust-c50c4f8bbb8da046c19765f0bd909e634bdd9cdb.zip
internal: Use assoc items as anchors for spans
-rw-r--r--crates/hir-expand/src/span_map.rs60
-rw-r--r--crates/ide-db/src/defs.rs11
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs9
-rw-r--r--crates/syntax/src/ast/node_ext.rs11
4 files changed, 74 insertions, 17 deletions
diff --git a/crates/hir-expand/src/span_map.rs b/crates/hir-expand/src/span_map.rs
index 3713578478e..eae2c8fb632 100644
--- a/crates/hir-expand/src/span_map.rs
+++ b/crates/hir-expand/src/span_map.rs
@@ -1,12 +1,13 @@
 //! Span maps for real files and macro expansions.
 
 use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId};
-use syntax::{AstNode, TextRange};
+use stdx::TupleExt;
+use syntax::{ast, AstNode, TextRange};
 use triomphe::Arc;
 
 pub use span::RealSpanMap;
 
-use crate::db::ExpandDatabase;
+use crate::{attrs::collect_attrs, db::ExpandDatabase};
 
 pub type ExpansionSpanMap = span::SpanMap<SyntaxContextId>;
 
@@ -83,13 +84,54 @@ pub(crate) fn real_span_map(db: &dyn ExpandDatabase, file_id: FileId) -> Arc<Rea
     let mut pairs = vec![(syntax::TextSize::new(0), span::ROOT_ERASED_FILE_AST_ID)];
     let ast_id_map = db.ast_id_map(file_id.into());
     let tree = db.parse(file_id).tree();
-    // FIXME: Descend into modules and other item containing items that are not annotated with attributes
-    // and allocate pairs for those as well. This gives us finer grained span anchors resulting in
-    // better incrementality
-    pairs.extend(
-        tree.items()
-            .map(|item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase())),
-    );
+    // This is an incrementality layer. Basically we can't use absolute ranges for our spans as that
+    // would mean we'd invalidate everything whenever we type. So instead we make the text ranges
+    // relative to some AstIds reducing the risk of invalidation as typing somewhere no longer
+    // affects all following spans in the file.
+    // There is some stuff to bear in mind here though, for one, the more "anchors" we create, the
+    // easier it gets to invalidate things again as spans are as stable as their anchor's ID.
+    // The other problem is proc-macros. Proc-macros have a `Span::join` api that allows them
+    // to join two spans that come from the same file. rust-analyzer's proc-macro server
+    // can only join two spans if they belong to the same anchor though, as the spans are relative
+    // to that anchor. To do cross anchor joining we'd need to access to the ast id map to resolve
+    // them again, something we might get access to in the future. But even then, proc-macros doing
+    // this kind of joining makes them as stable as the AstIdMap (which is basically changing on
+    // every input of the file)…
+
+    let item_to_entry =
+        |item: ast::Item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase());
+    // Top level items make for great anchors as they are the most stable and a decent boundary
+    pairs.extend(tree.items().map(item_to_entry));
+    // Unfortunately, assoc items are very common in Rust, so descend into those as well and make
+    // them anchors too, but only if they have no attributes attached, as those might be proc-macros
+    // and using different anchors inside of them will prevent spans from being joinable.
+    tree.items().for_each(|item| match &item {
+        ast::Item::ExternBlock(it)
+            if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) =>
+        {
+            if let Some(extern_item_list) = it.extern_item_list() {
+                pairs.extend(
+                    extern_item_list.extern_items().map(ast::Item::from).map(item_to_entry),
+                );
+            }
+        }
+        ast::Item::Impl(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => {
+            if let Some(assoc_item_list) = it.assoc_item_list() {
+                pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry));
+            }
+        }
+        ast::Item::Module(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => {
+            if let Some(item_list) = it.item_list() {
+                pairs.extend(item_list.items().map(item_to_entry));
+            }
+        }
+        ast::Item::Trait(it) if !collect_attrs(it).map(TupleExt::tail).any(|it| it.is_left()) => {
+            if let Some(assoc_item_list) = it.assoc_item_list() {
+                pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry));
+            }
+        }
+        _ => (),
+    });
 
     Arc::new(RealSpanMap::from_file(
         file_id,
diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs
index 33970de1e4b..c0f0faba35c 100644
--- a/crates/ide-db/src/defs.rs
+++ b/crates/ide-db/src/defs.rs
@@ -407,7 +407,7 @@ impl NameClass {
     }
 
     pub fn classify(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<NameClass> {
-        let _p = tracing::span!(tracing::Level::INFO, "classify_name").entered();
+        let _p = tracing::span!(tracing::Level::INFO, "NameClass::classify").entered();
 
         let parent = name.syntax().parent()?;
 
@@ -499,7 +499,8 @@ impl NameClass {
         sema: &Semantics<'_, RootDatabase>,
         lifetime: &ast::Lifetime,
     ) -> Option<NameClass> {
-        let _p = tracing::span!(tracing::Level::INFO, "classify_lifetime", ?lifetime).entered();
+        let _p = tracing::span!(tracing::Level::INFO, "NameClass::classify_lifetime", ?lifetime)
+            .entered();
         let parent = lifetime.syntax().parent()?;
 
         if let Some(it) = ast::LifetimeParam::cast(parent.clone()) {
@@ -590,7 +591,8 @@ impl NameRefClass {
         sema: &Semantics<'_, RootDatabase>,
         name_ref: &ast::NameRef,
     ) -> Option<NameRefClass> {
-        let _p = tracing::span!(tracing::Level::INFO, "classify_name_ref", ?name_ref).entered();
+        let _p =
+            tracing::span!(tracing::Level::INFO, "NameRefClass::classify", ?name_ref).entered();
 
         let parent = name_ref.syntax().parent()?;
 
@@ -689,7 +691,8 @@ impl NameRefClass {
         sema: &Semantics<'_, RootDatabase>,
         lifetime: &ast::Lifetime,
     ) -> Option<NameRefClass> {
-        let _p = tracing::span!(tracing::Level::INFO, "classify_lifetime_ref", ?lifetime).entered();
+        let _p = tracing::span!(tracing::Level::INFO, "NameRefClass::classify_lifetime", ?lifetime)
+            .entered();
         let parent = lifetime.syntax().parent()?;
         match parent.kind() {
             SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index ae58e6b9b24..1c5a862c703 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -56,8 +56,6 @@ fn integrated_highlighting_benchmark() {
         vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
     };
 
-    let _g = crate::tracing::hprof::init("*>150");
-
     {
         let _it = stdx::timeit("initial");
         let analysis = host.analysis();
@@ -67,13 +65,16 @@ fn integrated_highlighting_benchmark() {
     {
         let _it = stdx::timeit("change");
         let mut text = host.analysis().file_text(file_id).unwrap().to_string();
-        text.push_str("\npub fn _dummy() {}\n");
+        text = text.replace(
+            "self.data.cargo_buildScripts_rebuildOnSave",
+            "self. data. cargo_buildScripts_rebuildOnSave",
+        );
         let mut change = ChangeWithProcMacros::new();
         change.change_file(file_id, Some(text));
         host.apply_change(change);
     }
 
-    let _g = crate::tracing::hprof::init("*>50");
+    let _g = crate::tracing::hprof::init("*>20");
 
     {
         let _it = stdx::timeit("after change");
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 1bc1ef8434f..c3d6f50e6b0 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -139,6 +139,17 @@ impl From<ast::AssocItem> for ast::Item {
     }
 }
 
+impl From<ast::ExternItem> for ast::Item {
+    fn from(extern_item: ast::ExternItem) -> Self {
+        match extern_item {
+            ast::ExternItem::Static(it) => ast::Item::Static(it),
+            ast::ExternItem::Fn(it) => ast::Item::Fn(it),
+            ast::ExternItem::MacroCall(it) => ast::Item::MacroCall(it),
+            ast::ExternItem::TypeAlias(it) => ast::Item::TypeAlias(it),
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum AttrKind {
     Inner,