1use std::borrow::Borrow;
3
4use either::Either;
5use span::{AstIdNode, ErasedFileAstId, FileAstId, FileId, SyntaxContext};
6use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize};
7
8use crate::{
9 EditionedFileId, HirFileId, MacroCallId, MacroKind,
10 db::{self, ExpandDatabase},
11 map_node_range_up, map_node_range_up_rooted, span_for_offset,
12};
13
14#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
22pub struct InFileWrapper<FileKind, T> {
23 pub file_id: FileKind,
24 pub value: T,
25}
26pub type InFile<T> = InFileWrapper<HirFileId, T>;
27pub type InMacroFile<T> = InFileWrapper<MacroCallId, T>;
28pub type InRealFile<T> = InFileWrapper<EditionedFileId, T>;
29
30#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
31pub struct FilePositionWrapper<FileKind> {
32 pub file_id: FileKind,
33 pub offset: TextSize,
34}
35pub type HirFilePosition = FilePositionWrapper<HirFileId>;
36pub type MacroFilePosition = FilePositionWrapper<MacroCallId>;
37pub type FilePosition = FilePositionWrapper<EditionedFileId>;
38
39impl FilePosition {
40 #[inline]
41 pub fn into_file_id(self, db: &dyn ExpandDatabase) -> FilePositionWrapper<FileId> {
42 FilePositionWrapper { file_id: self.file_id.file_id(db), offset: self.offset }
43 }
44}
45
46impl From<FileRange> for HirFileRange {
47 fn from(value: FileRange) -> Self {
48 HirFileRange { file_id: value.file_id.into(), range: value.range }
49 }
50}
51
52impl From<FilePosition> for HirFilePosition {
53 fn from(value: FilePosition) -> Self {
54 HirFilePosition { file_id: value.file_id.into(), offset: value.offset }
55 }
56}
57
58impl FilePositionWrapper<span::FileId> {
59 pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FilePosition {
60 FilePositionWrapper {
61 file_id: EditionedFileId::new(db, self.file_id, edition),
62 offset: self.offset,
63 }
64 }
65}
66
67impl FileRangeWrapper<span::FileId> {
68 pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FileRange {
69 FileRangeWrapper {
70 file_id: EditionedFileId::new(db, self.file_id, edition),
71 range: self.range,
72 }
73 }
74}
75
76impl<T> InFileWrapper<span::FileId, T> {
77 pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> InRealFile<T> {
78 InRealFile { file_id: EditionedFileId::new(db, self.file_id, edition), value: self.value }
79 }
80}
81
82impl HirFileRange {
83 pub fn file_range(self) -> Option<FileRange> {
84 Some(FileRange { file_id: self.file_id.file_id()?, range: self.range })
85 }
86}
87
88#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
89pub struct FileRangeWrapper<FileKind> {
90 pub file_id: FileKind,
91 pub range: TextRange,
92}
93pub type HirFileRange = FileRangeWrapper<HirFileId>;
94pub type MacroFileRange = FileRangeWrapper<MacroCallId>;
95pub type FileRange = FileRangeWrapper<EditionedFileId>;
96
97impl FileRange {
98 #[inline]
99 pub fn into_file_id(self, db: &dyn ExpandDatabase) -> FileRangeWrapper<FileId> {
100 FileRangeWrapper { file_id: self.file_id.file_id(db), range: self.range }
101 }
102}
103
104pub type AstId<N> = crate::InFile<FileAstId<N>>;
108
109impl<N: AstNode> AstId<N> {
110 pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
111 self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
112 }
113 pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
114 self.to_ptr(db).text_range()
115 }
116 pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile<N> {
117 crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
118 }
119 pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr<N> {
120 db.ast_id_map(self.file_id).get(self.value)
121 }
122 pub fn erase(&self) -> ErasedAstId {
123 crate::InFile::new(self.file_id, self.value.erase())
124 }
125 #[inline]
126 pub fn upcast<M: AstIdNode>(self) -> AstId<M>
127 where
128 N: Into<M>,
129 {
130 self.map(|it| it.upcast())
131 }
132}
133
134pub type ErasedAstId = crate::InFile<ErasedFileAstId>;
135
136impl ErasedAstId {
137 pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
138 self.to_ptr(db).text_range()
139 }
140 pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr {
141 db.ast_id_map(self.file_id).get_erased(self.value)
142 }
143}
144
145impl<FileKind, T> InFileWrapper<FileKind, T> {
146 pub fn new(file_id: FileKind, value: T) -> Self {
147 Self { file_id, value }
148 }
149
150 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> InFileWrapper<FileKind, U> {
151 InFileWrapper::new(self.file_id, f(self.value))
152 }
153}
154
155impl<FileKind: Copy, T> InFileWrapper<FileKind, T> {
156 pub fn with_value<U>(&self, value: U) -> InFileWrapper<FileKind, U> {
157 InFileWrapper::new(self.file_id, value)
158 }
159
160 pub fn as_ref(&self) -> InFileWrapper<FileKind, &T> {
161 self.with_value(&self.value)
162 }
163
164 pub fn borrow<U>(&self) -> InFileWrapper<FileKind, &U>
165 where
166 T: Borrow<U>,
167 {
168 self.with_value(self.value.borrow())
169 }
170}
171
172impl<FileKind: Copy, T: Clone> InFileWrapper<FileKind, &T> {
173 pub fn cloned(&self) -> InFileWrapper<FileKind, T> {
174 self.with_value(self.value.clone())
175 }
176}
177
178impl<T> From<InMacroFile<T>> for InFile<T> {
179 fn from(InMacroFile { file_id, value }: InMacroFile<T>) -> Self {
180 InFile { file_id: file_id.into(), value }
181 }
182}
183
184impl<T> From<InRealFile<T>> for InFile<T> {
185 fn from(InRealFile { file_id, value }: InRealFile<T>) -> Self {
186 InFile { file_id: file_id.into(), value }
187 }
188}
189
190impl<FileKind, T> InFileWrapper<FileKind, Option<T>> {
193 pub fn transpose(self) -> Option<InFileWrapper<FileKind, T>> {
194 Some(InFileWrapper::new(self.file_id, self.value?))
195 }
196}
197
198impl<FileKind, L, R> InFileWrapper<FileKind, Either<L, R>> {
199 pub fn transpose(self) -> Either<InFileWrapper<FileKind, L>, InFileWrapper<FileKind, R>> {
200 match self.value {
201 Either::Left(l) => Either::Left(InFileWrapper::new(self.file_id, l)),
202 Either::Right(r) => Either::Right(InFileWrapper::new(self.file_id, r)),
203 }
204 }
205}
206
207trait FileIdToSyntax: Copy {
210 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode;
211}
212
213impl FileIdToSyntax for EditionedFileId {
214 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
215 db.parse(self).syntax_node()
216 }
217}
218impl FileIdToSyntax for MacroCallId {
219 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
220 db.parse_macro_expansion(self).value.0.syntax_node()
221 }
222}
223impl FileIdToSyntax for HirFileId {
224 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
225 db.parse_or_expand(self)
226 }
227}
228
229#[allow(private_bounds)]
230impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> {
231 pub fn file_syntax(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
232 FileIdToSyntax::file_syntax(self.file_id, db)
233 }
234}
235
236#[allow(private_bounds)]
237impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> {
238 pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
239 self.value.to_node(&self.file_syntax(db))
240 }
241}
242
243impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
244 pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
245 self.with_value(self.value.syntax())
246 }
247 pub fn node_file_range(&self) -> FileRangeWrapper<FileId> {
248 FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() }
249 }
250}
251
252impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
253 pub fn syntax_ref(&self) -> InFileWrapper<FileId, &SyntaxNode> {
255 self.with_value(self.value.syntax())
256 }
257}
258
259impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> {
261 pub fn file_range(&self) -> FileRangeWrapper<FileId> {
262 FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() }
263 }
264}
265
266impl<SN: Borrow<SyntaxNode>> InFile<SN> {
267 pub fn parent_ancestors_with_macros(
268 self,
269 db: &dyn db::ExpandDatabase,
270 ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
271 let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
272 Some(parent) => Some(node.with_value(parent)),
273 None => db
274 .lookup_intern_macro_call(node.file_id.macro_file()?)
275 .to_node_item(db)
276 .syntax()
277 .cloned()
278 .map(|node| node.parent())
279 .transpose(),
280 };
281 std::iter::successors(succ(&self.borrow().cloned()), succ)
282 }
283
284 pub fn ancestors_with_macros(
285 self,
286 db: &dyn db::ExpandDatabase,
287 ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
288 let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
289 Some(parent) => Some(node.with_value(parent)),
290 None => db
291 .lookup_intern_macro_call(node.file_id.macro_file()?)
292 .to_node_item(db)
293 .syntax()
294 .cloned()
295 .map(|node| node.parent())
296 .transpose(),
297 };
298 std::iter::successors(Some(self.borrow().cloned()), succ)
299 }
300
301 pub fn kind(&self) -> parser::SyntaxKind {
302 self.value.borrow().kind()
303 }
304
305 pub fn text_range(&self) -> TextRange {
306 self.value.borrow().text_range()
307 }
308
309 pub fn original_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
314 self.borrow().map(SyntaxNode::text_range).original_node_file_range_rooted(db)
315 }
316
317 pub fn original_file_range_with_macro_call_input(
319 self,
320 db: &dyn db::ExpandDatabase,
321 ) -> FileRange {
322 self.borrow().map(SyntaxNode::text_range).original_node_file_range_with_macro_call_input(db)
323 }
324
325 pub fn original_syntax_node_rooted(
326 self,
327 db: &dyn db::ExpandDatabase,
328 ) -> Option<InRealFile<SyntaxNode>> {
329 let file_id = match self.file_id {
332 HirFileId::FileId(file_id) => {
333 return Some(InRealFile { file_id, value: self.value.borrow().clone() });
334 }
335 HirFileId::MacroFile(m)
336 if matches!(m.kind(db), MacroKind::Attr | MacroKind::AttrBuiltIn) =>
337 {
338 m
339 }
340 _ => return None,
341 };
342
343 let FileRange { file_id: editioned_file_id, range } = map_node_range_up_rooted(
344 db,
345 &db.expansion_span_map(file_id),
346 self.value.borrow().text_range(),
347 )?;
348
349 let kind = self.kind();
350 let value = db
351 .parse(editioned_file_id)
352 .syntax_node()
353 .covering_element(range)
354 .ancestors()
355 .take_while(|it| it.text_range() == range)
356 .find(|it| it.kind() == kind)?;
357 Some(InRealFile::new(editioned_file_id, value))
358 }
359}
360
361impl InFile<&SyntaxNode> {
362 pub fn original_file_range_opt(
364 self,
365 db: &dyn db::ExpandDatabase,
366 ) -> Option<(FileRange, SyntaxContext)> {
367 self.borrow().map(SyntaxNode::text_range).original_node_file_range_opt(db)
368 }
369}
370
371impl InMacroFile<SyntaxToken> {
372 pub fn upmap_once(
373 self,
374 db: &dyn db::ExpandDatabase,
375 ) -> InFile<smallvec::SmallVec<[TextRange; 1]>> {
376 self.file_id.expansion_info(db).map_range_up_once(db, self.value.text_range())
377 }
378}
379
380impl InFile<SyntaxToken> {
381 pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange {
383 match self.file_id {
384 HirFileId::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
385 HirFileId::MacroFile(mac_file) => {
386 let (range, ctxt) = span_for_offset(
387 db,
388 &db.expansion_span_map(mac_file),
389 self.value.text_range().start(),
390 );
391
392 if ctxt.is_root() {
395 return range;
396 }
397
398 let loc = db.lookup_intern_macro_call(mac_file);
400 loc.kind.original_call_range(db)
401 }
402 }
403 }
404
405 pub fn original_file_range_opt(self, db: &dyn db::ExpandDatabase) -> Option<FileRange> {
407 match self.file_id {
408 HirFileId::FileId(file_id) => {
409 Some(FileRange { file_id, range: self.value.text_range() })
410 }
411 HirFileId::MacroFile(mac_file) => {
412 let (range, ctxt) = span_for_offset(
413 db,
414 &db.expansion_span_map(mac_file),
415 self.value.text_range().start(),
416 );
417
418 if ctxt.is_root() { Some(range) } else { None }
421 }
422 }
423 }
424}
425
426impl InMacroFile<TextSize> {
427 pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> (FileRange, SyntaxContext) {
428 span_for_offset(db, &db.expansion_span_map(self.file_id), self.value)
429 }
430}
431
432impl InFile<TextRange> {
433 pub fn original_node_file_range(
434 self,
435 db: &dyn db::ExpandDatabase,
436 ) -> (FileRange, SyntaxContext) {
437 match self.file_id {
438 HirFileId::FileId(file_id) => {
439 (FileRange { file_id, range: self.value }, SyntaxContext::root(file_id.edition(db)))
440 }
441 HirFileId::MacroFile(mac_file) => {
442 match map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) {
443 Some(it) => it,
444 None => {
445 let loc = db.lookup_intern_macro_call(mac_file);
446 (loc.kind.original_call_range(db), SyntaxContext::root(loc.def.edition))
447 }
448 }
449 }
450 }
451 }
452
453 pub fn original_node_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
454 match self.file_id {
455 HirFileId::FileId(file_id) => FileRange { file_id, range: self.value },
456 HirFileId::MacroFile(mac_file) => {
457 match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
458 Some(it) => it,
459 _ => {
460 let loc = db.lookup_intern_macro_call(mac_file);
461 loc.kind.original_call_range(db)
462 }
463 }
464 }
465 }
466 }
467
468 pub fn original_node_file_range_with_macro_call_input(
469 self,
470 db: &dyn db::ExpandDatabase,
471 ) -> FileRange {
472 match self.file_id {
473 HirFileId::FileId(file_id) => FileRange { file_id, range: self.value },
474 HirFileId::MacroFile(mac_file) => {
475 match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
476 Some(it) => it,
477 _ => {
478 let loc = db.lookup_intern_macro_call(mac_file);
479 loc.kind.original_call_range_with_input(db)
480 }
481 }
482 }
483 }
484 }
485
486 pub fn original_node_file_range_opt(
487 self,
488 db: &dyn db::ExpandDatabase,
489 ) -> Option<(FileRange, SyntaxContext)> {
490 match self.file_id {
491 HirFileId::FileId(file_id) => Some((
492 FileRange { file_id, range: self.value },
493 SyntaxContext::root(file_id.edition(db)),
494 )),
495 HirFileId::MacroFile(mac_file) => {
496 map_node_range_up(db, &db.expansion_span_map(mac_file), self.value)
497 }
498 }
499 }
500
501 pub fn original_node_file_range_rooted_opt(
502 self,
503 db: &dyn db::ExpandDatabase,
504 ) -> Option<FileRange> {
505 match self.file_id {
506 HirFileId::FileId(file_id) => Some(FileRange { file_id, range: self.value }),
507 HirFileId::MacroFile(mac_file) => {
508 map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value)
509 }
510 }
511 }
512}
513
514impl<N: AstNode> InFile<N> {
515 pub fn original_ast_node_rooted(self, db: &dyn db::ExpandDatabase) -> Option<InRealFile<N>> {
516 let file_id = match self.file_id {
519 HirFileId::FileId(file_id) => {
520 return Some(InRealFile { file_id, value: self.value });
521 }
522 HirFileId::MacroFile(m) => m,
523 };
524 if !matches!(file_id.kind(db), MacroKind::Attr | MacroKind::AttrBuiltIn) {
525 return None;
526 }
527
528 let FileRange { file_id: editioned_file_id, range } = map_node_range_up_rooted(
529 db,
530 &db.expansion_span_map(file_id),
531 self.value.syntax().text_range(),
532 )?;
533
534 let anc = db.parse(editioned_file_id).syntax_node().covering_element(range);
536 let value = anc.ancestors().find_map(N::cast)?;
537 Some(InRealFile::new(editioned_file_id, value))
538 }
539}
540
541impl<T> InFile<T> {
542 pub fn into_real_file(self) -> Result<InRealFile<T>, InFile<T>> {
543 match self.file_id {
544 HirFileId::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }),
545 HirFileId::MacroFile(_) => Err(self),
546 }
547 }
548}