1use 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 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 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 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 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 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 _ => 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}