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 #[inline]
104 pub fn file_text(self, db: &dyn ExpandDatabase) -> &triomphe::Arc<str> {
105 db.file_text(self.file_id.file_id(db)).text(db)
106 }
107
108 #[inline]
109 pub fn text(self, db: &dyn ExpandDatabase) -> &str {
110 &self.file_text(db)[self.range]
111 }
112}
113
114pub type AstId<N> = crate::InFile<FileAstId<N>>;
118
119impl<N: AstNode> AstId<N> {
120 pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
121 self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
122 }
123 pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
124 self.to_ptr(db).text_range()
125 }
126 pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile<N> {
127 crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
128 }
129 pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr<N> {
130 db.ast_id_map(self.file_id).get(self.value)
131 }
132 pub fn erase(&self) -> ErasedAstId {
133 crate::InFile::new(self.file_id, self.value.erase())
134 }
135 #[inline]
136 pub fn upcast<M: AstIdNode>(self) -> AstId<M>
137 where
138 N: Into<M>,
139 {
140 self.map(|it| it.upcast())
141 }
142}
143
144pub type ErasedAstId = crate::InFile<ErasedFileAstId>;
145
146impl ErasedAstId {
147 pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
148 self.to_ptr(db).text_range()
149 }
150 pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr {
151 db.ast_id_map(self.file_id).get_erased(self.value)
152 }
153}
154
155impl<FileKind, T> InFileWrapper<FileKind, T> {
156 pub fn new(file_id: FileKind, value: T) -> Self {
157 Self { file_id, value }
158 }
159
160 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> InFileWrapper<FileKind, U> {
161 InFileWrapper::new(self.file_id, f(self.value))
162 }
163}
164
165impl<FileKind: Copy, T> InFileWrapper<FileKind, T> {
166 pub fn with_value<U>(&self, value: U) -> InFileWrapper<FileKind, U> {
167 InFileWrapper::new(self.file_id, value)
168 }
169
170 pub fn as_ref(&self) -> InFileWrapper<FileKind, &T> {
171 self.with_value(&self.value)
172 }
173
174 pub fn borrow<U>(&self) -> InFileWrapper<FileKind, &U>
175 where
176 T: Borrow<U>,
177 {
178 self.with_value(self.value.borrow())
179 }
180}
181
182impl<FileKind: Copy, T: Clone> InFileWrapper<FileKind, &T> {
183 pub fn cloned(&self) -> InFileWrapper<FileKind, T> {
184 self.with_value(self.value.clone())
185 }
186}
187
188impl<T> From<InMacroFile<T>> for InFile<T> {
189 fn from(InMacroFile { file_id, value }: InMacroFile<T>) -> Self {
190 InFile { file_id: file_id.into(), value }
191 }
192}
193
194impl<T> From<InRealFile<T>> for InFile<T> {
195 fn from(InRealFile { file_id, value }: InRealFile<T>) -> Self {
196 InFile { file_id: file_id.into(), value }
197 }
198}
199
200impl<FileKind, T> InFileWrapper<FileKind, Option<T>> {
203 pub fn transpose(self) -> Option<InFileWrapper<FileKind, T>> {
204 Some(InFileWrapper::new(self.file_id, self.value?))
205 }
206}
207
208impl<FileKind, L, R> InFileWrapper<FileKind, Either<L, R>> {
209 pub fn transpose(self) -> Either<InFileWrapper<FileKind, L>, InFileWrapper<FileKind, R>> {
210 match self.value {
211 Either::Left(l) => Either::Left(InFileWrapper::new(self.file_id, l)),
212 Either::Right(r) => Either::Right(InFileWrapper::new(self.file_id, r)),
213 }
214 }
215}
216
217trait FileIdToSyntax: Copy {
220 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode;
221}
222
223impl FileIdToSyntax for EditionedFileId {
224 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
225 db.parse(self).syntax_node()
226 }
227}
228impl FileIdToSyntax for MacroCallId {
229 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
230 db.parse_macro_expansion(self).value.0.syntax_node()
231 }
232}
233impl FileIdToSyntax for HirFileId {
234 fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
235 db.parse_or_expand(self)
236 }
237}
238
239#[allow(private_bounds)]
240impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> {
241 pub fn file_syntax(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
242 FileIdToSyntax::file_syntax(self.file_id, db)
243 }
244}
245
246#[allow(private_bounds)]
247impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> {
248 pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
249 self.value.to_node(&self.file_syntax(db))
250 }
251}
252
253impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
254 pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
255 self.with_value(self.value.syntax())
256 }
257 pub fn node_file_range(&self) -> FileRangeWrapper<FileId> {
258 FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() }
259 }
260}
261
262impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
263 pub fn syntax_ref(&self) -> InFileWrapper<FileId, &SyntaxNode> {
265 self.with_value(self.value.syntax())
266 }
267}
268
269impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> {
271 pub fn file_range(&self) -> FileRangeWrapper<FileId> {
272 FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() }
273 }
274}
275
276impl<SN: Borrow<SyntaxNode>> InFile<SN> {
277 pub fn parent_ancestors_with_macros(
278 self,
279 db: &dyn db::ExpandDatabase,
280 ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
281 let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
282 Some(parent) => Some(node.with_value(parent)),
283 None => db
284 .lookup_intern_macro_call(node.file_id.macro_file()?)
285 .to_node_item(db)
286 .syntax()
287 .cloned()
288 .map(|node| node.parent())
289 .transpose(),
290 };
291 std::iter::successors(succ(&self.borrow().cloned()), succ)
292 }
293
294 pub fn ancestors_with_macros(
295 self,
296 db: &dyn db::ExpandDatabase,
297 ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
298 let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
299 Some(parent) => Some(node.with_value(parent)),
300 None => db
301 .lookup_intern_macro_call(node.file_id.macro_file()?)
302 .to_node_item(db)
303 .syntax()
304 .cloned()
305 .map(|node| node.parent())
306 .transpose(),
307 };
308 std::iter::successors(Some(self.borrow().cloned()), succ)
309 }
310
311 pub fn kind(&self) -> parser::SyntaxKind {
312 self.value.borrow().kind()
313 }
314
315 pub fn text_range(&self) -> TextRange {
316 self.value.borrow().text_range()
317 }
318
319 pub fn original_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
324 self.borrow().map(SyntaxNode::text_range).original_node_file_range_rooted(db)
325 }
326
327 pub fn original_file_range_with_macro_call_input(
329 self,
330 db: &dyn db::ExpandDatabase,
331 ) -> FileRange {
332 self.borrow().map(SyntaxNode::text_range).original_node_file_range_with_macro_call_input(db)
333 }
334
335 pub fn original_syntax_node_rooted(
336 self,
337 db: &dyn db::ExpandDatabase,
338 ) -> Option<InRealFile<SyntaxNode>> {
339 let file_id = match self.file_id {
342 HirFileId::FileId(file_id) => {
343 return Some(InRealFile { file_id, value: self.value.borrow().clone() });
344 }
345 HirFileId::MacroFile(m)
346 if matches!(m.kind(db), MacroKind::Attr | MacroKind::AttrBuiltIn) =>
347 {
348 m
349 }
350 _ => return None,
351 };
352
353 let FileRange { file_id: editioned_file_id, range } = map_node_range_up_rooted(
354 db,
355 &db.expansion_span_map(file_id),
356 self.value.borrow().text_range(),
357 )?;
358
359 let kind = self.kind();
360 let value = db
361 .parse(editioned_file_id)
362 .syntax_node()
363 .covering_element(range)
364 .ancestors()
365 .take_while(|it| it.text_range() == range)
366 .find(|it| it.kind() == kind)?;
367 Some(InRealFile::new(editioned_file_id, value))
368 }
369}
370
371impl InFile<&SyntaxNode> {
372 pub fn original_file_range_opt(
374 self,
375 db: &dyn db::ExpandDatabase,
376 ) -> Option<(FileRange, SyntaxContext)> {
377 self.borrow().map(SyntaxNode::text_range).original_node_file_range_opt(db)
378 }
379}
380
381impl InMacroFile<SyntaxToken> {
382 pub fn upmap_once(
383 self,
384 db: &dyn db::ExpandDatabase,
385 ) -> InFile<smallvec::SmallVec<[TextRange; 1]>> {
386 self.file_id.expansion_info(db).map_range_up_once(db, self.value.text_range())
387 }
388}
389
390impl InFile<SyntaxToken> {
391 pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange {
393 match self.file_id {
394 HirFileId::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
395 HirFileId::MacroFile(mac_file) => {
396 let (range, ctxt) = span_for_offset(
397 db,
398 &db.expansion_span_map(mac_file),
399 self.value.text_range().start(),
400 );
401
402 if ctxt.is_root() {
405 return range;
406 }
407
408 let loc = db.lookup_intern_macro_call(mac_file);
410 loc.kind.original_call_range(db)
411 }
412 }
413 }
414
415 pub fn original_file_range_opt(self, db: &dyn db::ExpandDatabase) -> Option<FileRange> {
417 match self.file_id {
418 HirFileId::FileId(file_id) => {
419 Some(FileRange { file_id, range: self.value.text_range() })
420 }
421 HirFileId::MacroFile(mac_file) => {
422 let (range, ctxt) = span_for_offset(
423 db,
424 &db.expansion_span_map(mac_file),
425 self.value.text_range().start(),
426 );
427
428 if ctxt.is_root() { Some(range) } else { None }
431 }
432 }
433 }
434}
435
436impl InMacroFile<TextSize> {
437 pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> (FileRange, SyntaxContext) {
438 span_for_offset(db, &db.expansion_span_map(self.file_id), self.value)
439 }
440}
441
442impl InFile<TextRange> {
443 pub fn original_node_file_range(
444 self,
445 db: &dyn db::ExpandDatabase,
446 ) -> (FileRange, SyntaxContext) {
447 match self.file_id {
448 HirFileId::FileId(file_id) => {
449 (FileRange { file_id, range: self.value }, SyntaxContext::root(file_id.edition(db)))
450 }
451 HirFileId::MacroFile(mac_file) => {
452 match map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) {
453 Some(it) => it,
454 None => {
455 let loc = db.lookup_intern_macro_call(mac_file);
456 (loc.kind.original_call_range(db), SyntaxContext::root(loc.def.edition))
457 }
458 }
459 }
460 }
461 }
462
463 pub fn original_node_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
464 match self.file_id {
465 HirFileId::FileId(file_id) => FileRange { file_id, range: self.value },
466 HirFileId::MacroFile(mac_file) => {
467 match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
468 Some(it) => it,
469 _ => {
470 let loc = db.lookup_intern_macro_call(mac_file);
471 loc.kind.original_call_range(db)
472 }
473 }
474 }
475 }
476 }
477
478 pub fn original_node_file_range_with_macro_call_input(
479 self,
480 db: &dyn db::ExpandDatabase,
481 ) -> FileRange {
482 match self.file_id {
483 HirFileId::FileId(file_id) => FileRange { file_id, range: self.value },
484 HirFileId::MacroFile(mac_file) => {
485 match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
486 Some(it) => it,
487 _ => {
488 let loc = db.lookup_intern_macro_call(mac_file);
489 loc.kind.original_call_range_with_input(db)
490 }
491 }
492 }
493 }
494 }
495
496 pub fn original_node_file_range_opt(
497 self,
498 db: &dyn db::ExpandDatabase,
499 ) -> Option<(FileRange, SyntaxContext)> {
500 match self.file_id {
501 HirFileId::FileId(file_id) => Some((
502 FileRange { file_id, range: self.value },
503 SyntaxContext::root(file_id.edition(db)),
504 )),
505 HirFileId::MacroFile(mac_file) => {
506 map_node_range_up(db, &db.expansion_span_map(mac_file), self.value)
507 }
508 }
509 }
510
511 pub fn original_node_file_range_rooted_opt(
512 self,
513 db: &dyn db::ExpandDatabase,
514 ) -> Option<FileRange> {
515 match self.file_id {
516 HirFileId::FileId(file_id) => Some(FileRange { file_id, range: self.value }),
517 HirFileId::MacroFile(mac_file) => {
518 map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value)
519 }
520 }
521 }
522}
523
524impl<N: AstNode> InFile<N> {
525 pub fn original_ast_node_rooted(self, db: &dyn db::ExpandDatabase) -> Option<InRealFile<N>> {
526 let file_id = match self.file_id {
529 HirFileId::FileId(file_id) => {
530 return Some(InRealFile { file_id, value: self.value });
531 }
532 HirFileId::MacroFile(m) => m,
533 };
534 if !matches!(file_id.kind(db), MacroKind::Attr | MacroKind::AttrBuiltIn) {
535 return None;
536 }
537
538 let FileRange { file_id: editioned_file_id, range } = map_node_range_up_rooted(
539 db,
540 &db.expansion_span_map(file_id),
541 self.value.syntax().text_range(),
542 )?;
543
544 let anc = db.parse(editioned_file_id).syntax_node().covering_element(range);
546 let value = anc.ancestors().find_map(N::cast)?;
547 Some(InRealFile::new(editioned_file_id, value))
548 }
549}
550
551impl<T> InFile<T> {
552 pub fn into_real_file(self) -> Result<InRealFile<T>, InFile<T>> {
553 match self.file_id {
554 HirFileId::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }),
555 HirFileId::MacroFile(_) => Err(self),
556 }
557 }
558}