From 0bc7d285189caaffc13e4d6856baf895f72ed80c Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Wed, 30 Oct 2019 18:41:50 +0300
Subject: [PATCH] refactor $crate handling

Introduce proper hygiene module, which should grow quite a bit
eventually.
---
 crates/ra_hir/src/expr/lower.rs      |   4 +-
 crates/ra_hir/src/impl_block.rs      |  12 ++--
 crates/ra_hir_def/src/attr.rs        |  18 ++---
 crates/ra_hir_def/src/hygiene.rs     |  40 +++++++++++
 crates/ra_hir_def/src/lib.rs         |   1 +
 crates/ra_hir_def/src/nameres/raw.rs |  31 ++++----
 crates/ra_hir_def/src/path.rs        | 101 +++++++++++++--------------
 7 files changed, 118 insertions(+), 89 deletions(-)
 create mode 100644 crates/ra_hir_def/src/hygiene.rs

diff --git a/crates/ra_hir/src/expr/lower.rs b/crates/ra_hir/src/expr/lower.rs
index ad029b868f9..575d78198c4 100644
--- a/crates/ra_hir/src/expr/lower.rs
+++ b/crates/ra_hir/src/expr/lower.rs
@@ -1,6 +1,7 @@
 //! FIXME: write short doc here
 
 use hir_def::{
+    hygiene::Hygiene,
     name::{self, AsName, Name},
     path::GenericArgs,
     type_ref::TypeRef,
@@ -597,7 +598,8 @@ where
     }
 
     fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
-        Path::from_src(Source { ast: path, file_id: self.current_file_id }, self.db)
+        let hygiene = Hygiene::new(self.db, self.current_file_id);
+        Path::from_src(path, &hygiene)
     }
 }
 
diff --git a/crates/ra_hir/src/impl_block.rs b/crates/ra_hir/src/impl_block.rs
index 51833071321..9e4a4001770 100644
--- a/crates/ra_hir/src/impl_block.rs
+++ b/crates/ra_hir/src/impl_block.rs
@@ -3,7 +3,7 @@
 use rustc_hash::FxHashMap;
 use std::sync::Arc;
 
