Skip to content

Commit a2e2741

Browse files
committed
Auto merge of #16819 - Veykril:span-upmapping, r=Veykril
internal: Improve rooted upmapping cc #16235
2 parents 9bc1eb4 + 9ba4493 commit a2e2741

File tree

24 files changed

+231
-153
lines changed

24 files changed

+231
-153
lines changed

crates/hir-def/src/nameres.rs

+29-2
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,16 @@ use std::ops::Deref;
6161

6262
use base_db::{CrateId, Edition, FileId};
6363
use hir_expand::{
64-
name::Name, proc_macro::ProcMacroKind, HirFileId, InFile, MacroCallId, MacroDefId,
64+
name::Name, proc_macro::ProcMacroKind, ErasedAstId, HirFileId, InFile, MacroCallId, MacroDefId,
6565
};
6666
use itertools::Itertools;
6767
use la_arena::Arena;
6868
use rustc_hash::{FxHashMap, FxHashSet};
69-
use span::FileAstId;
69+
use span::{FileAstId, ROOT_ERASED_FILE_AST_ID};
7070
use stdx::format_to;
7171
use syntax::{ast, SmolStr};
7272
use triomphe::Arc;
73+
use tt::TextRange;
7374

7475
use crate::{
7576
db::DefDatabase,
@@ -677,13 +678,39 @@ impl ModuleData {
677678
}
678679
}
679680

681+
pub fn definition_source_range(&self, db: &dyn DefDatabase) -> InFile<TextRange> {
682+
match &self.origin {
683+
&ModuleOrigin::File { definition, .. } | &ModuleOrigin::CrateRoot { definition } => {
684+
InFile::new(
685+
definition.into(),
686+
ErasedAstId::new(definition.into(), ROOT_ERASED_FILE_AST_ID)
687+
.to_range(db.upcast()),
688+
)
689+
}
690+
&ModuleOrigin::Inline { definition, definition_tree_id } => InFile::new(
691+
definition_tree_id.file_id(),
692+
AstId::new(definition_tree_id.file_id(), definition).to_range(db.upcast()),
693+
),
694+
ModuleOrigin::BlockExpr { block, .. } => {
695+
InFile::new(block.file_id, block.to_range(db.upcast()))
696+
}
697+
}
698+
}
699+
680700
/// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
681701
/// `None` for the crate root or block.
682702
pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option<InFile<ast::Module>> {
683703
let decl = self.origin.declaration()?;
684704
let value = decl.to_node(db.upcast());
685705
Some(InFile { file_id: decl.file_id, value })
686706
}
707+
708+
/// Returns the range which declares this module, either a `mod foo;` or a `mod foo {}`.
709+
/// `None` for the crate root or block.
710+
pub fn declaration_source_range(&self, db: &dyn DefDatabase) -> Option<InFile<TextRange>> {
711+
let decl = self.origin.declaration()?;
712+
Some(InFile { file_id: decl.file_id, value: decl.to_range(db.upcast()) })
713+
}
687714
}
688715

689716
#[derive(Debug, Clone, PartialEq, Eq)]

crates/hir-expand/src/files.rs

+44-69
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
1010

1111
use crate::{
1212
db::{self, ExpandDatabase},
13-
map_node_range_up, span_for_offset, MacroFileIdExt,
13+
map_node_range_up, map_node_range_up_rooted, span_for_offset, MacroFileIdExt,
1414
};
1515

