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