-use hir_def::{attr::Attr, type_ref::TypeRef};
+use hir_def::{attr::Attr, hygiene::Hygiene, type_ref::TypeRef};
 use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId};
 use ra_cfg::CfgOptions;
 use ra_syntax::{
@@ -227,10 +227,11 @@ impl ModuleImplBlocks {
         owner: &dyn ast::ModuleItemOwner,
         file_id: HirFileId,
     ) {
+        let hygiene = Hygiene::new(db, file_id);
         for item in owner.items_with_macros() {
             match item {
                 ast::ItemOrMacro::Item(ast::ModuleItem::ImplBlock(impl_block_ast)) => {
-                    let attrs = Attr::from_attrs_owner(file_id, &impl_block_ast, db);
+                    let attrs = Attr::from_attrs_owner(&impl_block_ast, &hygiene);
                     if attrs.map_or(false, |attrs| {
                         attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
                     }) {
@@ -247,7 +248,7 @@ impl ModuleImplBlocks {
                 }
                 ast::ItemOrMacro::Item(_) => (),
                 ast::ItemOrMacro::Macro(macro_call) => {
-                    let attrs = Attr::from_attrs_owner(file_id, &macro_call, db);
+                    let attrs = Attr::from_attrs_owner(&macro_call, &hygiene);
                     if attrs.map_or(false, |attrs| {
                         attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false))
                     }) {
@@ -256,9 +257,8 @@ impl ModuleImplBlocks {
 
                     //FIXME: we should really cut down on the boilerplate required to process a macro
                     let ast_id = AstId::new(file_id, db.ast_id_map(file_id).ast_id(&macro_call));
-                    if let Some(path) = macro_call
-                        .path()
-                        .and_then(|path| Path::from_src(Source { ast: path, file_id }, db))
+                    if let Some(path) =
+                        macro_call.path().and_then(|path| Path::from_src(path, &hygiene))
                     {
                         if let Some(def) = self.module.resolver(db).resolve_path_as_macro(db, &path)
                         {
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs
index 248f03cdfdd..71f92adc297 100644
--- a/crates/ra_hir_def/src/attr.rs
+++ b/crates/ra_hir_def/src/attr.rs
@@ -2,7 +2,6 @@
 
 use std::sync::Arc;
 
-use hir_expand::db::AstDatabase;
 use mbe::ast_to_token_tree;
 use ra_cfg::CfgOptions;
 use ra_syntax::{
@@ -11,7 +10,7 @@ use ra_syntax::{
 };
 use tt::Subtree;
 
-use crate::{path::Path, HirFileId, Source};
+use crate::{hygiene::Hygiene, path::Path};
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Attr {
@@ -26,11 +25,8 @@ pub enum AttrInput {
 }
 
 impl Attr {
-    pub(crate) fn from_src(
-        Source { file_id, ast }: Source<ast::Attr>,
-        db: &impl AstDatabase,
-    ) -> Option<Attr> {
-        let path = Path::from_src(Source { file_id, ast: ast.path()? }, db)?;
+    pub(crate) fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
+        let path = Path::from_src(ast.path()?, hygiene)?;
         let input = match ast.input() {
             None => None,
             Some(ast::AttrInput::Literal(lit)) => {
@@ -46,17 +42,13 @@ impl Attr {
         Some(Attr { path, input })
     }
 
-    pub fn from_attrs_owner(
-        file_id: HirFileId,
-        owner: &dyn AttrsOwner,
-        db: &impl AstDatabase,
-    ) -> Option<Arc<[Attr]>> {
+    pub fn from_attrs_owner(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Option<Arc<[Attr]>> {
         let mut attrs = owner.attrs().peekable();
         if attrs.peek().is_none() {
             // Avoid heap allocation
             return None;
         }
-        Some(attrs.flat_map(|ast| Attr::from_src(Source { file_id, ast }, db)).collect())
+        Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect())
     }
 
     pub fn is_simple_atom(&self, name: &str) -> bool {
diff --git a/crates/ra_hir_def/src/hygiene.rs b/crates/ra_hir_def/src/hygiene.rs
new file mode 100644
index 00000000000..e1ae58a3bc9
--- /dev/null
+++ b/crates/ra_hir_def/src/hygiene.rs
@@ -0,0 +1,40 @@
+//! This modules handles hygiene information.
+//!
+//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
+//! this moment, this is horribly incomplete and handles only `$crate`.
+// Should this be moved to `hir_expand`? Seems like it.
+
+use hir_expand::{db::AstDatabase, HirFileId};
+use ra_db::CrateId;
+use ra_syntax::ast;
+
+use crate::{
+    either::Either,
+    name::{AsName, Name},
+};
+
+#[derive(Debug)]
+pub struct Hygiene {
+    // This is what `$crate` expands to
+    def_crate: Option<CrateId>,
+}
+
+impl Hygiene {
+    pub fn new(db: &impl AstDatabase, file_id: HirFileId) -> Hygiene {
+        Hygiene { def_crate: file_id.macro_crate(db) }
+    }
+
+    pub(crate) fn new_unhygienic() -> Hygiene {
+        Hygiene { def_crate: None }
+    }
+
+    // FIXME: this should just return name
+    pub(crate) fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
+        if let Some(def_crate) = self.def_crate {
+            if name_ref.text() == "$crate" {
+                return Either::B(def_crate);
+            }
+        }
+        Either::A(name_ref.as_name())
+    }
+}
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs
index 95d50332568..61ccdb30d2c 100644
--- a/crates/ra_hir_def/src/lib.rs
+++ b/crates/ra_hir_def/src/lib.rs
@@ -13,6 +13,7 @@ pub mod attr;
 pub mod name;
 pub mod path;
 pub mod type_ref;
+pub mod hygiene;
 
 // FIXME: this should be private
 pub mod nameres;
diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs
index 86b4fef9659..636364628b2 100644
--- a/crates/ra_hir_def/src/nameres/raw.rs
+++ b/crates/ra_hir_def/src/nameres/raw.rs
@@ -13,6 +13,7 @@ use crate::{
     attr::Attr,
     db::DefDatabase2,
     either::Either,
+    hygiene::Hygiene,
     name::{AsName, Name},
     path::Path,
     FileAstId, HirFileId, ModuleSource, Source,
@@ -78,7 +79,7 @@ impl RawItems {
             source_ast_id_map: db.ast_id_map(file_id),
             source_map: ImportSourceMap::default(),
             file_id,
-            db,
+            hygiene: Hygiene::new(db, file_id),
         };
         if let Some(node) = db.parse_or_expand(file_id) {
             if let Some(source_file) = ast::SourceFile::cast(node.clone()) {
@@ -204,15 +205,15 @@ pub struct MacroData {
     pub export: bool,
 }
 
-struct RawItemsCollector<DB> {
+struct RawItemsCollector {
     raw_items: RawItems,
     source_ast_id_map: Arc<AstIdMap>,
     source_map: ImportSourceMap,
     file_id: HirFileId,
-    db: DB,
+    hygiene: Hygiene,
 }
 
-impl<DB: AstDatabase> RawItemsCollector<&DB> {
+impl RawItemsCollector {
     fn process_module(&mut self, current_module: Option<Module>, body: impl ast::ModuleItemOwner) {
         for item_or_macro in body.items_with_macros() {
             match item_or_macro {
@@ -309,9 +310,10 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
         let is_prelude = use_item.has_atom_attr("prelude_import");
         let attrs = self.parse_attrs(&use_item);
 
+        let mut buf = Vec::new();
         Path::expand_use_item(
             Source { ast: use_item, file_id: self.file_id },
-            self.db,
+            &self.hygiene,
             |path, use_tree, is_glob, alias| {
                 let import_data = ImportData {
                     path,
@@ -321,14 +323,12 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
                     is_extern_crate: false,
                     is_macro_use: false,
                 };
-                self.push_import(
-                    current_module,
-                    attrs.clone(),
-                    import_data,
-                    Either::A(AstPtr::new(use_tree)),
-                );
+                buf.push((import_data, Either::A(AstPtr::new(use_tree))));
             },
-        )
+        );
+        for (import_data, ptr) in buf {
+            self.push_import(current_module, attrs.clone(), import_data, ptr);
+        }
     }
 
     fn add_extern_crate_item(
@@ -361,10 +361,7 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
 
     fn add_macro(&mut self, current_module: Option<Module>, m: ast::MacroCall) {
         let attrs = self.parse_attrs(&m);
-        let path = match m
-            .path()
-            .and_then(|path| Path::from_src(Source { ast: path, file_id: self.file_id }, self.db))
-        {
+        let path = match m.path().and_then(|path| Path::from_src(path, &self.hygiene)) {
             Some(it) => it,
             _ => return,
         };
@@ -402,6 +399,6 @@ impl<DB: AstDatabase> RawItemsCollector<&DB> {
     }
 
     fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs {
-        Attr::from_attrs_owner(self.file_id, item, self.db)
+        Attr::from_attrs_owner(item, &self.hygiene)
     }
 }
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs
index fe060437df8..39f394c3f75 100644
--- a/crates/ra_hir_def/src/path.rs
+++ b/crates/ra_hir_def/src/path.rs
@@ -2,7 +2,6 @@
 
 use std::{iter, sync::Arc};
 
-use hir_expand::db::AstDatabase;
 use ra_db::CrateId;
 use ra_syntax::{
     ast::{self, NameOwner, TypeAscriptionOwner},
@@ -10,6 +9,8 @@ use ra_syntax::{
 };
 
 use crate::{
+    either::Either,
+    hygiene::Hygiene,
     name::{self, AsName, Name},
     type_ref::TypeRef,
     Source,
@@ -68,11 +69,11 @@ impl Path {
     /// Calls `cb` with all paths, represented by this use item.
     pub fn expand_use_item(
         item_src: Source<ast::UseItem>,
-        db: &impl AstDatabase,
+        hygiene: &Hygiene,
         mut cb: impl FnMut(Path, &ast::UseTree, bool, Option<Name>),
     ) {
         if let Some(tree) = item_src.ast.use_tree() {
-            expand_use_tree(None, tree, &|| item_src.file_id.macro_crate(db), &mut cb);
+            expand_use_tree(None, tree, hygiene, &mut cb);
         }
     }
 
@@ -89,17 +90,12 @@ impl Path {
     /// Converts an `ast::Path` to `Path`. Works with use trees.
     /// DEPRECATED: It does not handle `$crate` from macro call.
     pub fn from_ast(path: ast::Path) -> Option<Path> {
-        Path::parse(path, &|| None)
+        Path::from_src(path, &Hygiene::new_unhygienic())
     }
 
     /// 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<ast::Path>, db: &impl AstDatabase) -> Option<Path> {
-        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<CrateId>) -> Option<Path> {
+    pub fn from_src(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path> {
         let mut kind = PathKind::Plain;
         let mut segments = Vec::new();
         loop {
@@ -110,26 +106,28 @@ 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);
+                ast::PathSegmentKind::Name(name_ref) => {
+                    // FIXME: this should just return name
+                    match hygiene.name_ref_to_name(name_ref) {
+                        Either::A(name) => {
+                            let args = segment
+                                .type_arg_list()
+                                .and_then(GenericArgs::from_ast)
+                                .or_else(|| {
+                                    GenericArgs::from_fn_like_path_ast(
+                                        segment.param_list(),
+                                        segment.ret_type(),
+                                    )
+                                })
+                                .map(Arc::new);
+                            let segment = PathSegment { name, args_and_bindings: args };
+                            segments.push(segment);
+                        }
+                        Either::B(crate_id) => {
+                            kind = PathKind::DollarCrate(crate_id);
                             break;
                         }
                     }
-
-                    let args = segment
-                        .type_arg_list()
-                        .and_then(GenericArgs::from_ast)
-                        .or_else(|| {
-                            GenericArgs::from_fn_like_path_ast(
-                                segment.param_list(),
-                                segment.ret_type(),
-                            )
-                        })
-                        .map(Arc::new);
-                    let segment = PathSegment { name: name.as_name(), args_and_bindings: args };
-                    segments.push(segment);
                 }
                 ast::PathSegmentKind::Type { type_ref, trait_ref } => {
                     assert!(path.qualifier().is_none()); // this can only occur at the first segment
@@ -143,7 +141,7 @@ impl Path {
                         }
                         // <T as Trait<A>>::Foo desugars to Trait<Self=T, A>::Foo
                         Some(trait_ref) => {
-                            let path = Path::parse(trait_ref.path()?, macro_crate)?;
+                            let path = Path::from_src(trait_ref.path()?, hygiene)?;
                             kind = path.kind;
                             let mut prefix_segments = path.segments;
                             prefix_segments.reverse();
@@ -294,7 +292,7 @@ impl From<Name> for Path {
 fn expand_use_tree(
     prefix: Option<Path>,
     tree: ast::UseTree,
-    macro_crate: &impl Fn() -> Option<CrateId>,
+    hygiene: &Hygiene,
     cb: &mut impl FnMut(Path, &ast::UseTree, bool, Option<Name>),
 ) {
     if let Some(use_tree_list) = tree.use_tree_list() {
@@ -303,13 +301,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, macro_crate) {
+            Some(path) => match convert_path(prefix, path, hygiene) {
                 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, macro_crate, cb);
+            expand_use_tree(prefix.clone(), child_tree, hygiene, cb);
         }
     } else {
         let alias = tree.alias().and_then(|a| a.name()).map(|a| a.as_name());
@@ -326,7 +324,7 @@ fn expand_use_tree(
                     }
                 }
             }
-            if let Some(path) = convert_path(prefix, ast_path, macro_crate) {
+            if let Some(path) = convert_path(prefix, ast_path, hygiene) {
                 let is_glob = tree.has_star();
                 cb(path, &tree, is_glob, alias)
             }
@@ -336,37 +334,36 @@ fn expand_use_tree(
     }
 }
 
-fn convert_path(
-    prefix: Option<Path>,
-    path: ast::Path,
-    macro_crate: &impl Fn() -> Option<CrateId>,
-) -> Option<Path> {
+fn convert_path(prefix: Option<Path>, path: ast::Path, hygiene: &Hygiene) -> Option<Path> {
     let prefix = if let Some(qual) = path.qualifier() {
-        Some(convert_path(prefix, qual, macro_crate)?)
+        Some(convert_path(prefix, qual, hygiene)?)
     } 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() {
+        ast::PathSegmentKind::Name(name_ref) => {
+            match hygiene.name_ref_to_name(name_ref) {
+                Either::A(name) => {
+                    // no type args in use
+                    let mut res = prefix.unwrap_or_else(|| Path {
+                        kind: PathKind::Plain,
+                        segments: Vec::with_capacity(1),
+                    });
+                    res.segments.push(PathSegment {
+                        name,
+                        args_and_bindings: None, // no type args in use
+                    });
+                    res
+                }
+                Either::B(crate_id) => {
                     return Some(Path::from_simple_segments(
-                        PathKind::DollarCrate(krate),
+                        PathKind::DollarCrate(crate_id),
                         iter::empty(),
-                    ));
+                    ))
                 }
             }
-
-            // no type args in use
-            let mut res = prefix
-                .unwrap_or_else(|| Path { kind: PathKind::Plain, segments: Vec::with_capacity(1) });
-            res.segments.push(PathSegment {
-                name: name.as_name(),
-                args_and_bindings: None, // no type args in use
-            });
-            res
         }
         ast::PathSegmentKind::CrateKw => {
             if prefix.is_some() {