1use base_db::SourceDatabase;
4use span::Span;
5use syntax::{AstNode, TextRange, ast};
6
7pub use span::RealSpanMap;
8
9use crate::{HirFileId, MacroCallId};
10
11pub type ExpansionSpanMap = span::SpanMap;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum SpanMap<'db> {
16 ExpansionSpanMap(&'db ExpansionSpanMap),
18 RealSpanMap(&'db RealSpanMap),
20}
21
22impl syntax_bridge::SpanMapper for SpanMap<'_> {
23 fn span_for(&self, range: TextRange) -> Span {
24 self.span_for_range(range)
25 }
26}
27
28impl<'db> SpanMap<'db> {
29 pub fn span_for_range(&self, range: TextRange) -> Span {
30 match self {
31 Self::ExpansionSpanMap(span_map) => span_map.span_at(range.start()),
36 Self::RealSpanMap(span_map) => span_map.span_for_range(range),
37 }
38 }
39}
40
41impl HirFileId {
42 #[inline]
43 pub fn span_map<'db>(self, db: &'db dyn SourceDatabase) -> SpanMap<'db> {
44 match self {
45 HirFileId::FileId(file_id) => SpanMap::RealSpanMap(real_span_map(db, file_id)),
46 HirFileId::MacroFile(m) => SpanMap::ExpansionSpanMap(m.expansion_span_map(db)),
47 }
48 }
49}
50
51#[salsa::tracked(returns(ref))]
54pub(crate) fn real_span_map(
55 db: &dyn SourceDatabase,
56 editioned_file_id: base_db::EditionedFileId,
57) -> RealSpanMap {
58 use syntax::ast::HasModuleItem;
59 let mut pairs = vec![(syntax::TextSize::new(0), span::ROOT_ERASED_FILE_AST_ID)];
60 let ast_id_map = HirFileId::from(editioned_file_id).ast_id_map(db);
61
62 let tree = editioned_file_id.parse(db).tree();
63 let item_to_entry =
78 |item: ast::Item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase());
79 pairs.extend(tree.items().map(item_to_entry));
81 tree.items().for_each(|item| match &item {
85 ast::Item::ExternBlock(it) if ast::attrs_including_inner(it).next().is_none() => {
86 if let Some(extern_item_list) = it.extern_item_list() {
87 pairs.extend(
88 extern_item_list.extern_items().map(ast::Item::from).map(item_to_entry),
89 );
90 }
91 }
92 ast::Item::Impl(it) if ast::attrs_including_inner(it).next().is_none() => {
93 if let Some(assoc_item_list) = it.assoc_item_list() {
94 pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry));
95 }
96 }
97 ast::Item::Module(it) if ast::attrs_including_inner(it).next().is_none() => {
98 if let Some(item_list) = it.item_list() {
99 pairs.extend(item_list.items().map(item_to_entry));
100 }
101 }
102 ast::Item::Trait(it) if ast::attrs_including_inner(it).next().is_none() => {
103 if let Some(assoc_item_list) = it.assoc_item_list() {
104 pairs.extend(assoc_item_list.assoc_items().map(ast::Item::from).map(item_to_entry));
105 }
106 }
107 _ => (),
108 });
109
110 RealSpanMap::from_file(
111 editioned_file_id.span_file_id(db),
112 pairs.into_boxed_slice(),
113 tree.syntax().text_range().end(),
114 )
115}
116
117impl MacroCallId {
118 pub fn expansion_span_map(self, db: &dyn SourceDatabase) -> &ExpansionSpanMap {
119 &self.parse_macro_expansion(db).value.1
120 }
121}