diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 293 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/diagnostics.rs | 17 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/macros.rs | 22 | ||||
-rw-r--r-- | crates/hir_expand/src/name.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 17 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/merge_imports.rs | 14 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/merge_imports.rs | 15 | ||||
-rw-r--r-- | crates/stdx/src/lib.rs | 28 | ||||
-rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 23 |
9 files changed, 312 insertions, 119 deletions
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 3896be25d..e76d039b8 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, AttrInput, 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, |
@@ -94,14 +94,16 @@ pub(super) fn collect_defs( | |||
94 | unresolved_imports: Vec::new(), | 94 | unresolved_imports: Vec::new(), |
95 | resolved_imports: Vec::new(), | 95 | resolved_imports: Vec::new(), |
96 | 96 | ||
97 | unexpanded_macros: Vec::new(), | 97 | unresolved_macros: Vec::new(), |
98 | mod_dirs: FxHashMap::default(), | 98 | mod_dirs: FxHashMap::default(), |
99 | cfg_options, | 99 | cfg_options, |
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 | skip_attrs: Default::default(), |
104 | derive_helpers_in_scope: FxHashMap::default(), | 104 | derive_helpers_in_scope: Default::default(), |
105 | registered_attrs: Default::default(), | ||
106 | registered_tools: Default::default(), | ||
105 | }; | 107 | }; |
106 | match block { | 108 | match block { |
107 | Some(block) => { | 109 | Some(block) => { |
@@ -237,7 +239,7 @@ struct DefCollector<'a> { | |||
237 | glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>, | 239 | glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>, |
238 | unresolved_imports: Vec<ImportDirective>, | 240 | unresolved_imports: Vec<ImportDirective>, |
239 | resolved_imports: Vec<ImportDirective>, | 241 | resolved_imports: Vec<ImportDirective>, |
240 | unexpanded_macros: Vec<MacroDirective>, | 242 | unresolved_macros: Vec<MacroDirective>, |
241 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, | 243 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, |
242 | cfg_options: &'a CfgOptions, | 244 | cfg_options: &'a CfgOptions, |
243 | /// List of procedural macros defined by this crate. This is read from the dynamic library | 245 | /// List of procedural macros defined by this crate. This is read from the dynamic library |
@@ -247,10 +249,20 @@ struct DefCollector<'a> { | |||
247 | proc_macros: Vec<(Name, ProcMacroExpander)>, | 249 | proc_macros: Vec<(Name, ProcMacroExpander)>, |
248 | exports_proc_macros: bool, | 250 | exports_proc_macros: bool, |
249 | from_glob_import: PerNsGlobImports, | 251 | from_glob_import: PerNsGlobImports, |
250 | ignore_attrs_on: FxHashSet<InFile<ModItem>>, | 252 | /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute. |
253 | /// This map is used to skip all attributes up to and including the one that failed to resolve, | ||
254 | /// in order to not expand them twice. | ||
255 | /// | ||
256 | /// This also stores the attributes to skip when we resolve derive helpers and non-macro | ||
257 | /// non-builtin attributes in general. | ||
258 | skip_attrs: FxHashMap<InFile<ModItem>, AttrId>, | ||
251 | /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper | 259 | /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper |
252 | /// attributes. | 260 | /// attributes. |
253 | derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>, | 261 | derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>, |
262 | /// Custom attributes registered with `#![register_attr]`. | ||
263 | registered_attrs: Vec<String>, | ||
264 | /// Custom tool modules registered with `#![register_tool]`. | ||
265 | registered_tools: Vec<String>, | ||
254 | } | 266 | } |
255 | 267 | ||
256 | impl DefCollector<'_> { | 268 | impl DefCollector<'_> { |
@@ -259,11 +271,39 @@ impl DefCollector<'_> { | |||
259 | let item_tree = self.db.file_item_tree(file_id.into()); | 271 | let item_tree = self.db.file_item_tree(file_id.into()); |
260 | let module_id = self.def_map.root; | 272 | let module_id = self.def_map.root; |
261 | self.def_map.modules[module_id].origin = ModuleOrigin::CrateRoot { definition: file_id }; | 273 | self.def_map.modules[module_id].origin = ModuleOrigin::CrateRoot { definition: file_id }; |
262 | if item_tree | 274 | |
263 | .top_level_attrs(self.db, self.def_map.krate) | 275 | let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate); |
264 | .cfg() | 276 | if attrs.cfg().map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)) { |
265 | .map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)) | 277 | // Process other crate-level attributes. |
266 | { | 278 | for attr in &*attrs { |
279 | let attr_name = match attr.path.as_ident() { | ||
280 | Some(name) => name, | ||
281 | None => continue, | ||
282 | }; | ||
283 | |||
284 | let registered_name = if *attr_name == hir_expand::name![register_attr] | ||
285 | || *attr_name == hir_expand::name![register_tool] | ||
286 | { | ||
287 | match &attr.input { | ||
288 | Some(AttrInput::TokenTree(subtree)) => match &*subtree.token_trees { | ||
289 | [tt::TokenTree::Leaf(tt::Leaf::Ident(name))] => name.as_name(), | ||
290 | _ => continue, | ||
291 | }, | ||
292 | _ => continue, | ||
293 | } | ||
294 | } else { | ||
295 | continue; | ||
296 | }; | ||
297 | |||
298 | if *attr_name == hir_expand::name![register_attr] { | ||
299 | self.registered_attrs.push(registered_name.to_string()); | ||
300 | cov_mark::hit!(register_attr); | ||
301 | } else { | ||
302 | self.registered_tools.push(registered_name.to_string()); | ||
303 | cov_mark::hit!(register_tool); | ||
304 | } | ||
305 | } | ||
306 | |||
267 | ModCollector { | 307 | ModCollector { |
268 | def_collector: &mut *self, | 308 | def_collector: &mut *self, |
269 | macro_depth: 0, | 309 | macro_depth: 0, |
@@ -319,7 +359,7 @@ impl DefCollector<'_> { | |||
319 | } | 359 | } |
320 | } | 360 | } |
321 | 361 | ||
322 | if self.reseed_with_unresolved_attributes() == ReachedFixedPoint::Yes { | 362 | if self.reseed_with_unresolved_attribute() == ReachedFixedPoint::Yes { |
323 | break; | 363 | break; |
324 | } | 364 | } |
325 | } | 365 | } |
@@ -362,27 +402,23 @@ impl DefCollector<'_> { | |||
362 | } | 402 | } |
363 | 403 | ||
364 | /// When the fixed-point loop reaches a stable state, we might still have some unresolved | 404 | /// 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 | 405 | /// attributes (or unexpanded attribute proc macros) left over. This takes one of them, and |
366 | /// item they're applied to back into name resolution. | 406 | /// feeds the item it's applied to back into name resolution. |
367 | /// | 407 | /// |
368 | /// This effectively ignores the fact that the macro is there and just treats the items as | 408 | /// This effectively ignores the fact that the macro is there and just treats the items as |
369 | /// normal code. | 409 | /// normal code. |
370 | /// | 410 | /// |
371 | /// This improves UX when proc macros are turned off or don't work, and replicates the behavior | 411 | /// This improves UX when proc macros are turned off or don't work, and replicates the behavior |
372 | /// before we supported proc. attribute macros. | 412 | /// before we supported proc. attribute macros. |
373 | fn reseed_with_unresolved_attributes(&mut self) -> ReachedFixedPoint { | 413 | fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint { |
374 | cov_mark::hit!(unresolved_attribute_fallback); | 414 | cov_mark::hit!(unresolved_attribute_fallback); |
375 | 415 | ||
376 | let mut added_items = false; | 416 | let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new()); |
377 | let unexpanded_macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); | 417 | let pos = unresolved_macros.iter().position(|directive| { |
378 | for directive in &unexpanded_macros { | 418 | if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind { |
379 | if let MacroDirectiveKind::Attr { ast_id, mod_item, .. } = &directive.kind { | 419 | self.skip_attrs.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 | 420 | ||
385 | let file_id = self.def_map[directive.module_id].definition_source(self.db).file_id; | 421 | let file_id = ast_id.ast_id.file_id; |
386 | let item_tree = self.db.file_item_tree(file_id); | 422 | let item_tree = self.db.file_item_tree(file_id); |
387 | let mod_dir = self.mod_dirs[&directive.module_id].clone(); | 423 | let mod_dir = self.mod_dirs[&directive.module_id].clone(); |
388 | ModCollector { | 424 | ModCollector { |
@@ -394,14 +430,20 @@ impl DefCollector<'_> { | |||
394 | mod_dir, | 430 | mod_dir, |
395 | } | 431 | } |
396 | .collect(&[*mod_item]); | 432 | .collect(&[*mod_item]); |
397 | added_items = true; | 433 | true |
434 | } else { | ||
435 | false | ||
398 | } | 436 | } |
437 | }); | ||
438 | |||
439 | if let Some(pos) = pos { | ||
440 | unresolved_macros.remove(pos); | ||
399 | } | 441 | } |
400 | 442 | ||
401 | // The collection above might add new unresolved macros (eg. derives), so merge the lists. | 443 | // The collection above might add new unresolved macros (eg. derives), so merge the lists. |
402 | self.unexpanded_macros.extend(unexpanded_macros); | 444 | self.unresolved_macros.extend(unresolved_macros); |
403 | 445 | ||
404 | if added_items { | 446 | if pos.is_some() { |
405 | // Continue name resolution with the new data. | 447 | // Continue name resolution with the new data. |
406 | ReachedFixedPoint::No | 448 | ReachedFixedPoint::No |
407 | } else { | 449 | } else { |
@@ -873,7 +915,7 @@ impl DefCollector<'_> { | |||
873 | } | 915 | } |
874 | 916 | ||
875 | fn resolve_macros(&mut self) -> ReachedFixedPoint { | 917 | fn resolve_macros(&mut self) -> ReachedFixedPoint { |
876 | let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); | 918 | let mut macros = std::mem::replace(&mut self.unresolved_macros, Vec::new()); |
877 | let mut resolved = Vec::new(); | 919 | let mut resolved = Vec::new(); |
878 | let mut res = ReachedFixedPoint::Yes; | 920 | let mut res = ReachedFixedPoint::Yes; |
879 | macros.retain(|directive| { | 921 | macros.retain(|directive| { |
@@ -922,14 +964,43 @@ impl DefCollector<'_> { | |||
922 | Err(UnresolvedMacro { .. }) => (), | 964 | Err(UnresolvedMacro { .. }) => (), |
923 | } | 965 | } |
924 | } | 966 | } |
925 | MacroDirectiveKind::Attr { .. } => { | 967 | MacroDirectiveKind::Attr { ast_id, mod_item, attr } => { |
926 | // not yet :) | 968 | if let Some(ident) = ast_id.path.as_ident() { |
969 | if let Some(helpers) = self.derive_helpers_in_scope.get(&ast_id.ast_id) { | ||
970 | if helpers.contains(ident) { | ||
971 | cov_mark::hit!(resolved_derive_helper); | ||
972 | |||
973 | // Resolved to derive helper. Collect the item's attributes again, | ||
974 | // starting after the derive helper. | ||
975 | let file_id = ast_id.ast_id.file_id; | ||
976 | let item_tree = self.db.file_item_tree(file_id); | ||
977 | let mod_dir = self.mod_dirs[&directive.module_id].clone(); | ||
978 | self.skip_attrs.insert(InFile::new(file_id, *mod_item), *attr); | ||
979 | ModCollector { | ||
980 | def_collector: &mut *self, | ||
981 | macro_depth: directive.depth, | ||
982 | module_id: directive.module_id, | ||
983 | file_id, | ||
984 | item_tree: &item_tree, | ||
985 | mod_dir, | ||
986 | } | ||
987 | .collect(&[*mod_item]); | ||
988 | |||
989 | // Remove the original directive since we resolved it. | ||
990 | return false; | ||
991 | } | ||
992 | } | ||
993 | } | ||
994 | |||
995 | // Not resolved to a derive helper, so try to resolve as a macro. | ||
996 | // FIXME: not yet :) | ||
927 | } | 997 | } |
928 | } | 998 | } |
929 | 999 | ||
930 | true | 1000 | true |
931 | }); | 1001 | }); |
932 | self.unexpanded_macros = macros; | 1002 | // Attribute resolution can add unresolved macro invocations, so concatenate the lists. |
1003 | self.unresolved_macros.extend(macros); | ||
933 | 1004 | ||
934 | for (module_id, macro_call_id, depth) in resolved { | 1005 | for (module_id, macro_call_id, depth) in resolved { |
935 | self.collect_macro_expansion(module_id, macro_call_id, depth); | 1006 | self.collect_macro_expansion(module_id, macro_call_id, depth); |
@@ -1000,7 +1071,7 @@ impl DefCollector<'_> { | |||
1000 | fn finish(mut self) -> DefMap { | 1071 | fn finish(mut self) -> DefMap { |
1001 | // Emit diagnostics for all remaining unexpanded macros. | 1072 | // Emit diagnostics for all remaining unexpanded macros. |
1002 | 1073 | ||
1003 | for directive in &self.unexpanded_macros { | 1074 | for directive in &self.unresolved_macros { |
1004 | match &directive.kind { | 1075 | match &directive.kind { |
1005 | MacroDirectiveKind::FnLike { ast_id, fragment } => match macro_call_as_call_id( | 1076 | MacroDirectiveKind::FnLike { ast_id, fragment } => match macro_call_as_call_id( |
1006 | ast_id, | 1077 | ast_id, |
@@ -1102,7 +1173,7 @@ impl ModCollector<'_, '_> { | |||
1102 | 1173 | ||
1103 | // Prelude module is always considered to be `#[macro_use]`. | 1174 | // Prelude module is always considered to be `#[macro_use]`. |
1104 | if let Some(prelude_module) = self.def_collector.def_map.prelude { | 1175 | if let Some(prelude_module) = self.def_collector.def_map.prelude { |
1105 | if prelude_module.krate != self.def_collector.def_map.krate { | 1176 | if prelude_module.krate != krate { |
1106 | cov_mark::hit!(prelude_is_macro_use); | 1177 | cov_mark::hit!(prelude_is_macro_use); |
1107 | self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); | 1178 | self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); |
1108 | } | 1179 | } |
@@ -1203,11 +1274,6 @@ impl ModCollector<'_, '_> { | |||
1203 | ModItem::Struct(id) => { | 1274 | ModItem::Struct(id) => { |
1204 | let it = &self.item_tree[id]; | 1275 | let it = &self.item_tree[id]; |
1205 | 1276 | ||
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 { | 1277 | def = Some(DefData { |
1212 | id: StructLoc { container: module, id: ItemTreeId::new(self.file_id, id) } | 1278 | id: StructLoc { container: module, id: ItemTreeId::new(self.file_id, id) } |
1213 | .intern(self.def_collector.db) | 1279 | .intern(self.def_collector.db) |
@@ -1220,11 +1286,6 @@ impl ModCollector<'_, '_> { | |||
1220 | ModItem::Union(id) => { | 1286 | ModItem::Union(id) => { |
1221 | let it = &self.item_tree[id]; | 1287 | let it = &self.item_tree[id]; |
1222 | 1288 | ||
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 { | 1289 | def = Some(DefData { |
1229 | id: UnionLoc { container: module, id: ItemTreeId::new(self.file_id, id) } | 1290 | id: UnionLoc { container: module, id: ItemTreeId::new(self.file_id, id) } |
1230 | .intern(self.def_collector.db) | 1291 | .intern(self.def_collector.db) |
@@ -1237,11 +1298,6 @@ impl ModCollector<'_, '_> { | |||
1237 | ModItem::Enum(id) => { | 1298 | ModItem::Enum(id) => { |
1238 | let it = &self.item_tree[id]; | 1299 | let it = &self.item_tree[id]; |
1239 | 1300 | ||
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 { | 1301 | def = Some(DefData { |
1246 | id: EnumLoc { container: module, id: ItemTreeId::new(self.file_id, id) } | 1302 | id: EnumLoc { container: module, id: ItemTreeId::new(self.file_id, id) } |
1247 | .intern(self.def_collector.db) | 1303 | .intern(self.def_collector.db) |
@@ -1453,76 +1509,103 @@ impl ModCollector<'_, '_> { | |||
1453 | /// | 1509 | /// |
1454 | /// Returns `Err` when some attributes could not be resolved to builtins and have been | 1510 | /// Returns `Err` when some attributes could not be resolved to builtins and have been |
1455 | /// registered as unresolved. | 1511 | /// registered as unresolved. |
1512 | /// | ||
1513 | /// If `ignore_up_to` is `Some`, attributes precending and including that attribute will be | ||
1514 | /// assumed to be resolved already. | ||
1456 | fn resolve_attributes(&mut self, attrs: &Attrs, mod_item: ModItem) -> Result<(), ()> { | 1515 | fn resolve_attributes(&mut self, attrs: &Attrs, mod_item: ModItem) -> Result<(), ()> { |
1457 | fn is_builtin_attr(path: &ModPath) -> bool { | 1516 | let mut ignore_up_to = |
1458 | if path.kind == PathKind::Plain { | 1517 | self.def_collector.skip_attrs.get(&InFile::new(self.file_id, mod_item)).copied(); |
1459 | if let Some(tool_module) = path.segments().first() { | 1518 | for attr in attrs.iter().skip_while(|attr| match ignore_up_to { |
1460 | let tool_module = tool_module.to_string(); | 1519 | Some(id) if attr.id == id => { |
1461 | if builtin_attr::TOOL_MODULES.iter().any(|m| tool_module == *m) { | 1520 | ignore_up_to = None; |
1462 | return true; | 1521 | true |
1463 | } | ||
1464 | } | ||
1465 | |||
1466 | if let Some(name) = path.as_ident() { | ||
1467 | let name = name.to_string(); | ||
1468 | if builtin_attr::INERT_ATTRIBUTES | ||
1469 | .iter() | ||
1470 | .chain(builtin_attr::EXTRA_ATTRIBUTES) | ||
1471 | .any(|attr| name == *attr) | ||
1472 | { | ||
1473 | return true; | ||
1474 | } | ||
1475 | } | ||
1476 | } | 1522 | } |
1477 | 1523 | Some(_) => true, | |
1478 | false | 1524 | None => false, |
1479 | } | 1525 | }) { |
1480 | 1526 | if attr.path.as_ident() == Some(&hir_expand::name![derive]) { | |
1481 | // We failed to resolve an attribute on this item earlier, and are falling back to treating | 1527 | self.collect_derive(attr, mod_item); |
1482 | // the item as-is. | 1528 | } else if self.is_builtin_or_registered_attr(&attr.path) { |
1483 | if self.def_collector.ignore_attrs_on.contains(&InFile::new(self.file_id, mod_item)) { | 1529 | continue; |
1484 | return Ok(()); | 1530 | } else { |
1485 | } | 1531 | log::debug!("non-builtin attribute {}", attr.path); |
1486 | |||
1487 | match attrs.iter().find(|attr| !is_builtin_attr(&attr.path)) { | ||
1488 | Some(non_builtin_attr) => { | ||
1489 | log::debug!("non-builtin attribute {}", non_builtin_attr.path); | ||
1490 | 1532 | ||
1491 | let ast_id = AstIdWithPath::new( | 1533 | let ast_id = AstIdWithPath::new( |
1492 | self.file_id, | 1534 | self.file_id, |
1493 | mod_item.ast_id(self.item_tree), | 1535 | mod_item.ast_id(self.item_tree), |
1494 | non_builtin_attr.path.as_ref().clone(), | 1536 | attr.path.as_ref().clone(), |
1495 | ); | 1537 | ); |
1496 | self.def_collector.unexpanded_macros.push(MacroDirective { | 1538 | self.def_collector.unresolved_macros.push(MacroDirective { |
1497 | module_id: self.module_id, | 1539 | module_id: self.module_id, |
1498 | depth: self.macro_depth + 1, | 1540 | depth: self.macro_depth + 1, |
1499 | kind: MacroDirectiveKind::Attr { ast_id, attr: non_builtin_attr.id, mod_item }, | 1541 | kind: MacroDirectiveKind::Attr { ast_id, attr: attr.id, mod_item }, |
1500 | }); | 1542 | }); |
1501 | 1543 | ||
1502 | Err(()) | 1544 | return Err(()); |
1503 | } | 1545 | } |
1504 | None => Ok(()), | ||
1505 | } | 1546 | } |
1547 | |||
1548 | Ok(()) | ||
1506 | } | 1549 | } |
1507 | 1550 | ||
1508 | fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) { | 1551 | fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool { |
1509 | for derive in attrs.by_key("derive").attrs() { | 1552 | if path.kind == PathKind::Plain { |
1510 | match derive.parse_derive() { | 1553 | if let Some(tool_module) = path.segments().first() { |
1511 | Some(derive_macros) => { | 1554 | let tool_module = tool_module.to_string(); |
1512 | for path in derive_macros { | 1555 | if builtin_attr::TOOL_MODULES |
1513 | let ast_id = AstIdWithPath::new(self.file_id, ast_id, path); | 1556 | .iter() |
1514 | self.def_collector.unexpanded_macros.push(MacroDirective { | 1557 | .copied() |
1515 | module_id: self.module_id, | 1558 | .chain(self.def_collector.registered_tools.iter().map(|s| &**s)) |
1516 | depth: self.macro_depth + 1, | 1559 | .any(|m| tool_module == *m) |
1517 | kind: MacroDirectiveKind::Derive { ast_id, derive_attr: derive.id }, | 1560 | { |
1518 | }); | 1561 | return true; |
1519 | } | ||
1520 | } | 1562 | } |
1521 | None => { | 1563 | } |
1522 | // FIXME: diagnose | 1564 | |
1523 | log::debug!("malformed derive: {:?}", derive); | 1565 | if let Some(name) = path.as_ident() { |
1566 | let name = name.to_string(); | ||
1567 | if builtin_attr::INERT_ATTRIBUTES | ||
1568 | .iter() | ||
1569 | .chain(builtin_attr::EXTRA_ATTRIBUTES) | ||
1570 | .copied() | ||
1571 | .chain(self.def_collector.registered_attrs.iter().map(|s| &**s)) | ||
1572 | .any(|attr| name == *attr) | ||
1573 | { | ||
1574 | return true; | ||
1575 | } | ||
1576 | } | ||
1577 | } | ||
1578 | |||
1579 | false | ||
1580 | } | ||
1581 | |||
1582 | fn collect_derive(&mut self, attr: &Attr, mod_item: ModItem) { | ||
1583 | let ast_id: FileAstId<ast::Item> = match mod_item { | ||
1584 | ModItem::Struct(it) => self.item_tree[it].ast_id.upcast(), | ||
1585 | ModItem::Union(it) => self.item_tree[it].ast_id.upcast(), | ||
1586 | ModItem::Enum(it) => self.item_tree[it].ast_id.upcast(), | ||
1587 | _ => { | ||
1588 | // Cannot use derive on this item. | ||
1589 | // FIXME: diagnose | ||
1590 | return; | ||
1591 | } | ||
1592 | }; | ||
1593 | |||
1594 | match attr.parse_derive() { | ||
1595 | Some(derive_macros) => { | ||
1596 | for path in derive_macros { | ||
1597 | let ast_id = AstIdWithPath::new(self.file_id, ast_id, path); | ||
1598 | self.def_collector.unresolved_macros.push(MacroDirective { | ||
1599 | module_id: self.module_id, | ||
1600 | depth: self.macro_depth + 1, | ||
1601 | kind: MacroDirectiveKind::Derive { ast_id, derive_attr: attr.id }, | ||
1602 | }); | ||
1524 | } | 1603 | } |
1525 | } | 1604 | } |
1605 | None => { | ||
1606 | // FIXME: diagnose | ||
1607 | log::debug!("malformed derive: {:?}", attr); | ||
1608 | } | ||
1526 | } | 1609 | } |
1527 | } | 1610 | } |
1528 | 1611 | ||
@@ -1686,7 +1769,7 @@ impl ModCollector<'_, '_> { | |||
1686 | ast_id.path.kind = PathKind::Super(0); | 1769 | ast_id.path.kind = PathKind::Super(0); |
1687 | } | 1770 | } |
1688 | 1771 | ||
1689 | self.def_collector.unexpanded_macros.push(MacroDirective { | 1772 | self.def_collector.unresolved_macros.push(MacroDirective { |
1690 | module_id: self.module_id, | 1773 | module_id: self.module_id, |
1691 | depth: self.macro_depth + 1, | 1774 | depth: self.macro_depth + 1, |
1692 | kind: MacroDirectiveKind::FnLike { ast_id, fragment: mac.fragment }, | 1775 | kind: MacroDirectiveKind::FnLike { ast_id, fragment: mac.fragment }, |
@@ -1731,14 +1814,16 @@ mod tests { | |||
1731 | glob_imports: FxHashMap::default(), | 1814 | glob_imports: FxHashMap::default(), |
1732 | unresolved_imports: Vec::new(), | 1815 | unresolved_imports: Vec::new(), |
1733 | resolved_imports: Vec::new(), | 1816 | resolved_imports: Vec::new(), |
1734 | unexpanded_macros: Vec::new(), | 1817 | unresolved_macros: Vec::new(), |
1735 | mod_dirs: FxHashMap::default(), | 1818 | mod_dirs: FxHashMap::default(), |
1736 | cfg_options: &CfgOptions::default(), | 1819 | cfg_options: &CfgOptions::default(), |
1737 | proc_macros: Default::default(), | 1820 | proc_macros: Default::default(), |
1738 | exports_proc_macros: false, | 1821 | exports_proc_macros: false, |
1739 | from_glob_import: Default::default(), | 1822 | from_glob_import: Default::default(), |
1740 | ignore_attrs_on: FxHashSet::default(), | 1823 | skip_attrs: Default::default(), |
1741 | derive_helpers_in_scope: FxHashMap::default(), | 1824 | derive_helpers_in_scope: Default::default(), |
1825 | registered_attrs: Default::default(), | ||
1826 | registered_tools: Default::default(), | ||
1742 | }; | 1827 | }; |
1743 | collector.seed_with_top_level(); | 1828 | collector.seed_with_top_level(); |
1744 | collector.collect(); | 1829 | collector.collect(); |
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs index 543975e07..75147d973 100644 --- a/crates/hir_def/src/nameres/tests/diagnostics.rs +++ b/crates/hir_def/src/nameres/tests/diagnostics.rs | |||
@@ -237,3 +237,20 @@ fn good_out_dir_diagnostic() { | |||
237 | "#, | 237 | "#, |
238 | ); | 238 | ); |
239 | } | 239 | } |
240 | |||
241 | #[test] | ||
242 | fn register_attr_and_tool() { | ||
243 | cov_mark::check!(register_attr); | ||
244 | cov_mark::check!(register_tool); | ||
245 | check_no_diagnostics( | ||
246 | r#" | ||
247 | #![register_tool(tool)] | ||
248 | #![register_attr(attr)] | ||
249 | |||
250 | #[tool::path] | ||
251 | #[attr] | ||
252 | struct S; | ||
253 | "#, | ||
254 | ); | ||
255 | // NB: we don't currently emit diagnostics here | ||
256 | } | ||
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( |
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs index 5a5dc9afd..ef67ea2e9 100644 --- a/crates/hir_expand/src/name.rs +++ b/crates/hir_expand/src/name.rs | |||
@@ -164,6 +164,8 @@ pub mod known { | |||
164 | doc, | 164 | doc, |
165 | cfg, | 165 | cfg, |
166 | cfg_attr, | 166 | cfg_attr, |
167 | register_attr, | ||
168 | register_tool, | ||
167 | // Components of known path (value or mod name) | 169 | // Components of known path (value or mod name) |
168 | std, | 170 | std, |
169 | core, | 171 | core, |
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index dda5a6631..d4748ef3a 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -33,20 +33,19 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
33 | // use super::AssistContext; | 33 | // use super::AssistContext; |
34 | // ``` | 34 | // ``` |
35 | // | 35 | // |
36 | // .Merge Behavior | 36 | // .Import Granularity |
37 | // | 37 | // |
38 | // It is possible to configure how use-trees are merged with the `importMergeBehavior` setting. | 38 | // It is possible to configure how use-trees are merged with the `importGranularity` setting. |
39 | // It has the following configurations: | 39 | // It has the following configurations: |
40 | // | 40 | // |
41 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the | 41 | // - `crate`: Merge imports from the same crate into a single use statement. This kind of |
42 | // same path prefix while also merging inner trees that share the same path-prefix. This kind of | ||
43 | // nesting is only supported in Rust versions later than 1.24. | 42 | // nesting is only supported in Rust versions later than 1.24. |
44 | // - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree | 43 | // - `module`: Merge imports from the same module into a single use statement. |
45 | // will only contain a nesting of single segment paths at the very end. | 44 | // - `item`: Don't merge imports at all, creating one import per item. |
46 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple | 45 | // - `preserve`: Do not change the granularity of any imports. For auto-import this has the same |
47 | // paths. | 46 | // effect as `item`. |
48 | // | 47 | // |
49 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehavior`. | 48 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importGranularity`. |
50 | // | 49 | // |
51 | // .Import Prefix | 50 | // .Import Prefix |
52 | // | 51 | // |
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs index 31854840c..fc117bd9a 100644 --- a/crates/ide_assists/src/handlers/merge_imports.rs +++ b/crates/ide_assists/src/handlers/merge_imports.rs | |||
@@ -213,6 +213,20 @@ pub(crate) use std::fmt::{Debug, Display}; | |||
213 | } | 213 | } |
214 | 214 | ||
215 | #[test] | 215 | #[test] |
216 | fn merge_pub_in_path_crate() { | ||
217 | check_assist( | ||
218 | merge_imports, | ||
219 | r" | ||
220 | pub(in this::path) use std::fmt$0::Debug; | ||
221 | pub(in this::path) use std::fmt::Display; | ||
222 | ", | ||
223 | r" | ||
224 | pub(in this::path) use std::fmt::{Debug, Display}; | ||
225 | ", | ||
226 | ) | ||
227 | } | ||
228 | |||
229 | #[test] | ||
216 | fn test_merge_nested() { | 230 | fn test_merge_nested() { |
217 | check_assist( | 231 | check_assist( |
218 | merge_imports, | 232 | merge_imports, |
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs index 697e8bcff..0dbabb44f 100644 --- a/crates/ide_db/src/helpers/merge_imports.rs +++ b/crates/ide_db/src/helpers/merge_imports.rs | |||
@@ -292,9 +292,7 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { | |||
292 | pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { | 292 | pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { |
293 | match (vis0, vis1) { | 293 | match (vis0, vis1) { |
294 | (None, None) => true, | 294 | (None, None) => true, |
295 | // FIXME: Don't use the string representation to check for equality | 295 | (Some(vis0), Some(vis1)) => vis0.is_eq_to(&vis1), |
296 | // spaces inside of the node would break this comparison | ||
297 | (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(), | ||
298 | _ => false, | 296 | _ => false, |
299 | } | 297 | } |
300 | } | 298 | } |
@@ -303,9 +301,14 @@ pub fn eq_attrs( | |||
303 | attrs0: impl Iterator<Item = ast::Attr>, | 301 | attrs0: impl Iterator<Item = ast::Attr>, |
304 | attrs1: impl Iterator<Item = ast::Attr>, | 302 | attrs1: impl Iterator<Item = ast::Attr>, |
305 | ) -> bool { | 303 | ) -> bool { |
306 | let attrs0 = attrs0.map(|attr| attr.to_string()); | 304 | // FIXME order of attributes should not matter |
307 | let attrs1 = attrs1.map(|attr| attr.to_string()); | 305 | let attrs0 = attrs0 |
308 | attrs0.eq(attrs1) | 306 | .flat_map(|attr| attr.syntax().descendants_with_tokens()) |
307 | .flat_map(|it| it.into_token()); | ||
308 | let attrs1 = attrs1 | ||
309 | .flat_map(|attr| attr.syntax().descendants_with_tokens()) | ||
310 | .flat_map(|it| it.into_token()); | ||
311 | stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text()) | ||
309 | } | 312 | } |
310 | 313 | ||
311 | fn path_is_self(path: &ast::Path) -> bool { | 314 | fn path_is_self(path: &ast::Path) -> bool { |
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 340fcacfa..18d5fadb9 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs | |||
@@ -140,6 +140,34 @@ impl JodChild { | |||
140 | } | 140 | } |
141 | } | 141 | } |
142 | 142 | ||
143 | // feature: iter_order_by | ||
144 | // Iterator::eq_by | ||
145 | pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool | ||
146 | where | ||
147 | I: IntoIterator, | ||
148 | I2: IntoIterator, | ||
149 | F: FnMut(I2::Item, I::Item) -> bool, | ||
150 | { | ||
151 | let mut other = other.into_iter(); | ||
152 | let mut this = this.into_iter(); | ||
153 | |||
154 | loop { | ||
155 | let x = match this.next() { | ||
156 | None => return other.next().is_none(), | ||
157 | Some(val) => val, | ||
158 | }; | ||
159 | |||
160 | let y = match other.next() { | ||
161 | None => return false, | ||
162 | Some(val) => val, | ||
163 | }; | ||
164 | |||
165 | if !eq(x, y) { | ||
166 | return false; | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | |||
143 | #[cfg(test)] | 171 | #[cfg(test)] |
144 | mod tests { | 172 | mod tests { |
145 | use super::*; | 173 | use super::*; |
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index bef49238f..df8f98b5b 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs | |||
@@ -608,6 +608,29 @@ impl ast::Visibility { | |||
608 | None => VisibilityKind::Pub, | 608 | None => VisibilityKind::Pub, |
609 | } | 609 | } |
610 | } | 610 | } |
611 | |||
612 | pub fn is_eq_to(&self, other: &Self) -> bool { | ||
613 | match (self.kind(), other.kind()) { | ||
614 | (VisibilityKind::In(this), VisibilityKind::In(other)) => { | ||
615 | stdx::iter_eq_by(this.segments(), other.segments(), |lhs, rhs| { | ||
616 | lhs.kind().zip(rhs.kind()).map_or(false, |it| match it { | ||
617 | (PathSegmentKind::CrateKw, PathSegmentKind::CrateKw) | ||
618 | | (PathSegmentKind::SelfKw, PathSegmentKind::SelfKw) | ||
619 | | (PathSegmentKind::SuperKw, PathSegmentKind::SuperKw) => true, | ||
620 | (PathSegmentKind::Name(lhs), PathSegmentKind::Name(rhs)) => { | ||
621 | lhs.text() == rhs.text() | ||
622 | } | ||
623 | _ => false, | ||
624 | }) | ||
625 | }) | ||
626 | } | ||
627 | (VisibilityKind::PubSelf, VisibilityKind::PubSelf) | ||
628 | | (VisibilityKind::PubSuper, VisibilityKind::PubSuper) | ||
629 | | (VisibilityKind::PubCrate, VisibilityKind::PubCrate) | ||
630 | | (VisibilityKind::Pub, VisibilityKind::Pub) => true, | ||
631 | _ => false, | ||
632 | } | ||
633 | } | ||
611 | } | 634 | } |
612 | 635 | ||
613 | impl ast::LifetimeParam { | 636 | impl ast::LifetimeParam { |