Skip to main content

hir_def/nameres/
assoc.rs

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