ra_ap_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 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 ExpandDatabase) -> 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 ExpandDatabase) -> &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 ExpandDatabase) -> &str {
86        &self.file_text(db)[self.range]
87    }
88}
89
90/// `AstId` points to an AST node in any file.
91///
92/// It is stable across reparses, and can be used as salsa key/value.
93pub type AstId<N> = crate::InFile<FileAstId<N>>;
94
95impl<N: AstNode> AstId<N> {
96    pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
97        self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
98    }
99    pub fn to_range(&self, db: &dyn ExpandDatabase) -> TextRange {
100        self.to_ptr(db).text_range()
101    }
102    pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile<N> {
103        crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
104    }
105    pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr<N> {
106        db.ast_id_map(self.file_id).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 ExpandDatabase) -> TextRange {
124        self.to_ptr(db).text_range()
125    }
126    pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr {
127        db.ast_id_map(self.file_id).get_erased(self.value)
128    }
129}
130
131impl<FileKind, T> InFileWrapper<FileKind, T> {
132    pub fn new(file_id: FileKind, value: T) -> Self {
133        Self { file_id, value }
134    }
135
136    pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> InFileWrapper<FileKind, U> {
137        InFileWrapper::new(self.file_id, f(self.value))
138    }
139}
140
141impl<FileKind: Copy, T> InFileWrapper<FileKind, T> {
142    pub fn with_value<U>(&self, value: U) -> InFileWrapper<FileKind, U> {
143        InFileWrapper::new(self.file_id, value)
144    }
145
146    pub fn as_ref(&self) -> InFileWrapper<FileKind, &T> {
147        self.with_value(&self.value)
148    }
149
150    pub fn borrow<U>(&self) -> InFileWrapper<FileKind, &U>
151    where
152        T: Borrow<U>,
153    {
154        self.with_value(self.value.borrow())
155    }
156}
157
158impl<FileKind: Copy, T: Clone> InFileWrapper<FileKind, &T> {
159    pub fn cloned(&self) -> InFileWrapper<FileKind, T> {
160        self.with_value(self.value.clone())
161    }
162}
163
164impl<T> From<InMacroFile<T>> for InFile<T> {
165    fn from(InMacroFile { file_id, value }: InMacroFile<T>) -> Self {
166        InFile { file_id: file_id.into(), value }
167    }
168}
169
170impl<T> From<InRealFile<T>> for InFile<T> {
171    fn from(InRealFile { file_id, value }: InRealFile<T>) -> Self {
172        InFile { file_id: file_id.into(), value }
173    }
174}
175
176// region:transpose impls
177
178impl<FileKind, T> InFileWrapper<FileKind, Option<T>> {
179    pub fn transpose(self) -> Option<InFileWrapper<FileKind, T>> {
180        Some(InFileWrapper::new(self.file_id, self.value?))
181    }
182}
183
184impl<FileKind, L, R> InFileWrapper<FileKind, Either<L, R>> {
185    pub fn transpose(self) -> Either<InFileWrapper<FileKind, L>, InFileWrapper<FileKind, R>> {
186        match self.value {
187            Either::Left(l) => Either::Left(InFileWrapper::new(self.file_id, l)),
188            Either::Right(r) => Either::Right(InFileWrapper::new(self.file_id, r)),
189        }
190    }
191}
192
193// endregion:transpose impls
194
195trait FileIdToSyntax: Copy {
196    fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode;
197}
198
199impl FileIdToSyntax for EditionedFileId {
200    fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
201        db.parse(self).syntax_node()
202    }
203}
204impl FileIdToSyntax for MacroCallId {
205    fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
206        db.parse_macro_expansion(self).value.0.syntax_node()
207    }
208}
209impl FileIdToSyntax for HirFileId {
210    fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
211        db.parse_or_expand(self)
212    }
213}
214
215#[allow(private_bounds)]
216impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> {
217    pub fn file_syntax(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
218        FileIdToSyntax::file_syntax(self.file_id, db)
219    }
220}
221
222#[allow(private_bounds)]
223impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> {
224    pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
225        self.value.to_node(&self.file_syntax(db))
226    }
227}
228
229impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
230    pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
231        self.with_value(self.value.syntax())
232    }
233    pub fn node_file_range(&self) -> FileRangeWrapper<FileId> {
234        FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() }
235    }
236}
237
238impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> {
239    // unfortunately `syntax` collides with the impl above, because `&_` is fundamental
240    pub fn syntax_ref(&self) -> InFileWrapper<FileId, &SyntaxNode> {
241        self.with_value(self.value.syntax())
242    }
243}
244
245// region:specific impls
246impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> {
247    pub fn file_range(&self) -> FileRangeWrapper<FileId> {
248        FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() }
249    }
250}
251
252impl<SN: Borrow<SyntaxNode>> InFile<SN> {
253    pub fn parent_ancestors_with_macros(
254        self,
255        db: &dyn db::ExpandDatabase,
256    ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
257        let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
258            Some(parent) => Some(node.with_value(parent)),
259            None => db
260                .lookup_intern_macro_call(node.file_id.macro_file()?)
261                .to_node_item(db)
262                .syntax()
263                .cloned()
264                .map(|node| node.parent())
265                .transpose(),
266        };
267        std::iter::successors(succ(&self.borrow().cloned()), succ)
268    }
269
270    pub fn ancestors_with_macros(
271        self,
272        db: &dyn db::ExpandDatabase,
273    ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
274        let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
275            Some(parent) => Some(node.with_value(parent)),
276            None => db
277                .lookup_intern_macro_call(node.file_id.macro_file()?)
278                .to_node_item(db)
279                .syntax()
280                .cloned()
281                .map(|node| node.parent())
282                .transpose(),
283        };
284        std::iter::successors(Some(self.borrow().cloned()), succ)
285    }
286
287    pub fn kind(&self) -> parser::SyntaxKind {
288        self.value.borrow().kind()
289    }
290
291    pub fn text_range(&self) -> TextRange {
292        self.value.borrow().text_range()
293    }
294
295    /// Falls back to the macro call range if the node cannot be mapped up fully.
296    ///
297    /// For attributes and derives, this will point back to the attribute only.
298    /// For the entire item use `InFile::original_file_range_full`.
299    pub fn original_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
300        self.borrow().map(SyntaxNode::text_range).original_node_file_range_rooted(db)
301    }
302
303    /// Falls back to the macro call range if the node cannot be mapped up fully.
304    pub fn original_file_range_with_macro_call_input(
305        self,
306        db: &dyn db::ExpandDatabase,
307    ) -> FileRange {
308        self.borrow().map(SyntaxNode::text_range).original_node_file_range_with_macro_call_input(db)
309    }
310
311    pub fn original_syntax_node_rooted(
312        self,
313        db: &dyn db::ExpandDatabase,
314    ) -> Option<InRealFile<SyntaxNode>> {
315        // This kind of upmapping can only be achieved in attribute expanded files,
316        // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
317        let file_id = match self.file_id {
318            HirFileId::FileId(file_id) => {
319                return Some(InRealFile { file_id, value: self.value.borrow().clone() });
320            }
321            HirFileId::MacroFile(m)
322                if matches!(m.kind(db), MacroKind::Attr | MacroKind::AttrBuiltIn) =>
323            {
324                m
325            }
326            _ => return None,
327        };
328
329        let FileRange { file_id: editioned_file_id, range } = map_node_range_up_rooted(
330            db,
331            &db.expansion_span_map(file_id),
332            self.value.borrow().text_range(),
333        )?;
334
335        let kind = self.kind();
336        let value = db
337            .parse(editioned_file_id)
338            .syntax_node()
339            .covering_element(range)
340            .ancestors()
341            .take_while(|it| it.text_range() == range)
342            .find(|it| it.kind() == kind)?;
343        Some(InRealFile::new(editioned_file_id, value))
344    }
345}
346
347impl InFile<&SyntaxNode> {
348    /// Attempts to map the syntax node back up its macro calls.
349    pub fn original_file_range_opt(
350        self,
351        db: &dyn db::ExpandDatabase,
352    ) -> Option<(FileRange, SyntaxContext)> {
353        self.borrow().map(SyntaxNode::text_range).original_node_file_range_opt(db)
354    }
355}
356
357impl InMacroFile<SyntaxToken> {
358    pub fn upmap_once(
359        self,
360        db: &dyn db::ExpandDatabase,
361    ) -> InFile<smallvec::SmallVec<[TextRange; 1]>> {
362        self.file_id.expansion_info(db).map_range_up_once(db, self.value.text_range())
363    }
364}
365
366impl InFile<SyntaxToken> {
367    /// Falls back to the macro call range if the node cannot be mapped up fully.
368    pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange {
369        match self.file_id {
370            HirFileId::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
371            HirFileId::MacroFile(mac_file) => {
372                let (range, ctxt) = span_for_offset(
373                    db,
374                    &db.expansion_span_map(mac_file),
375                    self.value.text_range().start(),
376                );
377
378                // FIXME: Figure out an API that makes proper use of ctx, this only exists to
379                // keep pre-token map rewrite behaviour.
380                if ctxt.is_root() {
381                    return range;
382                }
383
384                // Fall back to whole macro call.
385                let loc = db.lookup_intern_macro_call(mac_file);
386                loc.kind.original_call_range(db, loc.krate)
387            }
388        }
389    }
390
391    /// Attempts to map the syntax node back up its macro calls.
392    pub fn original_file_range_opt(self, db: &dyn db::ExpandDatabase) -> Option<FileRange> {
393        match self.file_id {
394            HirFileId::FileId(file_id) => {
395                Some(FileRange { file_id, range: self.value.text_range() })
396            }
397            HirFileId::MacroFile(mac_file) => {
398                let (range, ctxt) = span_for_offset(
399                    db,
400                    &db.expansion_span_map(mac_file),
401                    self.value.text_range().start(),
402                );
403
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 ctxt.is_root() { Some(range) } else { None }
407            }
408        }
409    }
410}
411
412impl InMacroFile<TextSize> {
413    pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> (FileRange, SyntaxContext) {
414        span_for_offset(db, &db.expansion_span_map(self.file_id), self.value)
415    }
416}
417
418impl InFile<TextRange> {
419    pub fn original_node_file_range(
420        self,
421        db: &dyn db::ExpandDatabase,
422    ) -> (FileRange, SyntaxContext) {
423        match self.file_id {
424            HirFileId::FileId(file_id) => {
425                (FileRange { file_id, range: self.value }, SyntaxContext::root(file_id.edition(db)))
426            }
427            HirFileId::MacroFile(mac_file) => {
428                match map_node_range_up(db, &db.expansion_span_map(mac_file), self.value) {
429                    Some(it) => it,
430                    None => {
431                        let loc = db.lookup_intern_macro_call(mac_file);
432                        (
433                            loc.kind.original_call_range(db, loc.krate),
434                            SyntaxContext::root(loc.def.edition),
435                        )
436                    }
437                }
438            }
439        }
440    }
441
442    pub fn original_node_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
443        match self.file_id {
444            HirFileId::FileId(file_id) => FileRange { file_id, range: self.value },
445            HirFileId::MacroFile(mac_file) => {
446                match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
447                    Some(it) => it,
448                    _ => {
449                        let loc = db.lookup_intern_macro_call(mac_file);
450                        loc.kind.original_call_range(db, loc.krate)
451                    }
452                }
453            }
454        }
455    }
456
457    pub fn original_node_file_range_with_macro_call_input(
458        self,
459        db: &dyn db::ExpandDatabase,
460    ) -> FileRange {
461        match self.file_id {
462            HirFileId::FileId(file_id) => FileRange { file_id, range: self.value },
463            HirFileId::MacroFile(mac_file) => {
464                match map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value) {
465                    Some(it) => it,
466                    _ => {
467                        let loc = db.lookup_intern_macro_call(mac_file);
468                        loc.kind.original_call_range_with_input(db)
469                    }
470                }
471            }
472        }
473    }
474
475    pub fn original_node_file_range_opt(
476        self,
477        db: &dyn db::ExpandDatabase,
478    ) -> Option<(FileRange, SyntaxContext)> {
479        match self.file_id {
480            HirFileId::FileId(file_id) => Some((
481                FileRange { file_id, range: self.value },
482                SyntaxContext::root(file_id.edition(db)),
483            )),
484            HirFileId::MacroFile(mac_file) => {
485                map_node_range_up(db, &db.expansion_span_map(mac_file), self.value)
486            }
487        }
488    }
489
490    pub fn original_node_file_range_rooted_opt(
491        self,
492        db: &dyn db::ExpandDatabase,
493    ) -> Option<FileRange> {
494        match self.file_id {
495            HirFileId::FileId(file_id) => Some(FileRange { file_id, range: self.value }),
496            HirFileId::MacroFile(mac_file) => {
497                map_node_range_up_rooted(db, &db.expansion_span_map(mac_file), self.value)
498            }
499        }
500    }
501}
502
503impl<N: AstNode> InFile<N> {
504    pub fn original_ast_node_rooted(self, db: &dyn db::ExpandDatabase) -> Option<InRealFile<N>> {
505        // This kind of upmapping can only be achieved in attribute expanded files,
506        // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
507        let file_id = match self.file_id {
508            HirFileId::FileId(file_id) => {
509                return Some(InRealFile { file_id, value: self.value });
510            }
511            HirFileId::MacroFile(m) => m,
512        };
513        if !matches!(file_id.kind(db), MacroKind::Attr | MacroKind::AttrBuiltIn) {
514            return None;
515        }
516
517        let FileRange { file_id: editioned_file_id, range } = map_node_range_up_rooted(
518            db,
519            &db.expansion_span_map(file_id),
520            self.value.syntax().text_range(),
521        )?;
522
523        // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes?
524        let anc = db.parse(editioned_file_id).syntax_node().covering_element(range);
525        let value = anc.ancestors().find_map(N::cast)?;
526        Some(InRealFile::new(editioned_file_id, value))
527    }
528}
529
530impl<T> InFile<T> {
531    pub fn into_real_file(self) -> Result<InRealFile<T>, InFile<T>> {
532        match self.file_id {
533            HirFileId::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }),
534            HirFileId::MacroFile(_) => Err(self),
535        }
536    }
537}