hir_def/nameres/
assoc.rs

1//! Expansion of associated items
2
3use std::mem;
4
5use cfg::CfgOptions;
6use hir_expand::{
7    AstId, AttrMacroAttrIds, ExpandTo, HirFileId, InFile, Intern, Lookup, MacroCallKind,
8    MacroDefKind,
9    mod_path::ModPath,
10    name::{AsName, Name},
11    span_map::SpanMap,
12};
13use intern::Interned;
14use span::AstIdMap;
15use syntax::{
16    AstNode,
17    ast::{self, HasModuleItem, HasName},
18};
19use thin_vec::ThinVec;
20use triomphe::Arc;
21
22use crate::{
23    AssocItemId, AstIdWithPath, ConstLoc, FunctionId, FunctionLoc, ImplId, ItemContainerId,
24    ItemLoc, MacroCallId, ModuleId, TraitId, TypeAliasId, TypeAliasLoc,
25    db::DefDatabase,
26    item_tree::AttrsOrCfg,
27    macro_call_as_call_id,
28    nameres::{
29        DefMap, LocalDefMap, MacroSubNs,
30        attr_resolution::ResolvedAttr,
31        diagnostics::{DefDiagnostic, DefDiagnostics},
32    },
33};
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct TraitItems {
37    pub items: Box<[(Name, AssocItemId)]>,
38    // `ThinVec` as the vec is usually empty anyways
39    pub macro_calls: ThinVec<(AstId<ast::Item>, MacroCallId)>,
40}
41
42#[salsa::tracked]
43impl TraitItems {
44    #[inline]
45    pub(crate) fn query(db: &dyn DefDatabase, tr: TraitId) -> &TraitItems {
46        &Self::query_with_diagnostics(db, tr).0
47    }
48
49    #[salsa::tracked(returns(ref))]
50    pub fn query_with_diagnostics(
51        db: &dyn DefDatabase,
52        tr: TraitId,
53    ) -> (TraitItems, DefDiagnostics) {
54        let ItemLoc { container: module_id, id: ast_id } = tr.lookup(db);
55        let ast_id_map = db.ast_id_map(ast_id.file_id);
56        let source = ast_id.with_value(ast_id_map.get(ast_id.value)).to_node(db);
57        if source.eq_token().is_some() {
58            // FIXME(trait-alias) probably needs special handling here
59            return (
60                TraitItems { macro_calls: ThinVec::new(), items: Box::default() },
61                DefDiagnostics::new(vec![]),
62            );
63        }
64
65        let collector =
66            AssocItemCollector::new(db, module_id, ItemContainerId::TraitId(tr), ast_id.file_id);
67        let (items, macro_calls, diagnostics) = collector.collect(source.assoc_item_list());
68
69        (TraitItems { macro_calls, items }, DefDiagnostics::new(diagnostics))
70    }
71
72    pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ {
73        self.items.iter().filter_map(|(_name, item)| match item {
74            AssocItemId::TypeAliasId(t) => Some(*t),
75            _ => None,
76        })
77    }
78
79    pub fn associated_type_by_name(&self, name: &Name) -> Option<TypeAliasId> {
80        self.items.iter().find_map(|(item_name, item)| match item {
81            AssocItemId::TypeAliasId(t) if item_name == name => Some(*t),
82            _ => None,
83        })
84    }
85
86    pub fn method_by_name(&self, name: &Name) -> Option<FunctionId> {
87        self.items.iter().find_map(|(item_name, item)| match item {
88            AssocItemId::FunctionId(t) if item_name == name => Some(*t),
89            _ => None,
90        })
91    }
92
93    pub fn assoc_item_by_name(&self, name: &Name) -> Option<AssocItemId> {
94        self.items.iter().find_map(|&(ref item_name, item)| match item {
95            AssocItemId::FunctionId(_) if item_name == name => Some(item),
96            AssocItemId::TypeAliasId(_) if item_name == name => Some(item),
97            AssocItemId::ConstId(_) if item_name == name => Some(item),
98            _ => None,
99        })
100    }
101
102    pub fn macro_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
103        self.macro_calls.iter().copied()
104    }
105}
106
107#[derive(Debug, PartialEq, Eq)]
108pub struct ImplItems {
109    pub items: Box<[(Name, AssocItemId)]>,
110    // `ThinVec` as the vec is usually empty anyways
111    pub macro_calls: ThinVec<(AstId<ast::Item>, MacroCallId)>,
112}
113
114#[salsa::tracked]
115impl ImplItems {
116    #[salsa::tracked(returns(ref))]
117    pub fn of(db: &dyn DefDatabase, id: ImplId) -> (ImplItems, DefDiagnostics) {
118        let _p = tracing::info_span!("impl_items_with_diagnostics_query").entered();
119        let ItemLoc { container: module_id, id: ast_id } = id.lookup(db);
120
121        let collector =
122            AssocItemCollector::new(db, module_id, ItemContainerId::ImplId(id), ast_id.file_id);
123        let source = ast_id.with_value(collector.ast_id_map.get(ast_id.value)).to_node(db);
124        let (items, macro_calls, diagnostics) = collector.collect(source.assoc_item_list());
125
126        (ImplItems { items, macro_calls }, DefDiagnostics::new(diagnostics))
127    }
128}
129
130impl ImplItems {
131    pub fn macro_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
132        self.macro_calls.iter().copied()
133    }
134}
135
136struct AssocItemCollector<'a> {
137    db: &'a dyn DefDatabase,
138    module_id: ModuleId,
139    def_map: &'a DefMap,
140    local_def_map: &'a LocalDefMap,
141    ast_id_map: Arc<AstIdMap>,
142    span_map: SpanMap,
143    cfg_options: &'a CfgOptions,
144    file_id: HirFileId,
145    diagnostics: Vec<DefDiagnostic>,
146    container: ItemContainerId,
147
148    depth: usize,
149    items: Vec<(Name, AssocItemId)>,
150    macro_calls: ThinVec<(AstId<ast::Item>, MacroCallId)>,
151}
152
153impl<'a> AssocItemCollector<'a> {
154    fn new(
155        db: &'a dyn DefDatabase,
156        module_id: ModuleId,
157        container: ItemContainerId,
158        file_id: HirFileId,
159    ) -> Self {
160        let (def_map, local_def_map) = module_id.local_def_map(db);
161        Self {
162            db,
163            module_id,
164            def_map,
165            local_def_map,
166            ast_id_map: db.ast_id_map(file_id),
167            span_map: db.span_map(file_id),
168            cfg_options: module_id.krate(db).cfg_options(db),
169            file_id,
170            container,
171            items: Vec::new(),
172
173            depth: 0,
174            macro_calls: ThinVec::new(),
175            diagnostics: Vec::new(),
176        }
177    }
178
179    fn collect(
180        mut self,
181        item_list: Option<ast::AssocItemList>,
182    ) -> (Box<[(Name, AssocItemId)]>, ThinVec<(AstId<ast::Item>, MacroCallId)>, Vec<DefDiagnostic>)
183    {
184        if let Some(item_list) = item_list {
185            for item in item_list.assoc_items() {
186                self.collect_item(item);
187            }
188        }
189        self.macro_calls.shrink_to_fit();
190        (self.items.into_boxed_slice(), self.macro_calls, self.diagnostics)
191    }
192
193    fn collect_item(&mut self, item: ast::AssocItem) {
194        let ast_id = self.ast_id_map.ast_id(&item);
195        let attrs =
196            match AttrsOrCfg::lower(self.db, &item, &|| self.cfg_options, self.span_map.as_ref()) {
197                AttrsOrCfg::Enabled { attrs } => attrs,
198                AttrsOrCfg::CfgDisabled(cfg) => {
199                    self.diagnostics.push(DefDiagnostic::unconfigured_code(
200                        self.module_id,
201                        InFile::new(self.file_id, ast_id.erase()),
202                        cfg.0,
203                        self.cfg_options.clone(),
204                    ));
205                    return;
206                }
207            };
208        let ast_id = InFile::new(self.file_id, ast_id.upcast());
209
210        'attrs: for (attr_id, attr) in attrs.as_ref().iter() {
211            let ast_id_with_path = AstIdWithPath { path: attr.path.clone(), ast_id };
212
213            match self.def_map.resolve_attr_macro(
214                self.local_def_map,
215                self.db,
216                self.module_id,
217                ast_id_with_path,
218                attr,
219                attr_id,
220            ) {
221                Ok(ResolvedAttr::Macro(call_id)) => {
222                    let loc = self.db.lookup_intern_macro_call(call_id);
223                    if let MacroDefKind::ProcMacro(_, exp, _) = loc.def.kind {
224                        // If there's no expander for the proc macro (e.g. the
225                        // proc macro is ignored, or building the proc macro
226                        // crate failed), skip expansion like we would if it was
227                        // disabled. This is analogous to the handling in
228                        // `DefCollector::collect_macros`.
229                        if let Some(err) = exp.as_expand_error(self.module_id.krate(self.db)) {
230                            self.diagnostics.push(DefDiagnostic::macro_error(
231                                self.module_id,
232                                ast_id,
233                                (*attr.path).clone(),
234                                err,
235                            ));
236                            continue 'attrs;
237                        }
238                    }
239
240                    self.macro_calls.push((ast_id, call_id));
241                    self.collect_macro_items(call_id);
242                    return;
243                }
244                Ok(_) => (),
245                Err(_) => {
246                    self.diagnostics.push(DefDiagnostic::unresolved_macro_call(
247                        self.module_id,
248                        MacroCallKind::Attr {
249                            ast_id,
250                            attr_args: None,
251                            censored_attr_ids: AttrMacroAttrIds::from_one(attr_id),
252                        },
253                        (*attr.path).clone(),
254                    ));
255                }
256            }
257        }
258
259        self.record_item(item);
260    }
261
262    fn record_item(&mut self, item: ast::AssocItem) {
263        match item {
264            ast::AssocItem::Fn(function) => {
265                let Some(name) = function.name() else { return };
266                let ast_id = self.ast_id_map.ast_id(&function);
267                let def = FunctionLoc {
268                    container: self.container,
269                    id: InFile::new(self.file_id, ast_id),
270                }
271                .intern(self.db);
272                self.items.push((name.as_name(), def.into()));
273            }
274            ast::AssocItem::TypeAlias(type_alias) => {
275                let Some(name) = type_alias.name() else { return };
276                let ast_id = self.ast_id_map.ast_id(&type_alias);
277                let def = TypeAliasLoc {
278                    container: self.container,
279                    id: InFile::new(self.file_id, ast_id),
280                }
281                .intern(self.db);
282                self.items.push((name.as_name(), def.into()));
283            }
284            ast::AssocItem::Const(konst) => {
285                let Some(name) = konst.name() else { return };
286                let ast_id = self.ast_id_map.ast_id(&konst);
287                let def =
288                    ConstLoc { container: self.container, id: InFile::new(self.file_id, ast_id) }
289                        .intern(self.db);
290                self.items.push((name.as_name(), def.into()));
291            }
292            ast::AssocItem::MacroCall(call) => {
293                let ast_id = self.ast_id_map.ast_id(&call);
294                let ast_id = InFile::new(self.file_id, ast_id);
295                let Some(path) = call.path() else { return };
296                let range = path.syntax().text_range();
297                let Some(path) = ModPath::from_src(self.db, path, &mut |range| {
298                    self.span_map.span_for_range(range).ctx
299                }) else {
300                    return;
301                };
302                let path = Interned::new(path);
303                let ctxt = self.span_map.span_for_range(range).ctx;
304
305                let resolver = |path: &_| {
306                    self.def_map
307                        .resolve_path(
308                            self.local_def_map,
309                            self.db,
310                            self.module_id,
311                            path,
312                            crate::item_scope::BuiltinShadowMode::Other,
313                            Some(MacroSubNs::Bang),
314                        )
315                        .0
316                        .take_macros()
317                        .map(|it| self.db.macro_def(it))
318                };
319                match macro_call_as_call_id(
320                    self.db,
321                    ast_id,
322                    &path,
323                    ctxt,
324                    ExpandTo::Items,
325                    self.module_id.krate(self.db),
326                    resolver,
327                    &mut |ptr, call_id| {
328                        self.macro_calls.push((ptr.map(|(_, it)| it.upcast()), call_id))
329                    },
330                ) {
331                    // FIXME: Expansion error?
332                    Ok(call_id) => match call_id.value {
333                        Some(call_id) => {
334                            self.macro_calls.push((ast_id.upcast(), call_id));
335                            self.collect_macro_items(call_id);
336                        }
337                        None => (),
338                    },
339                    Err(_) => {
340                        self.diagnostics.push(DefDiagnostic::unresolved_macro_call(
341                            self.module_id,
342                            MacroCallKind::FnLike {
343                                ast_id,
344                                expand_to: ExpandTo::Items,
345                                eager: None,
346                            },
347                            (*path).clone(),
348                        ));
349                    }
350                }
351            }
352        }
353    }
354
355    fn collect_macro_items(&mut self, macro_call_id: MacroCallId) {
356        if self.depth > self.def_map.recursion_limit() as usize {
357            tracing::warn!("macro expansion is too deep");
358            return;
359        }
360
361        let (syntax, span_map) = self.db.parse_macro_expansion(macro_call_id).value;
362        let old_file_id = mem::replace(&mut self.file_id, macro_call_id.into());
363        let old_ast_id_map = mem::replace(&mut self.ast_id_map, self.db.ast_id_map(self.file_id));
364        let old_span_map = mem::replace(&mut self.span_map, SpanMap::ExpansionSpanMap(span_map));
365        self.depth += 1;
366
367        let items = ast::MacroItems::cast(syntax.syntax_node()).expect("not `MacroItems`");
368        for item in items.items() {
369            let item = match item {
370                ast::Item::Fn(it) => ast::AssocItem::from(it),
371                ast::Item::Const(it) => it.into(),
372                ast::Item::TypeAlias(it) => it.into(),
373                ast::Item::MacroCall(it) => it.into(),
374                // FIXME: Should error on disallowed item kinds.
375                _ => continue,
376            };
377            self.collect_item(item);
378        }
379
380        self.depth -= 1;
381        self.file_id = old_file_id;
382        self.ast_id_map = old_ast_id_map;
383        self.span_map = old_span_map;
384    }
385}