From 2ecb126f5caeb248e333f8559eb1b7dfd34cc744 Mon Sep 17 00:00:00 2001 From: uHOOCCOOHu Date: Fri, 27 Sep 2019 01:59:38 +0800 Subject: Support `$crate` in item and expr place. --- crates/ra_assists/src/assists/auto_import.rs | 2 +- crates/ra_hir/src/expr/lower.rs | 21 ++++-- crates/ra_hir/src/generics.rs | 1 + crates/ra_hir/src/ids.rs | 11 +++ crates/ra_hir/src/impl_block.rs | 5 +- crates/ra_hir/src/marks.rs | 2 + crates/ra_hir/src/nameres.rs | 14 ++++ crates/ra_hir/src/nameres/raw.rs | 41 ++++++---- crates/ra_hir/src/nameres/tests/macros.rs | 105 ++++++++++++++++++++++++++ crates/ra_hir/src/path.rs | 64 +++++++++++++--- crates/ra_hir/src/source_binder.rs | 2 + crates/ra_hir/src/ty/tests.rs | 33 ++++++++ crates/ra_hir/src/type_ref.rs | 2 + crates/ra_mbe/src/mbe_expander/transcriber.rs | 2 +- 14 files changed, 268 insertions(+), 37 deletions(-) diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/auto_import.rs index 5aae98546..43c14ad23 100644 --- a/crates/ra_assists/src/assists/auto_import.rs +++ b/crates/ra_assists/src/assists/auto_import.rs @@ -512,7 +512,7 @@ pub fn collect_hir_path_segments(path: &hir::Path) -> Option> { hir::PathKind::Plain => {} hir::PathKind::Self_ => ps.push("self".into()), hir::PathKind::Super => ps.push("super".into()), - hir::PathKind::Type(_) => return None, + hir::PathKind::Type(_) | hir::PathKind::DollarCrate(_) => return None, } for s in path.segments.iter() { ps.push(s.name.to_string().into()); diff --git a/crates/ra_hir/src/expr/lower.rs b/crates/ra_hir/src/expr/lower.rs index 61535d24f..6d6f60506 100644 --- a/crates/ra_hir/src/expr/lower.rs +++ b/crates/ra_hir/src/expr/lower.rs @@ -272,8 +272,11 @@ where self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr) } ast::Expr::PathExpr(e) => { - let path = - e.path().and_then(Path::from_ast).map(Expr::Path).unwrap_or(Expr::Missing); + let path = e + .path() + .and_then(|path| self.parse_path(path)) + .map(Expr::Path) + .unwrap_or(Expr::Missing); self.alloc_expr(path, syntax_ptr) } ast::Expr::ContinueExpr(_e) => { @@ -295,7 +298,7 @@ where self.alloc_expr(Expr::Return { expr }, syntax_ptr) } ast::Expr::RecordLit(e) => { - let path = e.path().and_then(Path::from_ast); + let path = e.path().and_then(|path| self.parse_path(path)); let mut field_ptrs = Vec::new(); let record_lit = if let Some(nfl) = e.record_field_list() { let fields = nfl @@ -459,7 +462,7 @@ where .ast_id(&e) .with_file_id(self.current_file_id); - if let Some(path) = e.path().and_then(Path::from_ast) { + if let Some(path) = e.path().and_then(|path| self.parse_path(path)) { if let Some(def) = self.resolver.resolve_path_as_macro(self.db, &path) { let call_id = MacroCallLoc { def: def.id, ast_id }.id(self.db); let file_id = call_id.as_file(MacroFileKind::Expr); @@ -529,7 +532,7 @@ where Pat::Bind { name, mode: annotation, subpat } } ast::Pat::TupleStructPat(p) => { - let path = p.path().and_then(Path::from_ast); + let path = p.path().and_then(|path| self.parse_path(path)); let args = p.args().map(|p| self.collect_pat(p)).collect(); Pat::TupleStruct { path, args } } @@ -539,7 +542,7 @@ where Pat::Ref { pat, mutability } } ast::Pat::PathPat(p) => { - let path = p.path().and_then(Path::from_ast); + let path = p.path().and_then(|path| self.parse_path(path)); path.map(Pat::Path).unwrap_or(Pat::Missing) } ast::Pat::TuplePat(p) => { @@ -548,7 +551,7 @@ where } ast::Pat::PlaceholderPat(_) => Pat::Wild, ast::Pat::RecordPat(p) => { - let path = p.path().and_then(Path::from_ast); + let path = p.path().and_then(|path| self.parse_path(path)); let record_field_pat_list = p.record_field_pat_list().expect("every struct should have a field list"); let mut fields: Vec<_> = record_field_pat_list @@ -589,6 +592,10 @@ where self.missing_pat() } } + + fn parse_path(&mut self, path: ast::Path) -> Option { + Path::from_src(Source { ast: path, file_id: self.current_file_id }, self.db) + } } impl From for BinaryOp { diff --git a/crates/ra_hir/src/generics.rs b/crates/ra_hir/src/generics.rs index 6865d34ba..4ce7551c3 100644 --- a/crates/ra_hir/src/generics.rs +++ b/crates/ra_hir/src/generics.rs @@ -132,6 +132,7 @@ impl GenericParams { fn fill_params(&mut self, params: ast::TypeParamList, start: u32) { for (idx, type_param) in params.type_params().enumerate() { let name = type_param.name().map_or_else(Name::missing, |it| it.as_name()); + // FIXME: Use `Path::from_src` let default = type_param.default_type().and_then(|t| t.path()).and_then(Path::from_ast); let param = GenericParam { idx: idx as u32 + start, name: name.clone(), default }; diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs index 246377100..bcbcd3dd7 100644 --- a/crates/ra_hir/src/ids.rs +++ b/crates/ra_hir/src/ids.rs @@ -58,6 +58,17 @@ impl HirFileId { } } + /// Get the crate which the macro lives in, if it is a macro file. + pub(crate) fn macro_crate(self, db: &impl AstDatabase) -> Option { + match self.0 { + HirFileIdRepr::File(_) => None, + HirFileIdRepr::Macro(macro_file) => { + let loc = macro_file.macro_call_id.loc(db); + Some(loc.def.krate) + } + } + } + pub(crate) fn parse_or_expand_query( db: &impl AstDatabase, file_id: HirFileId, diff --git a/crates/ra_hir/src/impl_block.rs b/crates/ra_hir/src/impl_block.rs index d830202bd..c66a1c6a6 100644 --- a/crates/ra_hir/src/impl_block.rs +++ b/crates/ra_hir/src/impl_block.rs @@ -218,7 +218,10 @@ impl ModuleImplBlocks { ast::ItemOrMacro::Macro(macro_call) => { //FIXME: we should really cut down on the boilerplate required to process a macro let ast_id = db.ast_id_map(file_id).ast_id(¯o_call).with_file_id(file_id); - if let Some(path) = macro_call.path().and_then(Path::from_ast) { + if let Some(path) = macro_call + .path() + .and_then(|path| Path::from_src(Source { ast: path, file_id }, db)) + { if let Some(def) = self.module.resolver(db).resolve_path_as_macro(db, &path) { let call_id = MacroCallLoc { def: def.id, ast_id }.id(db); diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs index b2111be05..72f76bb79 100644 --- a/crates/ra_hir/src/marks.rs +++ b/crates/ra_hir/src/marks.rs @@ -14,4 +14,6 @@ test_utils::marks!( macro_rules_from_other_crates_are_visible_with_macro_use prelude_is_macro_use coerce_merge_fail_fallback + macro_dollar_crate_self + macro_dollar_crate_other ); diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs index b808a0c36..15b5b4ee6 100644 --- a/crates/ra_hir/src/nameres.rs +++ b/crates/ra_hir/src/nameres.rs @@ -332,6 +332,20 @@ impl CrateDefMap { ) -> ResolvePathResult { let mut segments = path.segments.iter().enumerate(); let mut curr_per_ns: PerNs = match path.kind { + PathKind::DollarCrate(krate) => { + if krate == self.krate { + tested_by!(macro_dollar_crate_self); + PerNs::types(Module { krate: self.krate, module_id: self.root }.into()) + } else { + match krate.root_module(db) { + Some(module) => { + tested_by!(macro_dollar_crate_other); + PerNs::types(module.into()) + } + None => return ResolvePathResult::empty(ReachedFixedPoint::No), + } + } + } PathKind::Crate => { PerNs::types(Module { krate: self.krate, module_id: self.root }.into()) } diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs index 29aaddbf1..c607b8a11 100644 --- a/crates/ra_hir/src/nameres/raw.rs +++ b/crates/ra_hir/src/nameres/raw.rs @@ -9,7 +9,7 @@ use test_utils::tested_by; use crate::{ db::{AstDatabase, DefDatabase}, - AsName, AstIdMap, Either, FileAstId, HirFileId, ModuleSource, Name, Path, + AsName, AstIdMap, Either, FileAstId, HirFileId, ModuleSource, Name, Path, Source, }; /// `RawItems` is a set of top-level items in a file (except for impls). @@ -71,6 +71,8 @@ impl RawItems { raw_items: RawItems::default(), source_ast_id_map: db.ast_id_map(file_id), source_map: ImportSourceMap::default(), + file_id, + db, }; if let Some(node) = db.parse_or_expand(file_id) { if let Some(source_file) = ast::SourceFile::cast(node.clone()) { @@ -192,13 +194,15 @@ pub(super) struct MacroData { pub(super) export: bool, } -struct RawItemsCollector { +struct RawItemsCollector { raw_items: RawItems, source_ast_id_map: Arc, source_map: ImportSourceMap, + file_id: HirFileId, + db: DB, } -impl RawItemsCollector { +impl RawItemsCollector<&'_ DB> { fn process_module(&mut self, current_module: Option, body: impl ast::ModuleItemOwner) { for item_or_macro in body.items_with_macros() { match item_or_macro { @@ -300,17 +304,21 @@ impl RawItemsCollector { fn add_use_item(&mut self, current_module: Option, use_item: ast::UseItem) { let is_prelude = use_item.has_atom_attr("prelude_import"); - Path::expand_use_item(&use_item, |path, use_tree, is_glob, alias| { - let import_data = ImportData { - path, - alias, - is_glob, - is_prelude, - is_extern_crate: false, - is_macro_use: false, - }; - self.push_import(current_module, import_data, Either::A(AstPtr::new(use_tree))); - }) + Path::expand_use_item( + Source { ast: use_item, file_id: self.file_id }, + self.db, + |path, use_tree, is_glob, alias| { + let import_data = ImportData { + path, + alias, + is_glob, + is_prelude, + is_extern_crate: false, + is_macro_use: false, + }; + self.push_import(current_module, import_data, Either::A(AstPtr::new(use_tree))); + }, + ) } fn add_extern_crate_item( @@ -335,7 +343,10 @@ impl RawItemsCollector { } fn add_macro(&mut self, current_module: Option, m: ast::MacroCall) { - let path = match m.path().and_then(Path::from_ast) { + let path = match m + .path() + .and_then(|path| Path::from_src(Source { ast: path, file_id: self.file_id }, self.db)) + { Some(it) => it, _ => return, }; diff --git a/crates/ra_hir/src/nameres/tests/macros.rs b/crates/ra_hir/src/nameres/tests/macros.rs index bd60f4258..e4b408394 100644 --- a/crates/ra_hir/src/nameres/tests/macros.rs +++ b/crates/ra_hir/src/nameres/tests/macros.rs @@ -515,3 +515,108 @@ fn path_qualified_macros() { ⋮not_found: _ "###); } + +#[test] +fn macro_dollar_crate_is_correct_in_item() { + covers!(macro_dollar_crate_self); + covers!(macro_dollar_crate_other); + let map = def_map_with_crate_graph( + " + //- /main.rs + #[macro_use] + extern crate foo; + + #[macro_use] + mod m { + macro_rules! current { + () => { + use $crate::Foo as FooSelf; + } + } + } + + struct Foo; + + current!(); + not_current1!(); + foo::not_current2!(); + + //- /lib.rs + mod m { + #[macro_export] + macro_rules! not_current1 { + () => { + use $crate::Bar; + } + } + } + + #[macro_export] + macro_rules! not_current2 { + () => { + use $crate::Baz; + } + } + + struct Bar; + struct Baz; + ", + crate_graph! { + "main": ("/main.rs", ["foo"]), + "foo": ("/lib.rs", []), + }, + ); + assert_snapshot!(map, @r###" + ⋮crate + ⋮Bar: t v + ⋮Baz: t v + ⋮Foo: t v + ⋮FooSelf: t v + ⋮foo: t + ⋮m: t + ⋮ + ⋮crate::m + "###); +} + +#[test] +fn macro_dollar_crate_is_correct_in_indirect_deps() { + covers!(macro_dollar_crate_other); + // From std + let map = def_map_with_crate_graph( + r#" + //- /main.rs + foo!(); + + //- /std.rs + #[prelude_import] + use self::prelude::*; + + pub use core::foo; + + mod prelude {} + + #[macro_use] + mod std_macros; + + //- /core.rs + #[macro_export] + macro_rules! foo { + () => { + use $crate::bar; + } + } + + pub struct bar; + "#, + crate_graph! { + "main": ("/main.rs", ["std"]), + "std": ("/std.rs", ["core"]), + "core": ("/core.rs", []), + }, + ); + assert_snapshot!(map, @r###" + ⋮crate + ⋮bar: t v + "###); +} diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs index 39d1b7e46..158c853d4 100644 --- a/crates/ra_hir/src/path.rs +++ b/crates/ra_hir/src/path.rs @@ -5,7 +5,7 @@ use ra_syntax::{ AstNode, }; -use crate::{name, type_ref::TypeRef, AsName, Name}; +use crate::{db::AstDatabase, name, type_ref::TypeRef, AsName, Crate, Name, Source}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { @@ -52,16 +52,19 @@ pub enum PathKind { Abs, // Type based path like `::foo` Type(Box), + // `$crate` from macro expansion + DollarCrate(Crate), } impl Path { /// Calls `cb` with all paths, represented by this use item. pub fn expand_use_item( - item: &ast::UseItem, + item_src: Source, + db: &impl AstDatabase, mut cb: impl FnMut(Path, &ast::UseTree, bool, Option), ) { - if let Some(tree) = item.use_tree() { - expand_use_tree(None, tree, &mut cb); + if let Some(tree) = item_src.ast.use_tree() { + expand_use_tree(None, tree, &|| item_src.file_id.macro_crate(db), &mut cb); } } @@ -76,7 +79,19 @@ impl Path { } /// Converts an `ast::Path` to `Path`. Works with use trees. - pub fn from_ast(mut path: ast::Path) -> Option { + /// DEPRECATED: It does not handle `$crate` from macro call. + pub fn from_ast(path: ast::Path) -> Option { + Path::parse(path, &|| None) + } + + /// Converts an `ast::Path` to `Path`. Works with use trees. + /// It correctly handles `$crate` based path from macro call. + pub fn from_src(source: Source, db: &impl AstDatabase) -> Option { + let file_id = source.file_id; + Path::parse(source.ast, &|| file_id.macro_crate(db)) + } + + fn parse(mut path: ast::Path, macro_crate: &impl Fn() -> Option) -> Option { let mut kind = PathKind::Plain; let mut segments = Vec::new(); loop { @@ -88,6 +103,13 @@ impl Path { match segment.kind()? { ast::PathSegmentKind::Name(name) => { + if name.text() == "$crate" { + if let Some(macro_crate) = macro_crate() { + kind = PathKind::DollarCrate(macro_crate); + break; + } + } + let args = segment .type_arg_list() .and_then(GenericArgs::from_ast) @@ -113,7 +135,7 @@ impl Path { } // >::Foo desugars to Trait::Foo Some(trait_ref) => { - let path = Path::from_ast(trait_ref.path()?)?; + let path = Path::parse(trait_ref.path()?, macro_crate)?; kind = path.kind; let mut prefix_segments = path.segments; prefix_segments.reverse(); @@ -264,6 +286,7 @@ impl From for Path { fn expand_use_tree( prefix: Option, tree: ast::UseTree, + macro_crate: &impl Fn() -> Option, cb: &mut impl FnMut(Path, &ast::UseTree, bool, Option), ) { if let Some(use_tree_list) = tree.use_tree_list() { @@ -272,13 +295,13 @@ fn expand_use_tree( None => prefix, // E.g. `use something::{inner}` (prefix is `None`, path is `something`) // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`) - Some(path) => match convert_path(prefix, path) { + Some(path) => match convert_path(prefix, path, macro_crate) { Some(it) => Some(it), None => return, // FIXME: report errors somewhere }, }; for child_tree in use_tree_list.use_trees() { - expand_use_tree(prefix.clone(), child_tree, cb); + expand_use_tree(prefix.clone(), child_tree, macro_crate, cb); } } else { let alias = tree.alias().and_then(|a| a.name()).map(|a| a.as_name()); @@ -295,7 +318,7 @@ fn expand_use_tree( } } } - if let Some(path) = convert_path(prefix, ast_path) { + if let Some(path) = convert_path(prefix, ast_path, macro_crate) { let is_glob = tree.has_star(); cb(path, &tree, is_glob, alias) } @@ -305,12 +328,29 @@ fn expand_use_tree( } } -fn convert_path(prefix: Option, path: ast::Path) -> Option { - let prefix = - if let Some(qual) = path.qualifier() { Some(convert_path(prefix, qual)?) } else { prefix }; +fn convert_path( + prefix: Option, + path: ast::Path, + macro_crate: &impl Fn() -> Option, +) -> Option { + let prefix = if let Some(qual) = path.qualifier() { + Some(convert_path(prefix, qual, macro_crate)?) + } else { + prefix + }; + let segment = path.segment()?; let res = match segment.kind()? { ast::PathSegmentKind::Name(name) => { + if name.text() == "$crate" { + if let Some(krate) = macro_crate() { + return Some(Path::from_simple_segments( + PathKind::DollarCrate(krate), + iter::empty(), + )); + } + } + // no type args in use let mut res = prefix .unwrap_or_else(|| Path { kind: PathKind::Plain, segments: Vec::with_capacity(1) }); diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index bd4be8430..6e89bfc76 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -203,6 +203,7 @@ impl SourceAnalyzer { db: &impl HirDatabase, macro_call: &ast::MacroCall, ) -> Option { + // This must be a normal source file rather than macro file. let path = macro_call.path().and_then(Path::from_ast)?; self.resolver.resolve_path_as_macro(db, &path) } @@ -261,6 +262,7 @@ impl SourceAnalyzer { return Some(PathResolution::AssocItem(assoc)); } } + // This must be a normal source file rather than macro file. let hir_path = crate::Path::from_ast(path.clone())?; self.resolve_hir_path(db, &hir_path) } diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 4362bb27a..bd2b07755 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -3130,6 +3130,39 @@ fn test() { S.foo()<|>; } assert_eq!(t, "u128"); } +#[test] +fn infer_macro_with_dollar_crate_is_correct_in_expr() { + covers!(macro_dollar_crate_other); + let (mut db, pos) = MockDatabase::with_position( + r#" +//- /main.rs +fn test() { + let x = (foo::foo!(1), foo::foo!(2)); + x<|>; +} + +//- /lib.rs +#[macro_export] +macro_rules! foo { + (1) => { $crate::bar!() }; + (2) => { 1 + $crate::baz() }; +} + +#[macro_export] +macro_rules! bar { + () => { 42 } +} + +pub fn baz() -> usize { 31usize } +"#, + ); + db.set_crate_graph_from_fixture(crate_graph! { + "main": ("/main.rs", ["foo"]), + "foo": ("/lib.rs", []), + }); + assert_eq!("(i32, usize)", type_at_pos(&db, pos)); +} + #[ignore] #[test] fn method_resolution_trait_before_autoref() { diff --git a/crates/ra_hir/src/type_ref.rs b/crates/ra_hir/src/type_ref.rs index bc8acc7ee..2cf06b250 100644 --- a/crates/ra_hir/src/type_ref.rs +++ b/crates/ra_hir/src/type_ref.rs @@ -72,6 +72,7 @@ impl TypeRef { } ast::TypeRef::NeverType(..) => TypeRef::Never, ast::TypeRef::PathType(inner) => { + // FIXME: Use `Path::from_src` inner.path().and_then(Path::from_ast).map(TypeRef::Path).unwrap_or(TypeRef::Error) } ast::TypeRef::PointerType(inner) => { @@ -141,6 +142,7 @@ impl TypeBound { Some(p) => p, None => return TypeBound::Error, }; + // FIXME: Use `Path::from_src` let path = match Path::from_ast(path) { Some(p) => p, None => return TypeBound::Error, diff --git a/crates/ra_mbe/src/mbe_expander/transcriber.rs b/crates/ra_mbe/src/mbe_expander/transcriber.rs index c22680b93..ed094d5bb 100644 --- a/crates/ra_mbe/src/mbe_expander/transcriber.rs +++ b/crates/ra_mbe/src/mbe_expander/transcriber.rs @@ -86,7 +86,7 @@ fn expand_subtree(ctx: &mut ExpandCtx, template: &tt::Subtree) -> Result Result { let res = if v == "crate" { - // FIXME: Properly handle $crate token + // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path. let tt = tt::Leaf::from(tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }) .into(); -- cgit v1.2.3