Skip to main content

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                    let (_, attr) = attr_id.find_attr_range_with_source(db, krate, &adt);
130                    res[keys::DERIVE_MACRO_CALL]
131                        .insert(AstPtr::new(&attr), (attr_id, call_id, calls.into()));
132                });
133            },
134        );
135        self.iter_macro_invoc().filter(|(id, _)| id.file_id == file_id).for_each(
136            |(ast_id, &call)| {
137                let ast = ast_id.to_ptr(db);
138                res[keys::MACRO_CALL].insert(ast, call);
139            },
140        );
141        fn add_module_def(
142            db: &dyn DefDatabase,
143            map: &mut DynMap,
144            file_id: HirFileId,
145            item: ModuleDefId,
146        ) {
147            match item {
148                ModuleDefId::FunctionId(id) => {
149                    insert_item_loc(db, map, file_id, id, keys::FUNCTION)
150                }
151                ModuleDefId::ConstId(id) => insert_item_loc(db, map, file_id, id, keys::CONST),
152                ModuleDefId::TypeAliasId(id) => {
153                    insert_item_loc(db, map, file_id, id, keys::TYPE_ALIAS)
154                }
155                ModuleDefId::StaticId(id) => insert_item_loc(db, map, file_id, id, keys::STATIC),
156                ModuleDefId::TraitId(id) => insert_item_loc(db, map, file_id, id, keys::TRAIT),
157                ModuleDefId::AdtId(adt) => match adt {
158                    AdtId::StructId(id) => insert_item_loc(db, map, file_id, id, keys::STRUCT),
159                    AdtId::UnionId(id) => insert_item_loc(db, map, file_id, id, keys::UNION),
160                    AdtId::EnumId(id) => insert_item_loc(db, map, file_id, id, keys::ENUM),
161                },
162                ModuleDefId::MacroId(id) => match id {
163                    MacroId::Macro2Id(id) => insert_item_loc(db, map, file_id, id, keys::MACRO2),
164                    MacroId::MacroRulesId(id) => {
165                        insert_item_loc(db, map, file_id, id, keys::MACRO_RULES)
166                    }
167                    MacroId::ProcMacroId(id) => {
168                        insert_item_loc(db, map, file_id, id, keys::PROC_MACRO)
169                    }
170                },
171                ModuleDefId::ModuleId(_)
172                | ModuleDefId::EnumVariantId(_)
173                | ModuleDefId::BuiltinType(_) => (),
174            }
175        }
176    }
177}
178
179impl ChildBySource for VariantId {
180    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) {
181        let arena_map = self.child_source(db);
182        let arena_map = arena_map.as_ref();
183        let parent = *self;
184        for (local_id, source) in arena_map.value.iter() {
185            let id = FieldId { parent, local_id };
186            match source.clone() {
187                Either::Left(source) => res[keys::TUPLE_FIELD].insert(AstPtr::new(&source), id),
188                Either::Right(source) => res[keys::RECORD_FIELD].insert(AstPtr::new(&source), id),
189            }
190        }
191        let (_, sm) = self.fields_with_source_map(db);
192        sm.expansions().for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id));
193    }
194}
195
196impl ChildBySource for EnumId {
197    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
198        let loc = &self.lookup(db);
199        if file_id != loc.id.file_id {
200            return;
201        }
202
203        let ast_id_map = db.ast_id_map(loc.id.file_id);
204
205        self.enum_variants(db).variants.iter().for_each(|&(variant, _, _)| {
206            res[keys::ENUM_VARIANT].insert(ast_id_map.get(variant.lookup(db).id.value), variant);
207        });
208        let (_, source_map) = EnumSignature::with_source_map(db, *self);
209        source_map
210            .expansions()
211            .filter(|(ast, _)| ast.file_id == file_id)
212            .for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id));
213    }
214}
215
216impl ChildBySource for DefWithBodyId {
217    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
218        let (body, sm) = Body::with_source_map(db, *self);
219        if let &DefWithBodyId::VariantId(v) = self {
220            VariantId::EnumVariantId(v).child_by_source_to(db, res, file_id)
221        }
222
223        sm.expansions().filter(|(ast, _)| ast.file_id == file_id).for_each(|(ast, &exp_id)| {
224            res[keys::MACRO_CALL].insert(ast.value, exp_id);
225        });
226
227        for (block, def_map) in body.blocks(db) {
228            // All block expressions are merged into the same map, because they logically all add
229            // inner items to the containing `DefWithBodyId`.
230            def_map[def_map.root].scope.child_by_source_to(db, res, file_id);
231            res[keys::BLOCK].insert(block.lookup(db).ast_id.to_ptr(db), block);
232        }
233    }
234}
235
236impl ChildBySource for GenericDefId {
237    fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
238        let (gfile_id, generic_params_list) = self.file_id_and_params_of(db);
239        if gfile_id != file_id {
240            return;
241        }
242
243        let (generic_params, _, source_map) = GenericParams::with_source_map(db, *self);
244        let mut toc_idx_iter = generic_params.iter_type_or_consts().map(|(idx, _)| idx);
245        let lts_idx_iter = generic_params.iter_lt().map(|(idx, _)| idx);
246
247        // For traits the first type index is `Self`, skip it.
248        if let GenericDefId::TraitId(_) = *self {
249            toc_idx_iter.next().unwrap(); // advance_by(1);
250        }
251
252        if let Some(generic_params_list) = generic_params_list {
253            for (local_id, ast_param) in
254                toc_idx_iter.zip(generic_params_list.type_or_const_params())
255            {
256                let id = TypeOrConstParamId { parent: *self, local_id };
257                match ast_param {
258                    ast::TypeOrConstParam::Type(a) => {
259                        res[keys::TYPE_PARAM].insert(AstPtr::new(&a), id)
260                    }
261                    ast::TypeOrConstParam::Const(a) => {
262                        res[keys::CONST_PARAM].insert(AstPtr::new(&a), id)
263                    }
264                }
265            }
266            for (local_id, ast_param) in lts_idx_iter.zip(generic_params_list.lifetime_params()) {
267                let id = LifetimeParamId { parent: *self, local_id };
268                res[keys::LIFETIME_PARAM].insert(AstPtr::new(&ast_param), id);
269            }
270        }
271
272        source_map
273            .expansions()
274            .filter(|(ast, _)| ast.file_id == file_id)
275            .for_each(|(ast, &exp_id)| res[keys::MACRO_CALL].insert(ast.value, exp_id));
276    }
277}
278
279fn insert_item_loc<ID, N, Data>(
280    db: &dyn DefDatabase,
281    res: &mut DynMap,
282    file_id: HirFileId,
283    id: ID,
284    key: Key<N, ID>,
285) where
286    ID: Lookup<Database = dyn DefDatabase, Data = Data> + 'static,
287    Data: AstIdLoc<Ast = N>,
288    N: AstIdNode + 'static,
289{
290    let loc = id.lookup(db);
291    if loc.ast_id().file_id == file_id {
292        res[key].insert(loc.ast_ptr(db).value, id)
293    }
294}
295
296fn add_assoc_item(db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId, item: AssocItemId) {
297    match item {
298        AssocItemId::FunctionId(func) => insert_item_loc(db, res, file_id, func, keys::FUNCTION),
299        AssocItemId::ConstId(konst) => insert_item_loc(db, res, file_id, konst, keys::CONST),
300        AssocItemId::TypeAliasId(ty) => insert_item_loc(db, res, file_id, ty, keys::TYPE_ALIAS),
301    }
302}