1616
/// `InFile<T>` stores a value of `T` inside a particular file/syntax tree.
@@ -38,6 +38,9 @@ impl<N: AstIdNode> AstId<N> {
3838
pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
3939
self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
4040
}
41+
pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
42+
self.to_ptr(db).text_range()
43+
}
4144
pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile<N> {
4245
crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
4346
}
@@ -49,6 +52,9 @@ impl<N: AstIdNode> AstId<N> {
4952
pub type ErasedAstId = crate::InFile<ErasedFileAstId>;
5053

5154
impl ErasedAstId {
55+
pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
56+
self.to_ptr(db).text_range()
57+
}
5258
pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr {
5359
db.ast_id_map(self.file_id).get_erased(self.value)
5460
}
@@ -173,66 +179,27 @@ impl InFile<&SyntaxNode> {
173179
///
174180
/// For attributes and derives, this will point back to the attribute only.
175181
/// For the entire item use [`InFile::original_file_range_full`].
176-
pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange {
177-
match self.file_id.repr() {
178-
HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
179-
HirFileIdRepr::MacroFile(mac_file) => {
180-
if let Some((res, ctxt)) =
181-
map_node_range_up(db, &db.expansion_span_map(mac_file), self.value.text_range())
182-
{
183-
// FIXME: Figure out an API that makes proper use of ctx, this only exists to
184-
// keep pre-token map rewrite behaviour.
185-
if ctxt.is_root() {
186-
return res;
187-
}
188-
}
189-
// Fall back to whole macro call.
190-
let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
191-
loc.kind.original_call_range(db)
192-
}
193-
}
182+
pub fn original_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
183+
self.map(SyntaxNode::text_range).original_node_file_range_rooted(db)
194184
}
195185

196186
/// Falls back to the macro call range if the node cannot be mapped up fully.
197187
pub fn original_file_range_with_macro_call_body(
198188
self,
199189
db: &dyn db::ExpandDatabase,
200190
) -> FileRange {
201-
match self.file_id.repr() {
202-
HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
203-
HirFileIdRepr::MacroFile(mac_file) => {
204-
if let Some((res, ctxt)) =
205-
map_node_range_up(db, &db.expansion_span_map(mac_file), self.value.text_range())
206-
{
207-
// FIXME: Figure out an API that makes proper use of ctx, this only exists to
208-
// keep pre-token map rewrite behaviour.
209-
if ctxt.is_root() {
210-
return res;
211-
}
212-
}
213-
// Fall back to whole macro call.
214-
let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
215-
loc.kind.original_call_range_with_body(db)
216-
}
217-
}
191+
self.map(SyntaxNode::text_range).original_node_file_range_with_macro_call_body(db)
218192
}
219193

220194
/// Attempts to map the syntax node back up its macro calls.
221195
pub fn original_file_range_opt(
222196
self,
223197
db: &dyn db::ExpandDatabase,
224198
) -> Option<(FileRange, SyntaxContextId)> {
225-
match self.file_id.repr() {
226-
HirFileIdRepr::FileId(file_id) => {
227-
Some((FileRange { file_id, range: self.value.text_range() }, SyntaxContextId::ROOT))
228-
}
229-
HirFileIdRepr::MacroFile(mac_file) => {
230-
map_node_range_up(db, &db.expansion_span_map(mac_file), self.value.text_range())
231-
}
232-
}
199+
self.map(SyntaxNode::text_range).original_node_file_range_opt(db)
233200
}
234201

235-
pub fn original_syntax_node(
202+
pub fn original_syntax_node_rooted(
236203
self,
237204
db: &dyn db::ExpandDatabase,
238205
) -> Option<InRealFile<SyntaxNode>> {
@@ -242,25 +209,21 @@ impl InFile<&SyntaxNode> {
242209
HirFileIdRepr::FileId(file_id) => {
243210
return Some(InRealFile { file_id, value: self.value.clone() })
244211
}
245-
HirFileIdRepr::MacroFile(m) => m,
212+
HirFileIdRepr::MacroFile(m) if m.is_attr_macro(db) => m,
213+
_ => return None,
246214
};
247-
if !file_id.is_attr_macro(db) {
248-
return None;
249-
}
250215

251-
let (FileRange { file_id, range }, ctx) =
252-
map_node_range_up(db, &db.expansion_span_map(file_id), self.value.text_range())?;
216+
let FileRange { file_id, range } =
217+
map_node_range_up_rooted(db, &db.expansion_span_map(file_id), self.value.text_range())?;
253218

254-
// FIXME: Figure out an API that makes proper use of ctx, this only exists to
255-
// keep pre-token map rewrite behavior.
256-
if !ctx.is_root() {
257-
return None;
258-
}
259-
260-
let anc = db.parse(file_id).syntax_node().covering_element(range);
261219
let kind = self.value.kind();
262-
// FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes?
263-
let value = anc.ancestors().find(|it| it.kind() == kind)?;
220+
let value = db
221+
.parse(file_id)
222+
.syntax_node()
223+
.covering_element(range)
224+
.ancestors()
225+
.take_while(|it| it.text_range() == range)
226+
.find(|it| it.kind() == kind)?;
264227
Some(InRealFile::new(file_id, value))
265228
}
266229
}
@@ -355,8 +318,8 @@ impl InFile<TextRange> {
355318
match self.file_id.repr() {
356319
HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value },
357320
HirFileIdRepr::MacroFile(mac_file) => {
358-
match map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) {
359-
Some((it, SyntaxContextId::ROOT)) => it,
321+
match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
322+
Some(it) => it,
360323
_ => {
361324
let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
362325
loc.kind.original_call_range(db)
@@ -366,6 +329,24 @@ impl InFile<TextRange> {
366329
}
367330
}
368331

332+
pub fn original_node_file_range_with_macro_call_body(
333+
self,
334+
db: &dyn db::ExpandDatabase,
335+
) -> FileRange {
336+
match self.file_id.repr() {
337+
HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value },
338+
HirFileIdRepr::MacroFile(mac_file) => {
339+
match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
340+
Some(it) => it,
341+
_ => {
342+
let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
343+
loc.kind.original_call_range_with_body(db)
344+
}
345+
}
346+
}
347+
}
348+
}
349+
369350
pub fn original_node_file_range_opt(
370351
self,
371352
db: &dyn db::ExpandDatabase,
@@ -395,18 +376,12 @@ impl<N: AstNode> InFile<N> {
395376
return None;
396377
}
397378

398-
let (FileRange { file_id, range }, ctx) = map_node_range_up(
379+
let FileRange { file_id, range } = map_node_range_up_rooted(
399380
db,
400381
&db.expansion_span_map(file_id),
401382
self.value.syntax().text_range(),
402383
)?;
403384

404-
// FIXME: Figure out an API that makes proper use of ctx, this only exists to
405-
// keep pre-token map rewrite behaviour.
406-
if !ctx.is_root() {
407-
return None;
408-
}
409-
410385
// FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes?
411386
let anc = db.parse(file_id).syntax_node().covering_element(range);
412387
let value = anc.ancestors().find_map(N::cast)?;

crates/hir-expand/src/lib.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ pub mod span_map;
2525
mod cfg_process;
2626
mod fixup;
2727
use attrs::collect_attrs;
28+
use rustc_hash::FxHashMap;
2829
use triomphe::Arc;
2930

3031
use std::{fmt, hash::Hash};
3132

3233
use base_db::{salsa::impl_intern_value_trivial, CrateId, Edition, FileId};
3334
use either::Either;
34-
use span::{ErasedFileAstId, FileRange, HirFileIdRepr, Span, SyntaxContextData, SyntaxContextId};
35+
use span::{
36+
ErasedFileAstId, FileRange, HirFileIdRepr, Span, SpanAnchor, SyntaxContextData, SyntaxContextId,
37+
};
3538
use syntax::{
3639
ast::{self, AstNode},
3740
SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -683,6 +686,8 @@ impl ExpansionInfo {
683686
}
684687

685688
/// Maps the passed in file range down into a macro expansion if it is the input to a macro call.
689+
///
690+
/// Note this does a linear search through the entire backing vector of the spanmap.
686691
pub fn map_range_down(
687692
&self,
688693
span: Span,
@@ -793,7 +798,34 @@ impl ExpansionInfo {
793798
}
794799
}
795800

801+
/// Maps up the text range out of the expansion hierarchy back into the original file its from only
802+
/// considering the root spans contained.
803+
/// Unlike [`map_node_range_up`], this will not return `None` if any anchors or syntax contexts differ.
804+
pub fn map_node_range_up_rooted(
805+
db: &dyn ExpandDatabase,
806+
exp_map: &ExpansionSpanMap,
807+
range: TextRange,
808+
) -> Option<FileRange> {
809+
let mut spans = exp_map.spans_for_range(range).filter(|span| span.ctx.is_root());
810+
let Span { range, anchor, ctx: _ } = spans.next()?;
811+
let mut start = range.start();
812+
let mut end = range.end();
813+
814+
for span in spans {
815+
if span.anchor != anchor {
816+
return None;
817+
}
818+
start = start.min(span.range.start());
819+
end = end.max(span.range.end());
820+
}
821+
let anchor_offset =
822+
db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start();
823+
Some(FileRange { file_id: anchor.file_id, range: TextRange::new(start, end) + anchor_offset })
824+
}
825+
796826
/// Maps up the text range out of the expansion hierarchy back into the original file its from.
827+
///
828+
/// this will return `None` if any anchors or syntax contexts differ.
797829
pub fn map_node_range_up(
798830
db: &dyn ExpandDatabase,
799831
exp_map: &ExpansionSpanMap,
@@ -819,6 +851,29 @@ pub fn map_node_range_up(
819851
))
820852
}
821853

854+
/// Maps up the text range out of the expansion hierarchy back into the original file its from.
855+
/// This version will aggregate the ranges of all spans with the same anchor and syntax context.
856+
pub fn map_node_range_up_aggregated(
857+
db: &dyn ExpandDatabase,
858+
exp_map: &ExpansionSpanMap,
859+
range: TextRange,
860+
) -> FxHashMap<(SpanAnchor, SyntaxContextId), TextRange> {
861+
let mut map = FxHashMap::default();
862+
for span in exp_map.spans_for_range(range) {
863+
let range = map.entry((span.anchor, span.ctx)).or_insert_with(|| span.range);
864+
*range = TextRange::new(
865+
range.start().min(span.range.start()),
866+
range.end().max(span.range.end()),
867+
);
868+
}
869+
for ((anchor, _), range) in &mut map {
870+
let anchor_offset =
871+
db.ast_id_map(anchor.file_id.into()).get_erased(anchor.ast_id).text_range().start();
872+
*range += anchor_offset;
873+
}
874+
map
875+
}
876+
822877
/// Looks up the span at the given offset.
823878
pub fn span_for_offset(
824879
db: &dyn ExpandDatabase,

crates/hir-expand/src/span_map.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Span maps for real files and macro expansions.
2-
use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span};
2+
use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId};
33
use syntax::{AstNode, TextRange};
44
use triomphe::Arc;
55

66
pub use span::RealSpanMap;
77

88
use crate::db::ExpandDatabase;
99

10-
pub type ExpansionSpanMap = span::SpanMap<Span>;
10+
pub type ExpansionSpanMap = span::SpanMap<SyntaxContextId>;
1111

1212
/// Spanmap for a macro file or a real file
1313
#[derive(Clone, Debug, PartialEq, Eq)]

crates/hir-ty/src/tests.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
164164
Some(value) => value,
165165
None => continue,
166166
};
167-
let range = node.as_ref().original_file_range(&db);
167+
let range = node.as_ref().original_file_range_rooted(&db);
168168
if let Some(expected) = types.remove(&range) {
169169
let actual = if display_source {
170170
ty.display_source_code(&db, def.module(&db), true).unwrap()
@@ -180,7 +180,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
180180
Some(value) => value,
181181
None => continue,
182182
};
183-
let range = node.as_ref().original_file_range(&db);
183+
let range = node.as_ref().original_file_range_rooted(&db);
184184
if let Some(expected) = types.remove(&range) {
185185
let actual = if display_source {
186186
ty.display_source_code(&db, def.module(&db), true).unwrap()
@@ -211,7 +211,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
211211
}) else {
212212
continue;
213213
};
214-
let range = node.as_ref().original_file_range(&db);
214+
let range = node.as_ref().original_file_range_rooted(&db);
215215
let actual = format!(
216216
"expected {}, got {}",
217217
mismatch.expected.display_test(&db),

0 commit comments

Comments
 (0)