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