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