ra_ap_hir/
has_source.rs

1//! Provides set of implementation for hir's objects that allows get back location in file.
2
3use either::Either;
4use hir_def::{
5    nameres::{ModuleOrigin, ModuleSource},
6    src::{HasChildSource, HasSource as _},
7    CallableDefId, Lookup, MacroId, VariantId,
8};
9use hir_expand::{HirFileId, InFile};
10use hir_ty::db::InternedClosure;
11use span::EditionedFileId;
12use syntax::ast;
13use tt::TextRange;
14
15use crate::{
16    db::HirDatabase, Adt, Callee, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl,
17    InlineAsmOperand, Label, LifetimeParam, LocalSource, Macro, Module, Param, SelfParam, Static,
18    Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant,
19};
20
21pub trait HasSource {
22    type Ast;
23    /// Fetches the definition's source node.
24    /// Using [`crate::Semantics::source`] is preferred when working with [`crate::Semantics`],
25    /// as that caches the parsed file in the semantics' cache.
26    ///
27    /// The current some implementations can return `InFile` instead of `Option<InFile>`.
28    /// But we made this method `Option` to support rlib in the future
29    /// by <https://github.com/rust-lang/rust-analyzer/issues/6913>
30    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>>;
31}
32
33/// NB: Module is !HasSource, because it has two source nodes at the same time:
34/// definition and declaration.
35impl Module {
36    /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items.
37    pub fn definition_source(self, db: &dyn HirDatabase) -> InFile<ModuleSource> {
38        let def_map = self.id.def_map(db.upcast());
39        def_map[self.id.local_id].definition_source(db.upcast())
40    }
41
42    /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items.
43    pub fn definition_source_range(self, db: &dyn HirDatabase) -> InFile<TextRange> {
44        let def_map = self.id.def_map(db.upcast());
45        def_map[self.id.local_id].definition_source_range(db.upcast())
46    }
47
48    pub fn definition_source_file_id(self, db: &dyn HirDatabase) -> HirFileId {
49        let def_map = self.id.def_map(db.upcast());
50        def_map[self.id.local_id].definition_source_file_id()
51    }
52
53    pub fn is_mod_rs(self, db: &dyn HirDatabase) -> bool {
54        let def_map = self.id.def_map(db.upcast());
55        match def_map[self.id.local_id].origin {
56            ModuleOrigin::File { is_mod_rs, .. } => is_mod_rs,
57            _ => false,
58        }
59    }
60
61    pub fn as_source_file_id(self, db: &dyn HirDatabase) -> Option<EditionedFileId> {
62        let def_map = self.id.def_map(db.upcast());
63        match def_map[self.id.local_id].origin {
64            ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition, .. } => {
65                Some(definition)
66            }
67            _ => None,
68        }
69    }
70
71    pub fn is_inline(self, db: &dyn HirDatabase) -> bool {
72        let def_map = self.id.def_map(db.upcast());
73        def_map[self.id.local_id].origin.is_inline()
74    }
75
76    /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
77    /// `None` for the crate root.
78    pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> {
79        let def_map = self.id.def_map(db.upcast());
80        def_map[self.id.local_id].declaration_source(db.upcast())
81    }
82
83    /// Returns a text range which declares this module, either a `mod foo;` or a `mod foo {}`.
84    /// `None` for the crate root.
85    pub fn declaration_source_range(self, db: &dyn HirDatabase) -> Option<InFile<TextRange>> {
86        let def_map = self.id.def_map(db.upcast());
87        def_map[self.id.local_id].declaration_source_range(db.upcast())
88    }
89}
90
91impl HasSource for Field {
92    type Ast = FieldSource;
93    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
94        let var = VariantId::from(self.parent);
95        let src = var.child_source(db.upcast());
96        let field_source = src.map(|it| match it[self.id].clone() {
97            Either::Left(it) => FieldSource::Pos(it),
98            Either::Right(it) => FieldSource::Named(it),
99        });
100        Some(field_source)
101    }
102}
103impl HasSource for Adt {
104    type Ast = ast::Adt;
105    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
106        match self {
107            Adt::Struct(s) => Some(s.source(db)?.map(ast::Adt::Struct)),
108            Adt::Union(u) => Some(u.source(db)?.map(ast::Adt::Union)),
109            Adt::Enum(e) => Some(e.source(db)?.map(ast::Adt::Enum)),
110        }
111    }
112}
113impl HasSource for Struct {
114    type Ast = ast::Struct;
115    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
116        Some(self.id.lookup(db.upcast()).source(db.upcast()))
117    }
118}
119impl HasSource for Union {
120    type Ast = ast::Union;
121    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
122        Some(self.id.lookup(db.upcast()).source(db.upcast()))
123    }
124}
125impl HasSource for Enum {
126    type Ast = ast::Enum;
127    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
128        Some(self.id.lookup(db.upcast()).source(db.upcast()))
129    }
130}
131impl HasSource for Variant {
132    type Ast = ast::Variant;
133    fn source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Variant>> {
134        Some(self.id.lookup(db.upcast()).source(db.upcast()))
135    }
136}
137impl HasSource for Function {
138    type Ast = ast::Fn;
139    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
140        Some(self.id.lookup(db.upcast()).source(db.upcast()))
141    }
142}
143impl HasSource for Const {
144    type Ast = ast::Const;
145    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
146        Some(self.id.lookup(db.upcast()).source(db.upcast()))
147    }
148}
149impl HasSource for Static {
150    type Ast = ast::Static;
151    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
152        Some(self.id.lookup(db.upcast()).source(db.upcast()))
153    }
154}
155impl HasSource for Trait {
156    type Ast = ast::Trait;
157    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
158        Some(self.id.lookup(db.upcast()).source(db.upcast()))
159    }
160}
161impl HasSource for TraitAlias {
162    type Ast = ast::TraitAlias;
163    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
164        Some(self.id.lookup(db.upcast()).source(db.upcast()))
165    }
166}
167impl HasSource for TypeAlias {
168    type Ast = ast::TypeAlias;
169    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
170        Some(self.id.lookup(db.upcast()).source(db.upcast()))
171    }
172}
173impl HasSource for Macro {
174    type Ast = Either<ast::Macro, ast::Fn>;
175    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
176        match self.id {
177            MacroId::Macro2Id(it) => Some(
178                it.lookup(db.upcast())
179                    .source(db.upcast())
180                    .map(ast::Macro::MacroDef)
181                    .map(Either::Left),
182            ),
183            MacroId::MacroRulesId(it) => Some(
184                it.lookup(db.upcast())
185                    .source(db.upcast())
186                    .map(ast::Macro::MacroRules)
187                    .map(Either::Left),
188            ),
189            MacroId::ProcMacroId(it) => {
190                Some(it.lookup(db.upcast()).source(db.upcast()).map(Either::Right))
191            }
192        }
193    }
194}
195impl HasSource for Impl {
196    type Ast = ast::Impl;
197    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
198        Some(self.id.lookup(db.upcast()).source(db.upcast()))
199    }
200}
201
202impl HasSource for TypeOrConstParam {
203    type Ast = Either<ast::TypeOrConstParam, ast::TraitOrAlias>;
204    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
205        let child_source = self.id.parent.child_source(db.upcast());
206        child_source.map(|it| it.get(self.id.local_id).cloned()).transpose()
207    }
208}
209
210impl HasSource for LifetimeParam {
211    type Ast = ast::LifetimeParam;
212    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
213        let child_source = self.id.parent.child_source(db.upcast());
214        child_source.map(|it| it.get(self.id.local_id).cloned()).transpose()
215    }
216}
217
218impl HasSource for LocalSource {
219    type Ast = Either<ast::IdentPat, ast::SelfParam>;
220
221    fn source(self, _: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
222        Some(self.source)
223    }
224}
225
226impl HasSource for Param {
227    type Ast = Either<ast::SelfParam, ast::Param>;
228
229    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
230        match self.func {
231            Callee::Def(CallableDefId::FunctionId(func)) => {
232                let InFile { file_id, value } = Function { id: func }.source(db)?;
233                let params = value.param_list()?;
234                if let Some(self_param) = params.self_param() {
235                    if let Some(idx) = self.idx.checked_sub(1) {
236                        params.params().nth(idx).map(Either::Right)
237                    } else {
238                        Some(Either::Left(self_param))
239                    }
240                } else {
241                    params.params().nth(self.idx).map(Either::Right)
242                }
243                .map(|value| InFile { file_id, value })
244            }
245            Callee::Closure(closure, _) => {
246                let InternedClosure(owner, expr_id) = db.lookup_intern_closure(closure.into());
247                let (_, source_map) = db.body_with_source_map(owner);
248                let ast @ InFile { file_id, value } = source_map.expr_syntax(expr_id).ok()?;
249                let root = db.parse_or_expand(file_id);
250                match value.to_node(&root) {
251                    Either::Left(ast::Expr::ClosureExpr(it)) => it
252                        .param_list()?
253                        .params()
254                        .nth(self.idx)
255                        .map(Either::Right)
256                        .map(|value| InFile { file_id: ast.file_id, value }),
257                    _ => None,
258                }
259            }
260            _ => None,
261        }
262    }
263}
264
265impl HasSource for SelfParam {
266    type Ast = ast::SelfParam;
267
268    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
269        let InFile { file_id, value } = Function::from(self.func).source(db)?;
270        value
271            .param_list()
272            .and_then(|params| params.self_param())
273            .map(|value| InFile { file_id, value })
274    }
275}
276
277impl HasSource for Label {
278    type Ast = ast::Label;
279
280    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
281        let (_body, source_map) = db.body_with_source_map(self.parent);
282        let src = source_map.label_syntax(self.label_id);
283        let root = src.file_syntax(db.upcast());
284        Some(src.map(|ast| ast.to_node(&root)))
285    }
286}
287
288impl HasSource for ExternCrateDecl {
289    type Ast = ast::ExternCrate;
290
291    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
292        Some(self.id.lookup(db.upcast()).source(db.upcast()))
293    }
294}
295
296impl HasSource for InlineAsmOperand {
297    type Ast = ast::AsmOperandNamed;
298    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
299        let (_body, source_map) = db.body_with_source_map(self.owner);
300        if let Ok(src) = source_map.expr_syntax(self.expr) {
301            let root = src.file_syntax(db.upcast());
302            return src
303                .map(|ast| match ast.to_node(&root) {
304                    Either::Left(ast::Expr::AsmExpr(asm)) => asm
305                        .asm_pieces()
306                        .filter_map(|it| match it {
307                            ast::AsmPiece::AsmOperandNamed(it) => Some(it),
308                            _ => None,
309                        })
310                        .nth(self.index),
311                    _ => None,
312                })
313                .transpose();
314        }
315        None
316    }
317}