diff options
author | Jonas Schievink <[email protected]> | 2021-05-20 18:56:04 +0100 |
---|---|---|
committer | Jonas Schievink <[email protected]> | 2021-05-20 18:56:04 +0100 |
commit | 036e5b2806256601408d91b5bbb4907bfb110760 (patch) | |
tree | 8742d859eed0fdd5a5ae8bb334718c514780350f /crates/hir_def | |
parent | f088606d8946d992c61153dc7f208efdaf9fb12d (diff) |
Refactor name resolution to resolve derive helpers
Diffstat (limited to 'crates/hir_def')
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 198 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/macros.rs | 22 |
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}; | |||
20 | use syntax::ast; | 20 | use syntax::ast; |
21 | 21 | ||
22 | use crate::{ | 22 | use 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] |
739 | fn 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] | ||
747 | struct S; | ||
748 | |||
749 | //- /proc.rs crate:proc | ||
750 | #[proc_macro_derive(Derive, attributes(helper))] | ||
751 | fn derive() {} | ||
752 | "#, | ||
753 | expect![[r#" | ||
754 | crate | ||
755 | S: t v | ||
756 | "#]], | ||
757 | ) | ||
758 | } | ||
759 | |||
760 | #[test] | ||
739 | fn macro_expansion_overflow() { | 761 | fn macro_expansion_overflow() { |
740 | cov_mark::check!(macro_expansion_overflow); | 762 | cov_mark::check!(macro_expansion_overflow); |
741 | check( | 763 | check( |