diff options
Diffstat (limited to 'crates/hir_def/src')
-rw-r--r-- | crates/hir_def/src/intern.rs | 8 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 180 |
2 files changed, 160 insertions, 28 deletions
diff --git a/crates/hir_def/src/intern.rs b/crates/hir_def/src/intern.rs index abc304ef0..5cc7f2df6 100644 --- a/crates/hir_def/src/intern.rs +++ b/crates/hir_def/src/intern.rs | |||
@@ -4,7 +4,7 @@ | |||
4 | 4 | ||
5 | use std::{ | 5 | use std::{ |
6 | collections::HashMap, | 6 | collections::HashMap, |
7 | fmt::{self, Debug}, | 7 | fmt::{self, Debug, Display}, |
8 | hash::{BuildHasherDefault, Hash, Hasher}, | 8 | hash::{BuildHasherDefault, Hash, Hasher}, |
9 | ops::Deref, | 9 | ops::Deref, |
10 | sync::Arc, | 10 | sync::Arc, |
@@ -171,6 +171,12 @@ impl<T: Debug + Internable + ?Sized> Debug for Interned<T> { | |||
171 | } | 171 | } |
172 | } | 172 | } |
173 | 173 | ||
174 | impl<T: Display + Internable + ?Sized> Display for Interned<T> { | ||
175 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
176 | (*self.arc).fmt(f) | ||
177 | } | ||
178 | } | ||
179 | |||
174 | pub struct InternStorage<T: ?Sized> { | 180 | pub struct InternStorage<T: ?Sized> { |
175 | map: OnceCell<InternMap<T>>, | 181 | map: OnceCell<InternMap<T>>, |
176 | } | 182 | } |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index d4840be2f..221a5a556 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -21,6 +21,7 @@ use syntax::ast; | |||
21 | 21 | ||
22 | use crate::{ | 22 | use crate::{ |
23 | attr::{AttrId, Attrs}, | 23 | attr::{AttrId, Attrs}, |
24 | builtin_attr, | ||
24 | db::DefDatabase, | 25 | db::DefDatabase, |
25 | derive_macro_as_call_id, | 26 | derive_macro_as_call_id, |
26 | intern::Interned, | 27 | intern::Interned, |
@@ -99,6 +100,7 @@ pub(super) fn collect_defs( | |||
99 | proc_macros, | 100 | proc_macros, |
100 | exports_proc_macros: false, | 101 | exports_proc_macros: false, |
101 | from_glob_import: Default::default(), | 102 | from_glob_import: Default::default(), |
103 | ignore_attrs_on: FxHashSet::default(), | ||
102 | }; | 104 | }; |
103 | match block { | 105 | match block { |
104 | Some(block) => { | 106 | Some(block) => { |
@@ -217,6 +219,7 @@ struct MacroDirective { | |||
217 | enum MacroDirectiveKind { | 219 | enum MacroDirectiveKind { |
218 | FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind }, | 220 | FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind }, |
219 | Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId }, | 221 | Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId }, |
222 | Attr { ast_id: AstIdWithPath<ast::Item>, attr: AttrId, mod_item: ModItem }, | ||
220 | } | 223 | } |
221 | 224 | ||
222 | struct DefData<'a> { | 225 | struct DefData<'a> { |
@@ -243,6 +246,7 @@ struct DefCollector<'a> { | |||
243 | proc_macros: Vec<(Name, ProcMacroExpander)>, | 246 | proc_macros: Vec<(Name, ProcMacroExpander)>, |
244 | exports_proc_macros: bool, | 247 | exports_proc_macros: bool, |
245 | from_glob_import: PerNsGlobImports, | 248 | from_glob_import: PerNsGlobImports, |
249 | ignore_attrs_on: FxHashSet<ModItem>, | ||
246 | } | 250 | } |
247 | 251 | ||
248 | impl DefCollector<'_> { | 252 | impl DefCollector<'_> { |
@@ -292,16 +296,26 @@ impl DefCollector<'_> { | |||
292 | fn collect(&mut self) { | 296 | fn collect(&mut self) { |
293 | // main name resolution fixed-point loop. | 297 | // main name resolution fixed-point loop. |
294 | let mut i = 0; | 298 | let mut i = 0; |
295 | loop { | 299 | 'outer: loop { |
296 | self.db.check_canceled(); | 300 | loop { |
297 | self.resolve_imports(); | 301 | self.db.check_canceled(); |
302 | loop { | ||
303 | if self.resolve_imports() == ReachedFixedPoint::Yes { | ||
304 | break; | ||
305 | } | ||
306 | } | ||
307 | if self.resolve_macros() == ReachedFixedPoint::Yes { | ||
308 | break; | ||
309 | } | ||
298 | 310 | ||
299 | match self.resolve_macros() { | 311 | i += 1; |
300 | ReachedFixedPoint::Yes => break, | 312 | if i == FIXED_POINT_LIMIT { |
301 | ReachedFixedPoint::No => i += 1, | 313 | log::error!("name resolution is stuck"); |
314 | break 'outer; | ||
315 | } | ||
302 | } | 316 | } |
303 | if i == FIXED_POINT_LIMIT { | 317 | |
304 | log::error!("name resolution is stuck"); | 318 | if self.reseed_with_unresolved_attributes() == ReachedFixedPoint::Yes { |
305 | break; | 319 | break; |
306 | } | 320 | } |
307 | } | 321 | } |
@@ -343,6 +357,50 @@ impl DefCollector<'_> { | |||
343 | } | 357 | } |
344 | } | 358 | } |
345 | 359 | ||
360 | /// When the fixed-point loop reaches a stable state, we might still have some unresolved | ||
361 | /// attributes (or unexpanded attribute proc macros) left over. This takes them, and feeds the | ||
362 | /// item they're applied to back into name resolution. | ||
363 | /// | ||
364 | /// This effectively ignores the fact that the macro is there and just treats the items as | ||
365 | /// normal code. | ||
366 | /// | ||
367 | /// This improves UX when proc macros are turned off or don't work, and replicates the behavior | ||
368 | /// before we supported proc. attribute macros. | ||
369 | fn reseed_with_unresolved_attributes(&mut self) -> ReachedFixedPoint { | ||
370 | let mut added_items = false; | ||
371 | let unexpanded_macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); | ||
372 | for directive in &unexpanded_macros { | ||
373 | if let MacroDirectiveKind::Attr { mod_item, .. } = &directive.kind { | ||
374 | // Make sure to only add such items once. | ||
375 | if !self.ignore_attrs_on.insert(*mod_item) { | ||
376 | continue; | ||
377 | } | ||
378 | |||
379 | let file_id = self.def_map[directive.module_id].definition_source(self.db).file_id; | ||
380 | let item_tree = self.db.file_item_tree(file_id); | ||
381 | let mod_dir = self.mod_dirs[&directive.module_id].clone(); | ||
382 | ModCollector { | ||
383 | def_collector: &mut *self, | ||
384 | macro_depth: directive.depth, | ||
385 | module_id: directive.module_id, | ||
386 | file_id, | ||
387 | item_tree: &item_tree, | ||
388 | mod_dir, | ||
389 | } | ||
390 | .collect(&[*mod_item]); | ||
391 | added_items = true; | ||
392 | } | ||
393 | } | ||
394 | self.unexpanded_macros = unexpanded_macros; | ||
395 | |||
396 | if added_items { | ||
397 | // Continue name resolution with the new data. | ||
398 | ReachedFixedPoint::No | ||
399 | } else { | ||
400 | ReachedFixedPoint::Yes | ||
401 | } | ||
402 | } | ||
403 | |||
346 | /// Adds a definition of procedural macro `name` to the root module. | 404 | /// Adds a definition of procedural macro `name` to the root module. |
347 | /// | 405 | /// |
348 | /// # Notes on procedural macro resolution | 406 | /// # Notes on procedural macro resolution |
@@ -504,35 +562,35 @@ impl DefCollector<'_> { | |||
504 | } | 562 | } |
505 | } | 563 | } |
506 | 564 | ||
507 | /// Import resolution | 565 | /// Tries to resolve every currently unresolved import. |
508 | /// | 566 | fn resolve_imports(&mut self) -> ReachedFixedPoint { |
509 | /// This is a fix point algorithm. We resolve imports until no forward | 567 | let mut res = ReachedFixedPoint::Yes; |
510 | /// progress in resolving imports is made | 568 | let imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); |
511 | fn resolve_imports(&mut self) { | 569 | let imports = imports |
512 | let mut n_previous_unresolved = self.unresolved_imports.len() + 1; | 570 | .into_iter() |
513 | 571 | .filter_map(|mut directive| { | |
514 | while self.unresolved_imports.len() < n_previous_unresolved { | ||
515 | n_previous_unresolved = self.unresolved_imports.len(); | ||
516 | let imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); | ||
517 | for mut directive in imports { | ||
518 | directive.status = self.resolve_import(directive.module_id, &directive.import); | 572 | directive.status = self.resolve_import(directive.module_id, &directive.import); |
519 | match directive.status { | 573 | match directive.status { |
520 | PartialResolvedImport::Indeterminate(_) => { | 574 | PartialResolvedImport::Indeterminate(_) => { |
521 | self.record_resolved_import(&directive); | 575 | self.record_resolved_import(&directive); |
522 | // FIXME: For avoid performance regression, | 576 | // FIXME: For avoid performance regression, |
523 | // we consider an imported resolved if it is indeterminate (i.e not all namespace resolved) | 577 | // we consider an imported resolved if it is indeterminate (i.e not all namespace resolved) |
524 | self.resolved_imports.push(directive) | 578 | self.resolved_imports.push(directive); |
579 | res = ReachedFixedPoint::No; | ||
580 | None | ||
525 | } | 581 | } |
526 | PartialResolvedImport::Resolved(_) => { | 582 | PartialResolvedImport::Resolved(_) => { |
527 | self.record_resolved_import(&directive); | 583 | self.record_resolved_import(&directive); |
528 | self.resolved_imports.push(directive) | 584 | self.resolved_imports.push(directive); |
529 | } | 585 | res = ReachedFixedPoint::No; |
530 | PartialResolvedImport::Unresolved => { | 586 | None |
531 | self.unresolved_imports.push(directive); | ||
532 | } | 587 | } |
588 | PartialResolvedImport::Unresolved => Some(directive), | ||
533 | } | 589 | } |
534 | } | 590 | }) |
535 | } | 591 | .collect(); |
592 | self.unresolved_imports = imports; | ||
593 | res | ||
536 | } | 594 | } |
537 | 595 | ||
538 | fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport { | 596 | fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport { |
@@ -856,6 +914,9 @@ impl DefCollector<'_> { | |||
856 | Err(UnresolvedMacro { .. }) => (), | 914 | Err(UnresolvedMacro { .. }) => (), |
857 | } | 915 | } |
858 | } | 916 | } |
917 | MacroDirectiveKind::Attr { .. } => { | ||
918 | // not yet :) | ||
919 | } | ||
859 | } | 920 | } |
860 | 921 | ||
861 | true | 922 | true |
@@ -948,7 +1009,7 @@ impl DefCollector<'_> { | |||
948 | )); | 1009 | )); |
949 | } | 1010 | } |
950 | }, | 1011 | }, |
951 | MacroDirectiveKind::Derive { .. } => { | 1012 | MacroDirectiveKind::Derive { .. } | MacroDirectiveKind::Attr { .. } => { |
952 | // FIXME: we might want to diagnose this too | 1013 | // FIXME: we might want to diagnose this too |
953 | } | 1014 | } |
954 | } | 1015 | } |
@@ -1056,6 +1117,14 @@ impl ModCollector<'_, '_> { | |||
1056 | continue; | 1117 | continue; |
1057 | } | 1118 | } |
1058 | } | 1119 | } |
1120 | |||
1121 | if let Err(()) = self.resolve_attributes(&attrs, item) { | ||
1122 | // Do not process the item. It has at least one non-builtin attribute, which *must* | ||
1123 | // resolve to a proc macro (or fail to resolve), so we'll never see this item during | ||
1124 | // normal name resolution. | ||
1125 | continue; | ||
1126 | } | ||
1127 | |||
1059 | let module = self.def_collector.def_map.module_id(self.module_id); | 1128 | let module = self.def_collector.def_map.module_id(self.module_id); |
1060 | 1129 | ||
1061 | let mut def = None; | 1130 | let mut def = None; |
@@ -1362,6 +1431,62 @@ impl ModCollector<'_, '_> { | |||
1362 | res | 1431 | res |
1363 | } | 1432 | } |
1364 | 1433 | ||
1434 | /// Resolves attributes on an item. | ||
1435 | /// | ||
1436 | /// Returns `Err` when some attributes could not be resolved to builtins and have been | ||
1437 | /// registered as unresolved. | ||
1438 | fn resolve_attributes(&mut self, attrs: &Attrs, mod_item: ModItem) -> Result<(), ()> { | ||
1439 | fn is_builtin_attr(path: &ModPath) -> bool { | ||
1440 | if path.kind == PathKind::Plain { | ||
1441 | if let Some(tool_module) = path.segments().first() { | ||
1442 | let tool_module = tool_module.to_string(); | ||
1443 | if builtin_attr::TOOL_MODULES.iter().any(|m| tool_module == *m) { | ||
1444 | return true; | ||
1445 | } | ||
1446 | } | ||
1447 | |||
1448 | if let Some(name) = path.as_ident() { | ||
1449 | let name = name.to_string(); | ||
1450 | if builtin_attr::INERT_ATTRIBUTES | ||
1451 | .iter() | ||
1452 | .chain(builtin_attr::EXTRA_ATTRIBUTES) | ||
1453 | .any(|attr| name == *attr) | ||
1454 | { | ||
1455 | return true; | ||
1456 | } | ||
1457 | } | ||
1458 | } | ||
1459 | |||
1460 | false | ||
1461 | } | ||
1462 | |||
1463 | // We failed to resolve an attribute on this item earlier, and are falling back to treating | ||
1464 | // the item as-is. | ||
1465 | if self.def_collector.ignore_attrs_on.contains(&mod_item) { | ||
1466 | return Ok(()); | ||
1467 | } | ||
1468 | |||
1469 | match attrs.iter().find(|attr| !is_builtin_attr(&attr.path)) { | ||
1470 | Some(non_builtin_attr) => { | ||
1471 | log::debug!("non-builtin attribute {}", non_builtin_attr.path); | ||
1472 | |||
1473 | let ast_id = AstIdWithPath::new( | ||
1474 | self.file_id, | ||
1475 | mod_item.ast_id(self.item_tree), | ||
1476 | non_builtin_attr.path.as_ref().clone(), | ||
1477 | ); | ||
1478 | self.def_collector.unexpanded_macros.push(MacroDirective { | ||
1479 | module_id: self.module_id, | ||
1480 | depth: self.macro_depth + 1, | ||
1481 | kind: MacroDirectiveKind::Attr { ast_id, attr: non_builtin_attr.id, mod_item }, | ||
1482 | }); | ||
1483 | |||
1484 | Err(()) | ||
1485 | } | ||
1486 | None => Ok(()), | ||
1487 | } | ||
1488 | } | ||
1489 | |||
1365 | fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) { | 1490 | fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) { |
1366 | for derive in attrs.by_key("derive").attrs() { | 1491 | for derive in attrs.by_key("derive").attrs() { |
1367 | match derive.parse_derive() { | 1492 | match derive.parse_derive() { |
@@ -1594,6 +1719,7 @@ mod tests { | |||
1594 | proc_macros: Default::default(), | 1719 | proc_macros: Default::default(), |
1595 | exports_proc_macros: false, | 1720 | exports_proc_macros: false, |
1596 | from_glob_import: Default::default(), | 1721 | from_glob_import: Default::default(), |
1722 | ignore_attrs_on: FxHashSet::default(), | ||
1597 | }; | 1723 | }; |
1598 | collector.seed_with_top_level(); | 1724 | collector.seed_with_top_level(); |
1599 | collector.collect(); | 1725 | collector.collect(); |