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