diff options
121 files changed, 3558 insertions, 717 deletions
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index c29446d8235..fae07111806 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -485,6 +485,7 @@ impl AttrsWithOwner { }, AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it), AttrDefId::ExternCrateId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::UseId(it) => attrs_from_item_tree_loc(db, it), }; let attrs = raw_attrs.filter(db.upcast(), def.krate(db)); @@ -570,6 +571,7 @@ impl AttrsWithOwner { }, AttrDefId::ExternBlockId(id) => any_has_attrs(db, id), AttrDefId::ExternCrateId(id) => any_has_attrs(db, id), + AttrDefId::UseId(id) => any_has_attrs(db, id), }; AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs index bb79e28f267..4cfd318a433 100644 --- a/crates/hir-def/src/child_by_source.rs +++ b/crates/hir-def/src/child_by_source.rs @@ -14,8 +14,8 @@ use crate::{ item_scope::ItemScope, nameres::DefMap, src::{HasChildSource, HasSource}, - AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, FieldId, ImplId, Lookup, MacroId, - ModuleDefId, ModuleId, TraitId, VariantId, + AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, ImplId, + Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId, VariantId, }; pub trait ChildBySource { @@ -91,6 +91,8 @@ impl ChildBySource for ItemScope { fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) { self.declarations().for_each(|item| add_module_def(db, res, file_id, item)); self.impls().for_each(|imp| add_impl(db, res, file_id, imp)); + self.extern_crate_decls().for_each(|ext| add_extern_crate(db, res, file_id, ext)); + self.use_decls().for_each(|ext| add_use(db, res, file_id, ext)); self.unnamed_consts().for_each(|konst| { let loc = konst.lookup(db); if loc.id.file_id() == file_id { @@ -167,6 +169,23 @@ impl ChildBySource for ItemScope { map[keys::IMPL].insert(loc.source(db).value, imp) } } + fn add_extern_crate( + db: &dyn DefDatabase, + map: &mut DynMap, + file_id: HirFileId, + ext: ExternCrateId, + ) { + let loc = ext.lookup(db); + if loc.id.file_id() == file_id { + map[keys::EXTERN_CRATE].insert(loc.source(db).value, ext) + } + } + fn add_use(db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId, ext: UseId) { + let loc = ext.lookup(db); + if loc.id.file_id() == file_id { + map[keys::USE].insert(loc.source(db).value, ext) + } + } } } diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 54fe9a2e844..91db68058b0 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -2,6 +2,7 @@ pub mod adt; +use base_db::CrateId; use hir_expand::{ name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefKind, }; @@ -467,6 +468,7 @@ pub struct ExternCrateDeclData { pub name: Name, pub alias: Option<ImportAlias>, pub visibility: RawVisibility, + pub crate_id: Option<CrateId>, } impl ExternCrateDeclData { @@ -478,10 +480,21 @@ impl ExternCrateDeclData { let item_tree = loc.id.item_tree(db); let extern_crate = &item_tree[loc.id.value]; + let name = extern_crate.name.clone(); + let crate_id = if name == hir_expand::name![self] { + Some(loc.container.krate()) + } else { + db.crate_def_map(loc.container.krate()) + .extern_prelude() + .find(|&(prelude_name, ..)| *prelude_name == name) + .map(|(_, root)| root.krate()) + }; + Arc::new(Self { name: extern_crate.name.clone(), visibility: item_tree[extern_crate.visibility].clone(), alias: extern_crate.alias.clone(), + crate_id, }) } } diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 82e6dfb30c8..e34a6768f28 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -23,17 +23,17 @@ use crate::{ visibility::{self, Visibility}, AttrDefId, BlockId, BlockLoc, ConstBlockId, ConstBlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, - FunctionLoc, GenericDefId, ImplId, ImplLoc, ImportId, ImportLoc, InTypeConstId, InTypeConstLoc, - LocalEnumVariantId, LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, - ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, - TraitAliasLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, VariantId, + FunctionLoc, GenericDefId, ImplId, ImplLoc, InTypeConstId, InTypeConstLoc, LocalEnumVariantId, + LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, + StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, + TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId, }; #[salsa::query_group(InternDatabaseStorage)] pub trait InternDatabase: SourceDatabase { // region: items #[salsa::interned] - fn intern_import(&self, loc: ImportLoc) -> ImportId; + fn intern_use(&self, loc: UseLoc) -> UseId; #[salsa::interned] fn intern_extern_crate(&self, loc: ExternCrateLoc) -> ExternCrateId; #[salsa::interned] diff --git a/crates/hir-def/src/dyn_map/keys.rs b/crates/hir-def/src/dyn_map/keys.rs index 4197d010608..d0f2bfab432 100644 --- a/crates/hir-def/src/dyn_map/keys.rs +++ b/crates/hir-def/src/dyn_map/keys.rs @@ -10,7 +10,7 @@ use crate::{ dyn_map::{DynMap, Policy}, ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, - TypeOrConstParamId, UnionId, + TypeOrConstParamId, UnionId, UseId, }; pub type Key<K, V> = crate::dyn_map::Key<K, V, AstPtrPolicy<K, V>>; @@ -26,6 +26,7 @@ pub const STRUCT: Key<ast::Struct, StructId> = Key::new(); pub const UNION: Key<ast::Union, UnionId> = Key::new(); pub const ENUM: Key<ast::Enum, EnumId> = Key::new(); pub const EXTERN_CRATE: Key<ast::ExternCrate, ExternCrateId> = Key::new(); +pub const USE: Key<ast::Use, UseId> = Key::new(); pub const VARIANT: Key<ast::Variant, EnumVariantId> = Key::new(); pub const TUPLE_FIELD: Key<ast::TupleField, FieldId> = Key::new(); diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index 2ac1bcdc079..873accafb43 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -16,6 +16,7 @@ use syntax::ast; use crate::{ db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ExternCrateId, HasModule, ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId, + UseId, }; #[derive(Copy, Clone, Debug)] @@ -113,6 +114,17 @@ impl ItemScope { self.declarations.iter().copied() } + pub fn extern_crate_decls( + &self, + ) -> impl Iterator<Item = ExternCrateId> + ExactSizeIterator + '_ { + self.extern_crate_decls.iter().copied() + } + + pub fn use_decls(&self) -> impl Iterator<Item = UseId> + ExactSizeIterator + '_ { + // FIXME: to be implemented + std::iter::empty() + } + pub fn impls(&self) -> impl Iterator<Item = ImplId> + ExactSizeIterator + '_ { self.impls.iter().copied() } diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 6f80bb6e07c..c9b0f75f1a8 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -188,7 +188,7 @@ impl ItemTree { fn shrink_to_fit(&mut self) { if let Some(data) = &mut self.data { let ItemTreeData { - imports, + uses, extern_crates, extern_blocks, functions, @@ -211,7 +211,7 @@ impl ItemTree { vis, } = &mut **data; - imports.shrink_to_fit(); + uses.shrink_to_fit(); extern_crates.shrink_to_fit(); extern_blocks.shrink_to_fit(); functions.shrink_to_fit(); @@ -262,7 +262,7 @@ static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(P #[derive(Default, Debug, Eq, PartialEq)] struct ItemTreeData { - imports: Arena<Import>, + uses: Arena<Use>, extern_crates: Arena<ExternCrate>, extern_blocks: Arena<ExternBlock>, functions: Arena<Function>, @@ -486,7 +486,7 @@ macro_rules! mod_items { } mod_items! { - Import in imports -> ast::Use, + Use in uses -> ast::Use, ExternCrate in extern_crates -> ast::ExternCrate, ExternBlock in extern_blocks -> ast::ExternBlock, Function in functions -> ast::Fn, @@ -541,7 +541,7 @@ impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree { } #[derive(Debug, Clone, Eq, PartialEq)] -pub struct Import { +pub struct Use { pub visibility: RawVisibilityId, pub ast_id: FileAstId<ast::Use>, pub use_tree: UseTree, @@ -744,7 +744,7 @@ pub struct MacroDef { pub ast_id: FileAstId<ast::MacroDef>, } -impl Import { +impl Use { /// Maps a `UseTree` contained in this import back to its AST node. pub fn use_tree_to_ast( &self, @@ -870,7 +870,7 @@ macro_rules! impl_froms { impl ModItem { pub fn as_assoc_item(&self) -> Option<AssocItem> { match self { - ModItem::Import(_) + ModItem::Use(_) | ModItem::ExternCrate(_) | ModItem::ExternBlock(_) | ModItem::Struct(_) @@ -892,7 +892,7 @@ impl ModItem { pub fn ast_id(&self, tree: &ItemTree) -> FileAstId<ast::Item> { match self { - ModItem::Import(it) => tree[it.index].ast_id().upcast(), + ModItem::Use(it) => tree[it.index].ast_id().upcast(), ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(), ModItem::ExternBlock(it) => tree[it.index].ast_id().upcast(), ModItem::Function(it) => tree[it.index].ast_id().upcast(), diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index 46633667ed3..7b898e62dba 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -502,13 +502,13 @@ impl<'a> Ctx<'a> { Some(id(self.data().impls.alloc(res))) } - fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> { + fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Use>> { let visibility = self.lower_visibility(use_item); let ast_id = self.source_ast_id_map.ast_id(use_item); let (use_tree, _) = lower_use_tree(self.db, self.hygiene(), use_item.use_tree()?)?; - let res = Import { visibility, ast_id, use_tree }; - Some(id(self.data().imports.alloc(res))) + let res = Use { visibility, ast_id, use_tree }; + Some(id(self.data().uses.alloc(res))) } fn lower_extern_crate( diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index ddf668d20b0..da30830fe45 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -198,8 +198,8 @@ impl Printer<'_> { self.print_attrs_of(item); match item { - ModItem::Import(it) => { - let Import { visibility, use_tree, ast_id: _ } = &self.tree[it]; + ModItem::Use(it) => { + let Use { visibility, use_tree, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); w!(self, "use "); self.print_use_tree(use_tree); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 1e74e2dfcb4..1901db8a0f9 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -88,8 +88,8 @@ use crate::{ builtin_type::BuiltinType, data::adt::VariantData, item_tree::{ - Const, Enum, ExternCrate, Function, Impl, Import, ItemTreeId, ItemTreeNode, MacroDef, - MacroRules, Static, Struct, Trait, TraitAlias, TypeAlias, Union, + Const, Enum, ExternCrate, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, + Static, Struct, Trait, TraitAlias, TypeAlias, Union, Use, }, }; @@ -121,6 +121,12 @@ impl From<CrateRootModuleId> for ModuleDefId { } } +impl From<CrateId> for CrateRootModuleId { + fn from(krate: CrateId) -> Self { + CrateRootModuleId { krate } + } +} + impl TryFrom<ModuleId> for CrateRootModuleId { type Error = (); @@ -318,9 +324,9 @@ type ImplLoc = ItemLoc<Impl>; impl_intern!(ImplId, ImplLoc, intern_impl, lookup_intern_impl); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ImportId(salsa::InternId); -type ImportLoc = ItemLoc<Import>; -impl_intern!(ImportId, ImportLoc, intern_import, lookup_intern_import); +pub struct UseId(salsa::InternId); +type UseLoc = ItemLoc<Use>; +impl_intern!(UseId, UseLoc, intern_use, lookup_intern_use); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct ExternCrateId(salsa::InternId); @@ -836,6 +842,7 @@ pub enum AttrDefId { GenericParamId(GenericParamId), ExternBlockId(ExternBlockId), ExternCrateId(ExternCrateId), + UseId(UseId), } impl_from!( @@ -1073,6 +1080,7 @@ impl AttrDefId { } AttrDefId::MacroId(it) => it.module(db).krate, AttrDefId::ExternCrateId(it) => it.lookup(db).container.krate, + AttrDefId::UseId(it) => it.lookup(db).container.krate, } } } @@ -1083,7 +1091,7 @@ pub trait AsMacroCall { &self, db: &dyn ExpandDatabase, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy, ) -> Option<MacroCallId> { self.as_call_id_with_errors(db, krate, resolver).ok()?.value } @@ -1092,7 +1100,7 @@ pub trait AsMacroCall { &self, db: &dyn ExpandDatabase, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy, ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro>; } @@ -1101,7 +1109,7 @@ impl AsMacroCall for InFile<&ast::MacroCall> { &self, db: &dyn ExpandDatabase, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy, ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> { let expands_to = hir_expand::ExpandTo::from_call_site(self.value); let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); @@ -1112,12 +1120,13 @@ impl AsMacroCall for InFile<&ast::MacroCall> { return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation"))); }; - macro_call_as_call_id_( + macro_call_as_call_id_with_eager( db, &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), expands_to, krate, resolver, + resolver, ) } } @@ -1140,33 +1149,39 @@ fn macro_call_as_call_id( call: &AstIdWithPath<ast::MacroCall>, expand_to: ExpandTo, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy, ) -> Result<Option<MacroCallId>, UnresolvedMacro> { - macro_call_as_call_id_(db, call, expand_to, krate, resolver).map(|res| res.value) + macro_call_as_call_id_with_eager(db, call, expand_to, krate, resolver, resolver) + .map(|res| res.value) } -fn macro_call_as_call_id_( +fn macro_call_as_call_id_with_eager( db: &dyn ExpandDatabase, call: &AstIdWithPath<ast::MacroCall>, expand_to: ExpandTo, krate: CrateId, - resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, + resolver: impl FnOnce(path::ModPath) -> Option<MacroDefId>, + eager_resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> { let def = resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?; - let res = if let MacroDefKind::BuiltInEager(..) = def.kind { - let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db)); - expand_eager_macro_input(db, krate, macro_call, def, &resolver)? - } else { - ExpandResult { + let res = match def.kind { + MacroDefKind::BuiltInEager(..) => { + let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db)); + expand_eager_macro_input(db, krate, macro_call, def, &|path| { + eager_resolver(path).filter(MacroDefId::is_fn_like) + }) + } + _ if def.is_fn_like() => ExpandResult { value: Some(def.as_lazy_macro( db, krate, MacroCallKind::FnLike { ast_id: call.ast_id, expand_to }, )), err: None, - } + }, + _ => return Err(UnresolvedMacro { path: call.path.clone() }), }; Ok(res) } @@ -1251,6 +1266,7 @@ fn derive_macro_as_call_id( resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>, ) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> { let (macro_id, def_id) = resolver(item_attr.path.clone()) + .filter(|(_, def_id)| def_id.is_derive()) .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?; let call_id = def_id.as_lazy_macro( db.upcast(), diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index b232651db96..1250cbb742c 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -238,7 +238,7 @@ fn main() { /* error: expected expression */; /* error: expected expression, expected COMMA */; /* error: expected expression */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(), ::core::fmt::Display::fmt), ]); - /* error: expected expression, expected R_PAREN */; + /* error: expected expression, expected expression */; ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(5), ::core::fmt::Display::fmt), ]); } "##]], diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index b26f9867580..2170cadcf83 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -100,6 +100,30 @@ fn#19 main#20(#21)#21 {#22 } #[test] +fn eager_expands_with_unresolved_within() { + check( + r#" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main(foo: ()) { + format_args!("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar") +} +"#, + expect![[r##" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main(foo: ()) { + /* error: unresolved macro identity */::core::fmt::Arguments::new_v1(&["", " ", " ", ], &[::core::fmt::ArgumentV1::new(&(::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(0), ::core::fmt::Display::fmt), ])), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(foo), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(identity!(10)), ::core::fmt::Display::fmt), ]) +} +"##]], + ); +} + +#[test] fn token_mapping_eager() { check( r#" @@ -849,6 +873,37 @@ fn foo() { } #[test] +fn test_type_path_is_transcribed_as_expr_path() { + check( + r#" +macro_rules! m { + ($p:path) => { let $p; } +} +fn test() { + m!(S) + m!(S<i32>) + m!(S<S<i32>>) + m!(S<{ module::CONST < 42 }>) +} +"#, + expect![[r#" +macro_rules! m { + ($p:path) => { let $p; } +} +fn test() { + let S; + let S:: <i32> ; + let S:: <S:: <i32>> ; + let S:: < { + module::CONST<42 + } + > ; +} +"#]], + ); +} + +#[test] fn test_expr() { check( r#" diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index c048716d740..eef54fc492e 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -38,7 +38,7 @@ use crate::{ self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, }, - macro_call_as_call_id, macro_id_to_def_id, + macro_call_as_call_id, macro_call_as_call_id_with_eager, macro_id_to_def_id, nameres::{ diagnostics::DefDiagnostic, mod_resolution::ModDir, @@ -52,10 +52,10 @@ use crate::{ tt, visibility::{RawVisibility, Visibility}, AdtId, AstId, AstIdWithPath, ConstLoc, CrateRootModuleId, EnumLoc, EnumVariantId, - ExternBlockLoc, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, ImportLoc, Intern, - ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, - MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, - TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, + ExternBlockLoc, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, + LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, + ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, + TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseLoc, }; static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); @@ -146,7 +146,7 @@ impl PartialResolvedImport { #[derive(Clone, Debug, Eq, PartialEq)] enum ImportSource { - Import { id: ItemTreeId<item_tree::Import>, use_tree: Idx<ast::UseTree> }, + Use { id: ItemTreeId<item_tree::Use>, use_tree: Idx<ast::UseTree> }, ExternCrate(ItemTreeId<item_tree::ExternCrate>), } @@ -166,7 +166,7 @@ impl Import { db: &dyn DefDatabase, krate: CrateId, tree: &ItemTree, - id: ItemTreeId<item_tree::Import>, + id: ItemTreeId<item_tree::Use>, mut cb: impl FnMut(Self), ) { let it = &tree[id.value]; @@ -181,7 +181,7 @@ impl Import { kind, is_prelude, is_macro_use: false, - source: ImportSource::Import { id, use_tree: idx }, + source: ImportSource::Use { id, use_tree: idx }, }); }); } @@ -1474,7 +1474,7 @@ impl DefCollector<'_> { } for directive in &self.unresolved_imports { - if let ImportSource::Import { id: import, use_tree } = directive.import.source { + if let ImportSource::Use { id: import, use_tree } = directive.import.source { if matches!( (directive.import.path.segments().first(), &directive.import.path.kind), (Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate) @@ -1576,12 +1576,10 @@ impl ModCollector<'_, '_> { match item { ModItem::Mod(m) => self.collect_module(m, &attrs), - ModItem::Import(import_id) => { - let _import_id = ImportLoc { - container: module, - id: ItemTreeId::new(self.tree_id, import_id), - } - .intern(db); + ModItem::Use(import_id) => { + let _import_id = + UseLoc { container: module, id: ItemTreeId::new(self.tree_id, import_id) } + .intern(db); Import::from_use( db, krate, @@ -2187,7 +2185,7 @@ impl ModCollector<'_, '_> { // scopes without eager expansion. // Case 1: try to resolve macro calls with single-segment name and expand macro_rules - if let Ok(res) = macro_call_as_call_id( + if let Ok(res) = macro_call_as_call_id_with_eager( db.upcast(), &ast_id, mac.expand_to, @@ -2210,19 +2208,34 @@ impl ModCollector<'_, '_> { .map(|it| macro_id_to_def_id(self.def_collector.db, it)) }) }, - ) { - // Legacy macros need to be expanded immediately, so that any macros they produce - // are in scope. - if let Some(val) = res { - self.def_collector.collect_macro_expansion( + |path| { + let resolved_res = self.def_collector.def_map.resolve_path_fp_with_macro( + db, + ResolveMode::Other, self.module_id, - val, - self.macro_depth + 1, - container, + &path, + BuiltinShadowMode::Module, + Some(MacroSubNs::Bang), ); - } + resolved_res.resolved_def.take_macros().map(|it| macro_id_to_def_id(db, it)) + }, + ) { + // FIXME: if there were errors, this mightve been in the eager expansion from an + // unresolved macro, so we need to push this into late macro resolution. see fixme above + if res.err.is_none() { + // Legacy macros need to be expanded immediately, so that any macros they produce + // are in scope. + if let Some(val) = res.value { + self.def_collector.collect_macro_expansion( + self.module_id, + val, + self.macro_depth + 1, + container, + ); + } - return; + return; + } } // Case 2: resolve in module scope, expand during name resolution. diff --git a/crates/hir-def/src/nameres/diagnostics.rs b/crates/hir-def/src/nameres/diagnostics.rs index e82e97b628e..9cffb3c9f37 100644 --- a/crates/hir-def/src/nameres/diagnostics.rs +++ b/crates/hir-def/src/nameres/diagnostics.rs @@ -19,7 +19,7 @@ pub enum DefDiagnosticKind { UnresolvedExternCrate { ast: AstId<ast::ExternCrate> }, - UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> }, + UnresolvedImport { id: ItemTreeId<item_tree::Use>, index: Idx<ast::UseTree> }, UnconfiguredCode { ast: ErasedAstId, cfg: CfgExpr, opts: CfgOptions }, @@ -70,7 +70,7 @@ impl DefDiagnostic { pub(super) fn unresolved_import( container: LocalModuleId, - id: ItemTreeId<item_tree::Import>, + id: ItemTreeId<item_tree::Use>, index: Idx<ast::UseTree>, ) -> Self { Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } } diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs index 4931c36bbca..40d3a16540d 100644 --- a/crates/hir-def/src/nameres/tests/incremental.rs +++ b/crates/hir-def/src/nameres/tests/incremental.rs @@ -213,17 +213,17 @@ pub type Ty = (); for (_, res) in module_data.scope.resolutions() { match res.values.or(res.types).unwrap().0 { - ModuleDefId::FunctionId(f) => drop(db.function_data(f)), + ModuleDefId::FunctionId(f) => _ = db.function_data(f), ModuleDefId::AdtId(adt) => match adt { - AdtId::StructId(it) => drop(db.struct_data(it)), - AdtId::UnionId(it) => drop(db.union_data(it)), - AdtId::EnumId(it) => drop(db.enum_data(it)), + AdtId::StructId(it) => _ = db.struct_data(it), + AdtId::UnionId(it) => _ = db.union_data(it), + AdtId::EnumId(it) => _ = db.enum_data(it), }, - ModuleDefId::ConstId(it) => drop(db.const_data(it)), - ModuleDefId::StaticId(it) => drop(db.static_data(it)), - ModuleDefId::TraitId(it) => drop(db.trait_data(it)), - ModuleDefId::TraitAliasId(it) => drop(db.trait_alias_data(it)), - ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)), + ModuleDefId::ConstId(it) => _ = db.const_data(it), + ModuleDefId::StaticId(it) => _ = db.static_data(it), + ModuleDefId::TraitId(it) => _ = db.trait_data(it), + ModuleDefId::TraitAliasId(it) => _ = db.trait_alias_data(it), + ModuleDefId::TypeAliasId(it) => _ = db.type_alias_data(it), ModuleDefId::EnumVariantId(_) | ModuleDefId::ModuleId(_) | ModuleDefId::MacroId(_) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 10f5702845e..b112c1070d4 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -25,7 +25,7 @@ use crate::{ EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, - TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, VariantId, + TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId, }; #[derive(Debug, Clone)] @@ -1024,6 +1024,12 @@ impl HasResolver for ExternCrateId { } } +impl HasResolver for UseId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db) + } +} + impl HasResolver for TypeOwnerId { fn resolver(self, db: &dyn DefDatabase) -> Resolver { match self { diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 309c0930d1a..5292a5fa1b1 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -430,14 +430,13 @@ fn macro_arg_node( let loc = db.lookup_intern_macro_call(id); let arg = if let MacroDefKind::BuiltInEager(..) = loc.def.kind { let res = if let Some(EagerCallInfo { arg, .. }) = loc.eager.as_deref() { - Some(mbe::token_tree_to_syntax_node(&arg.0, mbe::TopEntryPoint::Expr).0) + Some(mbe::token_tree_to_syntax_node(&arg.0, mbe::TopEntryPoint::MacroEagerInput).0) } else { loc.kind .arg(db) .and_then(|arg| ast::TokenTree::cast(arg.value)) - .map(|tt| tt.reparse_as_expr().to_syntax()) + .map(|tt| tt.reparse_as_comma_separated_expr().to_syntax()) }; - match res { Some(res) if res.errors().is_empty() => res.syntax_node(), Some(res) => { diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs index 876813eab5d..4110f284759 100644 --- a/crates/hir-expand/src/eager.rs +++ b/crates/hir-expand/src/eager.rs @@ -19,7 +19,7 @@ //! //! See the full discussion : <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Eager.20expansion.20of.20built-in.20macros> use base_db::CrateId; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{ted, Parse, SyntaxNode, TextRange, TextSize, WalkEvent}; use triomphe::Arc; @@ -29,7 +29,7 @@ use crate::{ hygiene::Hygiene, mod_path::ModPath, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, InFile, MacroCallId, MacroCallKind, - MacroCallLoc, MacroDefId, MacroDefKind, UnresolvedMacro, + MacroCallLoc, MacroDefId, MacroDefKind, }; pub fn expand_eager_macro_input( @@ -38,7 +38,7 @@ pub fn expand_eager_macro_input( macro_call: InFile<ast::MacroCall>, def: MacroDefId, resolver: &dyn Fn(ModPath) -> Option<MacroDefId>, -) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> { +) -> ExpandResult<Option<MacroCallId>> { let ast_map = db.ast_id_map(macro_call.file_id); // the expansion which the ast id map is built upon has no whitespace, so the offsets are wrong as macro_call is from the token tree that has whitespace! let call_id = InFile::new(macro_call.file_id, ast_map.ast_id(¯o_call.value)); @@ -71,41 +71,46 @@ pub fn expand_eager_macro_input( InFile::new(arg_id.as_file(), arg_exp.syntax_node()), krate, resolver, - )? + ) }; let err = parse_err.or(err); let Some((expanded_eager_input, mapping)) = expanded_eager_input else { - return Ok(ExpandResult { value: None, err }); + return ExpandResult { value: None, err }; }; - let og_tmap = mbe::syntax_node_to_token_map( - macro_call.value.token_tree().expect("macro_arg_text succeeded").syntax(), - ); - let (mut subtree, expanded_eager_input_token_map) = mbe::syntax_node_to_token_tree(&expanded_eager_input); - // The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside - // so we need to remap them to the original input of the eager macro. - subtree.visit_ids(&|id| { - // Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix + let og_tmap = if let Some(tt) = macro_call.value.token_tree() { + let mut ids_used = FxHashSet::default(); + let mut og_tmap = mbe::syntax_node_to_token_map(tt.syntax()); + // The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside + // so we need to remap them to the original input of the eager macro. + subtree.visit_ids(&mut |id| { + // Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix - if let Some(range) = - expanded_eager_input_token_map.first_range_by_token(id, syntax::SyntaxKind::TOMBSTONE) - { - // remap from expanded eager input to eager input expansion - if let Some(og_range) = mapping.get(&range) { - // remap from eager input expansion to original eager input - if let Some(&og_range) = ws_mapping.get(og_range) { - if let Some(og_token) = og_tmap.token_by_range(og_range) { - return og_token; + if let Some(range) = expanded_eager_input_token_map + .first_range_by_token(id, syntax::SyntaxKind::TOMBSTONE) + { + // remap from expanded eager input to eager input expansion + if let Some(og_range) = mapping.get(&range) { + // remap from eager input expansion to original eager input + if let Some(&og_range) = ws_mapping.get(og_range) { + if let Some(og_token) = og_tmap.token_by_range(og_range) { + ids_used.insert(og_token); + return og_token; + } } } } - } - tt::TokenId::UNSPECIFIED - }); + tt::TokenId::UNSPECIFIED + }); + og_tmap.filter(|id| ids_used.contains(&id)); + og_tmap + } else { + Default::default() + }; subtree.delimiter = crate::tt::Delimiter::unspecified(); let loc = MacroCallLoc { @@ -119,7 +124,7 @@ pub fn expand_eager_macro_input( kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, }; - Ok(ExpandResult { value: Some(db.intern_macro_call(loc)), err }) + ExpandResult { value: Some(db.intern_macro_call(loc)), err } } fn lazy_expand( @@ -145,13 +150,13 @@ fn eager_macro_recur( curr: InFile<SyntaxNode>, krate: CrateId, macro_resolver: &dyn Fn(ModPath) -> Option<MacroDefId>, -) -> Result<ExpandResult<Option<(SyntaxNode, FxHashMap<TextRange, TextRange>)>>, UnresolvedMacro> { +) -> ExpandResult<Option<(SyntaxNode, FxHashMap<TextRange, TextRange>)>> { let original = curr.value.clone_for_update(); let mut mapping = FxHashMap::default(); let mut replacements = Vec::new(); - // Note: We only report a single error inside of eager expansions + // FIXME: We only report a single error inside of eager expansions let mut error = None; let mut offset = 0i32; let apply_offset = |it: TextSize, offset: i32| { @@ -182,7 +187,14 @@ fn eager_macro_recur( } }; let def = match call.path().and_then(|path| ModPath::from_src(db, path, hygiene)) { - Some(path) => macro_resolver(path.clone()).ok_or(UnresolvedMacro { path })?, + Some(path) => match macro_resolver(path.clone()) { + Some(def) => def, + None => { + error = + Some(ExpandError::other(format!("unresolved macro {}", path.display(db)))); + continue; + } + }, None => { error = Some(ExpandError::other("malformed macro invocation")); continue; @@ -190,32 +202,31 @@ fn eager_macro_recur( }; let ExpandResult { value, err } = match def.kind { MacroDefKind::BuiltInEager(..) => { - let ExpandResult { value, err } = match expand_eager_macro_input( + let ExpandResult { value, err } = expand_eager_macro_input( db, krate, curr.with_value(call.clone()), def, macro_resolver, - ) { - Ok(it) => it, - Err(err) => return Err(err), - }; + ); match value { Some(call_id) => { let ExpandResult { value, err: err2 } = db.parse_macro_expansion(call_id.as_macro_file()); - let call_tt_start = - call.token_tree().unwrap().syntax().text_range().start(); - let call_start = apply_offset(call.syntax().text_range().start(), offset); - if let Some((_, arg_map, _)) = db.macro_arg(call_id).value.as_deref() { - mapping.extend(arg_map.entries().filter_map(|(tid, range)| { - value - .1 - .first_range_by_token(tid, syntax::SyntaxKind::TOMBSTONE) - .map(|r| (r + call_start, range + call_tt_start)) - })); - }; + if let Some(tt) = call.token_tree() { + let call_tt_start = tt.syntax().text_range().start(); + let call_start = + apply_offset(call.syntax().text_range().start(), offset); + if let Some((_, arg_map, _)) = db.macro_arg(call_id).value.as_deref() { + mapping.extend(arg_map.entries().filter_map(|(tid, range)| { + value + .1 + .first_range_by_token(tid, syntax::SyntaxKind::TOMBSTONE) + .map(|r| (r + call_start, range + call_tt_start)) + })); + } + } ExpandResult { value: Some(value.0.syntax_node().clone_for_update()), @@ -247,25 +258,27 @@ fn eager_macro_recur( parse.as_ref().map(|it| it.syntax_node()), krate, macro_resolver, - )?; + ); let err = err.or(error); - let call_tt_start = call.token_tree().unwrap().syntax().text_range().start(); - let call_start = apply_offset(call.syntax().text_range().start(), offset); - if let Some((_tt, arg_map, _)) = parse - .file_id - .macro_file() - .and_then(|id| db.macro_arg(id.macro_call_id).value) - .as_deref() - { - mapping.extend(arg_map.entries().filter_map(|(tid, range)| { - tm.first_range_by_token( - decl_mac.as_ref().map(|it| it.map_id_down(tid)).unwrap_or(tid), - syntax::SyntaxKind::TOMBSTONE, - ) - .map(|r| (r + call_start, range + call_tt_start)) - })); - }; + if let Some(tt) = call.token_tree() { + let call_tt_start = tt.syntax().text_range().start(); + let call_start = apply_offset(call.syntax().text_range().start(), offset); + if let Some((_tt, arg_map, _)) = parse + .file_id + .macro_file() + .and_then(|id| db.macro_arg(id.macro_call_id).value) + .as_deref() + { + mapping.extend(arg_map.entries().filter_map(|(tid, range)| { + tm.first_range_by_token( + decl_mac.as_ref().map(|it| it.map_id_down(tid)).unwrap_or(tid), + syntax::SyntaxKind::TOMBSTONE, + ) + .map(|r| (r + call_start, range + call_tt_start)) + })); + } + } // FIXME: Do we need to re-use _m here? ExpandResult { value: value.map(|(n, _m)| n), err } } @@ -275,7 +288,7 @@ fn eager_macro_recur( } // check if the whole original syntax is replaced if call.syntax() == &original { - return Ok(ExpandResult { value: value.zip(Some(mapping)), err: error }); + return ExpandResult { value: value.zip(Some(mapping)), err: error }; } if let Some(insert) = value { @@ -286,5 +299,5 @@ fn eager_macro_recur( } replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new)); - Ok(ExpandResult { value: Some((original, mapping)), err: error }) + ExpandResult { value: Some((original, mapping)), err: error } } diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs index 54e74d50c87..ade4a592893 100644 --- a/crates/hir-expand/src/hygiene.rs +++ b/crates/hir-expand/src/hygiene.rs @@ -173,7 +173,7 @@ fn make_hygiene_info( db: &dyn ExpandDatabase, macro_file: MacroFile, loc: &MacroCallLoc, -) -> Option<HygieneInfo> { +) -> HygieneInfo { let def = loc.def.ast_id().left().and_then(|id| { let def_tt = match id.to_node(db) { ast::Macro::MacroRules(mac) => mac.token_tree()?, @@ -204,7 +204,7 @@ fn make_hygiene_info( )) }); - Some(HygieneInfo { + HygieneInfo { file: macro_file, attr_input_or_mac_def_start: attr_input_or_mac_def .map(|it| it.map(|tt| tt.syntax().text_range().start())), @@ -212,7 +212,7 @@ fn make_hygiene_info( macro_arg, macro_def, exp_map, - }) + } } impl HygieneFrame { @@ -221,8 +221,7 @@ impl HygieneFrame { None => (None, None, false), Some(macro_file) => { let loc = db.lookup_intern_macro_call(macro_file.macro_call_id); - let info = - make_hygiene_info(db, macro_file, &loc).map(|info| (loc.kind.file_id(), info)); + let info = Some((make_hygiene_info(db, macro_file, &loc), loc.kind.file_id())); match loc.def.kind { MacroDefKind::Declarative(_) => { (info, Some(loc.def.krate), loc.def.local_inner) @@ -236,17 +235,14 @@ impl HygieneFrame { } }; - let (calling_file, info) = match info { - None => { - return HygieneFrame { - expansion: None, - local_inner, - krate, - call_site: None, - def_site: None, - }; + let Some((info, calling_file)) = info else { + return HygieneFrame { + expansion: None, + local_inner, + krate, + call_site: None, + def_site: None, } - Some(it) => it, }; let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id)); diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 9ed6c31ddde..1f1e20f49e3 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -415,6 +415,24 @@ impl MacroDefId { ) } + pub fn is_derive(&self) -> bool { + matches!( + self.kind, + MacroDefKind::BuiltInDerive(..) + | MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _) + ) + } + + pub fn is_fn_like(&self) -> bool { + matches!( + self.kind, + MacroDefKind::BuiltIn(..) + | MacroDefKind::ProcMacro(_, ProcMacroKind::FuncLike, _) + | MacroDefKind::BuiltInEager(..) + | MacroDefKind::Declarative(..) + ) + } + pub fn is_attribute_derive(&self) -> bool { matches!(self.kind, MacroDefKind::BuiltInAttr(expander, ..) if expander.is_derive()) } diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index 98ebe557245..43792994988 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -1428,14 +1428,14 @@ fn builtin_derive_macro() { #[derive(Clone)] struct Y { field1: i32, - field2: u8, + field2: ((i32, u8), i64), } const GOAL: u8 = { - let x = X(2, Z::Foo(Y { field1: 4, field2: 5 }), 8); + let x = X(2, Z::Foo(Y { field1: 4, field2: ((32, 5), 12) }), 8); let x = x.clone(); let Z::Foo(t) = x.1; - t.field2 + t.field2.0 .1 }; "#, 5, @@ -1633,6 +1633,34 @@ const GOAL: i32 = { } #[test] +fn closure_capture_unsized_type() { + check_number( + r#" + //- minicore: fn, copy, slice, index, coerce_unsized + fn f<T: A>(x: &<T as A>::Ty) -> &<T as A>::Ty { + let c = || &*x; + c() + } + + trait A { + type Ty; + } + + impl A for i32 { + type Ty = [u8]; + } + + const GOAL: u8 = { + let k: &[u8] = &[1, 2, 3]; + let k = f::<i32>(k); + k[0] + k[1] + k[2] + } + "#, + 6, + ); +} + +#[test] fn closure_and_impl_fn() { check_number( r#" @@ -2521,12 +2549,16 @@ fn const_trait_assoc() { ); check_number( r#" - //- minicore: size_of + //- minicore: size_of, fn //- /a/lib.rs crate:a use core::mem::size_of; pub struct S<T>(T); impl<T> S<T> { - pub const X: usize = core::mem::size_of::<T>(); + pub const X: usize = { + let k: T; + let f = || core::mem::size_of::<T>(); + f() + }; } //- /main.rs crate:main deps:a use a::{S}; diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs index 9253e31d77b..b0682866004 100644 --- a/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -438,6 +438,8 @@ fn atomic() { pub fn atomic_nand_seqcst<T: Copy>(dst: *mut T, src: T) -> T; pub fn atomic_or_release<T: Copy>(dst: *mut T, src: T) -> T; pub fn atomic_xor_seqcst<T: Copy>(dst: *mut T, src: T) -> T; + pub fn atomic_fence_seqcst(); + pub fn atomic_singlethreadfence_acqrel(); } fn should_not_reach() { @@ -452,6 +454,7 @@ fn atomic() { if (30, true) != atomic_cxchg_release_seqcst(&mut y, 30, 40) { should_not_reach(); } + atomic_fence_seqcst(); if (40, false) != atomic_cxchg_release_seqcst(&mut y, 30, 50) { should_not_reach(); } @@ -459,6 +462,7 @@ fn atomic() { should_not_reach(); } let mut z = atomic_xsub_seqcst(&mut x, -200); + atomic_singlethreadfence_acqrel(); atomic_xor_seqcst(&mut x, 1024); atomic_load_seqcst(&x) + z * 3 + atomic_load_seqcst(&y) * 2 }; diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index 5aaa2bcc7c2..a94a962c1f8 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -176,6 +176,7 @@ impl<'a> DeclValidator<'a> { AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()), AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()), AttrDefId::ExternCrateId(id) => Some(id.lookup(self.db.upcast()).container.into()), + AttrDefId::UseId(id) => Some(id.lookup(self.db.upcast()).container.into()), // These warnings should not explore macro definitions at all AttrDefId::MacroId(_) => None, AttrDefId::AdtId(aid) => match aid { diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 96787959e1f..1b4ee4613d6 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -48,22 +48,15 @@ use crate::{ }; pub trait HirWrite: fmt::Write { - fn start_location_link(&mut self, location: ModuleDefId); - fn end_location_link(&mut self); + fn start_location_link(&mut self, _location: ModuleDefId) {} + fn end_location_link(&mut self) {} } // String will ignore link metadata -impl HirWrite for String { - fn start_location_link(&mut self, _: ModuleDefId) {} - - fn end_location_link(&mut self) {} -} +impl HirWrite for String {} // `core::Formatter` will ignore metadata -impl HirWrite for fmt::Formatter<'_> { - fn start_location_link(&mut self, _: ModuleDefId) {} - fn end_location_link(&mut self) {} -} +impl HirWrite for fmt::Formatter<'_> {} pub struct HirFormatter<'a> { pub db: &'a dyn HirDatabase, @@ -885,6 +878,13 @@ impl HirDisplay for Ty { TyKind::FnDef(def, parameters) => { let def = from_chalk(db, *def); let sig = db.callable_item_signature(def).substitute(Interner, parameters); + + if f.display_target.is_source_code() { + // `FnDef` is anonymous and there's no surface syntax for it. Show it as a + // function pointer type. + return sig.hir_fmt(f); + } + f.start_location_link(def.into()); match def { CallableDefId::FunctionId(ff) => { diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 0a617dae7d4..b4915dbf0f9 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -13,6 +13,15 @@ //! to certain types. To record this, we use the union-find implementation from //! the `ena` crate, which is extracted from rustc. +mod cast; +pub(crate) mod closure; +mod coerce; +mod expr; +mod mutability; +mod pat; +mod path; +pub(crate) mod unify; + use std::{convert::identity, ops::Index}; use chalk_ir::{ @@ -60,15 +69,8 @@ pub use coerce::could_coerce; #[allow(unreachable_pub)] pub use unify::could_unify; -pub(crate) use self::closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy}; - -pub(crate) mod unify; -mod path; -mod expr; -mod pat; -mod coerce; -pub(crate) mod closure; -mod mutability; +use cast::CastCheck; +pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy}; /// The entry point of type inference. pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> { @@ -508,6 +510,8 @@ pub(crate) struct InferenceContext<'a> { diverges: Diverges, breakables: Vec<BreakableContext>, + deferred_cast_checks: Vec<CastCheck>, + // fields related to closure capture current_captures: Vec<CapturedItemWithoutTy>, current_closure: Option<ClosureId>, @@ -582,7 +586,8 @@ impl<'a> InferenceContext<'a> { resolver, diverges: Diverges::Maybe, breakables: Vec::new(), - current_captures: vec![], + deferred_cast_checks: Vec::new(), + current_captures: Vec::new(), current_closure: None, deferred_closures: FxHashMap::default(), closure_dependencies: FxHashMap::default(), @@ -594,7 +599,7 @@ impl<'a> InferenceContext<'a> { // used this function for another workaround, mention it here. If you really need this function and believe that // there is no problem in it being `pub(crate)`, remove this comment. pub(crate) fn resolve_all(self) -> InferenceResult { - let InferenceContext { mut table, mut result, .. } = self; + let InferenceContext { mut table, mut result, deferred_cast_checks, .. } = self; // Destructure every single field so whenever new fields are added to `InferenceResult` we // don't forget to handle them here. let InferenceResult { @@ -622,6 +627,13 @@ impl<'a> InferenceContext<'a> { table.fallback_if_possible(); + // Comment from rustc: + // Even though coercion casts provide type hints, we check casts after fallback for + // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. + for cast in deferred_cast_checks { + cast.check(&mut table); + } + // FIXME resolve obligations as well (use Guidance if necessary) table.resolve_obligations_as_possible(); diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs new file mode 100644 index 00000000000..9e1c74b16fa --- /dev/null +++ b/crates/hir-ty/src/infer/cast.rs @@ -0,0 +1,46 @@ +//! Type cast logic. Basically coercion + additional casts. + +use crate::{infer::unify::InferenceTable, Interner, Ty, TyExt, TyKind}; + +#[derive(Clone, Debug)] +pub(super) struct CastCheck { + expr_ty: Ty, + cast_ty: Ty, +} + +impl CastCheck { + pub(super) fn new(expr_ty: Ty, cast_ty: Ty) -> Self { + Self { expr_ty, cast_ty } + } + + pub(super) fn check(self, table: &mut InferenceTable<'_>) { + // FIXME: This function currently only implements the bits that influence the type + // inference. We should return the adjustments on success and report diagnostics on error. + let expr_ty = table.resolve_ty_shallow(&self.expr_ty); + let cast_ty = table.resolve_ty_shallow(&self.cast_ty); + + if expr_ty.contains_unknown() || cast_ty.contains_unknown() { + return; + } + + if table.coerce(&expr_ty, &cast_ty).is_ok() { + return; + } + + if check_ref_to_ptr_cast(expr_ty, cast_ty, table) { + // Note that this type of cast is actually split into a coercion to a + // pointer type and a cast: + // &[T; N] -> *[T; N] -> *T + return; + } + + // FIXME: Check other kinds of non-coercion casts and report error if any? + } +} + +fn check_ref_to_ptr_cast(expr_ty: Ty, cast_ty: Ty, table: &mut InferenceTable<'_>) -> bool { + let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { return false; }; + let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { return false; }; + let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { return false; }; + table.coerce(expr_elt_ty, cast_inner_ty).is_ok() +} diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 72e6443beb7..501c0b4bd77 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -46,8 +46,8 @@ use crate::{ }; use super::{ - coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, Expectation, - InferenceContext, InferenceDiagnostic, TypeMismatch, + cast::CastCheck, coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, + Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, }; impl InferenceContext<'_> { @@ -574,16 +574,8 @@ impl InferenceContext<'_> { } Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); - // FIXME: propagate the "castable to" expectation - let inner_ty = self.infer_expr_no_expect(*expr); - match (inner_ty.kind(Interner), cast_ty.kind(Interner)) { - (TyKind::Ref(_, _, inner), TyKind::Raw(_, cast)) => { - // FIXME: record invalid cast diagnostic in case of mismatch - self.unify(inner, cast); - } - // FIXME check the other kinds of cast... - _ => (), - } + let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone())); + self.deferred_cast_checks.push(CastCheck::new(expr_ty, cast_ty.clone())); cast_ty } Expr::Ref { expr, rawness, mutability } => { @@ -1592,7 +1584,7 @@ impl InferenceContext<'_> { output: Ty, inputs: Vec<Ty>, ) -> Vec<Ty> { - if let Some(expected_ty) = expected_output.to_option(&mut self.table) { + if let Some(expected_ty) = expected_output.only_has_type(&mut self.table) { self.table.fudge_inference(|table| { if table.try_unify(&expected_ty, &output).is_ok() { table.resolve_with_fallback(inputs, &|var, kind, _, _| match kind { diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index ffc7a6f2ebd..b15339d4434 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -14,7 +14,7 @@ use triomphe::Arc; use crate::{ consteval::try_const_usize, db::HirDatabase, infer::normalize, layout::adt::struct_variant_idx, - utils::ClosureSubst, Interner, Substitution, TraitEnvironment, Ty, + utils::ClosureSubst, Interner, ProjectionTy, Substitution, TraitEnvironment, Ty, }; pub use self::{ @@ -279,7 +279,15 @@ pub fn layout_of_ty_query( // return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr))); // } - let unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone()); + let mut unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone()); + if let TyKind::AssociatedType(id, subst) = unsized_part.kind(Interner) { + unsized_part = TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy { + associated_ty_id: *id, + substitution: subst.clone(), + })) + .intern(Interner); + } + unsized_part = normalize(db, trait_env.clone(), unsized_part); let metadata = match unsized_part.kind(Interner) { TyKind::Slice(_) | TyKind::Str => { scalar_unit(dl, Primitive::Int(dl.ptr_sized_integer(), false)) @@ -362,8 +370,16 @@ pub fn layout_of_ty_query( return Err(LayoutError::NotImplemented) } TyKind::Error => return Err(LayoutError::HasErrorType), - TyKind::AssociatedType(_, _) - | TyKind::Alias(_) + TyKind::AssociatedType(id, subst) => { + // Try again with `TyKind::Alias` to normalize the associated type. + let ty = TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy { + associated_ty_id: *id, + substitution: subst.clone(), + })) + .intern(Interner); + return db.layout_of_ty(ty, trait_env); + } + TyKind::Alias(_) | TyKind::Placeholder(_) | TyKind::BoundVar(_) | TyKind::InferenceVar(_, _) => return Err(LayoutError::HasPlaceholder), diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 7bd2756c14f..2d5d7c7b1f7 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -313,6 +313,7 @@ pub enum MirEvalError { InvalidVTableId(usize), CoerceUnsizedError(Ty), LangItemNotFound(LangItem), + BrokenLayout(Layout), } impl MirEvalError { @@ -399,6 +400,7 @@ impl MirEvalError { | MirEvalError::TargetDataLayoutNotAvailable | MirEvalError::CoerceUnsizedError(_) | MirEvalError::LangItemNotFound(_) + | MirEvalError::BrokenLayout(_) | MirEvalError::InvalidVTableId(_) => writeln!(f, "{:?}", err)?, } Ok(()) @@ -433,6 +435,7 @@ impl std::fmt::Debug for MirEvalError { Self::CoerceUnsizedError(arg0) => { f.debug_tuple("CoerceUnsizedError").field(arg0).finish() } + Self::BrokenLayout(arg0) => f.debug_tuple("BrokenLayout").field(arg0).finish(), Self::InvalidVTableId(arg0) => f.debug_tuple("InvalidVTableId").field(arg0).finish(), Self::NotSupported(arg0) => f.debug_tuple("NotSupported").field(arg0).finish(), Self::InvalidConst(arg0) => { @@ -702,9 +705,7 @@ impl Evaluator<'_> { } fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result<Arc<Layout>> { - self.db.layout_of_adt(adt, subst.clone(), self.trait_env.clone()).map_err(|e| { - MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner)) - }) + self.layout(&TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner)) } fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals) -> Result<Ty> { @@ -1543,12 +1544,18 @@ impl Evaluator<'_> { ) -> Result<Vec<u8>> { let mut result = vec![0; size]; if let Some((offset, size, value)) = tag { - result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]); + match result.get_mut(offset..offset + size) { + Some(it) => it.copy_from_slice(&value.to_le_bytes()[0..size]), + None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())), + } } for (i, op) in values.enumerate() { let offset = variant_layout.fields.offset(i).bytes_usize(); let op = op.get(&self)?; - result[offset..offset + op.len()].copy_from_slice(op); + match result.get_mut(offset..offset + op.len()) { + Some(it) => it.copy_from_slice(op), + None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())), + } } Ok(result) } diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index 9ad6087cad9..356bf70a5fb 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -124,9 +124,85 @@ impl Evaluator<'_> { destination.write_from_bytes(self, &result)?; return Ok(true); } + if let ItemContainerId::TraitId(t) = def.lookup(self.db.upcast()).container { + if self.db.lang_attr(t.into()) == Some(LangItem::Clone) { + let [self_ty] = generic_args.as_slice(Interner) else { + not_supported!("wrong generic arg count for clone"); + }; + let Some(self_ty) = self_ty.ty(Interner) else { + not_supported!("wrong generic arg kind for clone"); + }; + // Clone has special impls for tuples and function pointers + if matches!(self_ty.kind(Interner), TyKind::Function(_) | TyKind::Tuple(..)) { + self.exec_clone(def, args, self_ty.clone(), locals, destination, span)?; + return Ok(true); + } + } + } Ok(false) } + /// Clone has special impls for tuples and function pointers + fn exec_clone( + &mut self, + def: FunctionId, + args: &[IntervalAndTy], + self_ty: Ty, + locals: &Locals, + destination: Interval, + span: MirSpan, + ) -> Result<()> { + match self_ty.kind(Interner) { + TyKind::Function(_) => { + let [arg] = args else { + not_supported!("wrong arg count for clone"); + }; + let addr = Address::from_bytes(arg.get(self)?)?; + return destination + .write_from_interval(self, Interval { addr, size: destination.size }); + } + TyKind::Tuple(_, subst) => { + let [arg] = args else { + not_supported!("wrong arg count for clone"); + }; + let addr = Address::from_bytes(arg.get(self)?)?; + let layout = self.layout(&self_ty)?; + for (i, ty) in subst.iter(Interner).enumerate() { + let ty = ty.assert_ty_ref(Interner); + let size = self.layout(ty)?.size.bytes_usize(); + let tmp = self.heap_allocate(self.ptr_size(), self.ptr_size())?; + let arg = IntervalAndTy { + interval: Interval { addr: tmp, size: self.ptr_size() }, + ty: TyKind::Ref(Mutability::Not, static_lifetime(), ty.clone()) + .intern(Interner), + }; + let offset = layout.fields.offset(i).bytes_usize(); + self.write_memory(tmp, &addr.offset(offset).to_bytes())?; + self.exec_clone( + def, + &[arg], + ty.clone(), + locals, + destination.slice(offset..offset + size), + span, + )?; + } + } + _ => { + self.exec_fn_with_args( + def, + args, + Substitution::from1(Interner, self_ty), + locals, + destination, + None, + span, + )?; + } + } + Ok(()) + } + fn exec_alloc_fn( &mut self, alloc_fn: &str, @@ -1057,7 +1133,14 @@ impl Evaluator<'_> { _span: MirSpan, ) -> Result<()> { // We are a single threaded runtime with no UB checking and no optimization, so - // we can implement these as normal functions. + // we can implement atomic intrinsics as normal functions. + + if name.starts_with("singlethreadfence_") || name.starts_with("fence_") { + return Ok(()); + } + + // The rest of atomic intrinsics have exactly one generic arg + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("atomic intrinsic generic arg is not provided")); }; diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index 93f4b699147..4e21a963276 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -614,6 +614,50 @@ fn main() { } #[test] +fn self_with_capital_s() { + check_pass( + r#" +//- minicore: fn, add, copy + +struct S1; + +impl S1 { + fn f() { + Self; + } +} + +struct S2 { + f1: i32, +} + +impl S2 { + fn f() { + Self { f1: 5 }; + } +} + +struct S3(i32); + +impl S3 { + fn f() { + Self(2); + Self; + let this = Self; + this(2); + } +} + +fn main() { + S1::f(); + S2::f(); + S3::f(); +} + "#, + ); +} + +#[test] fn syscalls() { check_pass( r#" diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 9f25175a3a9..566204bb70d 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -486,13 +486,10 @@ impl<'ctx> MirLowerCtx<'ctx> { ); Ok(Some(current)) } - ValueNs::FunctionId(_) | ValueNs::StructId(_) => { + ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::ImplSelf(_) => { // It's probably a unit struct or a zero sized function, so no action is needed. Ok(Some(current)) } - it => { - not_supported!("unknown name {it:?} in value name space"); - } } } Expr::If { condition, then_branch, else_branch } => { @@ -660,6 +657,11 @@ impl<'ctx> MirLowerCtx<'ctx> { expr_id.into(), ) } + TyKind::Closure(_, _) => { + not_supported!( + "method resolution not emitted for closure (Are Fn traits available?)" + ); + } TyKind::Error => { return Err(MirLowerError::MissingFunctionDefinition(self.owner, expr_id)) } diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs index 425432479e8..e75b037e38d 100644 --- a/crates/hir-ty/src/tests/display_source_code.rs +++ b/crates/hir-ty/src/tests/display_source_code.rs @@ -227,3 +227,22 @@ fn f(a: impl Foo<i8, Assoc<i16> = i32>) { "#, ); } + +#[test] +fn fn_def_is_shown_as_fn_ptr() { + check_types_source_code( + r#" +fn foo(_: i32) -> i64 { 42 } +struct S<T>(T); +enum E { A(usize) } +fn test() { + let f = foo; + //^ fn(i32) -> i64 + let f = S::<i8>; + //^ fn(i8) -> S<i8> + let f = E::A; + //^ fn(usize) -> E +} +"#, + ); +} diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 8b95110233f..375014d6c7f 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -1978,3 +1978,23 @@ fn x(a: [i32; 4]) { "#, ); } + +#[test] +fn dont_unify_on_casts() { + // #15246 + check_types( + r#" +fn unify(_: [bool; 1]) {} +fn casted(_: *const bool) {} +fn default<T>() -> T { loop {} } + +fn test() { + let foo = default(); + //^^^ [bool; 1] + + casted(&foo as *const _); + unify(foo); +} +"#, + ); +} diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index a0ff628435f..2ad7946c8ac 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -3513,7 +3513,6 @@ fn func() { ); } -// FIXME #[test] fn castable_to() { check_infer( @@ -3538,10 +3537,10 @@ fn func() { 120..122 '{}': () 138..184 '{ ...0]>; }': () 148..149 'x': Box<[i32; 0]> - 152..160 'Box::new': fn new<[{unknown}; 0]>([{unknown}; 0]) -> Box<[{unknown}; 0]> - 152..164 'Box::new([])': Box<[{unknown}; 0]> + 152..160 'Box::new': fn new<[i32; 0]>([i32; 0]) -> Box<[i32; 0]> + 152..164 'Box::new([])': Box<[i32; 0]> 152..181 'Box::n...2; 0]>': Box<[i32; 0]> - 161..163 '[]': [{unknown}; 0] + 161..163 '[]': [i32; 0] "#]], ); } @@ -3578,6 +3577,21 @@ fn f<T>(t: Ark<T>) { } #[test] +fn ref_to_array_to_ptr_cast() { + check_types( + r#" +fn default<T>() -> T { loop {} } +fn foo() { + let arr = [default()]; + //^^^ [i32; 1] + let ref_to_arr = &arr; + let casted = ref_to_arr as *const i32; +} +"#, + ); +} + +#[test] fn const_dependent_on_local() { check_types( r#" diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index cf8db2a5a24..0f2fb2c8118 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -12,9 +12,9 @@ use hir_ty::db::HirDatabase; use syntax::{ast, AstNode}; use crate::{ - Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, - Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, TypeParam, Union, - Variant, + Adt, AssocItem, Const, ConstParam, Enum, ExternCrateDecl, Field, Function, GenericParam, Impl, + LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, + TypeParam, Union, Variant, }; pub trait HasAttrs { @@ -120,6 +120,39 @@ impl HasAttrs for AssocItem { } } +impl HasAttrs for ExternCrateDecl { + fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { + let def = AttrDefId::ExternCrateId(self.into()); + db.attrs_with_owner(def) + } + fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { + let crate_docs = self.resolved_crate(db)?.root_module().attrs(db).docs().map(String::from); + let def = AttrDefId::ExternCrateId(self.into()); + let decl_docs = db.attrs(def).docs().map(String::from); + match (decl_docs, crate_docs) { + (None, None) => None, + (Some(decl_docs), None) => Some(decl_docs), + (None, Some(crate_docs)) => Some(crate_docs), + (Some(mut decl_docs), Some(crate_docs)) => { + decl_docs.push('\n'); + decl_docs.push('\n'); + decl_docs += &crate_docs; + Some(decl_docs) + } + } + .map(Documentation::new) + } + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option<Namespace>, + ) -> Option<ModuleDef> { + let def = AttrDefId::ExternCrateId(self.into()); + resolve_doc_path(db, def, link, ns).map(ModuleDef::from) + } +} + /// Resolves the item `link` points to in the scope of `def`. fn resolve_doc_path( db: &dyn HirDatabase, @@ -140,6 +173,7 @@ fn resolve_doc_path( AttrDefId::TypeAliasId(it) => it.resolver(db.upcast()), AttrDefId::ImplId(it) => it.resolver(db.upcast()), AttrDefId::ExternBlockId(it) => it.resolver(db.upcast()), + AttrDefId::UseId(it) => it.resolver(db.upcast()), AttrDefId::MacroId(it) => it.resolver(db.upcast()), AttrDefId::ExternCrateId(it) => it.resolver(db.upcast()), AttrDefId::GenericParamId(it) => match it { diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index f3a0608944b..936581bfe32 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -10,8 +10,3 @@ pub use hir_expand::db::{ MacroExpandQuery, ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, }; pub use hir_ty::db::*; - -#[test] -fn hir_database_is_object_safe() { - fn _assert_object_safe(_: &dyn HirDatabase) {} -} diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 4de9c872ad6..9dfb98e459b 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -18,9 +18,9 @@ use hir_ty::{ }; use crate::{ - Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, Field, Function, GenericParam, - HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TraitAlias, - TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, + Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, ExternCrateDecl, Field, + Function, GenericParam, HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, + Trait, TraitAlias, TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, }; impl HirDisplay for Function { @@ -238,6 +238,18 @@ impl HirDisplay for Type { } } +impl HirDisplay for ExternCrateDecl { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; + f.write_str("extern crate ")?; + write!(f, "{}", self.name(f.db).display(f.db.upcast()))?; + if let Some(alias) = self.alias(f.db) { + write!(f, " as {alias}",)?; + } + Ok(()) + } +} + impl HirDisplay for GenericParam { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match self { diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs index de23902199f..fc4bbffdb83 100644 --- a/crates/hir/src/from_id.rs +++ b/crates/hir/src/from_id.rs @@ -15,7 +15,7 @@ use crate::{ }; macro_rules! from_id { - ($(($id:path, $ty:path)),*) => {$( + ($(($id:path, $ty:path)),* $(,)?) => {$( impl From<$id> for $ty { fn from(id: $id) -> $ty { $ty { id } @@ -47,7 +47,8 @@ from_id![ (hir_def::TypeParamId, crate::TypeParam), (hir_def::ConstParamId, crate::ConstParam), (hir_def::LifetimeParamId, crate::LifetimeParam), - (hir_def::MacroId, crate::Macro) + (hir_def::MacroId, crate::Macro), + (hir_def::ExternCrateId, crate::ExternCrateDecl), ]; impl From<AdtId> for Adt { diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs index b46a3856d45..31cf8ba3364 100644 --- a/crates/hir/src/has_source.rs +++ b/crates/hir/src/has_source.rs @@ -11,9 +11,9 @@ use hir_expand::{HirFileId, InFile}; use syntax::ast; use crate::{ - db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, - LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, - Union, Variant, + db::HirDatabase, Adt, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl, + LifetimeParam, LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, + TypeOrConstParam, Union, Variant, }; pub trait HasSource { @@ -207,3 +207,11 @@ impl HasSource for LocalSource { Some(self.source) } } + +impl HasSource for ExternCrateDecl { + type Ast = ast::ExternCrate; + + fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> { + Some(self.id.lookup(db.upcast()).source(db.upcast())) + } +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index b094bb7a068..bf041b61f2f 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -48,14 +48,15 @@ use hir_def::{ layout::{self, ReprOptions, TargetDataLayout}, macro_id_to_def_id, nameres::{self, diagnostics::DefDiagnostic}, + path::ImportAlias, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, - AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, - EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, InTypeConstId, ItemContainerId, - LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, - StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, - UnionId, + AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, + EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, HasModule, ImplId, + InTypeConstId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, + MacroExpander, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, + TypeOrConstParamId, TypeParamId, UnionId, }; use hir_expand::{name::name, MacroCallKind}; use hir_ty::{ @@ -200,9 +201,8 @@ impl Crate { db.crate_graph().transitive_rev_deps(self.id).map(|id| Crate { id }) } - pub fn root_module(self, db: &dyn HirDatabase) -> Module { - let def_map = db.crate_def_map(self.id); - Module { id: def_map.crate_root().into() } + pub fn root_module(self) -> Module { + Module { id: CrateRootModuleId::from(self.id).into() } } pub fn modules(self, db: &dyn HirDatabase) -> Vec<Module> { @@ -247,7 +247,7 @@ impl Crate { /// Try to get the root URL of the documentation of a crate. pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option<String> { // Look for #![doc(html_root_url = "...")] - let attrs = db.attrs(AttrDefId::ModuleId(self.root_module(db).into())); + let attrs = db.attrs(AttrDefId::ModuleId(self.root_module().into())); let doc_url = attrs.by_key("doc").find_string_value_in_tt("html_root_url"); doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/") } @@ -2129,6 +2129,47 @@ impl HasVisibility for Function { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ExternCrateDecl { + pub(crate) id: ExternCrateId, +} + +impl ExternCrateDecl { + pub fn module(self, db: &dyn HirDatabase) -> Module { + self.id.module(db.upcast()).into() + } + + pub fn resolved_crate(self, db: &dyn HirDatabase) -> Option<Crate> { + db.extern_crate_decl_data(self.id).crate_id.map(Into::into) + } + + pub fn name(self, db: &dyn HirDatabase) -> Name { + db.extern_crate_decl_data(self.id).name.clone() + } + + pub fn alias(self, db: &dyn HirDatabase) -> Option<ImportAlias> { + db.extern_crate_decl_data(self.id).alias.clone() + } + + /// Returns the name under which this crate is made accessible, taking `_` into account. + pub fn alias_or_name(self, db: &dyn HirDatabase) -> Option<Name> { + let extern_crate_decl_data = db.extern_crate_decl_data(self.id); + match &extern_crate_decl_data.alias { + Some(ImportAlias::Underscore) => None, + Some(ImportAlias::Alias(alias)) => Some(alias.clone()), + None => Some(extern_crate_decl_data.name.clone()), + } + } +} + +impl HasVisibility for ExternCrateDecl { + fn visibility(&self, db: &dyn HirDatabase) -> Visibility { + db.extern_crate_decl_data(self.id) + .visibility + .resolve(db.upcast(), &self.id.resolver(db.upcast())) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct InTypeConst { pub(crate) id: InTypeConstId, } @@ -4715,6 +4756,12 @@ pub trait HasContainer { fn container(&self, db: &dyn HirDatabase) -> ItemContainer; } +impl HasContainer for ExternCrateDecl { + fn container(&self, db: &dyn HirDatabase) -> ItemContainer { + container_id_to_hir(self.id.lookup(db.upcast()).container.into()) + } +} + impl HasContainer for Module { fn container(&self, db: &dyn HirDatabase) -> ItemContainer { // FIXME: handle block expressions as modules (their parent is in a different DefMap) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 39a3e1c4489..e99d2984c36 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -15,11 +15,7 @@ use hir_def::{ type_ref::Mutability, AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId, }; -use hir_expand::{ - db::ExpandDatabase, - name::{known, AsName}, - ExpansionInfo, MacroCallId, -}; +use hir_expand::{db::ExpandDatabase, name::AsName, ExpansionInfo, MacroCallId}; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; @@ -439,10 +435,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_path(path) } - pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> { - self.imp.resolve_extern_crate(extern_crate) - } - pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> { self.imp.resolve_variant(record_lit).map(VariantDef::from) } @@ -1242,18 +1234,6 @@ impl<'db> SemanticsImpl<'db> { self.analyze(path.syntax())?.resolve_path(self.db, path) } - fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> { - let krate = self.scope(extern_crate.syntax())?.krate(); - let name = extern_crate.name_ref()?.as_name(); - if name == known::SELF_PARAM { - return Some(krate); - } - krate - .dependencies(self.db) - .into_iter() - .find_map(|dep| (dep.name == name).then_some(dep.krate)) - } - fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> { self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit) } @@ -1603,6 +1583,7 @@ to_def_impls![ (crate::Local, ast::SelfParam, self_param_to_def), (crate::Label, ast::Label, label_to_def), (crate::Adt, ast::Adt, adt_to_def), + (crate::ExternCrateDecl, ast::ExternCrate, extern_crate_to_def), ]; fn find_root(node: &SyntaxNode) -> SyntaxNode { diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index b971ca62387..aabda365560 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -93,9 +93,9 @@ use hir_def::{ DynMap, }, hir::{BindingId, LabelId}, - AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId, - GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId, - TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, + AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, + FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, + StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId, }; use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId}; use rustc_hash::FxHashMap; @@ -203,6 +203,16 @@ impl SourceToDefCtx<'_, '_> { ) -> Option<EnumVariantId> { self.to_def(src, keys::VARIANT) } + pub(super) fn extern_crate_to_def( + &mut self, + src: InFile<ast::ExternCrate>, + ) -> Option<ExternCrateId> { + self.to_def(src, keys::EXTERN_CRATE) + } + #[allow(dead_code)] + pub(super) fn use_to_def(&mut self, src: InFile<ast::Use>) -> Option<UseId> { + self.to_def(src, keys::USE) + } pub(super) fn adt_to_def( &mut self, InFile { file_id, value }: InFile<ast::Adt>, diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index d07c6372628..6aca716bb60 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -3,7 +3,10 @@ use syntax::ast::{self, make, AstNode}; use crate::{ assist_context::{AssistContext, Assists}, - utils::{add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, DefaultMethods}, + utils::{ + add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, DefaultMethods, + IgnoreAssocItems, + }, AssistId, AssistKind, }; @@ -43,6 +46,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext<'_ acc, ctx, DefaultMethods::No, + IgnoreAssocItems::DocHiddenAttrPresent, "add_impl_missing_members", "Implement missing members", ) @@ -87,6 +91,7 @@ pub(crate) fn add_missing_default_members( acc, ctx, DefaultMethods::Only, + IgnoreAssocItems::DocHiddenAttrPresent, "add_impl_default_members", "Implement default members", ) @@ -96,6 +101,7 @@ fn add_missing_impl_members_inner( acc: &mut Assists, ctx: &AssistContext<'_>, mode: DefaultMethods, + ignore_items: IgnoreAssocItems, assist_id: &'static str, label: &'static str, ) -> Option<()> { @@ -115,10 +121,21 @@ fn add_missing_impl_members_inner( let trait_ref = impl_.trait_ref(ctx.db())?; let trait_ = trait_ref.trait_(); + let mut ign_item = ignore_items; + + if let IgnoreAssocItems::DocHiddenAttrPresent = ignore_items { + // Relax condition for local crates. + let db = ctx.db(); + if trait_.module(db).krate().origin(db).is_local() { + ign_item = IgnoreAssocItems::No; + } + } + let missing_items = filter_assoc_items( &ctx.sema, &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def), mode, + ign_item, ); if missing_items.is_empty() { @@ -1966,4 +1983,169 @@ impl AnotherTrait<i32> for () { "#, ); } + + #[test] + fn doc_hidden_default_impls_ignored() { + // doc(hidden) attr is ignored trait and impl both belong to the local crate. + check_assist( + add_missing_default_members, + r#" +struct Foo; +trait Trait { + #[doc(hidden)] + fn func_with_default_impl() -> u32 { + 42 + } + fn another_default_impl() -> u32 { + 43 + } +} +impl Tra$0it for Foo {}"#, + r#" +struct Foo; +trait Trait { + #[doc(hidden)] + fn func_with_default_impl() -> u32 { + 42 + } + fn another_default_impl() -> u32 { + 43 + } +} +impl Trait for Foo { + $0fn func_with_default_impl() -> u32 { + 42 + } + + fn another_default_impl() -> u32 { + 43 + } +}"#, + ) + } + + #[test] + fn doc_hidden_default_impls_lang_crates() { + // Not applicable because Eq has a single method and this has a #[doc(hidden)] attr set. + check_assist_not_applicable( + add_missing_default_members, + r#" +//- minicore: eq +use core::cmp::Eq; +struct Foo; +impl E$0q for Foo { /* $0 */ } +"#, + ) + } + + #[test] + fn doc_hidden_default_impls_lib_crates() { + check_assist( + add_missing_default_members, + r#" + //- /main.rs crate:a deps:b + struct B; + impl b::Exte$0rnTrait for B {} + //- /lib.rs crate:b new_source_root:library + pub trait ExternTrait { + #[doc(hidden)] + fn hidden_default() -> Option<()> { + todo!() + } + + fn unhidden_default() -> Option<()> { + todo!() + } + + fn unhidden_nondefault() -> Option<()>; + } + "#, + r#" + struct B; + impl b::ExternTrait for B { + $0fn unhidden_default() -> Option<()> { + todo!() + } + } + "#, + ) + } + + #[test] + fn doc_hidden_default_impls_local_crates() { + check_assist( + add_missing_default_members, + r#" +trait LocalTrait { + #[doc(hidden)] + fn no_skip_default() -> Option<()> { + todo!() + } + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + +struct B; +impl Loc$0alTrait for B {} + "#, + r#" +trait LocalTrait { + #[doc(hidden)] + fn no_skip_default() -> Option<()> { + todo!() + } + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + +struct B; +impl LocalTrait for B { + $0fn no_skip_default() -> Option<()> { + todo!() + } + + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + "#, + ) + } + + #[test] + fn doc_hidden_default_impls_workspace_crates() { + check_assist( + add_missing_default_members, + r#" +//- /lib.rs crate:b new_source_root:local +trait LocalTrait { + #[doc(hidden)] + fn no_skip_default() -> Option<()> { + todo!() + } + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + +//- /main.rs crate:a deps:b +struct B; +impl b::Loc$0alTrait for B {} + "#, + r#" +struct B; +impl b::LocalTrait for B { + $0fn no_skip_default() -> Option<()> { + todo!() + } + + fn no_skip_default_2() -> Option<()> { + todo!() + } +} + "#, + ) + } } diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index ac0b74ee8e7..3b162d7c4d8 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -37,9 +37,9 @@ use crate::{utils, AssistContext, AssistId, AssistKind, Assists}; pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let match_expr = ctx.find_node_at_offset_with_descend::<ast::MatchExpr>()?; let match_arm_list = match_expr.match_arm_list()?; - let target_range = ctx.sema.original_range(match_expr.syntax()).range; + let arm_list_range = ctx.sema.original_range_opt(match_arm_list.syntax())?; - if let None = cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list) { + if cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list).is_none() { let arm_list_range = ctx.sema.original_range(match_arm_list.syntax()).range; let cursor_in_range = arm_list_range.contains_range(ctx.selection_trimmed()); if cursor_in_range { @@ -198,7 +198,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) acc.add( AssistId("add_missing_match_arms", AssistKind::QuickFix), "Fill match arms", - target_range, + ctx.sema.original_range(match_expr.syntax()).range, |edit| { let new_match_arm_list = match_arm_list.clone_for_update(); @@ -262,9 +262,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) // Just replace the element that the original range came from let old_place = { // Find the original element - let old_file_range = ctx.sema.original_range(match_arm_list.syntax()); - let file = ctx.sema.parse(old_file_range.file_id); - let old_place = file.syntax().covering_element(old_file_range.range); + let file = ctx.sema.parse(arm_list_range.file_id); + let old_place = file.syntax().covering_element(arm_list_range.range); // Make `old_place` mut match old_place { @@ -1922,4 +1921,24 @@ fn foo(t: E) { }"#, ); } + + #[test] + fn not_applicable_when_match_arm_list_cannot_be_upmapped() { + check_assist_not_applicable( + add_missing_match_arms, + r#" +macro_rules! foo { + ($($t:tt)*) => { + $($t)* {} + } +} + +enum E { A } + +fn main() { + foo!(match E::A$0); +} +"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/add_turbo_fish.rs b/crates/ide-assists/src/handlers/add_turbo_fish.rs index acf82e4b257..36f68d17677 100644 --- a/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -42,7 +42,9 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let name_ref = ast::NameRef::cast(ident.parent()?)?; let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { .. } => return None, + NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => { + return None + } }; let fun = match def { Definition::Function(it) => it, diff --git a/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs b/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs index b1b0f587cd3..6a5b11f5425 100644 --- a/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs +++ b/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs @@ -1,3 +1,6 @@ +use hir::Semantics; +use ide_db::RootDatabase; +use stdx::format_to; use syntax::ast::{self, AstNode}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -24,6 +27,7 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { + use ArmBodyExpression::*; let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; let match_arm_list = match_expr.match_arm_list()?; let mut arms = match_arm_list.arms(); @@ -33,21 +37,20 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro( cov_mark::hit!(non_two_arm_match); return None; } - let first_arm_expr = first_arm.expr(); - let second_arm_expr = second_arm.expr(); + let first_arm_expr = first_arm.expr()?; + let second_arm_expr = second_arm.expr()?; + let first_arm_body = is_bool_literal_expr(&ctx.sema, &first_arm_expr)?; + let second_arm_body = is_bool_literal_expr(&ctx.sema, &second_arm_expr)?; - let invert_matches = if is_bool_literal_expr(&first_arm_expr, true) - && is_bool_literal_expr(&second_arm_expr, false) - { - false - } else if is_bool_literal_expr(&first_arm_expr, false) - && is_bool_literal_expr(&second_arm_expr, true) - { - true - } else { + if !matches!( + (&first_arm_body, &second_arm_body), + (Literal(true), Literal(false)) + | (Literal(false), Literal(true)) + | (Expression(_), Literal(false)) + ) { cov_mark::hit!(non_invert_bool_literal_arms); return None; - }; + } let target_range = ctx.sema.original_range(match_expr.syntax()).range; let expr = match_expr.expr()?; @@ -59,28 +62,55 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro( |builder| { let mut arm_str = String::new(); if let Some(pat) = &first_arm.pat() { - arm_str += &pat.to_string(); + format_to!(arm_str, "{pat}"); } if let Some(guard) = &first_arm.guard() { arm_str += &format!(" {guard}"); } - if invert_matches { - builder.replace(target_range, format!("!matches!({expr}, {arm_str})")); - } else { - builder.replace(target_range, format!("matches!({expr}, {arm_str})")); - } + + let replace_with = match (first_arm_body, second_arm_body) { + (Literal(true), Literal(false)) => { + format!("matches!({expr}, {arm_str})") + } + (Literal(false), Literal(true)) => { + format!("!matches!({expr}, {arm_str})") + } + (Expression(body_expr), Literal(false)) => { + arm_str.push_str(match &first_arm.guard() { + Some(_) => " && ", + _ => " if ", + }); + format!("matches!({expr}, {arm_str}{body_expr})") + } + _ => { + unreachable!() + } + }; + builder.replace(target_range, replace_with); }, ) } -fn is_bool_literal_expr(expr: &Option<ast::Expr>, expect_bool: bool) -> bool { - if let Some(ast::Expr::Literal(lit)) = expr { +enum ArmBodyExpression { + Literal(bool), + Expression(ast::Expr), +} + +fn is_bool_literal_expr( + sema: &Semantics<'_, RootDatabase>, + expr: &ast::Expr, +) -> Option<ArmBodyExpression> { + if let ast::Expr::Literal(lit) = expr { if let ast::LiteralKind::Bool(b) = lit.kind() { - return b == expect_bool; + return Some(ArmBodyExpression::Literal(b)); } } - return false; + if !sema.type_of_expr(expr)?.original.is_bool() { + return None; + } + + Some(ArmBodyExpression::Expression(expr.clone())) } #[cfg(test)] @@ -122,21 +152,6 @@ fn foo(a: Option<u32>) -> bool { } #[test] - fn not_applicable_non_bool_literal_arms() { - cov_mark::check!(non_invert_bool_literal_arms); - check_assist_not_applicable( - convert_two_arm_bool_match_to_matches_macro, - r#" -fn foo(a: Option<u32>) -> bool { - match a$0 { - Some(val) => val == 3, - _ => false - } -} - "#, - ); - } - #[test] fn not_applicable_both_false_arms() { cov_mark::check!(non_invert_bool_literal_arms); check_assist_not_applicable( @@ -291,4 +306,40 @@ fn main() { }", ); } + + #[test] + fn convert_non_literal_bool() { + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn main() { + match 0$0 { + a @ 0..15 => a == 0, + _ => false, + } +} +"#, + r#" +fn main() { + matches!(0, a @ 0..15 if a == 0) +} +"#, + ); + check_assist( + convert_two_arm_bool_match_to_matches_macro, + r#" +fn main() { + match 0$0 { + a @ 0..15 if thing() => a == 0, + _ => false, + } +} +"#, + r#" +fn main() { + matches!(0, a @ 0..15 if thing() && a == 0) +} +"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs index ea71d165e6a..f30ca2552d3 100644 --- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -114,7 +114,7 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat let usages = ctx.sema.to_def(&ident_pat).map(|def| { Definition::Local(def) .usages(&ctx.sema) - .in_scope(SearchScope::single_file(ctx.file_id())) + .in_scope(&SearchScope::single_file(ctx.file_id())) .all() }); diff --git a/crates/ide-assists/src/handlers/expand_glob_import.rs b/crates/ide-assists/src/handlers/expand_glob_import.rs index 3aff5c9144f..9beb616d99b 100644 --- a/crates/ide-assists/src/handlers/expand_glob_import.rs +++ b/crates/ide-assists/src/handlers/expand_glob_import.rs @@ -120,7 +120,7 @@ fn find_parent_and_path( fn def_is_referenced_in(def: Definition, ctx: &AssistContext<'_>) -> bool { let search_scope = SearchScope::single_file(ctx.file_id()); - def.usages(&ctx.sema).in_scope(search_scope).at_least_one() + def.usages(&ctx.sema).in_scope(&search_scope).at_least_one() } #[derive(Debug, Clone)] diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index e9db38aca0f..b8b781ea48d 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -384,7 +384,7 @@ impl LocalUsages { Self( Definition::Local(var) .usages(&ctx.sema) - .in_scope(SearchScope::single_file(ctx.file_id())) + .in_scope(&SearchScope::single_file(ctx.file_id())) .all(), ) } diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs index de37f5f130f..6839c5820dc 100644 --- a/crates/ide-assists/src/handlers/extract_module.rs +++ b/crates/ide-assists/src/handlers/extract_module.rs @@ -478,7 +478,7 @@ impl Module { let selection_range = ctx.selection_trimmed(); let curr_file_id = ctx.file_id(); let search_scope = SearchScope::single_file(curr_file_id); - let usage_res = def.usages(&ctx.sema).in_scope(search_scope).all(); + let usage_res = def.usages(&ctx.sema).in_scope(&search_scope).all(); let file = ctx.sema.parse(curr_file_id); let mut exists_inside_sel = false; diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index 8085572497a..5b13e01b133 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -1878,7 +1878,6 @@ where #[test] fn add_function_with_fn_arg() { - // FIXME: The argument in `bar` is wrong. check_assist( generate_function, r" @@ -1899,7 +1898,7 @@ fn foo() { bar(Baz::new); } -fn bar(new: fn) ${0:-> _} { +fn bar(new: fn() -> Baz) ${0:-> _} { todo!() } ", diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 67036029f5e..ffab58509b1 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -80,7 +80,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> let is_recursive_fn = usages .clone() - .in_scope(SearchScope::file_range(FileRange { + .in_scope(&SearchScope::file_range(FileRange { file_id: def_file, range: func_body.syntax().text_range(), })) diff --git a/crates/ide-assists/src/handlers/move_const_to_impl.rs b/crates/ide-assists/src/handlers/move_const_to_impl.rs index e1849eb71d5..22d536b5afc 100644 --- a/crates/ide-assists/src/handlers/move_const_to_impl.rs +++ b/crates/ide-assists/src/handlers/move_const_to_impl.rs @@ -82,17 +82,19 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> return None; } - let usages = - Definition::Const(def).usages(&ctx.sema).in_scope(SearchScope::file_range(FileRange { - file_id: ctx.file_id(), - range: parent_fn.syntax().text_range(), - })); - acc.add( AssistId("move_const_to_impl", crate::AssistKind::RefactorRewrite), "Move const to impl block", const_.syntax().text_range(), |builder| { + let usages = Definition::Const(def) + .usages(&ctx.sema) + .in_scope(&SearchScope::file_range(FileRange { + file_id: ctx.file_id(), + range: parent_fn.syntax().text_range(), + })) + .all(); + let range_to_delete = match const_.syntax().next_sibling_or_token() { Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => { // Remove following whitespaces too. @@ -103,7 +105,7 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> builder.delete(range_to_delete); let const_ref = format!("Self::{}", name.display(ctx.db())); - for range in usages.all().file_ranges().map(|it| it.range) { + for range in usages.file_ranges().map(|it| it.range) { builder.replace(range, const_ref.clone()); } diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs new file mode 100644 index 00000000000..dd4839351fb --- /dev/null +++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs @@ -0,0 +1,739 @@ +use std::collections::{hash_map::Entry, HashMap}; + +use hir::{InFile, Module, ModuleSource}; +use ide_db::{ + base_db::FileRange, + defs::Definition, + search::{FileReference, ReferenceCategory, SearchScope}, + RootDatabase, +}; +use syntax::{ast, AstNode}; +use text_edit::TextRange; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: remove_unused_imports +// +// Removes any use statements in the current selection that are unused. +// +// ``` +// struct X(); +// mod foo { +// use super::X$0; +// } +// ``` +// -> +// ``` +// struct X(); +// mod foo { +// } +// ``` +pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + // First, grab the uses that intersect with the current selection. + let selected_el = match ctx.covering_element() { + syntax::NodeOrToken::Node(n) => n, + syntax::NodeOrToken::Token(t) => t.parent()?, + }; + + // This applies to all uses that are selected, or are ancestors of our selection. + let uses_up = selected_el.ancestors().skip(1).filter_map(ast::Use::cast); + let uses_down = selected_el + .descendants() + .filter(|x| x.text_range().intersect(ctx.selection_trimmed()).is_some()) + .filter_map(ast::Use::cast); + let uses = uses_up.chain(uses_down).collect::<Vec<_>>(); + + // Maps use nodes to the scope that we should search through to find + let mut search_scopes = HashMap::<Module, Vec<SearchScope>>::new(); + + // iterator over all unused use trees + let mut unused = uses + .into_iter() + .flat_map(|u| u.syntax().descendants().filter_map(ast::UseTree::cast)) + .filter(|u| u.use_tree_list().is_none()) + .filter_map(|u| { + // Find any uses trees that are unused + + let use_module = ctx.sema.scope(&u.syntax()).map(|s| s.module())?; + let scope = match search_scopes.entry(use_module) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(v) => v.insert(module_search_scope(ctx.db(), use_module)), + }; + + // Gets the path associated with this use tree. If there isn't one, then ignore this use tree. + let path = if let Some(path) = u.path() { + path + } else if u.star_token().is_some() { + // This case maps to the situation where the * token is braced. + // In this case, the parent use tree's path is the one we should use to resolve the glob. + match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) { + Some(parent_u) if parent_u.path().is_some() => parent_u.path().unwrap(), + _ => return None, + } + } else { + return None; + }; + + // Get the actual definition associated with this use item. + let res = match ctx.sema.resolve_path(&path) { + Some(x) => x, + None => { + return None; + } + }; + + let def = match res { + hir::PathResolution::Def(d) => Definition::from(d), + _ => return None, + }; + + if u.star_token().is_some() { + // Check if any of the children of this module are used + let def_mod = match def { + Definition::Module(module) => module, + _ => return None, + }; + + if !def_mod + .scope(ctx.db(), Some(use_module)) + .iter() + .filter_map(|(_, x)| match x { + hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)), + _ => None, + }) + .any(|d| used_once_in_scope(ctx, d, scope)) + { + return Some(u); + } + } else if let Definition::Trait(ref t) = def { + // If the trait or any item is used. + if !std::iter::once(def) + .chain(t.items(ctx.db()).into_iter().map(Definition::from)) + .any(|d| used_once_in_scope(ctx, d, scope)) + { + return Some(u); + } + } else { + if !used_once_in_scope(ctx, def, &scope) { + return Some(u); + } + } + + None + }) + .peekable(); + + // Peek so we terminate early if an unused use is found. Only do the rest of the work if the user selects the assist. + if unused.peek().is_some() { + acc.add( + AssistId("remove_unused_imports", AssistKind::QuickFix), + "Remove all the unused imports", + selected_el.text_range(), + |builder| { + let unused: Vec<ast::UseTree> = unused.map(|x| builder.make_mut(x)).collect(); + for node in unused { + node.remove_recursive(); + } + }, + ) + } else { + None + } +} + +fn used_once_in_scope(ctx: &AssistContext<'_>, def: Definition, scopes: &Vec<SearchScope>) -> bool { + let mut found = false; + + for scope in scopes { + let mut search_non_import = |_, r: FileReference| { + // The import itself is a use; we must skip that. + if r.category != Some(ReferenceCategory::Import) { + found = true; + true + } else { + false + } + }; + def.usages(&ctx.sema).in_scope(scope).search(&mut search_non_import); + if found { + break; + } + } + + found +} + +/// Build a search scope spanning the given module but none of its submodules. +fn module_search_scope(db: &RootDatabase, module: hir::Module) -> Vec<SearchScope> { + let (file_id, range) = { + let InFile { file_id, value } = module.definition_source(db); + if let Some((file_id, call_source)) = file_id.original_call_node(db) { + (file_id, Some(call_source.text_range())) + } else { + ( + file_id.original_file(db), + match value { + ModuleSource::SourceFile(_) => None, + ModuleSource::Module(it) => Some(it.syntax().text_range()), + ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()), + }, + ) + } + }; + + fn split_at_subrange(first: TextRange, second: TextRange) -> (TextRange, Option<TextRange>) { + let intersect = first.intersect(second); + if let Some(intersect) = intersect { + let start_range = TextRange::new(first.start(), intersect.start()); + + if intersect.end() < first.end() { + (start_range, Some(TextRange::new(intersect.end(), first.end()))) + } else { + (start_range, None) + } + } else { + (first, None) + } + } + + let mut scopes = Vec::new(); + if let Some(range) = range { + let mut ranges = vec![range]; + + for child in module.children(db) { + let rng = match child.definition_source(db).value { + ModuleSource::SourceFile(_) => continue, + ModuleSource::Module(it) => it.syntax().text_range(), + ModuleSource::BlockExpr(_) => continue, + }; + let mut new_ranges = Vec::new(); + for old_range in ranges.iter_mut() { + let split = split_at_subrange(old_range.clone(), rng); + *old_range = split.0; + new_ranges.extend(split.1); + } + + ranges.append(&mut new_ranges); + } + + for range in ranges { + scopes.push(SearchScope::file_range(FileRange { file_id, range })); + } + } else { + scopes.push(SearchScope::single_file(file_id)); + } + + scopes +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn remove_unused() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + $0use super::X; + use super::Y;$0 +} +"#, + r#" +struct X(); +struct Y(); +mod z { +} +"#, + ); + } + + #[test] + fn remove_unused_is_precise() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod z { +$0use super::X;$0 + +fn w() { + struct X(); + let x = X(); +} +} +"#, + r#" +struct X(); +mod z { + +fn w() { + struct X(); + let x = X(); +} +} +"#, + ); + } + + #[test] + fn trait_name_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(); +} + +impl Y for X { + fn f() {} +} +mod z { +$0use super::X; +use super::Y;$0 + +fn w() { + X::f(); +} +} +"#, + ); + } + + #[test] + fn trait_item_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(self); +} + +impl Y for X { + fn f(self) {} +} +mod z { +$0use super::X; +use super::Y;$0 + +fn w() { + let x = X(); + x.f(); +} +} +"#, + ); + } + + #[test] + fn ranamed_trait_item_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(self); +} + +impl Y for X { + fn f(self) {} +} +mod z { +$0use super::X; +use super::Y as Z;$0 + +fn w() { + let x = X(); + x.f(); +} +} +"#, + ); + } + + #[test] + fn ranamed_underscore_trait_item_use_is_use() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +trait Y { + fn f(self); +} + +impl Y for X { + fn f(self) {} +} +mod z { +$0use super::X; +use super::Y as _;$0 + +fn w() { + let x = X(); + x.f(); +} +} +"#, + ); + } + + #[test] + fn dont_remove_used() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { +$0use super::X; +use super::Y;$0 + +fn w() { + let x = X(); + let y = Y(); +} +} +"#, + ); + } + + #[test] + fn remove_unused_in_braces() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + $0use super::{X, Y};$0 + + fn w() { + let x = X(); + } +} +"#, + r#" +struct X(); +struct Y(); +mod z { + use super::{X}; + + fn w() { + let x = X(); + } +} +"#, + ); + } + + #[test] + fn remove_unused_under_cursor() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod z { + use super::X$0; +} +"#, + r#" +struct X(); +mod z { +} +"#, + ); + } + + #[test] + fn remove_multi_use_block() { + check_assist( + remove_unused_imports, + r#" +struct X(); +$0mod y { + use super::X; +} +mod z { + use super::X; +}$0 +"#, + r#" +struct X(); +mod y { +} +mod z { +} +"#, + ); + } + + #[test] + fn remove_nested() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X, y::Y}$0; + fn f() { + let x = X(); + } + } +} +"#, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X}; + fn f() { + let x = X(); + } + } +} +"#, + ); + } + + #[test] + fn remove_nested_first_item() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X, y::Y}$0; + fn f() { + let y = Y(); + } + } +} +"#, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{y::Y}; + fn f() { + let y = Y(); + } + } +} +"#, + ); + } + + #[test] + fn remove_nested_all_unused() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod y { + struct Y(); + mod z { + use crate::{X, y::Y}$0; + } +} +"#, + r#" +struct X(); +mod y { + struct Y(); + mod z { + } +} +"#, + ); + } + + #[test] + fn remove_unused_glob() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + use super::*$0; +} +"#, + r#" +struct X(); +struct Y(); +mod z { +} +"#, + ); + } + + #[test] + fn remove_unused_braced_glob() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + use super::{*}$0; +} +"#, + r#" +struct X(); +struct Y(); +mod z { +} +"#, + ); + } + + #[test] + fn dont_remove_used_glob() { + check_assist_not_applicable( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + use super::*$0; + + fn f() { + let x = X(); + } +} +"#, + ); + } + + #[test] + fn only_remove_from_selection() { + check_assist( + remove_unused_imports, + r#" +struct X(); +struct Y(); +mod z { + $0use super::X;$0 + use super::Y; +} +mod w { + use super::Y; +} +"#, + r#" +struct X(); +struct Y(); +mod z { + use super::Y; +} +mod w { + use super::Y; +} +"#, + ); + } + + #[test] + fn test_several_files() { + check_assist( + remove_unused_imports, + r#" +//- /foo.rs +pub struct X(); +pub struct Y(); + +//- /main.rs +$0use foo::X; +use foo::Y; +$0 +mod foo; +mod z { + use crate::foo::X; +} +"#, + r#" + +mod foo; +mod z { + use crate::foo::X; +} +"#, + ); + } + + #[test] + fn use_in_submodule_doesnt_count() { + check_assist( + remove_unused_imports, + r#" +struct X(); +mod z { + use super::X$0; + + mod w { + use crate::X; + + fn f() { + let x = X(); + } + } +} +"#, + r#" +struct X(); +mod z { + + mod w { + use crate::X; + + fn f() { + let x = X(); + } + } +} +"#, + ); + } + + #[test] + fn use_in_submodule_file_doesnt_count() { + check_assist( + remove_unused_imports, + r#" +//- /z/foo.rs +use crate::X; +fn f() { + let x = X(); +} + +//- /main.rs +pub struct X(); + +mod z { + use crate::X$0; + mod foo; +} +"#, + r#" +pub struct X(); + +mod z { + mod foo; +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index c03bc2f41d5..ac45581b7b4 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -10,7 +10,7 @@ use crate::{ assist_context::{AssistContext, Assists, SourceChangeBuilder}, utils::{ add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, - generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, + generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, IgnoreAssocItems, }, AssistId, AssistKind, }; @@ -172,7 +172,17 @@ fn impl_def_from_trait( ) -> Option<(ast::Impl, ast::AssocItem)> { let trait_ = trait_?; let target_scope = sema.scope(annotated_name.syntax())?; - let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No); + + // Keep assoc items of local crates even if they have #[doc(hidden)] attr. + let ignore_items = if trait_.module(sema.db).krate().origin(sema.db).is_local() { + IgnoreAssocItems::No + } else { + IgnoreAssocItems::DocHiddenAttrPresent + }; + + let trait_items = + filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No, ignore_items); + if trait_items.is_empty() { return None; } diff --git a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs index e7b62d49bb8..c7c0be4c7d4 100644 --- a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs +++ b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs @@ -157,7 +157,7 @@ fn find_usages( file_id: FileId, ) -> UsageSearchResult { let file_range = FileRange { file_id, range: fn_.syntax().text_range() }; - type_param_def.usages(sema).in_scope(SearchScope::file_range(file_range)).all() + type_param_def.usages(sema).in_scope(&SearchScope::file_range(file_range)).all() } fn check_valid_usages(usages: &UsageSearchResult, param_list_range: TextRange) -> bool { diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index a82f1f9dd8b..2ebb5ef9b19 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -184,6 +184,7 @@ mod handlers { mod raw_string; mod remove_dbg; mod remove_mut; + mod remove_unused_imports; mod remove_unused_param; mod remove_parentheses; mod reorder_fields; @@ -294,6 +295,7 @@ mod handlers { raw_string::make_usual_string, raw_string::remove_hash, remove_mut::remove_mut, + remove_unused_imports::remove_unused_imports, remove_unused_param::remove_unused_param, remove_parentheses::remove_parentheses, reorder_fields::reorder_fields, diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 344f2bfcce1..cc3e251a8f2 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -132,8 +132,13 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty()) .expect("Assist did not contain any source changes"); let mut actual = before; - if let Some(source_file_edit) = source_change.get_source_edit(file_id) { + if let Some((source_file_edit, snippet_edit)) = + source_change.get_source_and_snippet_edit(file_id) + { source_file_edit.apply(&mut actual); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut actual); + } } actual }; @@ -191,9 +196,12 @@ fn check_with_config( && source_change.file_system_edits.len() == 0; let mut buf = String::new(); - for (file_id, edit) in source_change.source_file_edits { + for (file_id, (edit, snippet_edit)) in source_change.source_file_edits { let mut text = db.file_text(file_id).as_ref().to_owned(); edit.apply(&mut text); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut text); + } if !skip_header { let sr = db.file_source_root(file_id); let sr = db.source_root(sr); @@ -485,18 +493,21 @@ pub fn test_some_range(a: int) -> bool { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "let $0var_name = 5;\n ", - delete: 45..45, - }, - Indel { - insert: "var_name", - delete: 59..60, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "let $0var_name = 5;\n ", + delete: 45..45, + }, + Indel { + insert: "var_name", + delete: 59..60, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: true, @@ -544,18 +555,21 @@ pub fn test_some_range(a: int) -> bool { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "let $0var_name = 5;\n ", - delete: 45..45, - }, - Indel { - insert: "var_name", - delete: 59..60, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "let $0var_name = 5;\n ", + delete: 45..45, + }, + Indel { + insert: "var_name", + delete: 59..60, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: true, @@ -581,18 +595,21 @@ pub fn test_some_range(a: int) -> bool { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "fun_name()", - delete: 59..60, - }, - Indel { - insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}", - delete: 110..110, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "fun_name()", + delete: 59..60, + }, + Indel { + insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}", + delete: 110..110, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: true, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 4d47a199b7c..ec3822c3d11 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2234,6 +2234,24 @@ fn main() { } #[test] +fn doctest_remove_unused_imports() { + check_doc_test( + "remove_unused_imports", + r#####" +struct X(); +mod foo { + use super::X$0; +} +"#####, + r#####" +struct X(); +mod foo { +} +"#####, + ) +} + +#[test] fn doctest_remove_unused_param() { check_doc_test( "remove_unused_param", diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 03d8553506f..a262570d94e 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -3,7 +3,7 @@ use std::ops; pub(crate) use gen_trait_fn_body::gen_trait_fn_body; -use hir::{db::HirDatabase, HirDisplay, InFile, Semantics}; +use hir::{db::HirDatabase, HasAttrs as HirHasAttrs, HirDisplay, InFile, Semantics}; use ide_db::{ famous_defs::FamousDefs, path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase, SnippetCap, @@ -84,6 +84,12 @@ pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> { }) } +#[derive(Clone, Copy, PartialEq)] +pub enum IgnoreAssocItems { + DocHiddenAttrPresent, + No, +} + #[derive(Copy, Clone, PartialEq)] pub enum DefaultMethods { Only, @@ -94,11 +100,16 @@ pub fn filter_assoc_items( sema: &Semantics<'_, RootDatabase>, items: &[hir::AssocItem], default_methods: DefaultMethods, + ignore_items: IgnoreAssocItems, ) -> Vec<InFile<ast::AssocItem>> { return items .iter() - // Note: This throws away items with no source. .copied() + .filter(|assoc_item| { + !(ignore_items == IgnoreAssocItems::DocHiddenAttrPresent + && assoc_item.attrs(sema.db).has_doc_hidden()) + }) + // Note: This throws away items with no source. .filter_map(|assoc_item| { let item = match assoc_item { hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn), diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 760834bfafc..5e4562d9c58 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -7,10 +7,10 @@ use arrayvec::ArrayVec; use hir::{ - Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, Field, - Function, GenericParam, HasVisibility, Impl, Label, Local, Macro, Module, ModuleDef, Name, - PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TypeAlias, Variant, - Visibility, + Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, + ExternCrateDecl, Field, Function, GenericParam, HasVisibility, Impl, Label, Local, Macro, + Module, ModuleDef, Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, + TypeAlias, Variant, Visibility, }; use stdx::impl_from; use syntax::{ @@ -42,6 +42,7 @@ pub enum Definition { DeriveHelper(DeriveHelper), BuiltinAttr(BuiltinAttr), ToolModule(ToolModule), + ExternCrateDecl(ExternCrateDecl), } impl Definition { @@ -73,6 +74,7 @@ impl Definition { Definition::Local(it) => it.module(db), Definition::GenericParam(it) => it.module(db), Definition::Label(it) => it.module(db), + Definition::ExternCrateDecl(it) => it.module(db), Definition::DeriveHelper(it) => it.derive().module(db), Definition::BuiltinAttr(_) | Definition::BuiltinType(_) | Definition::ToolModule(_) => { return None @@ -93,6 +95,7 @@ impl Definition { Definition::TraitAlias(it) => it.visibility(db), Definition::TypeAlias(it) => it.visibility(db), Definition::Variant(it) => it.visibility(db), + Definition::ExternCrateDecl(it) => it.visibility(db), Definition::BuiltinType(_) => Visibility::Public, Definition::Macro(_) => return None, Definition::BuiltinAttr(_) @@ -127,6 +130,7 @@ impl Definition { Definition::BuiltinAttr(_) => return None, // FIXME Definition::ToolModule(_) => return None, // FIXME Definition::DeriveHelper(it) => it.name(db), + Definition::ExternCrateDecl(it) => return it.alias_or_name(db), }; Some(name) } @@ -196,6 +200,10 @@ impl IdentClass { res.push(Definition::Local(local_ref)); res.push(Definition::Field(field_ref)); } + IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => { + res.push(Definition::ExternCrateDecl(decl)); + res.push(Definition::Module(krate.root_module())); + } IdentClass::Operator( OperatorClass::Await(func) | OperatorClass::Prefix(func) @@ -222,6 +230,10 @@ impl IdentClass { res.push(Definition::Local(local_ref)); res.push(Definition::Field(field_ref)); } + IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => { + res.push(Definition::ExternCrateDecl(decl)); + res.push(Definition::Module(krate.root_module())); + } IdentClass::Operator(_) => (), } res @@ -310,6 +322,7 @@ impl NameClass { ast::Item::Enum(it) => Definition::Adt(hir::Adt::Enum(sema.to_def(&it)?)), ast::Item::Struct(it) => Definition::Adt(hir::Adt::Struct(sema.to_def(&it)?)), ast::Item::Union(it) => Definition::Adt(hir::Adt::Union(sema.to_def(&it)?)), + ast::Item::ExternCrate(it) => Definition::ExternCrateDecl(sema.to_def(&it)?), _ => return None, }; Some(definition) @@ -346,10 +359,8 @@ impl NameClass { let path = use_tree.path()?; sema.resolve_path(&path).map(Definition::from) } else { - let extern_crate = rename.syntax().parent().and_then(ast::ExternCrate::cast)?; - let krate = sema.resolve_extern_crate(&extern_crate)?; - let root_module = krate.root_module(sema.db); - Some(Definition::Module(root_module)) + sema.to_def(&rename.syntax().parent().and_then(ast::ExternCrate::cast)?) + .map(Definition::ExternCrateDecl) } } } @@ -427,7 +438,19 @@ impl OperatorClass { #[derive(Debug)] pub enum NameRefClass { Definition(Definition), - FieldShorthand { local_ref: Local, field_ref: Field }, + FieldShorthand { + local_ref: Local, + field_ref: Field, + }, + /// The specific situation where we have an extern crate decl without a rename + /// Here we have both a declaration and a reference. + /// ```rs + /// extern crate foo; + /// ``` + ExternCrateShorthand { + decl: ExternCrateDecl, + krate: Crate, + }, } impl NameRefClass { @@ -513,10 +536,14 @@ impl NameRefClass { } None }, - ast::ExternCrate(extern_crate) => { - let krate = sema.resolve_extern_crate(&extern_crate)?; - let root_module = krate.root_module(sema.db); - Some(NameRefClass::Definition(Definition::Module(root_module))) + ast::ExternCrate(extern_crate_ast) => { + let extern_crate = sema.to_def(&extern_crate_ast)?; + let krate = extern_crate.resolved_crate(sema.db)?; + Some(if extern_crate_ast.rename().is_some() { + NameRefClass::Definition(Definition::Module(krate.root_module())) + } else { + NameRefClass::ExternCrateShorthand { krate, decl: extern_crate } + }) }, _ => None } diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index c8341fed1c7..b63dde2c21e 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -167,7 +167,7 @@ impl FamousDefs<'_, '_> { lang_crate => lang_crate, }; let std_crate = self.find_lang_crate(lang_crate)?; - let mut module = std_crate.root_module(db); + let mut module = std_crate.root_module(); for segment in path { module = module.children(db).find_map(|child| { let name = child.name(db)?; diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 52a23b4b8f3..aa0bb7cce69 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -82,8 +82,9 @@ impl Definition { } /// Textual range of the identifier which will change when renaming this - /// `Definition`. Note that some definitions, like builtin types, can't be - /// renamed. + /// `Definition`. Note that builtin types can't be + /// renamed and extern crate names will report its range, though a rename will introduce + /// an alias instead. pub fn range_for_rename(self, sema: &Semantics<'_, RootDatabase>) -> Option<FileRange> { let res = match self { Definition::Macro(mac) => { @@ -146,6 +147,16 @@ impl Definition { let lifetime = src.value.lifetime()?; src.with_value(lifetime.syntax()).original_file_range_opt(sema.db) } + Definition::ExternCrateDecl(it) => { + let src = it.source(sema.db)?; + if let Some(rename) = src.value.rename() { + let name = rename.name()?; + src.with_value(name.syntax()).original_file_range_opt(sema.db) + } else { + let name = src.value.name_ref()?; + src.with_value(name.syntax()).original_file_range_opt(sema.db) + } + } Definition::BuiltinType(_) => return None, Definition::SelfType(_) => return None, Definition::BuiltinAttr(_) => return None, @@ -526,6 +537,9 @@ fn source_edit_from_def( TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), ), + Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => { + (TextRange::empty(range.end()), format!(" as {new_name}")) + } _ => (range, new_name.to_owned()), }; edit.replace(range, new_name); diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index f3c0f79c589..d5abd099126 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -127,7 +127,7 @@ impl SearchScope { } /// Build a search scope spanning the given module and all its submodules. - fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope { + pub fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope { let mut entries = IntMap::default(); let (file_id, range) = { @@ -329,7 +329,7 @@ impl Definition { pub struct FindUsages<'a> { def: Definition, sema: &'a Semantics<'a, RootDatabase>, - scope: Option<SearchScope>, + scope: Option<&'a SearchScope>, /// The container of our definition should it be an assoc item assoc_item_container: Option<hir::AssocItemContainer>, /// whether to search for the `Self` type of the definition @@ -338,7 +338,7 @@ pub struct FindUsages<'a> { search_self_mod: bool, } -impl FindUsages<'_> { +impl<'a> FindUsages<'a> { /// Enable searching for `Self` when the definition is a type or `self` for modules. pub fn include_self_refs(mut self) -> Self { self.include_self_kw_refs = def_to_ty(self.sema, &self.def); @@ -347,12 +347,12 @@ impl FindUsages<'_> { } /// Limit the search to a given [`SearchScope`]. - pub fn in_scope(self, scope: SearchScope) -> Self { + pub fn in_scope(self, scope: &'a SearchScope) -> Self { self.set_scope(Some(scope)) } /// Limit the search to a given [`SearchScope`]. - pub fn set_scope(mut self, scope: Option<SearchScope>) -> Self { + pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self { assert!(self.scope.is_none()); self.scope = scope; self @@ -376,7 +376,7 @@ impl FindUsages<'_> { res } - fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) { + pub fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) { let _p = profile::span("FindUsages:search"); let sema = self.sema; diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index fad0ca51a02..39763479c65 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -7,17 +7,17 @@ use std::{collections::hash_map::Entry, iter, mem}; use crate::SnippetCap; use base_db::{AnchoredPathBuf, FileId}; +use itertools::Itertools; use nohash_hasher::IntMap; use stdx::never; use syntax::{ - algo, ast, ted, AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, - TextSize, + algo, AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize, }; use text_edit::{TextEdit, TextEditBuilder}; #[derive(Default, Debug, Clone)] pub struct SourceChange { - pub source_file_edits: IntMap<FileId, TextEdit>, + pub source_file_edits: IntMap<FileId, (TextEdit, Option<SnippetEdit>)>, pub file_system_edits: Vec<FileSystemEdit>, pub is_snippet: bool, } @@ -26,7 +26,7 @@ impl SourceChange { /// Creates a new SourceChange with the given label /// from the edits. pub fn from_edits( - source_file_edits: IntMap<FileId, TextEdit>, + source_file_edits: IntMap<FileId, (TextEdit, Option<SnippetEdit>)>, file_system_edits: Vec<FileSystemEdit>, ) -> Self { SourceChange { source_file_edits, file_system_edits, is_snippet: false } @@ -34,7 +34,7 @@ impl SourceChange { pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self { SourceChange { - source_file_edits: iter::once((file_id, edit)).collect(), + source_file_edits: iter::once((file_id, (edit, None))).collect(), ..Default::default() } } @@ -42,12 +42,31 @@ impl SourceChange { /// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing /// edits for a file if some already exist. pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) { + self.insert_source_and_snippet_edit(file_id, edit, None) + } + + /// Inserts a [`TextEdit`] and potentially a [`SnippetEdit`] for the given [`FileId`]. + /// This properly handles merging existing edits for a file if some already exist. + pub fn insert_source_and_snippet_edit( + &mut self, + file_id: FileId, + edit: TextEdit, + snippet_edit: Option<SnippetEdit>, + ) { match self.source_file_edits.entry(file_id) { Entry::Occupied(mut entry) => { - never!(entry.get_mut().union(edit).is_err(), "overlapping edits for same file"); + let value = entry.get_mut(); + never!(value.0.union(edit).is_err(), "overlapping edits for same file"); + never!( + value.1.is_some() && snippet_edit.is_some(), + "overlapping snippet edits for same file" + ); + if value.1.is_none() { + value.1 = snippet_edit; + } } Entry::Vacant(entry) => { - entry.insert(edit); + entry.insert((edit, snippet_edit)); } } } @@ -56,7 +75,10 @@ impl SourceChange { self.file_system_edits.push(edit); } - pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> { + pub fn get_source_and_snippet_edit( + &self, + file_id: FileId, + ) -> Option<&(TextEdit, Option<SnippetEdit>)> { self.source_file_edits.get(&file_id) } @@ -70,7 +92,18 @@ impl SourceChange { impl Extend<(FileId, TextEdit)> for SourceChange { fn extend<T: IntoIterator<Item = (FileId, TextEdit)>>(&mut self, iter: T) { - iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit)); + self.extend(iter.into_iter().map(|(file_id, edit)| (file_id, (edit, None)))) + } +} + +impl Extend<(FileId, (TextEdit, Option<SnippetEdit>))> for SourceChange { + fn extend<T: IntoIterator<Item = (FileId, (TextEdit, Option<SnippetEdit>))>>( + &mut self, + iter: T, + ) { + iter.into_iter().for_each(|(file_id, (edit, snippet_edit))| { + self.insert_source_and_snippet_edit(file_id, edit, snippet_edit) + }); } } @@ -82,6 +115,8 @@ impl Extend<FileSystemEdit> for SourceChange { impl From<IntMap<FileId, TextEdit>> for SourceChange { fn from(source_file_edits: IntMap<FileId, TextEdit>) -> SourceChange { + let source_file_edits = + source_file_edits.into_iter().map(|(file_id, edit)| (file_id, (edit, None))).collect(); SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } } } @@ -94,6 +129,65 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SnippetEdit(Vec<(u32, TextRange)>); + +impl SnippetEdit { + pub fn new(snippets: Vec<Snippet>) -> Self { + let mut snippet_ranges = snippets + .into_iter() + .zip(1..) + .with_position() + .map(|pos| { + let (snippet, index) = match pos { + itertools::Position::First(it) | itertools::Position::Middle(it) => it, + // last/only snippet gets index 0 + itertools::Position::Last((snippet, _)) + | itertools::Position::Only((snippet, _)) => (snippet, 0), + }; + + let range = match snippet { + Snippet::Tabstop(pos) => TextRange::empty(pos), + Snippet::Placeholder(range) => range, + }; + (index, range) + }) + .collect_vec(); + + snippet_ranges.sort_by_key(|(_, range)| range.start()); + + // Ensure that none of the ranges overlap + let disjoint_ranges = snippet_ranges + .iter() + .zip(snippet_ranges.iter().skip(1)) + .all(|((_, left), (_, right))| left.end() <= right.start() || left == right); + stdx::always!(disjoint_ranges); + + SnippetEdit(snippet_ranges) + } + + /// Inserts all of the snippets into the given text. + pub fn apply(&self, text: &mut String) { + // Start from the back so that we don't have to adjust ranges + for (index, range) in self.0.iter().rev() { + if range.is_empty() { + // is a tabstop + text.insert_str(range.start().into(), &format!("${index}")); + } else { + // is a placeholder + text.insert(range.end().into(), '}'); + text.insert_str(range.start().into(), &format!("${{{index}:")); + } + } + } + + /// Gets the underlying snippet index + text range + /// Tabstops are represented by an empty range, and placeholders use the range that they were given + pub fn into_edit_ranges(self) -> Vec<(u32, TextRange)> { + self.0 + } +} + pub struct SourceChangeBuilder { pub edit: TextEditBuilder, pub file_id: FileId, @@ -152,24 +246,19 @@ impl SourceChangeBuilder { } fn commit(&mut self) { - // Render snippets first so that they get bundled into the tree diff - if let Some(mut snippets) = self.snippet_builder.take() { - // Last snippet always has stop index 0 - let last_stop = snippets.places.pop().unwrap(); - last_stop.place(0); - - for (index, stop) in snippets.places.into_iter().enumerate() { - stop.place(index + 1) - } - } + let snippet_edit = self.snippet_builder.take().map(|builder| { + SnippetEdit::new( + builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(), + ) + }); if let Some(tm) = self.mutated_tree.take() { - algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit) + algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit); } let edit = mem::take(&mut self.edit).finish(); - if !edit.is_empty() { - self.source_change.insert_source_edit(self.file_id, edit); + if !edit.is_empty() || snippet_edit.is_some() { + self.source_change.insert_source_and_snippet_edit(self.file_id, edit, snippet_edit); } } @@ -275,6 +364,16 @@ impl SourceChangeBuilder { pub fn finish(mut self) -> SourceChange { self.commit(); + + // Only one file can have snippet edits + stdx::never!(self + .source_change + .source_file_edits + .iter() + .filter(|(_, (_, snippet_edit))| snippet_edit.is_some()) + .at_most_one() + .is_err()); + mem::take(&mut self.source_change) } } @@ -296,6 +395,13 @@ impl From<FileSystemEdit> for SourceChange { } } +pub enum Snippet { + /// A tabstop snippet (e.g. `$0`). + Tabstop(TextSize), + /// A placeholder snippet (e.g. `${0:placeholder}`). + Placeholder(TextRange), +} + enum PlaceSnippet { /// Place a tabstop before an element Before(SyntaxElement), @@ -306,57 +412,11 @@ enum PlaceSnippet { } impl PlaceSnippet { - /// Places the snippet before or over an element with the given tab stop index - fn place(self, order: usize) { - // ensure the target element is still attached - match &self { - PlaceSnippet::Before(element) - | PlaceSnippet::After(element) - | PlaceSnippet::Over(element) => { - // element should still be in the tree, but if it isn't - // then it's okay to just ignore this place - if stdx::never!(element.parent().is_none()) { - return; - } - } - } - + fn finalize_position(self) -> Snippet { match self { - PlaceSnippet::Before(element) => { - ted::insert_raw(ted::Position::before(&element), Self::make_tab_stop(order)); - } - PlaceSnippet::After(element) => { - ted::insert_raw(ted::Position::after(&element), Self::make_tab_stop(order)); - } - PlaceSnippet::Over(element) => { - let position = ted::Position::before(&element); - element.detach(); - - let snippet = ast::SourceFile::parse(&format!("${{{order}:_}}")) - .syntax_node() - .clone_for_update(); - - let placeholder = - snippet.descendants().find_map(ast::UnderscoreExpr::cast).unwrap(); - ted::replace(placeholder.syntax(), element); - - ted::insert_raw(position, snippet); - } + PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()), + PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()), + PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()), } } - - fn make_tab_stop(order: usize) -> SyntaxNode { - let stop = ast::SourceFile::parse(&format!("stop!(${order})")) - .syntax_node() - .descendants() - .find_map(ast::TokenTree::cast) - .unwrap() - .syntax() - .clone_for_update(); - - stop.first_token().unwrap().detach(); - stop.last_token().unwrap().detach(); - - stop - } } diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs index 937e2f96642..f54cdd63bbb 100644 --- a/crates/ide-diagnostics/src/handlers/macro_error.rs +++ b/crates/ide-diagnostics/src/handlers/macro_error.rs @@ -51,6 +51,9 @@ macro_rules! compile_error { () => {} } compile_error!("compile_error macro works"); //^^^^^^^^^^^^^ error: compile_error macro works + + compile_error! { "compile_error macro braced works" } +//^^^^^^^^^^^^^ error: compile_error macro braced works "#, ); } @@ -77,7 +80,7 @@ macro_rules! m { fn f() { m!(); - //^^^^ error: unresolved macro `$crate::private::concat!` + //^^^^ error: unresolved macro $crate::private::concat } //- /core.rs crate:core diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs index bb0e36ff3a1..acc31cd117a 100644 --- a/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -208,7 +208,7 @@ fn get_default_constructor( } let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate(); - let module = krate.root_module(ctx.sema.db); + let module = krate.root_module(); // Look for a ::new() associated function let has_new_func = ty diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs index 4ac9d0a9fb7..ee0e0354906 100644 --- a/crates/ide-diagnostics/src/tests.rs +++ b/crates/ide-diagnostics/src/tests.rs @@ -49,8 +49,11 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) { let file_id = *source_change.source_file_edits.keys().next().unwrap(); let mut actual = db.file_text(file_id).to_string(); - for edit in source_change.source_file_edits.values() { + for (edit, snippet_edit) in source_change.source_file_edits.values() { edit.apply(&mut actual); + if let Some(snippet_edit) = snippet_edit { + snippet_edit.apply(&mut actual); + } } actual }; diff --git a/crates/ide-ssr/src/search.rs b/crates/ide-ssr/src/search.rs index 96c193bd539..ca76d0a87b9 100644 --- a/crates/ide-ssr/src/search.rs +++ b/crates/ide-ssr/src/search.rs @@ -121,7 +121,7 @@ impl MatchFinder<'_> { // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two // lookups in the case of a cache hit. if usage_cache.find(&definition).is_none() { - let usages = definition.usages(&self.sema).in_scope(self.search_scope()).all(); + let usages = definition.usages(&self.sema).in_scope(&self.search_scope()).all(); usage_cache.usages.push((definition, usages)); return &usage_cache.usages.last().unwrap().1; } diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index c90ba212535..d240127f376 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -153,6 +153,9 @@ pub(crate) fn external_docs( NameRefClass::FieldShorthand { local_ref: _, field_ref } => { Definition::Field(field_ref) } + NameRefClass::ExternCrateShorthand { decl, .. } => { + Definition::ExternCrateDecl(decl) + } }, ast::Name(name) => match NameClass::classify(sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => it, @@ -209,6 +212,7 @@ pub(crate) fn resolve_doc_path_for_def( Definition::Macro(it) => it.resolve_doc_path(db, link, ns), Definition::Field(it) => it.resolve_doc_path(db, link, ns), Definition::SelfType(it) => it.resolve_doc_path(db, link, ns), + Definition::ExternCrateDecl(it) => it.resolve_doc_path(db, link, ns), Definition::BuiltinAttr(_) | Definition::ToolModule(_) | Definition::BuiltinType(_) @@ -617,6 +621,9 @@ fn filename_and_frag_for_def( // FIXME fragment numbering return Some((adt, file, Some(String::from("impl")))); } + Definition::ExternCrateDecl(it) => { + format!("{}/index.html", it.name(db).display(db.upcast())) + } Definition::Local(_) | Definition::GenericParam(_) | Definition::Label(_) diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs index e70bc2ec541..c39c696cfd9 100644 --- a/crates/ide/src/goto_declaration.rs +++ b/crates/ide/src/goto_declaration.rs @@ -37,11 +37,15 @@ pub(crate) fn goto_declaration( match parent { ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? { NameRefClass::Definition(it) => Some(it), - NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), + NameRefClass::FieldShorthand { field_ref, .. } => + return field_ref.try_to_nav(db), + NameRefClass::ExternCrateShorthand { decl, .. } => + return decl.try_to_nav(db), }, ast::Name(name) => match NameClass::classify(&sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), - NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), + NameClass::PatFieldShorthand { field_ref, .. } => + return field_ref.try_to_nav(db), }, _ => None } @@ -53,6 +57,7 @@ pub(crate) fn goto_declaration( Definition::Const(c) => c.as_assoc_item(db), Definition::TypeAlias(ta) => ta.as_assoc_item(db), Definition::Function(f) => f.as_assoc_item(db), + Definition::ExternCrateDecl(it) => return it.try_to_nav(db), _ => None, }?; @@ -211,4 +216,30 @@ fn main() { "#, ); } + + #[test] + fn goto_decl_for_extern_crate() { + check( + r#" +//- /main.rs crate:main deps:std +extern crate std$0; + /// ^^^ +//- /std/lib.rs crate:std +// empty +"#, + ) + } + + #[test] + fn goto_decl_for_renamed_extern_crate() { + check( + r#" +//- /main.rs crate:main deps:std +extern crate std as abc$0; + /// ^^^ +//- /std/lib.rs crate:std +// empty +"#, + ) + } } diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 4e641357e37..21471ab2a03 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -1,6 +1,9 @@ use std::mem::discriminant; -use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav}; +use crate::{ + doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, + RangeInfo, TryToNav, +}; use hir::{AsAssocItem, AssocItem, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileId, FileLoader}, @@ -73,6 +76,13 @@ pub(crate) fn goto_definition( .definitions() .into_iter() .flat_map(|def| { + if let Definition::ExternCrateDecl(crate_def) = def { + return crate_def + .resolved_crate(db) + .map(|it| it.root_module().to_nav(sema.db)) + .into_iter() + .collect(); + } try_filter_trait_item_definition(sema, &def) .unwrap_or_else(|| def_to_nav(sema.db, def)) }) diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index a1a119629a9..37166bdbd0c 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs @@ -34,54 +34,50 @@ pub(crate) fn goto_implementation( _ => 0, })?; let range = original_token.text_range(); - let navs = sema - .descend_into_macros(original_token) - .into_iter() - .filter_map(|token| token.parent().and_then(ast::NameLike::cast)) - .filter_map(|node| match &node { - ast::NameLike::Name(name) => { - NameClass::classify(&sema, name).map(|class| match class { - NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def, field_ref: _ } => { - Definition::Local(local_def) + let navs = + sema.descend_into_macros(original_token) + .into_iter() + .filter_map(|token| token.parent().and_then(ast::NameLike::cast)) + .filter_map(|node| match &node { + ast::NameLike::Name(name) => { + NameClass::classify(&sema, name).and_then(|class| match class { + NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), + NameClass::PatFieldShorthand { .. } => None, + }) + } + ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref) + .and_then(|class| match class { + NameRefClass::Definition(def) => Some(def), + NameRefClass::FieldShorthand { .. } + | NameRefClass::ExternCrateShorthand { .. } => None, + }), + ast::NameLike::Lifetime(_) => None, + }) + .unique() + .filter_map(|def| { + let navs = match def { + Definition::Trait(trait_) => impls_for_trait(&sema, trait_), + Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), + Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), + Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)), + Definition::Function(f) => { + let assoc = f.as_assoc_item(sema.db)?; + let name = assoc.name(sema.db)?; + let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; + impls_for_trait_item(&sema, trait_, name) } - }) - } - ast::NameLike::NameRef(name_ref) => { - NameRefClass::classify(&sema, name_ref).map(|class| match class { - NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { - Definition::Local(local_ref) + Definition::Const(c) => { + let assoc = c.as_assoc_item(sema.db)?; + let name = assoc.name(sema.db)?; + let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; + impls_for_trait_item(&sema, trait_, name) } - }) - } - ast::NameLike::Lifetime(_) => None, - }) - .unique() - .filter_map(|def| { - let navs = match def { - Definition::Trait(trait_) => impls_for_trait(&sema, trait_), - Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), - Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), - Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)), - Definition::Function(f) => { - let assoc = f.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; - impls_for_trait_item(&sema, trait_, name) - } - Definition::Const(c) => { - let assoc = c.as_assoc_item(sema.db)?; - let name = assoc.name(sema.db)?; - let trait_ = assoc.containing_trait_or_trait_impl(sema.db)?; - impls_for_trait_item(&sema, trait_, name) - } - _ => return None, - }; - Some(navs) - }) - .flatten() - .collect(); + _ => return None, + }; + Some(navs) + }) + .flatten() + .collect(); Some(RangeInfo { range, info: navs }) } diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 7e545491f8e..43e89a334bf 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -100,10 +100,7 @@ fn highlight_closure_captures( .flat_map(|local| { let usages = Definition::Local(local) .usages(sema) - .set_scope(Some(SearchScope::file_range(FileRange { - file_id, - range: search_range, - }))) + .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range })) .include_self_refs() .all() .references @@ -139,7 +136,7 @@ fn highlight_references( .iter() .filter_map(|&d| { d.usages(sema) - .set_scope(Some(SearchScope::single_file(file_id))) + .in_scope(&SearchScope::single_file(file_id)) .include_self_refs() .all() .references @@ -183,7 +180,7 @@ fn highlight_references( .filter_map(|item| { Definition::from(item) .usages(sema) - .set_scope(Some(SearchScope::file_range(FileRange { + .set_scope(Some(&SearchScope::file_range(FileRange { file_id, range: trait_item_use_scope.text_range(), }))) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 5ef6ac94807..40659e6c263 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -9,7 +9,7 @@ use either::Either; use hir::{db::DefDatabase, HasSource, LangItem, Semantics}; use ide_db::{ base_db::FileRange, - defs::{Definition, IdentClass, OperatorClass}, + defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, helpers::pick_best_token, FxIndexSet, RootDatabase, @@ -186,7 +186,20 @@ fn hover_simple( // rendering poll is very confusing return None; } - Some(class.definitions().into_iter().zip(iter::once(node).cycle())) + if let IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { + decl, + .. + }) = class + { + return Some(vec![(Definition::ExternCrateDecl(decl), node)]); + } + Some( + class + .definitions() + .into_iter() + .zip(iter::once(node).cycle()) + .collect::<Vec<_>>(), + ) }) .flatten() .unique_by(|&(def, _)| def) diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index ef33386a7e9..a33a6ee1816 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -257,7 +257,7 @@ pub(super) fn keyword( let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent); let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; - let docs = doc_owner.attrs(sema.db).docs()?; + let docs = doc_owner.docs(sema.db)?; let markup = process_markup( sema.db, Definition::Module(doc_owner), @@ -472,6 +472,7 @@ pub(super) fn definition( } Definition::GenericParam(it) => label_and_docs(db, it), Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db).display(db))), + Definition::ExternCrateDecl(it) => label_and_docs(db, it), // FIXME: We should be able to show more info about these Definition::BuiltinAttr(it) => return render_builtin_attr(db, it), Definition::ToolModule(it) => return Some(Markup::fenced_block(&it.name(db))), @@ -620,7 +621,7 @@ where D: HasAttrs + HirDisplay, { let label = def.display(db).to_string(); - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -645,7 +646,7 @@ where ) { format_to!(label, "{layout}"); } - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -677,7 +678,7 @@ where ) { format_to!(label, "{layout}"); } - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -696,7 +697,7 @@ where } else { def.display(db).to_string() }; - let docs = def.attrs(db).docs(); + let docs = def.docs(db); (label, docs) } @@ -727,14 +728,14 @@ fn builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Optio // std exposes prim_{} modules with docstrings on the root to document the builtins let primitive_mod = format!("prim_{}", builtin.name().display(famous_defs.0.db)); let doc_owner = find_std_module(famous_defs, &primitive_mod)?; - let docs = doc_owner.attrs(famous_defs.0.db).docs()?; + let docs = doc_owner.docs(famous_defs.0.db)?; markup(Some(docs.into()), builtin.name().display(famous_defs.0.db).to_string(), None) } fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module> { let db = famous_defs.0.db; let std_crate = famous_defs.std()?; - let std_root_module = std_crate.root_module(db); + let std_root_module = std_crate.root_module(); std_root_module.children(db).find(|module| { module.name(db).map_or(false, |module| module.display(db).to_string() == name) }) diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 00e21433daa..ddc71dffa8a 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -1616,6 +1616,9 @@ fn test_hover_extern_crate() { check( r#" //- /main.rs crate:main deps:std +//! Crate docs + +/// Decl docs! extern crate st$0d; //- /std/lib.rs crate:std //! Standard library for this test @@ -1624,23 +1627,32 @@ extern crate st$0d; //! abc123 "#, expect![[r#" - *std* + *std* - ```rust - extern crate std - ``` + ```rust + main + ``` - --- + ```rust + extern crate std + ``` + + --- - Standard library for this test + Decl docs! - Printed? - abc123 - "#]], + Standard library for this test + + Printed? + abc123 + "#]], ); check( r#" //- /main.rs crate:main deps:std +//! Crate docs + +/// Decl docs! extern crate std as ab$0c; //- /std/lib.rs crate:std //! Standard library for this test @@ -1649,19 +1661,25 @@ extern crate std as ab$0c; //! abc123 "#, expect![[r#" - *abc* + *abc* - ```rust - extern crate std - ``` + ```rust + main + ``` - --- + ```rust + extern crate std as abc + ``` - Standard library for this test + --- - Printed? - abc123 - "#]], + Decl docs! + + Standard library for this test + + Printed? + abc123 + "#]], ); } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0ad4c6c47e6..bf77d55d58e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -127,7 +127,7 @@ pub use ide_db::{ label::Label, line_index::{LineCol, LineIndex}, search::{ReferenceCategory, SearchScope}, - source_change::{FileSystemEdit, SourceChange}, + source_change::{FileSystemEdit, SnippetEdit, SourceChange}, symbol_index::Query, RootDatabase, SymbolKind, }; diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index d486a794e13..de8c5b8e830 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -247,6 +247,10 @@ pub(crate) fn def_to_moniker( name: s.name(db).display(db).to_string(), desc: MonikerDescriptorKind::Meta, }, + Definition::ExternCrateDecl(m) => MonikerDescriptor { + name: m.name(db).display(db).to_string(), + desc: MonikerDescriptorKind::Namespace, + }, }; description.push(name_desc); diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index c7abecb4f1e..d1479dd1e58 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -102,7 +102,7 @@ impl NavigationTarget { full_range, SymbolKind::Module, ); - res.docs = module.attrs(db).docs(); + res.docs = module.docs(db); res.description = Some(module.display(db).to_string()); return res; } @@ -217,6 +217,7 @@ impl TryToNav for Definition { Definition::Trait(it) => it.try_to_nav(db), Definition::TraitAlias(it) => it.try_to_nav(db), Definition::TypeAlias(it) => it.try_to_nav(db), + Definition::ExternCrateDecl(it) => Some(it.try_to_nav(db)?), Definition::BuiltinType(_) => None, Definition::ToolModule(_) => None, Definition::BuiltinAttr(_) => None, @@ -375,6 +376,30 @@ impl TryToNav for hir::Impl { } } +impl TryToNav for hir::ExternCrateDecl { + fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { + let src = self.source(db)?; + let InFile { file_id, value } = src; + let focus = value + .rename() + .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right)); + let (file_id, full_range, focus_range) = + orig_range_with_focus(db, file_id, value.syntax(), focus); + let mut res = NavigationTarget::from_syntax( + file_id, + self.alias_or_name(db).unwrap_or_else(|| self.name(db)).to_smol_str(), + focus_range, + full_range, + SymbolKind::Module, + ); + + res.docs = self.docs(db); + res.description = Some(self.display(db).to_string()); + res.container_name = container_name(db, *self); + Some(res) + } +} + impl TryToNav for hir::Field { fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { let src = self.source(db)?; diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index fdc5261ac38..813f9ed943f 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -74,7 +74,7 @@ pub(crate) fn find_all_refs( } }); let mut usages = - def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all(); + def.usages(sema).set_scope(search_scope.as_ref()).include_self_refs().all(); if literal_search { retain_adt_literal_usages(&mut usages, def, sema); @@ -137,6 +137,9 @@ pub(crate) fn find_defs<'a>( NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { Definition::Local(local_ref) } + NameRefClass::ExternCrateShorthand { decl, .. } => { + Definition::ExternCrateDecl(decl) + } } } ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? { diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index e10c4638102..dae8e71e8a0 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -145,7 +145,14 @@ fn find_definitions( if name .syntax() .parent() - .map_or(false, |it| ast::Rename::can_cast(it.kind())) => + .map_or(false, |it| ast::Rename::can_cast(it.kind())) + // FIXME: uncomment this once we resolve to usages to extern crate declarations + // && name + // .syntax() + // .ancestors() + // .nth(2) + // .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind())) + => { bail!("Renaming aliases is currently unsupported") } @@ -165,7 +172,12 @@ fn find_definitions( NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { Definition::Local(local_ref) } + NameRefClass::ExternCrateShorthand { decl, .. } => { + Definition::ExternCrateDecl(decl) + } }) + // FIXME: uncomment this once we resolve to usages to extern crate declarations + .filter(|def| !matches!(def, Definition::ExternCrateDecl(..))) .ok_or_else(|| format_err!("No references found at position")) .and_then(|def| { // if the name differs from the definitions name it has to be an alias @@ -367,7 +379,7 @@ mod tests { let mut file_id: Option<FileId> = None; for edit in source_change.source_file_edits { file_id = Some(edit.0); - for indel in edit.1.into_iter() { + for indel in edit.1 .0.into_iter() { text_edit_builder.replace(indel.delete, indel.insert); } } @@ -895,14 +907,17 @@ mod foo$0; source_file_edits: { FileId( 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -944,24 +959,30 @@ use crate::foo$0::FooContent; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 8..11, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 8..11, + }, + ], + }, + None, + ), FileId( 2, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 11..14, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 11..14, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -997,14 +1018,17 @@ mod fo$0o; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveDir { @@ -1047,14 +1071,17 @@ mod outer { mod fo$0o; } source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "bar", - delete: 16..19, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "bar", + delete: 16..19, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1120,24 +1147,30 @@ pub mod foo$0; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 27..30, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 27..30, + }, + ], + }, + None, + ), FileId( 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 8..11, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 8..11, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1187,14 +1220,17 @@ mod quux; source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1325,18 +1361,21 @@ pub fn baz() {} source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "r#fn", - delete: 4..7, - }, - Indel { - insert: "r#fn", - delete: 22..25, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "r#fn", + delete: 4..7, + }, + Indel { + insert: "r#fn", + delete: 22..25, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -1395,18 +1434,21 @@ pub fn baz() {} source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo", - delete: 4..8, - }, - Indel { - insert: "foo", - delete: 23..27, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "foo", + delete: 4..8, + }, + Indel { + insert: "foo", + delete: 23..27, + }, + ], + }, + None, + ), }, file_system_edits: [ MoveFile { @@ -2487,4 +2529,109 @@ fn main() { ", ) } + + #[test] + fn extern_crate() { + check_prepare( + r" +//- /lib.rs crate:main deps:foo +extern crate foo$0; +use foo as qux; +//- /foo.rs crate:foo +", + expect![[r#"No references found at position"#]], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations + // check( + // "bar", + // r" + // //- /lib.rs crate:main deps:foo + // extern crate foo$0; + // use foo as qux; + // //- /foo.rs crate:foo + // ", + // r" + // extern crate foo as bar; + // use bar as qux; + // ", + // ); + } + + #[test] + fn extern_crate_rename() { + check_prepare( + r" +//- /lib.rs crate:main deps:foo +extern crate foo as qux$0; +use qux as frob; +//- /foo.rs crate:foo +", + expect!["Renaming aliases is currently unsupported"], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate + // declarations + // check( + // "bar", + // r" + // //- /lib.rs crate:main deps:foo + // extern crate foo as qux$0; + // use qux as frob; + // //- /foo.rs crate:foo + // ", + // r" + // extern crate foo as bar; + // use bar as frob; + // ", + // ); + } + + #[test] + fn extern_crate_self() { + check_prepare( + r" +extern crate self$0; +use self as qux; +", + expect!["No references found at position"], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations + // check( + // "bar", + // r" + // extern crate self$0; + // use self as qux; + // ", + // r" + // extern crate self as bar; + // use self as qux; + // ", + // ); + } + + #[test] + fn extern_crate_self_rename() { + check_prepare( + r" +//- /lib.rs crate:main deps:foo +extern crate self as qux$0; +use qux as frob; +//- /foo.rs crate:foo +", + expect!["Renaming aliases is currently unsupported"], + ); + // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations + // check( + // "bar", + // r" + // //- /lib.rs crate:main deps:foo + // extern crate self as qux$0; + // use qux as frob; + // //- /foo.rs crate:foo + // ", + // r" + // extern crate self as bar; + // use bar as frob; + // ", + // ); + } } diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 9fa0e6449b8..5f87a78551d 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -232,7 +232,7 @@ fn find_related_tests( for def in defs { let defs = def .usages(sema) - .set_scope(search_scope.clone()) + .set_scope(search_scope.as_ref()) .all() .references .into_values() @@ -309,7 +309,7 @@ pub(crate) fn runnable_fn( ) -> Option<Runnable> { let name = def.name(sema.db).to_smol_str(); - let root = def.module(sema.db).krate().root_module(sema.db); + let root = def.module(sema.db).krate().root_module(); let kind = if name == "main" && def.module(sema.db) == root { RunnableKind::Bin diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs index deaf3c9c416..d8d81869a2f 100644 --- a/crates/ide/src/ssr.rs +++ b/crates/ide/src/ssr.rs @@ -126,14 +126,17 @@ mod tests { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "3", - delete: 33..34, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "3", + delete: 33..34, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: false, @@ -163,24 +166,30 @@ mod tests { source_file_edits: { FileId( 0, - ): TextEdit { - indels: [ - Indel { - insert: "3", - delete: 33..34, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "3", + delete: 33..34, + }, + ], + }, + None, + ), FileId( 1, - ): TextEdit { - indels: [ - Indel { - insert: "3", - delete: 11..12, - }, - ], - }, + ): ( + TextEdit { + indels: [ + Indel { + insert: "3", + delete: 11..12, + }, + ], + }, + None, + ), }, file_system_edits: [], is_snippet: false, diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 59e8300dcdb..d8696198d3b 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -88,7 +88,7 @@ pub struct StaticIndexedFile { fn all_modules(db: &dyn HirDatabase) -> Vec<Module> { let mut worklist: Vec<_> = - Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect(); + Crate::all(db).into_iter().map(|krate| krate.root_module()).collect(); let mut modules = Vec::new(); while let Some(module) = worklist.pop() { diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 3c40246a69d..8e96bfa01ad 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -269,7 +269,26 @@ fn highlight_name_ref( h } - NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(), + NameRefClass::FieldShorthand { field_ref, .. } => { + highlight_def(sema, krate, field_ref.into()) + } + NameRefClass::ExternCrateShorthand { decl, krate: resolved_krate } => { + let mut h = HlTag::Symbol(SymbolKind::Module).into(); + + if resolved_krate != krate { + h |= HlMod::Library + } + let is_public = decl.visibility(db) == hir::Visibility::Public; + if is_public { + h |= HlMod::Public + } + let is_from_builtin_crate = resolved_krate.is_builtin(db); + if is_from_builtin_crate { + h |= HlMod::DefaultLibrary; + } + h |= HlMod::CrateRoot; + h + } }; h.tag = match name_ref.token_kind() { @@ -474,6 +493,14 @@ fn highlight_def( } h } + Definition::ExternCrateDecl(extern_crate) => { + let mut highlight = + Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot; + if extern_crate.alias(db).is_none() { + highlight |= HlMod::Library; + } + highlight + } Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)), Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)), Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)), diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 901df147d32..2657a641482 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -288,7 +288,7 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri fn module_def_to_hl_tag(def: Definition) -> HlTag { let symbol = match def { - Definition::Module(_) => SymbolKind::Module, + Definition::Module(_) | Definition::ExternCrateDecl(_) => SymbolKind::Module, Definition::Function(_) => SymbolKind::Function, Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct, Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum, diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html index b15f7bca72b..88a008796b3 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html @@ -44,5 +44,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } </style> <pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root default_library library">std</span><span class="semicolon">;</span> -<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root default_library library">alloc</span> <span class="keyword">as</span> <span class="module crate_root default_library declaration library">abc</span><span class="semicolon">;</span> +<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root default_library library">alloc</span> <span class="keyword">as</span> <span class="module crate_root declaration">abc</span><span class="semicolon">;</span> +<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="unresolved_reference">unresolved</span> <span class="keyword">as</span> <span class="module crate_root declaration">definitely_unresolved</span><span class="semicolon">;</span> </code></pre> \ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html index fd3b39855e2..2043752bc74 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html @@ -43,7 +43,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; } .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } </style> -<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="self_keyword crate_root public">self</span><span class="semicolon">;</span> +<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="self_keyword crate_root">self</span><span class="semicolon">;</span> <span class="keyword">use</span> <span class="keyword crate_root public">crate</span><span class="semicolon">;</span> <span class="keyword">use</span> <span class="self_keyword crate_root public">self</span><span class="semicolon">;</span> diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index c5fcec75680..06b66b302ae 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -90,8 +90,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="brace">}</span> <span class="brace">}</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span> +<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">concat</span> <span class="brace">{</span><span class="brace">}</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span> +<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">include</span> <span class="brace">{</span><span class="brace">}</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span> +<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args</span> <span class="brace">{</span><span class="brace">}</span> + +<span class="macro">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="comma macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> - <span class="unresolved_reference">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, {}!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">dont_color_me_braces</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">noop</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro macro">noop</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> -<span class="brace">}</span></code></pre> \ No newline at end of file +<span class="brace">}</span> +</code></pre> \ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index dcac8eb7368..3ac8aa9cc9d 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -178,5 +178,5 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"mov eax, </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">backslash</span><span class="comma macro">,</span> <span class="none macro">format_args</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="none macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="none macro">toho</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="none macro">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="none macro">format_args</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="none macro">toho</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="brace">}</span></code></pre> \ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 696aa590025..8749d355c85 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -48,6 +48,7 @@ fn macros() { check_highlighting( r#" //- proc_macros: mirror +//- /lib.rs crate:lib proc_macros::mirror! { { ,i32 :x pub @@ -95,11 +96,23 @@ macro without_args { } } +#[rustc_builtin_macro] +macro_rules! concat {} +#[rustc_builtin_macro] +macro_rules! include {} +#[rustc_builtin_macro] +macro_rules! format_args {} + +include!(concat!("foo/", "foo.rs")); + fn main() { - println!("Hello, {}!", 92); + format_args!("Hello, {}!", 92); dont_color_me_braces!(); noop!(noop!(1)); } +//- /foo/foo.rs crate:foo +mod foo {} +use self::foo as bar; "#, expect_file!["./test_data/highlight_macros.html"], false, @@ -791,6 +804,7 @@ fn test_extern_crate() { //- /main.rs crate:main deps:std,alloc extern crate std; extern crate alloc as abc; +extern crate unresolved as definitely_unresolved; //- /std/lib.rs crate:std pub struct S; //- /alloc/lib.rs crate:alloc diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs index 8e2181e977e..f2d89d3efe5 100644 --- a/crates/mbe/src/expander.rs +++ b/crates/mbe/src/expander.rs @@ -123,4 +123,14 @@ enum Fragment { /// proc-macro delimiter=none. As we later discovered, "none" delimiters are /// tricky to handle in the parser, and rustc doesn't handle those either. Expr(tt::TokenTree), + /// There are roughly two types of paths: paths in expression context, where a + /// separator `::` between an identifier and its following generic argument list + /// is mandatory, and paths in type context, where `::` can be omitted. + /// + /// Unlike rustc, we need to transform the parsed fragments back into tokens + /// during transcription. When the matched path fragment is a type-context path + /// and is trasncribed as an expression-context path, verbatim transcription + /// would cause a syntax error. We need to fix it up just before transcribing; + /// see `transcriber::fix_up_and_push_path_tt()`. + Path(tt::TokenTree), } diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs index 1a7b7eed295..1471af98b75 100644 --- a/crates/mbe/src/expander/matcher.rs +++ b/crates/mbe/src/expander/matcher.rs @@ -742,7 +742,11 @@ fn match_meta_var( is_2021: bool, ) -> ExpandResult<Option<Fragment>> { let fragment = match kind { - MetaVarKind::Path => parser::PrefixEntryPoint::Path, + MetaVarKind::Path => { + return input + .expect_fragment(parser::PrefixEntryPoint::Path) + .map(|it| it.map(Fragment::Path)); + } MetaVarKind::Ty => parser::PrefixEntryPoint::Ty, MetaVarKind::Pat if is_2021 => parser::PrefixEntryPoint::PatTop, MetaVarKind::Pat => parser::PrefixEntryPoint::Pat, @@ -771,7 +775,7 @@ fn match_meta_var( .expect_fragment(parser::PrefixEntryPoint::Expr) .map(|tt| tt.map(Fragment::Expr)); } - _ => { + MetaVarKind::Ident | MetaVarKind::Tt | MetaVarKind::Lifetime | MetaVarKind::Literal => { let tt_result = match kind { MetaVarKind::Ident => input .expect_ident() @@ -799,7 +803,7 @@ fn match_meta_var( }) .map_err(|()| ExpandError::binding_error("expected literal")) } - _ => Err(ExpandError::UnexpectedToken), + _ => unreachable!(), }; return tt_result.map(|it| Some(Fragment::Tokens(it))).into(); } diff --git a/crates/mbe/src/expander/transcriber.rs b/crates/mbe/src/expander/transcriber.rs index 6161af18587..cdac2f1e3bb 100644 --- a/crates/mbe/src/expander/transcriber.rs +++ b/crates/mbe/src/expander/transcriber.rs @@ -400,7 +400,8 @@ fn push_fragment(buf: &mut Vec<tt::TokenTree>, fragment: Fragment) { } buf.push(tt.into()) } - Fragment::Tokens(tt) | Fragment::Expr(tt) => buf.push(tt), + Fragment::Path(tt::TokenTree::Subtree(tt)) => fix_up_and_push_path_tt(buf, tt), + Fragment::Tokens(tt) | Fragment::Expr(tt) | Fragment::Path(tt) => buf.push(tt), } } @@ -411,6 +412,45 @@ fn push_subtree(buf: &mut Vec<tt::TokenTree>, tt: tt::Subtree) { } } +/// Inserts the path separator `::` between an identifier and its following generic +/// argument list, and then pushes into the buffer. See [`Fragment::Path`] for why +/// we need this fixup. +fn fix_up_and_push_path_tt(buf: &mut Vec<tt::TokenTree>, subtree: tt::Subtree) { + stdx::always!(matches!(subtree.delimiter.kind, tt::DelimiterKind::Invisible)); + let mut prev_was_ident = false; + // Note that we only need to fix up the top-level `TokenTree`s because the + // context of the paths in the descendant `Subtree`s won't be changed by the + // mbe transcription. + for tt in subtree.token_trees { + if prev_was_ident { + // Pedantically, `(T) -> U` in `FnOnce(T) -> U` is treated as a generic + // argument list and thus needs `::` between it and `FnOnce`. However in + // today's Rust this type of path *semantically* cannot appear as a + // top-level expression-context path, so we can safely ignore it. + if let tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '<', .. })) = tt { + buf.push( + tt::Leaf::Punct(tt::Punct { + char: ':', + spacing: tt::Spacing::Joint, + span: tt::Span::unspecified(), + }) + .into(), + ); + buf.push( + tt::Leaf::Punct(tt::Punct { + char: ':', + spacing: tt::Spacing::Alone, + span: tt::Span::unspecified(), + }) + .into(), + ); + } + } + prev_was_ident = matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(_))); + buf.push(tt); + } +} + /// Handles `${count(t, depth)}`. `our_depth` is the recursion depth and `count_depth` is the depth /// defined by the metavar expression. fn count( diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index 665bce474a6..9d886a1c979 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs @@ -28,7 +28,6 @@ use crate::{ tt_iter::TtIter, }; -// FIXME: we probably should re-think `token_tree_to_syntax_node` interfaces pub use self::tt::{Delimiter, DelimiterKind, Punct}; pub use ::parser::TopEntryPoint; diff --git a/crates/mbe/src/token_map.rs b/crates/mbe/src/token_map.rs index 9b2df89f9c7..73a27df5dbc 100644 --- a/crates/mbe/src/token_map.rs +++ b/crates/mbe/src/token_map.rs @@ -117,4 +117,8 @@ impl TokenMap { TokenTextRange::Delimiter(_) => None, }) } + + pub fn filter(&mut self, id: impl Fn(tt::TokenId) -> bool) { + self.entries.retain(|&(tid, _)| id(tid)); + } } diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index a868419821d..333318f53b7 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -165,6 +165,40 @@ pub(crate) mod entry { } m.complete(p, ERROR); } + + pub(crate) fn eager_macro_input(p: &mut Parser<'_>) { + let m = p.start(); + + let closing_paren_kind = match p.current() { + T!['{'] => T!['}'], + T!['('] => T![')'], + T!['['] => T![']'], + _ => { + p.error("expected `{`, `[`, `(`"); + while !p.at(EOF) { + p.bump_any(); + } + m.complete(p, ERROR); + return; + } + }; + p.bump_any(); + while !p.at(EOF) && !p.at(closing_paren_kind) { + expressions::expr(p); + if !p.at(EOF) && !p.at(closing_paren_kind) { + p.expect(T![,]); + } + } + p.expect(closing_paren_kind); + if p.at(EOF) { + m.complete(p, MACRO_EAGER_INPUT); + return; + } + while !p.at(EOF) { + p.bump_any(); + } + m.complete(p, ERROR); + } } } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 1aba1f7674f..c155e8aaf67 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -75,6 +75,8 @@ pub enum TopEntryPoint { /// Edge case -- macros generally don't expand to attributes, with the /// exception of `cfg_attr` which does! MetaItem, + /// Edge case 2 -- eager macros expand their input to a delimited list of comma separated expressions + MacroEagerInput, } impl TopEntryPoint { @@ -87,6 +89,7 @@ impl TopEntryPoint { TopEntryPoint::Type => grammar::entry::top::type_, TopEntryPoint::Expr => grammar::entry::top::expr, TopEntryPoint::MetaItem => grammar::entry::top::meta_item, + TopEntryPoint::MacroEagerInput => grammar::entry::top::eager_macro_input, }; let mut p = parser::Parser::new(input); entry_point(&mut p); diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs index 6e3ae656b02..53cdad64992 100644 --- a/crates/parser/src/shortcuts.rs +++ b/crates/parser/src/shortcuts.rs @@ -223,7 +223,8 @@ fn n_attached_trivias<'a>( ) -> usize { match kind { CONST | ENUM | FN | IMPL | MACRO_CALL | MACRO_DEF | MACRO_RULES | MODULE | RECORD_FIELD - | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT => { + | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT + | EXTERN_CRATE => { let mut res = 0; let mut trivias = trivias.enumerate().peekable(); diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index a8fbcfacf7e..48f407623d8 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -262,6 +262,7 @@ pub enum SyntaxKind { TYPE_BOUND_LIST, MACRO_ITEMS, MACRO_STMTS, + MACRO_EAGER_INPUT, #[doc(hidden)] __LAST, } diff --git a/crates/proc-macro-srv/src/server.rs b/crates/proc-macro-srv/src/server.rs index 1980d4c78bb..fe18451d384 100644 --- a/crates/proc-macro-srv/src/server.rs +++ b/crates/proc-macro-srv/src/server.rs @@ -17,7 +17,10 @@ use token_stream::TokenStreamBuilder; mod symbol; pub use symbol::*; -use std::ops::{Bound, Range}; +use std::{ + iter, + ops::{Bound, Range}, +}; use crate::tt; @@ -80,9 +83,7 @@ impl server::TokenStream for RustAnalyzer { stream.is_empty() } fn from_str(&mut self, src: &str) -> Self::TokenStream { - use std::str::FromStr; - - Self::TokenStream::from_str(src).expect("cannot parse string") + src.parse().expect("cannot parse string") } fn to_string(&mut self, stream: &Self::TokenStream) -> String { stream.to_string() @@ -101,7 +102,7 @@ impl server::TokenStream for RustAnalyzer { }, }; let tree = TokenTree::from(group); - Self::TokenStream::from_iter(vec![tree]) + Self::TokenStream::from_iter(iter::once(tree)) } bridge::TokenTree::Ident(ident) => { @@ -111,7 +112,7 @@ impl server::TokenStream for RustAnalyzer { let ident: tt::Ident = tt::Ident { text, span: ident.span }; let leaf = tt::Leaf::from(ident); let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(vec![tree]) + Self::TokenStream::from_iter(iter::once(tree)) } bridge::TokenTree::Literal(literal) => { @@ -123,7 +124,7 @@ impl server::TokenStream for RustAnalyzer { let literal = tt::Literal { text, span: literal.0.span }; let leaf = tt::Leaf::from(literal); let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(vec![tree]) + Self::TokenStream::from_iter(iter::once(tree)) } bridge::TokenTree::Punct(p) => { @@ -134,7 +135,7 @@ impl server::TokenStream for RustAnalyzer { }; let leaf = tt::Leaf::from(punct); let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(vec![tree]) + Self::TokenStream::from_iter(iter::once(tree)) } } } @@ -355,12 +356,12 @@ impl server::Server for RustAnalyzer { } fn intern_symbol(ident: &str) -> Self::Symbol { - // FIXME: should be self.interner once the proc-macro api allows is + // FIXME: should be `self.interner` once the proc-macro api allows it. Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident)) } fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { - // FIXME: should be self.interner once the proc-macro api allows is + // FIXME: should be `self.interner` once the proc-macro api allows it. f(symbol.text(&SYMBOL_INTERNER).as_str()) } } diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 33d7b5ed878..f446a7c0596 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -128,7 +128,7 @@ impl flags::AnalysisStats { let mut visited_modules = FxHashSet::default(); let mut visit_queue = Vec::new(); for krate in krates { - let module = krate.root_module(db); + let module = krate.root_module(); let file_id = module.definition_source_file_id(db); let file_id = file_id.original_file(db); let source_root = db.file_source_root(file_id); diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index 0db5fb4740e..8541be715a9 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs @@ -80,7 +80,7 @@ impl flags::Diagnostics { fn all_modules(db: &dyn HirDatabase) -> Vec<Module> { let mut worklist: Vec<_> = - Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect(); + Crate::all(db).into_iter().map(|krate| krate.root_module()).collect(); let mut modules = Vec::new(); while let Some(module) = worklist.pop() { diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs index b63a266a57a..e1704199151 100644 --- a/crates/rust-analyzer/src/cli/run_tests.rs +++ b/crates/rust-analyzer/src/cli/run_tests.rs @@ -76,7 +76,7 @@ fn all_modules(db: &dyn HirDatabase) -> Vec<Module> { let mut worklist: Vec<_> = Crate::all(db) .into_iter() .filter(|x| x.origin(db).is_local()) - .map(|krate| krate.root_module(db)) + .map(|krate| krate.root_module()) .collect(); let mut modules = Vec::new(); diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index aad74b7466a..5f1f731cffb 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -353,7 +353,8 @@ pub(crate) fn handle_on_type_formatting( }; // This should be a single-file edit - let (_, text_edit) = edit.source_file_edits.into_iter().next().unwrap(); + let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap(); + stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets"); let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit); Ok(Some(change)) diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 18d9151d4aa..0a2bb822475 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -114,6 +114,11 @@ impl GlobalState { if self.proc_macro_clients.iter().any(|it| it.is_err()) { status.health = lsp_ext::Health::Warning; message.push_str("Failed to spawn one or more proc-macro servers.\n\n"); + for err in self.proc_macro_clients.iter() { + if let Err(err) = err { + format_to!(message, "- {err}\n"); + } + } } if !self.config.cargo_autoreload() && self.is_quiescent() diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 7a89533a5e9..7b32180e3eb 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -10,8 +10,8 @@ use ide::{ CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, - RenameError, Runnable, Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, - TextEdit, TextRange, TextSize, + RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind, + SymbolKind, TextEdit, TextRange, TextSize, }; use itertools::Itertools; use serde_json::to_value; @@ -22,7 +22,7 @@ use crate::{ config::{CallInfoConfig, Config}, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, PositionEncoding}, - lsp_ext, + lsp_ext::{self, SnippetTextEdit}, lsp_utils::invalid_params_error, semantic_tokens::{self, standard_fallback_type}, }; @@ -885,16 +885,136 @@ fn outside_workspace_annotation_id() -> String { String::from("OutsideWorkspace") } +fn merge_text_and_snippet_edits( + line_index: &LineIndex, + edit: TextEdit, + snippet_edit: SnippetEdit, +) -> Vec<SnippetTextEdit> { + let mut edits: Vec<SnippetTextEdit> = vec![]; + let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable(); + let mut text_edits = edit.into_iter(); + + while let Some(current_indel) = text_edits.next() { + let new_range = { + let insert_len = + TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX)); + TextRange::at(current_indel.delete.start(), insert_len) + }; + + // insert any snippets before the text edit + for (snippet_index, snippet_range) in + snippets.take_while_ref(|(_, range)| range.end() < new_range.start()) + { + let snippet_range = if !stdx::always!( + snippet_range.is_empty(), + "placeholder range {:?} is before current text edit range {:?}", + snippet_range, + new_range + ) { + // only possible for tabstops, so make sure it's an empty/insert range + TextRange::empty(snippet_range.start()) + } else { + snippet_range + }; + + let range = range(&line_index, snippet_range); + let new_text = format!("${snippet_index}"); + + edits.push(SnippetTextEdit { + range, + new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + }) + } + + if snippets.peek().is_some_and(|(_, range)| new_range.intersect(*range).is_some()) { + // at least one snippet edit intersects this text edit, + // so gather all of the edits that intersect this text edit + let mut all_snippets = snippets + .take_while_ref(|(_, range)| new_range.intersect(*range).is_some()) + .collect_vec(); + + // ensure all of the ranges are wholly contained inside of the new range + all_snippets.retain(|(_, range)| { + stdx::always!( + new_range.contains_range(*range), + "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range + ) + }); + + let mut text_edit = text_edit(line_index, current_indel); + + // escape out snippet text + stdx::replace(&mut text_edit.new_text, '\\', r"\\"); + stdx::replace(&mut text_edit.new_text, '$', r"\$"); + + // ...and apply! + for (index, range) in all_snippets.iter().rev() { + let start = (range.start() - new_range.start()).into(); + let end = (range.end() - new_range.start()).into(); + + if range.is_empty() { + text_edit.new_text.insert_str(start, &format!("${index}")); + } else { + text_edit.new_text.insert(end, '}'); + text_edit.new_text.insert_str(start, &format!("${{{index}:")); + } + } + + edits.push(SnippetTextEdit { + range: text_edit.range, + new_text: text_edit.new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + }) + } else { + // snippet edit was beyond the current one + // since it wasn't consumed, it's available for the next pass + edits.push(snippet_text_edit(line_index, false, current_indel)); + } + } + + // insert any remaining tabstops + edits.extend(snippets.map(|(snippet_index, snippet_range)| { + let snippet_range = if !stdx::always!( + snippet_range.is_empty(), + "found placeholder snippet {:?} without a text edit", + snippet_range + ) { + TextRange::empty(snippet_range.start()) + } else { + snippet_range + }; + + let range = range(&line_index, snippet_range); + let new_text = format!("${snippet_index}"); + + SnippetTextEdit { + range, + new_text, + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), + annotation_id: None, + } + })); + + edits +} + pub(crate) fn snippet_text_document_edit( snap: &GlobalStateSnapshot, is_snippet: bool, file_id: FileId, edit: TextEdit, + snippet_edit: Option<SnippetEdit>, ) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> { let text_document = optional_versioned_text_document_identifier(snap, file_id); let line_index = snap.file_line_index(file_id)?; - let mut edits: Vec<_> = - edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect(); + let mut edits = if let Some(snippet_edit) = snippet_edit { + merge_text_and_snippet_edits(&line_index, edit, snippet_edit) + } else { + edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect() + }; if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() { for edit in &mut edits { @@ -974,8 +1094,14 @@ pub(crate) fn snippet_workspace_edit( let ops = snippet_text_document_ops(snap, op)?; document_changes.extend_from_slice(&ops); } - for (file_id, edit) in source_change.source_file_edits { - let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?; + for (file_id, (edit, snippet_edit)) in source_change.source_file_edits { + let edit = snippet_text_document_edit( + snap, + source_change.is_snippet, + file_id, + edit, + snippet_edit, + )?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit { @@ -1414,7 +1540,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError { #[cfg(test)] mod tests { + use expect_test::{expect, Expect}; use ide::{Analysis, FilePosition}; + use ide_db::source_change::Snippet; use test_utils::extract_offset; use triomphe::Arc; @@ -1484,6 +1612,481 @@ fn bar(_: usize) {} assert!(!docs.contains("use crate::bar")); } + fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) { + let text = r#"/* place to put all ranges in */"#; + let line_index = LineIndex { + index: Arc::new(ide::LineIndex::new(text)), + endings: LineEndings::Unix, + encoding: PositionEncoding::Utf8, + }; + + let res = merge_text_and_snippet_edits(&line_index, edit, snippets); + expect.assert_debug_eq(&res); + } + + #[test] + fn snippet_rendering_only_tabstops() { + let edit = TextEdit::builder().finish(); + let snippets = SnippetEdit::new(vec![ + Snippet::Tabstop(0.into()), + Snippet::Tabstop(0.into()), + Snippet::Tabstop(1.into()), + Snippet::Tabstop(1.into()), + ]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$1", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$2", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 1, + }, + end: Position { + line: 0, + character: 1, + }, + }, + new_text: "$3", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 1, + }, + end: Position { + line: 0, + character: 1, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_only_text_edits() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abc".to_owned()); + edit.insert(3.into(), "def".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 3, + }, + end: Position { + line: 0, + character: 3, + }, + }, + new_text: "def", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_tabstop_after_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abc".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![Snippet::Tabstop(7.into())]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 7, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_tabstops_before_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(2.into(), "abc".to_owned()); + let edit = edit.finish(); + let snippets = + SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$1", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 2, + }, + end: Position { + line: 0, + character: 2, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_tabstops_between_text_edits() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abc".to_owned()); + edit.insert(7.into(), "abc".to_owned()); + let edit = edit.finish(); + let snippets = + SnippetEdit::new(vec![Snippet::Tabstop(4.into()), Snippet::Tabstop(4.into())]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 4, + }, + end: Position { + line: 0, + character: 4, + }, + }, + new_text: "$1", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 4, + }, + end: Position { + line: 0, + character: 4, + }, + }, + new_text: "$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 7, + }, + }, + new_text: "abc", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_multiple_tabstops_in_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abcdefghijkl".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![ + Snippet::Tabstop(0.into()), + Snippet::Tabstop(5.into()), + Snippet::Tabstop(12.into()), + ]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "$1abcde$2fghijkl$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_multiple_placeholders_in_text_edit() { + let mut edit = TextEdit::builder(); + edit.insert(0.into(), "abcdefghijkl".to_owned()); + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![ + Snippet::Placeholder(TextRange::new(0.into(), 3.into())), + Snippet::Placeholder(TextRange::new(5.into(), 7.into())), + Snippet::Placeholder(TextRange::new(10.into(), 12.into())), + ]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "${1:abc}de${2:fg}hij${0:kl}", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ); + } + + #[test] + fn snippet_rendering_escape_snippet_bits() { + // only needed for snippet formats + let mut edit = TextEdit::builder(); + edit.insert(0.into(), r"abc\def$".to_owned()); + edit.insert(8.into(), r"ghi\jkl$".to_owned()); + let edit = edit.finish(); + let snippets = + SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]); + + check_rendered_snippets( + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "${0:abc}\\\\def\\$", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 8, + }, + end: Position { + line: 0, + character: 8, + }, + }, + new_text: "ghi\\jkl$", + insert_text_format: None, + annotation_id: None, + }, + ] + "#]], + ); + } + // `Url` is not able to parse windows paths on unix machines. #[test] #[cfg(target_os = "windows")] diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index b096c997448..138ddd20897 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -72,6 +72,12 @@ TokenTree = MacroItems = Item* +MacroEagerInput = + '(' (Expr (',' Expr)* ','?)? ')' +| '{' (Expr (',' Expr)* ','?)? '}' +| '[' (Expr (',' Expr)* ','?)? ']' + + MacroStmts = statements:Stmt* Expr? diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 606804aea25..a150d9e6c07 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -380,6 +380,26 @@ impl Removable for ast::UseTree { } impl ast::UseTree { + /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty. + pub fn remove_recursive(self) { + let parent = self.syntax().parent(); + + self.remove(); + + if let Some(u) = parent.clone().and_then(ast::Use::cast) { + if u.use_tree().is_none() { + u.remove(); + } + } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) { + if u.use_trees().next().is_none() { + let parent = u.syntax().parent().and_then(ast::UseTree::cast); + if let Some(u) = parent { + u.remove_recursive(); + } + } + } + } + pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList { match self.use_tree_list() { Some(it) => it, @@ -487,6 +507,22 @@ impl Removable for ast::Use { } } } + let prev_ws = self + .syntax() + .prev_sibling_or_token() + .and_then(|it| it.into_token()) + .and_then(ast::Whitespace::cast); + if let Some(prev_ws) = prev_ws { + let ws_text = prev_ws.syntax().text(); + let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0); + let rest = &ws_text[0..prev_newline]; + if rest.is_empty() { + ted::remove(prev_ws.syntax()); + } else { + ted::replace(prev_ws.syntax(), make::tokens::whitespace(rest)); + } + } + ted::remove(self.syntax()); } } diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index e520801ea2e..0b27faa535d 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -198,6 +198,20 @@ impl ast::HasModuleItem for MacroItems {} impl MacroItems {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MacroEagerInput { + pub(crate) syntax: SyntaxNode, +} +impl MacroEagerInput { + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + pub fn exprs(&self) -> AstChildren<Expr> { support::children(&self.syntax) } + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } + pub fn l_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) } + pub fn r_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) } + pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) } + pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacroStmts { pub(crate) syntax: SyntaxNode, } @@ -1922,6 +1936,17 @@ impl AstNode for MacroItems { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for MacroEagerInput { + fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_EAGER_INPUT } + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for MacroStmts { fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_STMTS } fn cast(syntax: SyntaxNode) -> Option<Self> { @@ -4360,6 +4385,11 @@ impl std::fmt::Display for MacroItems { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for MacroEagerInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for MacroStmts { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index bed240a6d73..4cd668a0cd5 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs @@ -172,7 +172,7 @@ impl SourceFile { } impl ast::TokenTree { - pub fn reparse_as_expr(self) -> Parse<ast::Expr> { + pub fn reparse_as_comma_separated_expr(self) -> Parse<ast::MacroEagerInput> { let tokens = self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token); let mut parser_input = parser::Input::default(); @@ -203,7 +203,7 @@ impl ast::TokenTree { } } - let parser_output = parser::TopEntryPoint::Expr.parse(&parser_input); + let parser_output = parser::TopEntryPoint::MacroEagerInput.parse(&parser_input); let mut tokens = self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token); diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs index c5783b91a0f..e4db33f1c69 100644 --- a/crates/syntax/src/tests/ast_src.rs +++ b/crates/syntax/src/tests/ast_src.rs @@ -216,6 +216,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { // macro related "MACRO_ITEMS", "MACRO_STMTS", + "MACRO_EAGER_INPUT", ], }; diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs index 1b8d4ba42a5..b5a72bec079 100644 --- a/crates/tt/src/lib.rs +++ b/crates/tt/src/lib.rs @@ -65,12 +65,12 @@ pub mod token_id { } impl TokenTree { pub const fn empty() -> Self { - Self::Subtree(Subtree { delimiter: Delimiter::unspecified(), token_trees: vec![] }) + Self::Subtree(Subtree::empty()) } } impl Subtree { - pub fn visit_ids(&mut self, f: &impl Fn(TokenId) -> TokenId) { + pub fn visit_ids(&mut self, f: &mut impl FnMut(TokenId) -> TokenId) { self.delimiter.open = f(self.delimiter.open); self.delimiter.close = f(self.delimiter.close); self.token_trees.iter_mut().for_each(|tt| match tt { @@ -122,7 +122,6 @@ impl_from!(Literal<Span>, Punct<Span>, Ident<Span> for Leaf); #[derive(Clone, PartialEq, Eq, Hash)] pub struct Subtree<Span> { - // FIXME, this should not be Option pub delimiter: Delimiter<Span>, pub token_trees: Vec<TokenTree<Span>>, } diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 2cf985adabc..5dafd1a4c8c 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -949,6 +949,29 @@ Or it is possible to specify vars more granularly: You can use any valid regular expression as a mask. Also note that a full runnable name is something like *run bin_or_example_name*, *test some::mod::test_name* or *test-mod some::mod*, so it is possible to distinguish binaries, single tests, and test modules with this masks: `"^run"`, `"^test "` (the trailing space matters!), and `"^test-mod"` respectively. +If needed, you can set different values for different platforms: +```jsonc +"rust-analyzer.runnables.extraEnv": [ + { + "platform": "win32", // windows only + env: { + "APP_DATA": "windows specific data" + } + }, + { + "platform": ["linux"], + "env": { + "APP_DATA": "linux data", + } + }, + { // for all platforms + "env": { + "APP_COMMON_DATA": "xxx", + } + } +] +``` + ==== Compiler feedback from external commands Instead of relying on the built-in `cargo check`, you can configure Code to run a command in the background and use the `$rustc-watch` problem matcher to generate inline error markers from its output. diff --git a/editors/code/package.json b/editors/code/package.json index 8b20011ba50..76d7e91f381 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -328,6 +328,15 @@ "items": { "type": "object", "properties": { + "platform": { + "type": [ + "null", + "string", + "array" + ], + "default": null, + "markdownDescription": "Platform(s) filter like \"win32\" or [\"linux\", \"win32\"]. See [process.platform](https://nodejs.org/api/process.html#processplatform) values." + }, "mask": { "type": "string", "description": "Runnable name mask" diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index a047f9659a9..0e64054c11d 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -6,10 +6,12 @@ import type { Env } from "./client"; import { log } from "./util"; import { expectNotUndefined, unwrapUndefinable } from "./undefinable"; -export type RunnableEnvCfg = - | undefined - | Record<string, string> - | { mask?: string; env: Record<string, string> }[]; +export type RunnableEnvCfgItem = { + mask?: string; + env: Record<string, string>; + platform?: string | string[]; +}; +export type RunnableEnvCfg = undefined | Record<string, string> | RunnableEnvCfgItem[]; export class Config { readonly extensionId = "rust-lang.rust-analyzer"; diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index ac144cbebb2..72d8109bc80 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -449,7 +449,7 @@ export class Ctx { return; } if (statusBar.tooltip.value) { - statusBar.tooltip.appendText("\n\n"); + statusBar.tooltip.appendMarkdown("\n\n---\n\n"); } statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)"); statusBar.tooltip.appendMarkdown( diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index c893d390554..57881803a6a 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -5,7 +5,7 @@ import * as tasks from "./tasks"; import type { CtxInit } from "./ctx"; import { makeDebugConfig } from "./debug"; -import type { Config, RunnableEnvCfg } from "./config"; +import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config"; import { unwrapUndefinable } from "./undefinable"; const quickPickButtons = [ @@ -112,11 +112,21 @@ export function prepareEnv( } Object.assign(env, process.env as { [key: string]: string }); + const platform = process.platform; + + const checkPlatform = (it: RunnableEnvCfgItem) => { + if (it.platform) { + const platforms = Array.isArray(it.platform) ? it.platform : [it.platform]; + return platforms.indexOf(platform) >= 0; + } + return true; + }; if (runnableEnvCfg) { if (Array.isArray(runnableEnvCfg)) { for (const it of runnableEnvCfg) { - if (!it.mask || new RegExp(it.mask).test(runnable.label)) { + const masked = !it.mask || new RegExp(it.mask).test(runnable.label); + if (masked && checkPlatform(it)) { Object.assign(env, it.env); } } diff --git a/triagebot.toml b/triagebot.toml index a910e012b73..f0cd3539975 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -9,3 +9,7 @@ allow-unauthenticated = [ [autolabel."S-waiting-on-review"] new_pr = true + +[no-merges] +exclude_labels = ["sync"] +labels = ["has-merge-commits", "S-waiting-on-author"] |
