aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2021-05-20 18:56:04 +0100
committerJonas Schievink <[email protected]>2021-05-20 18:56:04 +0100
commit036e5b2806256601408d91b5bbb4907bfb110760 (patch)
tree8742d859eed0fdd5a5ae8bb334718c514780350f /crates
parentf088606d8946d992c61153dc7f208efdaf9fb12d (diff)
Refactor name resolution to resolve derive helpers
Diffstat (limited to 'crates')
-rw-r--r--crates/hir_def/src/nameres/collector.rs198
-rw-r--r--crates/hir_def/src/nameres/tests/macros.rs22
2 files changed, 135 insertions, 85 deletions
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 2c8f1b5b8..2d1cba632 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -20,7 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
20use syntax::ast; 20use syntax::ast;
21 21
22use crate::{ 22use crate::{
23 attr::{AttrId, Attrs}, 23 attr::{Attr, AttrId, Attrs},
24 builtin_attr, 24 builtin_attr,
25 db::DefDatabase, 25 db::DefDatabase,
26 derive_macro_as_call_id, 26 derive_macro_as_call_id,
@@ -100,8 +100,8 @@ pub(super) fn collect_defs(
100 proc_macros, 100 proc_macros,
101 exports_proc_macros: false, 101 exports_proc_macros: false,
102 from_glob_import: Default::default(), 102 from_glob_import: Default::default(),
103 ignore_attrs_on: FxHashSet::default(), 103 ignore_attrs_on: Default::default(),
104 derive_helpers_in_scope: FxHashMap::default(), 104 derive_helpers_in_scope: Default::default(),
105 }; 105 };
106 match block { 106 match block {
107 Some(block) => { 107 Some(block) => {
@@ -247,7 +247,13 @@ struct DefCollector<'a> {
247 proc_macros: Vec<(Name, ProcMacroExpander)>, 247 proc_macros: Vec<(Name, ProcMacroExpander)>,
248 exports_proc_macros: bool, 248 exports_proc_macros: bool,
249 from_glob_import: PerNsGlobImports, 249 from_glob_import: PerNsGlobImports,
250 ignore_attrs_on: FxHashSet<InFile<ModItem>>, 250 /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute.
251 /// This map is used to skip all attributes up to and including the one that failed to resolve,
252 /// in order to not expand them twice.
253 ///
254 /// This also stores the attributes to skip when we resolve derive helpers and non-macro
255 /// non-builtin attributes in general.
256 ignore_attrs_on: FxHashMap<InFile<ModItem>, AttrId>,
251 /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper 257 /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
252 /// attributes. 258 /// attributes.
253 derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>, 259 derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>,
@@ -319,7 +325,7 @@ impl DefCollector<'_> {
319 } 325 }
320 } 326 }
321 327
322 if self.reseed_with_unresolved_attributes() == ReachedFixedPoint::Yes { 328 if self.reseed_with_unresolved_attribute() == ReachedFixedPoint::Yes {
323 break; 329 break;
324 } 330 }
325 } 331 }
@@ -362,25 +368,21 @@ impl DefCollector<'_> {
362 } 368 }
363 369
364 /// When the fixed-point loop reaches a stable state, we might still have some unresolved 370 /// When the fixed-point loop reaches a stable state, we might still have some unresolved
365 /// attributes (or unexpanded attribute proc macros) left over. This takes them, and feeds the 371 /// attributes (or unexpanded attribute proc macros) left over. This takes one of them, and
366 /// item they're applied to back into name resolution. 372 /// feeds the item it's applied to back into name resolution.
367 /// 373 ///
368 /// This effectively ignores the fact that the macro is there and just treats the items as 374 /// This effectively ignores the fact that the macro is there and just treats the items as
369 /// normal code. 375 /// normal code.
370 /// 376 ///
371 /// This improves UX when proc macros are turned off or don't work, and replicates the behavior 377 /// This improves UX when proc macros are turned off or don't work, and replicates the behavior
372 /// before we supported proc. attribute macros. 378 /// before we supported proc. attribute macros.
373 fn reseed_with_unresolved_attributes(&mut self) -> ReachedFixedPoint { 379 fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint {
374 cov_mark::hit!(unresolved_attribute_fallback); 380 cov_mark::hit!(unresolved_attribute_fallback);
375 381
376 let mut added_items = false; 382 let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
377 let unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new()); 383 let pos = unresolved_macros.iter().position(|directive| {
378 for directive in &unresolved_macros { 384 if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind {
379 if let MacroDirectiveKind::Attr { ast_id, mod_item, .. } = &directive.kind { 385 self.ignore_attrs_on.insert(ast_id.ast_id.with_value(*mod_item), *attr);
380 // Make sure to only add such items once.
381 if !self.ignore_attrs_on.insert(ast_id.ast_id.with_value(*mod_item)) {
382 continue;
383 }
384 386
385 let file_id = self.def_map[directive.module_id].definition_source(self.db).file_id; 387 let file_id = self.def_map[directive.module_id].definition_source(self.db).file_id;
386 let item_tree = self.db.file_item_tree(file_id); 388 let item_tree = self.db.file_item_tree(file_id);
@@ -394,14 +396,20 @@ impl DefCollector<'_> {
394 mod_dir, 396 mod_dir,
395 } 397 }
396 .collect(&[*mod_item]); 398 .collect(&[*mod_item]);
397 added_items = true; 399 true
400 } else {
401 false
398 } 402 }
403 });
404
405 if let Some(pos) = pos {
406 unresolved_macros.remove(pos);
399 } 407 }
400 408
401 // The collection above might add new unresolved macros (eg. derives), so merge the lists. 409 // The collection above might add new unresolved macros (eg. derives), so merge the lists.
402 self.unresolved_macros.extend(unresolved_macros); 410 self.unresolved_macros.extend(unresolved_macros);
403 411
404 if added_items { 412 if pos.is_some() {
405 // Continue name resolution with the new data. 413 // Continue name resolution with the new data.
406 ReachedFixedPoint::No 414 ReachedFixedPoint::No
407 } else { 415 } else {
@@ -922,14 +930,45 @@ impl DefCollector<'_> {
922 Err(UnresolvedMacro { .. }) => (), 930 Err(UnresolvedMacro { .. }) => (),
923 } 931 }
924 } 932 }
925 MacroDirectiveKind::Attr { .. } => { 933 MacroDirectiveKind::Attr { ast_id, mod_item, attr } => {
926 // not yet :) 934 if let Some(ident) = ast_id.path.as_ident() {
935 if let Some(helpers) = self.derive_helpers_in_scope.get(&ast_id.ast_id) {
936 if helpers.contains(ident) {
937 cov_mark::hit!(resolved_derive_helper);
938
939 // Resolved to derive helper. Collect the item's attributes again,
940 // starting after the derive helper.
941 let file_id = self.def_map[directive.module_id]
942 .definition_source(self.db)
943 .file_id;
944 let item_tree = self.db.file_item_tree(file_id);
945 let mod_dir = self.mod_dirs[&directive.module_id].clone();
946 self.ignore_attrs_on.insert(InFile::new(file_id, *mod_item), *attr);
947 ModCollector {
948 def_collector: &mut *self,
949 macro_depth: directive.depth,
950 module_id: directive.module_id,
951 file_id,
952 item_tree: &item_tree,
953 mod_dir,
954 }
955 .collect(&[*mod_item]);
956
957 // Remove the original directive since we resolved it.
958 return false;
959 }
960 }
961 }
962
963 // Not resolved to a derive helper, so try to resolve as a macro.
964 // FIXME: not yet :)
927 } 965 }
928 } 966 }
929 967
930 true 968 true
931 }); 969 });
932 self.unresolved_macros = macros; 970 // Attribute resolution can add unresolved macro invocations, so concatenate the lists.
971 self.unresolved_macros.extend(macros);
933 972
934 for (module_id, macro_call_id, depth) in resolved { 973 for (module_id, macro_call_id, depth) in resolved {
935 self.collect_macro_expansion(module_id, macro_call_id, depth); 974 self.collect_macro_expansion(module_id, macro_call_id, depth);
@@ -1102,7 +1141,7 @@ impl ModCollector<'_, '_> {
1102 1141
1103 // Prelude module is always considered to be `#[macro_use]`. 1142 // Prelude module is always considered to be `#[macro_use]`.
1104 if let Some(prelude_module) = self.def_collector.def_map.prelude { 1143 if let Some(prelude_module) = self.def_collector.def_map.prelude {
1105 if prelude_module.krate != self.def_collector.def_map.krate { 1144 if prelude_module.krate != krate {
1106 cov_mark::hit!(prelude_is_macro_use); 1145 cov_mark::hit!(prelude_is_macro_use);
1107 self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); 1146 self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate);
1108 } 1147 }
@@ -1137,7 +1176,7 @@ impl ModCollector<'_, '_> {
1137 } 1176 }
1138 } 1177 }
1139 1178
1140 if let Err(()) = self.resolve_attributes(&attrs, None, item) { 1179 if let Err(()) = self.resolve_attributes(&attrs, item) {
1141 // Do not process the item. It has at least one non-builtin attribute, so the 1180 // Do not process the item. It has at least one non-builtin attribute, so the
1142 // fixed-point algorithm is required to resolve the rest of them. 1181 // fixed-point algorithm is required to resolve the rest of them.
1143 continue; 1182 continue;
@@ -1203,11 +1242,6 @@ impl ModCollector<'_, '_> {
1203 ModItem::Struct(id) => { 1242 ModItem::Struct(id) => {
1204 let it = &self.item_tree[id]; 1243 let it = &self.item_tree[id];
1205 1244
1206 // FIXME: check attrs to see if this is an attribute macro invocation;
1207 // in which case we don't add the invocation, just a single attribute
1208 // macro invocation
1209 self.collect_derives(&attrs, it.ast_id.upcast());
1210
1211 def = Some(DefData { 1245 def = Some(DefData {
1212 id: StructLoc { container: module, id: ItemTreeId::new(self.file_id, id) } 1246 id: StructLoc { container: module, id: ItemTreeId::new(self.file_id, id) }
1213 .intern(self.def_collector.db) 1247 .intern(self.def_collector.db)
@@ -1220,11 +1254,6 @@ impl ModCollector<'_, '_> {
1220 ModItem::Union(id) => { 1254 ModItem::Union(id) => {
1221 let it = &self.item_tree[id]; 1255 let it = &self.item_tree[id];
1222 1256
1223 // FIXME: check attrs to see if this is an attribute macro invocation;
1224 // in which case we don't add the invocation, just a single attribute
1225 // macro invocation
1226 self.collect_derives(&attrs, it.ast_id.upcast());
1227
1228 def = Some(DefData { 1257 def = Some(DefData {
1229 id: UnionLoc { container: module, id: ItemTreeId::new(self.file_id, id) } 1258 id: UnionLoc { container: module, id: ItemTreeId::new(self.file_id, id) }
1230 .intern(self.def_collector.db) 1259 .intern(self.def_collector.db)
@@ -1237,11 +1266,6 @@ impl ModCollector<'_, '_> {
1237 ModItem::Enum(id) => { 1266 ModItem::Enum(id) => {
1238 let it = &self.item_tree[id]; 1267 let it = &self.item_tree[id];
1239 1268
1240 // FIXME: check attrs to see if this is an attribute macro invocation;
1241 // in which case we don't add the invocation, just a single attribute
1242 // macro invocation
1243 self.collect_derives(&attrs, it.ast_id.upcast());
1244
1245 def = Some(DefData { 1269 def = Some(DefData {
1246 id: EnumLoc { container: module, id: ItemTreeId::new(self.file_id, id) } 1270 id: EnumLoc { container: module, id: ItemTreeId::new(self.file_id, id) }
1247 .intern(self.def_collector.db) 1271 .intern(self.def_collector.db)
@@ -1453,12 +1477,10 @@ impl ModCollector<'_, '_> {
1453 /// 1477 ///
1454 /// Returns `Err` when some attributes could not be resolved to builtins and have been 1478 /// Returns `Err` when some attributes could not be resolved to builtins and have been
1455 /// registered as unresolved. 1479 /// registered as unresolved.
1456 fn resolve_attributes( 1480 ///
1457 &mut self, 1481 /// If `ignore_up_to` is `Some`, attributes precending and including that attribute will be
1458 attrs: &Attrs, 1482 /// assumed to be resolved already.
1459 mut ignore_up_to: Option<AttrId>, 1483 fn resolve_attributes(&mut self, attrs: &Attrs, mod_item: ModItem) -> Result<(), ()> {
1460 mod_item: ModItem,
1461 ) -> Result<(), ()> {
1462 fn is_builtin_attr(path: &ModPath) -> bool { 1484 fn is_builtin_attr(path: &ModPath) -> bool {
1463 if path.kind == PathKind::Plain { 1485 if path.kind == PathKind::Plain {
1464 if let Some(tool_module) = path.segments().first() { 1486 if let Some(tool_module) = path.segments().first() {
@@ -1483,62 +1505,68 @@ impl ModCollector<'_, '_> {
1483 false 1505 false
1484 } 1506 }
1485 1507
1486 // We failed to resolve an attribute on this item earlier, and are falling back to treating 1508 let mut ignore_up_to =
1487 // the item as-is. 1509 self.def_collector.ignore_attrs_on.get(&InFile::new(self.file_id, mod_item)).copied();
1488 if self.def_collector.ignore_attrs_on.contains(&InFile::new(self.file_id, mod_item)) { 1510 for attr in attrs.iter().skip_while(|attr| match ignore_up_to {
1489 return Ok(()); 1511 Some(id) if attr.id == id => {
1490 } 1512 ignore_up_to = None;
1491 1513 true
1492 match attrs 1514 }
1493 .iter() 1515 Some(_) => true,
1494 .skip_while(|attr| match ignore_up_to { 1516 None => false,
1495 Some(id) if attr.id == id => { 1517 }) {
1496 ignore_up_to = None; 1518 if attr.path.as_ident() == Some(&hir_expand::name![derive]) {
1497 false 1519 self.collect_derive(attr, mod_item);
1498 } 1520 } else if is_builtin_attr(&attr.path) {
1499 Some(_) => true, 1521 continue;
1500 None => false, 1522 } else {
1501 }) 1523 log::debug!("non-builtin attribute {}", attr.path);
1502 .find(|attr| !is_builtin_attr(&attr.path))
1503 {
1504 Some(non_builtin_attr) => {
1505 log::debug!("non-builtin attribute {}", non_builtin_attr.path);
1506 1524
1507 let ast_id = AstIdWithPath::new( 1525 let ast_id = AstIdWithPath::new(
1508 self.file_id, 1526 self.file_id,
1509 mod_item.ast_id(self.item_tree), 1527 mod_item.ast_id(self.item_tree),
1510 non_builtin_attr.path.as_ref().clone(), 1528 attr.path.as_ref().clone(),
1511 ); 1529 );
1512 self.def_collector.unresolved_macros.push(MacroDirective { 1530 self.def_collector.unresolved_macros.push(MacroDirective {
1513 module_id: self.module_id, 1531 module_id: self.module_id,
1514 depth: self.macro_depth + 1, 1532 depth: self.macro_depth + 1,
1515 kind: MacroDirectiveKind::Attr { ast_id, attr: non_builtin_attr.id, mod_item }, 1533 kind: MacroDirectiveKind::Attr { ast_id, attr: attr.id, mod_item },
1516 }); 1534 });
1517 1535
1518 Err(()) 1536 return Err(());
1519 } 1537 }
1520 None => Ok(()),
1521 } 1538 }
1539
1540 Ok(())
1522 } 1541 }
1523 1542
1524 fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) { 1543 fn collect_derive(&mut self, attr: &Attr, mod_item: ModItem) {
1525 for derive in attrs.by_key("derive").attrs() { 1544 let ast_id: FileAstId<ast::Item> = match mod_item {
1526 match derive.parse_derive() { 1545 ModItem::Struct(it) => self.item_tree[it].ast_id.upcast(),
1527 Some(derive_macros) => { 1546 ModItem::Union(it) => self.item_tree[it].ast_id.upcast(),
1528 for path in derive_macros { 1547 ModItem::Enum(it) => self.item_tree[it].ast_id.upcast(),
1529 let ast_id = AstIdWithPath::new(self.file_id, ast_id, path); 1548 _ => {
1530 self.def_collector.unresolved_macros.push(MacroDirective { 1549 // Cannot use derive on this item.
1531 module_id: self.module_id, 1550 // FIXME: diagnose
1532 depth: self.macro_depth + 1, 1551 return;
1533 kind: MacroDirectiveKind::Derive { ast_id, derive_attr: derive.id }, 1552 }
1534 }); 1553 };
1535 } 1554
1536 } 1555 match attr.parse_derive() {
1537 None => { 1556 Some(derive_macros) => {
1538 // FIXME: diagnose 1557 for path in derive_macros {
1539 log::debug!("malformed derive: {:?}", derive); 1558 let ast_id = AstIdWithPath::new(self.file_id, ast_id, path);
1559 self.def_collector.unresolved_macros.push(MacroDirective {
1560 module_id: self.module_id,
1561 depth: self.macro_depth + 1,
1562 kind: MacroDirectiveKind::Derive { ast_id, derive_attr: attr.id },
1563 });
1540 } 1564 }
1541 } 1565 }
1566 None => {
1567 // FIXME: diagnose
1568 log::debug!("malformed derive: {:?}", attr);
1569 }
1542 } 1570 }
1543 } 1571 }
1544 1572
@@ -1753,7 +1781,7 @@ mod tests {
1753 proc_macros: Default::default(), 1781 proc_macros: Default::default(),
1754 exports_proc_macros: false, 1782 exports_proc_macros: false,
1755 from_glob_import: Default::default(), 1783 from_glob_import: Default::default(),
1756 ignore_attrs_on: FxHashSet::default(), 1784 ignore_attrs_on: Default::default(),
1757 derive_helpers_in_scope: FxHashMap::default(), 1785 derive_helpers_in_scope: FxHashMap::default(),
1758 }; 1786 };
1759 collector.seed_with_top_level(); 1787 collector.seed_with_top_level();
diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs
index 6eb5f97be..04de107f5 100644
--- a/crates/hir_def/src/nameres/tests/macros.rs
+++ b/crates/hir_def/src/nameres/tests/macros.rs
@@ -736,6 +736,28 @@ fn unresolved_attributes_fall_back_track_per_file_moditems() {
736} 736}
737 737
738#[test] 738#[test]
739fn resolves_derive_helper() {
740 cov_mark::check!(resolved_derive_helper);
741 check(
742 r#"
743//- /main.rs crate:main deps:proc
744#[derive(proc::Derive)]
745#[helper]
746#[unresolved]
747struct S;
748
749//- /proc.rs crate:proc
750#[proc_macro_derive(Derive, attributes(helper))]
751fn derive() {}
752 "#,
753 expect![[r#"
754 crate
755 S: t v
756 "#]],
757 )
758}
759
760#[test]
739fn macro_expansion_overflow() { 761fn macro_expansion_overflow() {
740 cov_mark::check!(macro_expansion_overflow); 762 cov_mark::check!(macro_expansion_overflow);
741 check( 763 check(