hir_expand/
files.rs

1//! Things to wrap other things in file ids.
2use 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/// `InFile<T>` stores a value of `T` inside a particular file/syntax tree.
15///
16/// Typical usages are:
17///
18/// * `InFile<SyntaxNode>` -- syntax node in a file
19/// * `InFile<ast::FnDef>` -- ast node in a file
20/// * `InFile<TextSize>` -- offset in a file
21#[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
114/// `AstId` points to an AST node in any file.
115///
116/// It is stable across reparses, and can be used as salsa key/value.
117pub 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
200// region:transpose impls
201
202impl<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
217// endregion:transpose impls
218
219trait 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    // unfortunately `syntax` collides with the impl above, because `&_` is fundamental
264    pub fn syntax_ref(&self) -> InFileWrapper<FileId, &SyntaxNode> {
265        self.with_value(self.value.syntax())
266    }
267}
268
269// region:specific impls
270impl<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    /// Falls back to the macro call range if the node cannot be mapped up fully.
320    ///
321    /// For attributes and derives, this will point back to the attribute only.
322    /// For the entire item use [`InFile::original_file_range_full`].
323    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    /// Falls back to the macro call range if the node cannot be mapped up fully.
328    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        // This kind of upmapping can only be achieved in attribute expanded files,
340        // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
341        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    /// Attempts to map the syntax node back up its macro calls.
373    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    /// Falls back to the macro call range if the node cannot be mapped up fully.
392    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                // FIXME: Figure out an API that makes proper use of ctx, this only exists to
403                // keep pre-token map rewrite behaviour.
404                if ctxt.is_root() {
405                    return range;
406                }
407
408                // Fall back to whole macro call.
409                let loc = db.lookup_intern_macro_call(mac_file);
410                loc.kind.original_call_range(db)
411            }
412        }
413    }
414
415    /// Attempts to map the syntax node back up its macro calls.
416    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                // FIXME: Figure out an API that makes proper use of ctx, this only exists to
429                // keep pre-token map rewrite behaviour.
430                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        // This kind of upmapping can only be achieved in attribute expanded files,
527        // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
528        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        // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes?
545        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}