1use std::mem;
4
5use cfg::CfgOptions;
6use hir_expand::{
7 AstId, ExpandTo, HirFileId, InFile, Intern, Lookup, MacroCallKind, MacroDefKind,
8 mod_path::ModPath,
9 name::{AsName, Name},
10 span_map::SpanMap,
11};
12use intern::Interned;
13use span::AstIdMap;
14use syntax::{
15 AstNode,
16 ast::{self, HasModuleItem, HasName},
17};
18use thin_vec::ThinVec;
19use triomphe::Arc;
20
21use crate::{
22 AssocItemId, AstIdWithPath, ConstLoc, FunctionId, FunctionLoc, ImplId, ItemContainerId,
23 ItemLoc, MacroCallId, ModuleId, TraitId, TypeAliasId, TypeAliasLoc,
24 attr::Attrs,
25 db::DefDatabase,
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 DefDatabase, 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 DefDatabase,
51 tr: TraitId,
52 ) -> (TraitItems, DefDiagnostics) {
53 let ItemLoc { container: module_id, id: ast_id } = tr.lookup(db);
54 let ast_id_map = db.ast_id_map(ast_id.file_id);
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 DefDatabase, 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<'a> {
136 db: &'a dyn DefDatabase,
137 module_id: ModuleId,
138 def_map: &'a DefMap,
139 local_def_map: &'a LocalDefMap,
140 ast_id_map: Arc<AstIdMap>,
141 span_map: SpanMap,
142 cfg_options: &'a CfgOptions,
143 file_id: HirFileId,
144 diagnostics: Vec<DefDiagnostic>,
145 container: ItemContainerId,
146
147 depth: usize,
148 items: Vec<(Name, AssocItemId)>,
149 macro_calls: ThinVec<(AstId<ast::Item>, MacroCallId)>,
150}
151
152impl<'a> AssocItemCollector<'a> {
153 fn new(
154 db: &'a dyn DefDatabase,
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: db.ast_id_map(file_id),
166 span_map: db.span_map(file_id),
167 cfg_options: module_id.krate.cfg_options(db),
168 file_id,
169 container,
170 items: Vec::new(),
171
172 depth: 0,
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 = Attrs::new(self.db, &item, self.span_map.as_ref(), self.cfg_options);
195 if let Err(cfg) = attrs.is_cfg_enabled(self.cfg_options) {
196 self.diagnostics.push(DefDiagnostic::unconfigured_code(
197 self.module_id.local_id,
198 InFile::new(self.file_id, ast_id.erase()),
199 cfg,
200 self.cfg_options.clone(),
201 ));
202 return;
203 }
204 let ast_id = InFile::new(self.file_id, ast_id.upcast());
205
206 'attrs: for attr in &*attrs {
207 let ast_id_with_path = AstIdWithPath { path: attr.path.clone(), ast_id };
208
209 match self.def_map.resolve_attr_macro(
210 self.local_def_map,
211 self.db,
212 self.module_id.local_id,
213 ast_id_with_path,
214 attr,
215 ) {
216 Ok(ResolvedAttr::Macro(call_id)) => {
217 let loc = self.db.lookup_intern_macro_call(call_id);
218 if let MacroDefKind::ProcMacro(_, exp, _) = loc.def.kind {
219 if let Some(err) = exp.as_expand_error(self.module_id.krate) {
225 self.diagnostics.push(DefDiagnostic::macro_error(
226 self.module_id.local_id,
227 ast_id,
228 (*attr.path).clone(),
229 err,
230 ));
231 continue 'attrs;
232 }
233 }
234
235 self.macro_calls.push((ast_id, call_id));
236 self.collect_macro_items(call_id);
237 return;
238 }
239 Ok(_) => (),
240 Err(_) => {
241 self.diagnostics.push(DefDiagnostic::unresolved_macro_call(
242 self.module_id.local_id,
243 MacroCallKind::Attr { ast_id, attr_args: None, invoc_attr_index: attr.id },
244 attr.path().clone(),
245 ));
246 }
247 }
248 }
249
250 self.record_item(item);
251 }
252
253 fn record_item(&mut self, item: ast::AssocItem) {
254 match item {
255 ast::AssocItem::Fn(function) => {
256 let Some(name) = function.name() else { return };
257 let ast_id = self.ast_id_map.ast_id(&function);
258 let def = FunctionLoc {
259 container: self.container,
260 id: InFile::new(self.file_id, ast_id),
261 }
262 .intern(self.db);
263 self.items.push((name.as_name(), def.into()));
264 }
265 ast::AssocItem::TypeAlias(type_alias) => {
266 let Some(name) = type_alias.name() else { return };
267 let ast_id = self.ast_id_map.ast_id(&type_alias);
268 let def = TypeAliasLoc {
269 container: self.container,
270 id: InFile::new(self.file_id, ast_id),
271 }
272 .intern(self.db);
273 self.items.push((name.as_name(), def.into()));
274 }
275 ast::AssocItem::Const(konst) => {
276 let Some(name) = konst.name() else { return };
277 let ast_id = self.ast_id_map.ast_id(&konst);
278 let def =
279 ConstLoc { container: self.container, id: InFile::new(self.file_id, ast_id) }
280 .intern(self.db);
281 self.items.push((name.as_name(), def.into()));
282 }
283 ast::AssocItem::MacroCall(call) => {
284 let ast_id = self.ast_id_map.ast_id(&call);
285 let ast_id = InFile::new(self.file_id, ast_id);
286 let Some(path) = call.path() else { return };
287 let range = path.syntax().text_range();
288 let Some(path) = ModPath::from_src(self.db, path, &mut |range| {
289 self.span_map.span_for_range(range).ctx
290 }) else {
291 return;
292 };
293 let path = Interned::new(path);
294 let ctxt = self.span_map.span_for_range(range).ctx;
295
296 let resolver = |path: &_| {
297 self.def_map
298 .resolve_path(
299 self.local_def_map,
300 self.db,
301 self.module_id.local_id,
302 path,
303 crate::item_scope::BuiltinShadowMode::Other,
304 Some(MacroSubNs::Bang),
305 )
306 .0
307 .take_macros()
308 .map(|it| self.db.macro_def(it))
309 };
310 match macro_call_as_call_id(
311 self.db,
312 ast_id,
313 &path,
314 ctxt,
315 ExpandTo::Items,
316 self.module_id.krate(),
317 resolver,
318 &mut |ptr, call_id| {
319 self.macro_calls.push((ptr.map(|(_, it)| it.upcast()), call_id))
320 },
321 ) {
322 Ok(call_id) => match call_id.value {
324 Some(call_id) => {
325 self.macro_calls.push((ast_id.upcast(), call_id));
326 self.collect_macro_items(call_id);
327 }
328 None => (),
329 },
330 Err(_) => {
331 self.diagnostics.push(DefDiagnostic::unresolved_macro_call(
332 self.module_id.local_id,
333 MacroCallKind::FnLike {
334 ast_id,
335 expand_to: ExpandTo::Items,
336 eager: None,
337 },
338 (*path).clone(),
339 ));
340 }
341 }
342 }
343 }
344 }
345
346 fn collect_macro_items(&mut self, macro_call_id: MacroCallId) {
347 if self.depth > self.def_map.recursion_limit() as usize {
348 tracing::warn!("macro expansion is too deep");
349 return;
350 }
351
352 let (syntax, span_map) = self.db.parse_macro_expansion(macro_call_id).value;
353 let old_file_id = mem::replace(&mut self.file_id, macro_call_id.into());
354 let old_ast_id_map = mem::replace(&mut self.ast_id_map, self.db.ast_id_map(self.file_id));
355 let old_span_map = mem::replace(&mut self.span_map, SpanMap::ExpansionSpanMap(span_map));
356 self.depth += 1;
357
358 let items = ast::MacroItems::cast(syntax.syntax_node()).expect("not `MacroItems`");
359 for item in items.items() {
360 let item = match item {
361 ast::Item::Fn(it) => ast::AssocItem::from(it),
362 ast::Item::Const(it) => it.into(),
363 ast::Item::TypeAlias(it) => it.into(),
364 ast::Item::MacroCall(it) => it.into(),
365 _ => continue,
367 };
368 self.collect_item(item);
369 }
370
371 self.depth -= 1;
372 self.file_id = old_file_id;
373 self.ast_id_map = old_ast_id_map;
374 self.span_map = old_span_map;
375 }
376}