|
1 | 1 | //! Span maps for real files and macro expansions.
|
2 | 2 |
|
3 | 3 | use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId};
|
4 |
| -use syntax::{AstNode, TextRange}; |
| 4 | +use stdx::TupleExt; |
| 5 | +use syntax::{ast, AstNode, TextRange}; |
5 | 6 | use triomphe::Arc;
|
6 | 7 |
|
7 | 8 | pub use span::RealSpanMap;
|
8 | 9 |
|
9 |
| -use crate::db::ExpandDatabase; |
| 10 | +use crate::{attrs::collect_attrs, db::ExpandDatabase}; |
10 | 11 |
|
11 | 12 | pub type ExpansionSpanMap = span::SpanMap<SyntaxContextId>;
|
12 | 13 |
|
@@ -83,13 +84,54 @@ pub(crate) fn real_span_map(db: &dyn ExpandDatabase, file_id: FileId) -> Arc<Rea
|
83 | 84 | let mut pairs = vec![(syntax::TextSize::new(0), span::ROOT_ERASED_FILE_AST_ID)];
|
84 | 85 | let ast_id_map = db.ast_id_map(file_id.into());
|
85 | 86 | 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 | + }); |
93 | 135 |
|
94 | 136 | Arc::new(RealSpanMap::from_file(
|
95 | 137 | file_id,
|
|
0 commit comments