hir/semantics/
child_by_source.rs

1//! When *constructing* `hir`, we start at some parent syntax node and recursively
2//! lower the children.
3//!
4//! This module allows one to go in the opposite direction: start with a syntax
5//! node for a *child*, and get its hir.
6
7use either::Either;
8use hir_expand::HirFileId;
9use span::AstIdNode;
10use syntax::{AstPtr, ast};
11
12use hir_def::{
13    AdtId, AssocItemId, AstIdLoc, DefWithBodyId, EnumId, FieldId, GenericDefId, ImplId,
14    LifetimeParamId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, TypeOrConstParamId,
15    VariantId,
16    db::DefDatabase,
17    dyn_map::{
18        DynMap,
19        keys::{self, Key},
20    },
21    expr_store::Body,
22    hir::generics::GenericParams,
23    item_scope::ItemScope,
24    signatures::{EnumSignature, ImplSignature, TraitSignature},
25    src::{HasChildSource, HasSource},
26};
27
28pub(crate) trait ChildBySource {
29    fn child_by_source(&self, db: &dyn DefDatabase, file_id: HirFileId) -> DynMap {
30        let mut res = DynMap::default();
31        self.child_by_source_to(db, &mut res, file_id);
32        res
33    }
34    fn child_by_source_to(&self, db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId);
35}
36
37impl ChildBySource for TraitId {
38    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
39        let data = self.trait_items(db);
40
41        data.macro_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each(
42            |(ast_id, call_id)| {
43                let ptr = ast_id.to_ptr(db);
44                if let Some(ptr) = ptr.cast::<ast::MacroCall>() {
45                    res[keys::MACRO_CALL].insert(ptr, call_id);
46                } else {
47                    res[keys::ATTR_MACRO_CALL].insert(ptr, call_id);
48                }
49            },
50        );
51        data.items.iter().for_each(|&(_, item)| {
52            add_assoc_item(db, res, file_id, item);
53        });
54        let (_, source_map) = TraitSignature::with_source_map(db, *self);
55        source_map.expansions().filter(|(ast, _)| ast.file_id == file_id).for_each(
56            |(ast, &exp_id)| {
57                res[keys::MACRO_CALL].insert(ast.value, exp_id);
58            },
59        );
60    }
61}
62
63impl ChildBySource for ImplId {
64    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
65        let data = self.impl_items(db);
66        data.macro_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each(
67            |(ast_id, call_id)| {
68                let ptr = ast_id.to_ptr(db);
69                if let Some(ptr) = ptr.cast::<ast::MacroCall>() {
70                    res[keys::MACRO_CALL].insert(ptr, call_id);
71                } else {
72                    res[keys::ATTR_MACRO_CALL].insert(ptr, call_id);
73                }
74            },
75        );
76        data.items.iter().for_each(|&(_, item)| {
77            add_assoc_item(db, res, file_id, item);
78        });
79        let (_, source_map) = ImplSignature::with_source_map(db, *self);
80        source_map.expansions().filter(|(ast, _)| ast.file_id == file_id).for_each(
81            |(ast, &exp_id)| {
82                res[keys::MACRO_CALL].insert(ast.value, exp_id);
83            },
84        );
85    }
86}
87
88impl ChildBySource for ModuleId {
89    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
90        let def_map = self.def_map(db);
91        let module_data = &def_map[*self];
92        module_data.scope.child_by_source_to(db, res, file_id);
93    }
94}
95
96impl ChildBySource for ItemScope {
97    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
98        self.declarations().for_each(|item| add_module_def(db, res, file_id, item));
99        self.impls().for_each(|imp| insert_item_loc(db, res, file_id, imp, keys::IMPL));
100        self.extern_blocks().for_each(|extern_block| {
101            insert_item_loc(db, res, file_id, extern_block, keys::EXTERN_BLOCK)
102        });
103        self.extern_crate_decls()
104            .for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::EXTERN_CRATE));
105        self.use_decls().for_each(|ext| insert_item_loc(db, res, file_id, ext, keys::USE));
106        self.unnamed_consts()
107            .for_each(|konst| insert_item_loc(db, res, file_id, konst, keys::CONST));
108        self.attr_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
109            |(ast_id, call_id)| {
110                res[keys::ATTR_MACRO_CALL].insert(ast_id.to_ptr(db), call_id);
111            },
112        );
113        self.legacy_macros().for_each(|(_, ids)| {
114            ids.iter().for_each(|&id| {
115                if let MacroId::MacroRulesId(id) = id {
116                    let loc = id.lookup(db);
117                    if loc.id.file_id == file_id {
118                        res[keys::MACRO_RULES].insert(loc.ast_ptr(db).value, id);
119                    }
120                }
121            })
122        });
123        self.derive_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
124            |(ast_id, calls)| {
125                let adt = ast_id.to_node(db);
126                calls.for_each(|(attr_id, call_id, calls)| {
127                    // FIXME: Is this the right crate?
128                    let krate = call_id.lookup(db).krate;
129                    // FIXME: Fix cfg_attr handling.
130                    let (attr, _, _, _) = attr_id.find_attr_range_with_source(db, krate, &adt);
131                    res[keys::DERIVE_MACRO_CALL]
132                        .insert(AstPtr::new(&attr), (attr_id, call_id, calls.into()));
133                });
134            },
135        );
136        self.iter_macro_invoc().filter(|(id, _)| id.file_id == file_id).for_each(
137            |(ast_id, &call)| {
138                let ast = ast_id.to_ptr(db);
139                res[keys::MACRO_CALL].insert(ast, call);
140            },
141        );
142        fn add_module_def(
143            db: &dyn DefDatabase,
144            map: &mut DynMap,
145            file_id: HirFileId,
146            item: ModuleDefId,
147        ) {
148            match item {
149                ModuleDefId::FunctionId(id) => {
150                    insert_item_loc(db, map, file_id, id, keys::FUNCTION)
151                }
152                ModuleDefId::ConstId(id) => insert_item_loc(db, map, file_id, id, keys::CONST),
153                ModuleDefId::TypeAliasId(id) => {
154                    insert_item_loc(db, map, file_id, id, keys::TYPE_ALIAS)
155                }
156                ModuleDefId::StaticId(id) => insert_item_loc(db, map, file_id, id, keys::STATIC),
157                ModuleDefId::TraitId(id) => insert_item_loc(db, map, file_id, id, keys::TRAIT),
158                ModuleDefId::AdtId(adt) => match adt {
159                    AdtId::StructId(id) => insert_item_loc(db, map, file_id, id, keys::STRUCT),
160                    AdtId::UnionId(id) => insert_item_loc(db, map, file_id, id, keys::UNION),
161                    AdtId::EnumId(id) => insert_item_loc(db, map, file_id, id, keys::ENUM),
162                },
163                ModuleDefId::MacroId(id) => match id {
164                    MacroId::Macro2Id(id) => insert_item_loc(db, map, file_id, id, keys::MACRO2),
165                    MacroId::MacroRulesId(id) => {
166                        insert_item_loc(db, map, file_id, id, keys::MACRO_RULES)
167                    }
168                    MacroId::ProcMacroId(id) => {
169                        insert_item_loc(db, map, file_id, id, keys::PROC_MACRO)
170                    }
171                },
172                ModuleDefId::ModuleId(_)
173                | ModuleDefId::EnumVariantId(_)
174                | ModuleDefId::BuiltinType(_) => (),
175            }
176        }
177    }
178}
179
180impl ChildBySource for VariantId {
181    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) {
182        let arena_map = self.child_source(db);
183        let arena_map = arena_map.as_ref();
184        let parent = *self;
185        for (local_id, source) in arena_map.value.iter() {
186            let id = FieldId { parent, local_id };
187            match source.clone() {
188                Either::Left(source) => res[keys::TUPLE_FIELD].insert(AstPtr::new(&source), id),
189                Either::Right(source) => res[keys::RECORD_FIELD].insert(AstPtr::new(&source), id),
190            }
191        }
192        let (_, sm) = self.fields_with_source_map(db);
193        sm.expansions().for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id));
194    }
195}
196
197impl ChildBySource for EnumId {
198    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
199        let loc = &self.lookup(db);
200        if file_id != loc.id.file_id {
201            return;
202        }
203
204        let ast_id_map = db.ast_id_map(loc.id.file_id);
205
206        self.enum_variants(db).variants.iter().for_each(|&(variant, _, _)| {
207            res[keys::ENUM_VARIANT].insert(ast_id_map.get(variant.lookup(db).id.value), variant);
208        });
209        let (_, source_map) = EnumSignature::with_source_map(db, *self);
210        source_map
211            .expansions()
212            .filter(|(ast, _)| ast.file_id == file_id)
213            .for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id));
214    }
215}
216
217impl ChildBySource for DefWithBodyId {
218    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
219        let (body, sm) = Body::with_source_map(db, *self);
220        if let &DefWithBodyId::VariantId(v) = self {
221            VariantId::EnumVariantId(v).child_by_source_to(db, res, file_id)
222        }
223
224        sm.expansions().filter(|(ast, _)| ast.file_id == file_id).for_each(|(ast, &exp_id)| {
225            res[keys::MACRO_CALL].insert(ast.value, exp_id);
226        });
227
228        for (block, def_map) in body.blocks(db) {
229            // All block expressions are merged into the same map, because they logically all add
230            // inner items to the containing `DefWithBodyId`.
231            def_map[def_map.root].scope.child_by_source_to(db, res, file_id);
232            res[keys::BLOCK].insert(block.lookup(db).ast_id.to_ptr(db), block);
233        }
234    }
235}
236
237impl ChildBySource for GenericDefId {
238    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
239        let (gfile_id, generic_params_list) = self.file_id_and_params_of(db);
240        if gfile_id != file_id {
241            return;
242        }
243
244        let (generic_params, _, source_map) = GenericParams::with_source_map(db, *self);
245        let mut toc_idx_iter = generic_params.iter_type_or_consts().map(|(idx, _)| idx);
246        let lts_idx_iter = generic_params.iter_lt().map(|(idx, _)| idx);
247
248        // For traits the first type index is `Self`, skip it.
249        if let GenericDefId::TraitId(_) = *self {
250            toc_idx_iter.next().unwrap(); // advance_by(1);
251        }
252
253        if let Some(generic_params_list) = generic_params_list {
254            for (local_id, ast_param) in
255                toc_idx_iter.zip(generic_params_list.type_or_const_params())
256            {
257                let id = TypeOrConstParamId { parent: *self, local_id };
258                match ast_param {
259                    ast::TypeOrConstParam::Type(a) => {
260                        res[keys::TYPE_PARAM].insert(AstPtr::new(&a), id)
261                    }
262                    ast::TypeOrConstParam::Const(a) => {
263                        res[keys::CONST_PARAM].insert(AstPtr::new(&a), id)
264                    }
265                }
266            }
267            for (local_id, ast_param) in lts_idx_iter.zip(generic_params_list.lifetime_params()) {
268                let id = LifetimeParamId { parent: *self, local_id };
269                res[keys::LIFETIME_PARAM].insert(AstPtr::new(&ast_param), id);
270            }
271        }
272
273        source_map
274            .expansions()
275            .filter(|(ast, _)| ast.file_id == file_id)
276            .for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id));
277    }
278}
279
280fn insert_item_loc<ID, N, Data>(
281    db: &dyn DefDatabase,
282    res: &mut DynMap,
283    file_id: HirFileId,
284    id: ID,
285    key: Key<N, ID>,
286) where
287    ID: Lookup<Database = dyn DefDatabase, Data = Data> + 'static,
288    Data: AstIdLoc<Ast = N>,
289    N: AstIdNode + 'static,
290{
291    let loc = id.lookup(db);
292    if loc.ast_id().file_id == file_id {
293        res[key].insert(loc.ast_ptr(db).value, id)
294    }
295}
296
297fn add_assoc_item(db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId, item: AssocItemId) {
298    match item {
299        AssocItemId::FunctionId(func) => insert_item_loc(db, res, file_id, func, keys::FUNCTION),
300        AssocItemId::ConstId(konst) => insert_item_loc(db, res, file_id, konst, keys::CONST),
301        AssocItemId::TypeAliasId(ty) => insert_item_loc(db, res, file_id, ty, keys::TYPE_ALIAS),
302    }
303}