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 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
104/// `AstId` points to an AST node in any file.
105///
106/// It is stable across reparses, and can be used as salsa key/value.
107pub 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
190// region:transpose impls
191
192impl<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
207// endregion:transpose impls
208
209trait 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    // unfortunately `syntax` collides with the impl above, because `&_` is fundamental
254    pub fn syntax_ref(&self) -> InFileWrapper<FileId, &SyntaxNode> {
255        self.with_value(self.value.syntax())
256    }
257}
258
259// region:specific impls
260impl<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    /// Falls back to the macro call range if the node cannot be mapped up fully.
310    ///
311    /// For attributes and derives, this will point back to the attribute only.
312    /// For the entire item use [`InFile::original_file_range_full`].
313    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    /// Falls back to the macro call range if the node cannot be mapped up fully.
318    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        // This kind of upmapping can only be achieved in attribute expanded files,
330        // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
331        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    /// Attempts to map the syntax node back up its macro calls.
363    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    /// Falls back to the macro call range if the node cannot be mapped up fully.
382    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                // FIXME: Figure out an API that makes proper use of ctx, this only exists to
393                // keep pre-token map rewrite behaviour.
394                if ctxt.is_root() {
395                    return range;
396                }
397
398                // Fall back to whole macro call.
399                let loc = db.lookup_intern_macro_call(mac_file);
400                loc.kind.original_call_range(db)
401            }
402        }
403    }
404
405    /// Attempts to map the syntax node back up its macro calls.
406    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                // FIXME: Figure out an API that makes proper use of ctx, this only exists to
419                // keep pre-token map rewrite behaviour.
420                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        // This kind of upmapping can only be achieved in attribute expanded files,
517        // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
518        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        // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes?
535        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}