Repository: rust-analyzer/rust-analyzer
Pull request #7212 (2021-01-08 20:10:17)

Simplify highlighting

matklad: cc @ltentrup, figured out how to simplify the thing!

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index bc38c5a37f..57383491a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -262,9 +262,9 @@ dependencies = [
 
 [[package]]
 name = "const_fn"
-version = "0.4.5"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
+checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
 
 [[package]]
 name = "crc32fast"
@@ -774,9 +774,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.82"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
+checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
 
 [[package]]
 name = "libloading"
@@ -1134,9 +1134,9 @@ checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1"
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.1"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8"
+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
 
 [[package]]
 name = "proc-macro2"
@@ -1449,9 +1449,9 @@ dependencies = [
 
 [[package]]
 name = "semver-parser"
-version = "0.10.2"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428"
 dependencies = [
  "pest",
 ]
@@ -1558,9 +1558,9 @@ version = "0.0.0"
 
 [[package]]
 name = "syn"
-version = "1.0.58"
+version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
+checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1643,9 +1643,9 @@ checksum = "db3c46be180f1af9673ebb27bc1235396f61ef6965b3fe0dbb2e624deb604f0e"
 
 [[package]]
 name = "thread_local"
-version = "1.1.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
 dependencies = [
  "lazy_static",
 ]
diff --git a/crates/completion/src/completions/attribute.rs b/crates/completion/src/completions/attribute.rs
index 3a29b5203f..10739750c7 100644
--- a/crates/completion/src/completions/attribute.rs
+++ b/crates/completion/src/completions/attribute.rs
@@ -21,15 +21,20 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
 
     let attribute = ctx.attribute_under_caret.as_ref()?;
     match (attribute.path(), attribute.token_tree()) {
-        (Some(path), Some(token_tree)) => match path.to_string().as_str() {
-            "derive" => complete_derive(acc, ctx, token_tree),
-            "feature" => complete_lint(acc, ctx, token_tree, FEATURES),
-            "allow" | "warn" | "deny" | "forbid" => {
-                complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
-                complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
-            }
-            _ => {}
-        },
+        (Some(path), Some(token_tree)) if path.to_string() == "derive" => {
+            complete_derive(acc, ctx, token_tree)
+        }
+        (Some(path), Some(token_tree)) if path.to_string() == "feature" => {
+            complete_lint(acc, ctx, token_tree, FEATURES);
+        }
+        (Some(path), Some(token_tree))
+            if ["allow", "warn", "deny", "forbid"]
+                .iter()
+                .any(|lint_level| lint_level == &path.to_string()) =>
+        {
+            complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
+            complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
+        }
         (_, Some(_token_tree)) => {}
         _ => complete_attribute_start(acc, ctx, attribute),
     }
@@ -49,8 +54,11 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
             item = item.lookup_by(lookup);
         }
 
-        if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
-            item = item.insert_snippet(cap, snippet);
+        match (attr_completion.snippet, ctx.config.snippet_cap) {
+            (Some(snippet), Some(cap)) => {
+                item = item.insert_snippet(cap, snippet);
+            }
+            _ => {}
         }
 
         if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
diff --git a/crates/completion/src/completions/mod_.rs b/crates/completion/src/completions/mod_.rs
index 00e951ca95..8c8eaeaa6f 100644
--- a/crates/completion/src/completions/mod_.rs
+++ b/crates/completion/src/completions/mod_.rs
@@ -1,7 +1,5 @@
 //! Completes mod declarations.
 
-use std::iter;
-
 use hir::{Module, ModuleSource};
 use ide_db::base_db::{SourceDatabaseExt, VfsPath};
 use ide_db::RootDatabase;
@@ -14,8 +12,9 @@ use crate::{context::CompletionContext, item::CompletionKind, Completions};
 /// Complete mod declaration, i.e. `mod $0 ;`
 pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
     let mod_under_caret = match &ctx.mod_declaration_under_caret {
-        Some(mod_under_caret) if mod_under_caret.item_list().is_none() => mod_under_caret,
-        _ => return None,
+        Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
+        Some(mod_under_caret) => mod_under_caret,
+        None => return None,
     };
 
     let _p = profile::span("completion::complete_mod");
@@ -50,13 +49,9 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
         .filter_map(|submodule_file| {
             let submodule_path = source_root.path_for_file(&submodule_file)?;
             let directory_with_submodule = submodule_path.parent()?;
-            let (name, ext) = submodule_path.name_and_extension()?;
-            if ext != Some("rs") {
-                return None;
-            }
-            match name {
-                "lib" | "main" => None,
-                "mod" => {
+            match submodule_path.name_and_extension()? {
+                ("lib", Some("rs")) | ("main", Some("rs")) => None,
+                ("mod", Some("rs")) => {
                     if directory_with_submodule.parent()? == directory_to_look_for_submodules {
                         match directory_with_submodule.name_and_extension()? {
                             (directory_name, None) => Some(directory_name.to_owned()),
@@ -66,7 +61,9 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
                         None
                     }
                 }
-                file_name if directory_with_submodule == directory_to_look_for_submodules => {
+                (file_name, Some("rs"))
+                    if directory_with_submodule == directory_to_look_for_submodules =>
+                {
                     Some(file_name.to_owned())
                 }
                 _ => None,
@@ -76,7 +73,7 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
         .for_each(|submodule_name| {
             let mut label = submodule_name;
             if mod_under_caret.semicolon_token().is_none() {
-                label.push(';');
+                label.push(';')
             }
             CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
                 .kind(CompletionItemKind::Module)
@@ -92,13 +89,11 @@ fn directory_to_look_for_submodules(
     module_file_path: &VfsPath,
 ) -> Option<VfsPath> {
     let directory_with_module_path = module_file_path.parent()?;
-    let (name, ext) = module_file_path.name_and_extension()?;
-    if ext != Some("rs") {
-        return None;
-    }
-    let base_directory = match name {
-        "mod" | "lib" | "main" => Some(directory_with_module_path),
-        regular_rust_file_name => {
+    let base_directory = match module_file_path.name_and_extension()? {
+        ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => {
+            Some(directory_with_module_path)
+        }
+        (regular_rust_file_name, Some("rs")) => {
             if matches!(
                 (
                     directory_with_module_path
@@ -115,25 +110,37 @@ fn directory_to_look_for_submodules(
                 directory_with_module_path.join(regular_rust_file_name)
             }
         }
+        _ => None,
     }?;
 
-    module_chain_to_containing_module_file(module, db)
-        .into_iter()
-        .filter_map(|module| module.name(db))
-        .try_fold(base_directory, |path, name| path.join(&name.to_string()))
+    let mut resulting_path = base_directory;
+    for module in module_chain_to_containing_module_file(module, db) {
+        if let Some(name) = module.name(db) {
+            resulting_path = resulting_path.join(&name.to_string())?;
+        }
+    }
+
+    Some(resulting_path)
 }
 
 fn module_chain_to_containing_module_file(
     current_module: Module,
     db: &RootDatabase,
 ) -> Vec<Module> {
-    let mut path =
-        iter::successors(Some(current_module), |current_module| current_module.parent(db))
-            .take_while(|current_module| {
-                matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
-            })
-            .collect::<Vec<_>>();
-    path.reverse();
+    let mut path = Vec::new();
+
+    let mut current_module = Some(current_module);
+    while let Some(ModuleSource::Module(_)) =
+        current_module.map(|module| module.definition_source(db).value)
+    {
+        if let Some(module) = current_module {
+            path.insert(0, module);
+            current_module = module.parent(db);
+        } else {
+            current_module = None;
+        }
+    }
+
     path
 }
 
diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs
index d5d4cf5b65..d01e1b33d9 100644
--- a/crates/hir/src/db.rs
+++ b/crates/hir/src/db.rs
@@ -10,8 +10,8 @@ pub use hir_def::db::{
     TypeAliasDataQuery, UnionDataQuery,
 };
 pub use hir_expand::db::{
-    AstDatabase, AstDatabaseStorage, AstIdMapQuery, HygieneFrameQuery, InternEagerExpansionQuery,
-    InternMacroQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery,
+    AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
+    MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery,
 };
 pub use hir_ty::db::*;
 
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 447faa04f1..b1c9241670 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -4,6 +4,6 @@ pub use hir_expand::diagnostics::{
     Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
 };
 pub use hir_ty::diagnostics::{
-    IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
+    IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr,
     NoSuchField, RemoveThisSemicolon,
 };
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index 3dd7c3cbba..e2bf85bbc3 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -305,7 +305,6 @@ pub use hir_expand::name as __name;
 macro_rules! __known_path {
     (core::iter::IntoIterator) => {};
     (core::result::Result) => {};
-    (core::option::Option) => {};
     (core::ops::Range) => {};
     (core::ops::RangeFrom) => {};
     (core::ops::RangeFull) => {};
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 9518ac109b..8a01e6eead 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -123,7 +123,7 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
     // We follow what it did anyway :)
     if segments.len() == 1 && kind == PathKind::Plain {
         if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
-            if let Some(crate_id) = hygiene.local_inner_macros(path) {
+            if let Some(crate_id) = hygiene.local_inner_macros() {
                 kind = PathKind::DollarCrate(crate_id);
             }
         }
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index ab2637b8ca..0a0d021e05 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -8,9 +8,9 @@ use parser::FragmentKind;
 use syntax::{algo::diff, ast::NameOwner, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode};
 
 use crate::{
-    ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander,
-    EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc,
-    MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
+    ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId,
+    HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind,
+    MacroFile, ProcMacroExpander,
 };
 
 /// Total limit on the number of tokens produced by any macro invocation.
@@ -94,8 +94,6 @@ pub trait AstDatabase: SourceDatabase {
     fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
 
     fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>;
-
-    fn hygiene_frame(&self, file_id: HirFileId) -> Arc<HygieneFrame>;
 }
 
 /// This expands the given macro call, but with different arguments. This is
@@ -371,10 +369,6 @@ fn parse_macro_with_arg(
     }
 }
 
-fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame> {
-    Arc::new(HygieneFrame::new(db, file_id))
-}
-
 /// Given a `MacroCallId`, return what `FragmentKind` it belongs to.
 /// FIXME: Not completed
 fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind {
diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs
index 8db581b774..7ab0a5e52e 100644
--- a/crates/hir_expand/src/hygiene.rs
+++ b/crates/hir_expand/src/hygiene.rs
@@ -2,209 +2,65 @@
 //!
 //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
 //! this moment, this is horribly incomplete and handles only `$crate`.
-use std::sync::Arc;
-
 use base_db::CrateId;
 use either::Either;
-use mbe::Origin;
-use parser::SyntaxKind;
-use syntax::{ast, AstNode, SyntaxNode, TextRange, TextSize};
+use syntax::ast;
 
 use crate::{
-    db::{self, AstDatabase},
+    db::AstDatabase,
     name::{AsName, Name},
-    HirFileId, HirFileIdRepr, InFile, MacroCallId, MacroCallLoc, MacroDefKind, MacroFile,
+    HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
 };
 
 #[derive(Clone, Debug)]
 pub struct Hygiene {
-    frames: Option<HygieneFrames>,
+    // This is what `$crate` expands to
+    def_crate: Option<CrateId>,
+
+    // Indicate this is a local inner macro
+    local_inner: bool,
 }
 
 impl Hygiene {
     pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
-        Hygiene { frames: Some(HygieneFrames::new(db, file_id.clone())) }
+        let (def_crate, local_inner) = match file_id.0 {
+            HirFileIdRepr::FileId(_) => (None, false),
+            HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
+                MacroCallId::LazyMacro(id) => {
+                    let loc = db.lookup_intern_macro(id);
+                    match loc.def.kind {
+                        MacroDefKind::Declarative => (Some(loc.def.krate), loc.def.local_inner),
+                        MacroDefKind::BuiltIn(_) => (Some(loc.def.krate), false),
+                        MacroDefKind::BuiltInDerive(_) => (None, false),
+                        MacroDefKind::BuiltInEager(_) => (None, false),
+                        MacroDefKind::ProcMacro(_) => (None, false),
+                    }
+                }
+                MacroCallId::EagerMacro(_id) => (None, false),
+            },
+        };
+        Hygiene { def_crate, local_inner }
     }
 
     pub fn new_unhygienic() -> Hygiene {
-        Hygiene { frames: None }
+        Hygiene { def_crate: None, local_inner: false }
     }
 
     // FIXME: this should just return name
     pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
-        if let Some(frames) = &self.frames {
+        if let Some(def_crate) = self.def_crate {
             if name_ref.text() == "$crate" {
-                if let Some(krate) = frames.root_crate(name_ref.syntax()) {
-                    return Either::Right(krate);
-                }
+                return Either::Right(def_crate);
             }
         }
-
         Either::Left(name_ref.as_name())
     }
 
-    pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> {
-        let mut token = path.syntax().first_token()?.text_range();
-        let frames = self.frames.as_ref()?;
-        let mut current = frames.0.clone();
-
-        loop {
-            let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(token)?;
-            if origin == Origin::Def {
-                return if current.local_inner { frames.root_crate(path.syntax()) } else { None };
-            }
-            current = current.call_site.as_ref()?.clone();
-            token = mapped.value;
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-struct HygieneFrames(Arc<HygieneFrame>);
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct HygieneFrame {
-    expansion: Option<HygieneInfo>,
-
-    // Indicate this is a local inner macro
-    local_inner: bool,
-    krate: Option<CrateId>,
-
-    call_site: Option<Arc<HygieneFrame>>,
-    def_site: Option<Arc<HygieneFrame>>,
-}
-
-impl HygieneFrames {
-    fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
-        HygieneFrames(Arc::new(HygieneFrame::new(db, file_id)))
-    }
-
-    fn root_crate(&self, node: &SyntaxNode) -> Option<CrateId> {
-        let mut token = node.first_token()?.text_range();
-        let mut result = self.0.krate;
-        let mut current = self.0.clone();
-
-        while let Some((mapped, origin)) =
-            current.expansion.as_ref().and_then(|it| it.map_ident_up(token))
-        {
-            result = current.krate;
-
-            let site = match origin {
-                Origin::Def => &current.def_site,
-                Origin::Call => &current.call_site,
-            };
-
-            let site = match site {
-                None => break,
-                Some(it) => it,
-            };
-
-            current = site.clone();
-            token = mapped.value;
+    pub fn local_inner_macros(&self) -> Option<CrateId> {
+        if self.local_inner {
+            self.def_crate
+        } else {
+            None
         }
-
-        result
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-struct HygieneInfo {
-    arg_start: InFile<TextSize>,
-    /// The `macro_rules!` arguments.
-    def_start: Option<InFile<TextSize>>,
-
-    macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
-    macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
-    exp_map: Arc<mbe::TokenMap>,
-}
-
-impl HygieneInfo {
-    fn map_ident_up(&self, token: TextRange) -> Option<(InFile<TextRange>, Origin)> {
-        let token_id = self.exp_map.token_by_range(token)?;
-
-        let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
-        let (token_map, tt) = match origin {
-            mbe::Origin::Call => (&self.macro_arg.1, self.arg_start),
-            mbe::Origin::Def => (
-                &self.macro_def.1,
-                self.def_start
-                    .as_ref()
-                    .expect("`Origin::Def` used with non-`macro_rules!` macro")
-                    .clone(),
-            ),
-        };
-
-        let range = token_map.range_by_token(token_id)?.by_kind(SyntaxKind::IDENT)?;
-        Some((tt.with_value(range + tt.value), origin))
-    }
-}
-
-fn make_hygiene_info(
-    db: &dyn AstDatabase,
-    macro_file: MacroFile,
-    loc: &MacroCallLoc,
-) -> Option<HygieneInfo> {
-    let arg_tt = loc.kind.arg(db)?;
-
-    let def_offset = loc.def.ast_id.and_then(|id| {
-        let def_tt = match id.to_node(db) {
-            ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(),
-            ast::Macro::MacroDef(_) => return None,
-        };
-        Some(InFile::new(id.file_id, def_tt))
-    });
-
-    let macro_def = db.macro_def(loc.def)?;
-    let (_, exp_map) = db.parse_macro_expansion(macro_file).value?;
-    let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
-
-    Some(HygieneInfo {
-        arg_start: InFile::new(loc.kind.file_id(), arg_tt.text_range().start()),
-        def_start: def_offset,
-        macro_arg,
-        macro_def,
-        exp_map,
-    })
-}
-
-impl HygieneFrame {
-    pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame {
-        let (info, krate, local_inner) = match file_id.0 {
-            HirFileIdRepr::FileId(_) => (None, None, false),
-            HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
-                MacroCallId::EagerMacro(_id) => (None, None, false),
-                MacroCallId::LazyMacro(id) => {
-                    let loc = db.lookup_intern_macro(id);
-                    let info = make_hygiene_info(db, macro_file, &loc);
-                    match loc.def.kind {
-                        MacroDefKind::Declarative => {
-                            (info, Some(loc.def.krate), loc.def.local_inner)
-                        }
-                        MacroDefKind::BuiltIn(_) => (info, Some(loc.def.krate), false),
-                        MacroDefKind::BuiltInDerive(_) => (info, None, false),
-                        MacroDefKind::BuiltInEager(_) => (info, None, false),
-                        MacroDefKind::ProcMacro(_) => (info, None, false),
-                    }
-                }
-            },
-        };
-
-        let info = match info {
-            None => {
-                return HygieneFrame {
-                    expansion: None,
-                    local_inner,
-                    krate,
-                    call_site: None,
-                    def_site: None,
-                };
-            }
-            Some(it) => it,
-        };
-
-        let def_site = info.def_start.map(|it| db.hygiene_frame(it.file_id));
-        let call_site = Some(db.hygiene_frame(info.arg_start.file_id));
-
-        HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site }
     }
 }
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index 95d853b6da..2f44876a8a 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -164,7 +164,6 @@ pub mod known {
         future,
         result,
         boxed,
-        option,
         // Components of known path (type name)
         Iterator,
         IntoIterator,
@@ -173,7 +172,6 @@ pub mod known {
         Ok,
         Future,
         Result,
-        Option,
         Output,
         Target,
         Box,
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index c67a289f28..14e18f5a11 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -186,10 +186,9 @@ impl Diagnostic for MissingMatchArms {
     }
 }
 
-// Diagnostic: missing-ok-or-some-in-tail-expr
+// Diagnostic: missing-ok-in-tail-expr
 //
-// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
-// or if a block that should return `Option` returns a value not wrapped in `Some`.
+// This diagnostic is triggered if block that should return `Result` returns a value not wrapped in `Ok`.
 //
 // Example:
 //
@@ -199,19 +198,17 @@ impl Diagnostic for MissingMatchArms {
 // }
 // ```
 #[derive(Debug)]
-pub struct MissingOkOrSomeInTailExpr {
+pub struct MissingOkInTailExpr {
     pub file: HirFileId,
     pub expr: AstPtr<ast::Expr>,
-    // `Some` or `Ok` depending on whether the return type is Result or Option
-    pub required: String,
 }
 
-impl Diagnostic for MissingOkOrSomeInTailExpr {
+impl Diagnostic for MissingOkInTailExpr {
     fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("missing-ok-or-some-in-tail-expr")
+        DiagnosticCode("missing-ok-in-tail-expr")
     }
     fn message(&self) -> String {
-        format!("wrap return expression in {}", self.required)
+        "wrap return expression in Ok".to_string()
     }
     fn display_source(&self) -> InFile<SyntaxNodePtr> {
         InFile { file_id: self.file, value: self.expr.clone().into() }
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index a1c484fdff..b4e453411a 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -11,8 +11,8 @@ use crate::{
     db::HirDatabase,
     diagnostics::{
         match_check::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
-        MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
-        MissingPatFields, RemoveThisSemicolon,
+        MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields,
+        RemoveThisSemicolon,
     },
     utils::variant_data,
     ApplicationTy, InferenceResult, Ty, TypeCtor,
@@ -306,40 +306,27 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
         };
 
         let core_result_path = path![core::result::Result];
-        let core_option_path = path![core::option::Option];
 
         let resolver = self.owner.resolver(db.upcast());
         let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
             Some(it) => it,
             _ => return,
         };
-        let core_option_enum = match resolver.resolve_known_enum(db.upcast(), &core_option_path) {
-            Some(it) => it,
-            _ => return,
-        };
 
         let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
-        let core_option_ctor = TypeCtor::Adt(AdtId::EnumId(core_option_enum));
-
-        let (params, required) = match &mismatch.expected {
+        let params = match &mismatch.expected {
             Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
-                (parameters, "Ok".to_string())
-            }
-            Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_option_ctor => {
-                (parameters, "Some".to_string())
+                parameters
             }
             _ => return,
         };
 
-        if params.len() > 0 && params[0] == mismatch.actual {
+        if params.len() == 2 && params[0] == mismatch.actual {
             let (_, source_map) = db.body_with_source_map(self.owner.into());
 
             if let Ok(source_ptr) = source_map.expr_syntax(id) {
-                self.sink.push(MissingOkOrSomeInTailExpr {
-                    file: source_ptr.file_id,
-                    expr: source_ptr.value,
-                    required,
-                });
+                self.sink
+                    .push(MissingOkInTailExpr { file: source_ptr.file_id, expr: source_ptr.value });
             }
         }
     }
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 055c0a79cf..6931a6190d 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -125,7 +125,7 @@ pub(crate) fn diagnostics(
         .on::<hir::diagnostics::MissingFields, _>(|d| {
             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
         })
-        .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
+        .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
             res.borrow_mut().push(diagnostic_with_fix(d, &sema));
         })
         .on::<hir::diagnostics::NoSuchField, _>(|d| {
@@ -304,40 +304,6 @@ mod tests {
         expect.assert_debug_eq(&diagnostics)
     }
 
-    #[test]
-    fn test_wrap_return_type_option() {
-        check_fix(
-            r#"
-//- /main.rs crate:main deps:core
-use core::option::Option::{self, Some, None};
-
-fn div(x: i32, y: i32) -> Option<i32> {
-    if y == 0 {
-        return None;
-    }
-    x / y$0
-}
-//- /core/lib.rs crate:core
-pub mod result {
-    pub enum Result<T, E> { Ok(T), Err(E) }
-}
-pub mod option {
-    pub enum Option<T> { Some(T), None }
-}
-"#,
-            r#"
-use core::option::Option::{self, Some, None};
-
-fn div(x: i32, y: i32) -> Option<i32> {
-    if y == 0 {
-        return None;
-    }
-    Some(x / y)
-}
-"#,
-        );
-    }
-
     #[test]
     fn test_wrap_return_type() {
         check_fix(
@@ -355,9 +321,6 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
-pub mod option {
-    pub enum Option<T> { Some(T), None }
-}
 "#,
             r#"
 use core::result::Result::{self, Ok, Err};
@@ -389,9 +352,6 @@ fn div<T>(x: T) -> Result<T, i32> {
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
-pub mod option {
-    pub enum Option<T> { Some(T), None }
-}
 "#,
             r#"
 use core::result::Result::{self, Ok, Err};
@@ -425,9 +385,6 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
-pub mod option {
-    pub enum Option<T> { Some(T), None }
-}
 "#,
             r#"
 use core::result::Result::{self, Ok, Err};
@@ -457,15 +414,12 @@ fn foo() -> Result<(), i32> { 0 }
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
-pub mod option {
-    pub enum Option<T> { Some(T), None }
-}
 "#,
         );
     }
 
     #[test]
-    fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
+    fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
         check_no_diagnostics(
             r#"
 //- /main.rs crate:main deps:core
@@ -479,9 +433,6 @@ fn foo() -> SomeOtherEnum { 0 }
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
-pub mod option {
-    pub enum Option<T> { Some(T), None }
-}
 "#,
         );
     }
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index d7ad88ed52..71ec4df92b 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -3,7 +3,7 @@
 use hir::{
     db::AstDatabase,
     diagnostics::{
-        Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField,
+        Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField,
         RemoveThisSemicolon, UnresolvedModule,
     },
     HasSource, HirDisplay, InFile, Semantics, VariantDef,
@@ -94,17 +94,15 @@ impl DiagnosticWithFix for MissingFields {
     }
 }
 
-impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
+impl DiagnosticWithFix for MissingOkInTailExpr {
     fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
         let root = sema.db.parse_or_expand(self.file)?;
         let tail_expr = self.expr.to_node(&root);
         let tail_expr_range = tail_expr.syntax().text_range();
-        let replacement = format!("{}({})", self.required, tail_expr.syntax());
-        let edit = TextEdit::replace(tail_expr_range, replacement);
+        let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
         let source_change =
             SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
-        let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
-        Some(Fix::new(name, source_change, tail_expr_range))
+        Some(Fix::new("Wrap with ok", source_change, tail_expr_range))
     }
 }
 
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index ba00852445..2eb63a0b7d 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -1,3 +1,6 @@
+mod highlights;
+mod injector;
+
 mod format;
 mod html;
 mod injection;
@@ -69,9 +72,7 @@ pub(crate) fn highlight(
     };
 
     let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
-    // We use a stack for the DFS traversal below.
-    // When we leave a node, the we use it to flatten the highlighted ranges.
-    let mut stack = HighlightedRangeStack::new();
+    let mut stack = highlights::Highlights::new(range_to_highlight);
 
     let mut current_macro_call: Option<ast::MacroCall> = None;
     let mut current_macro_rules: Option<ast::MacroRules> = None;
@@ -82,14 +83,8 @@ pub(crate) fn highlight(
     // Walk all nodes, keeping track of whether we are inside a macro or not.
     // If in macro, expand it first and highlight the expanded code.
     for event in root.preorder_with_tokens() {
-        match &event {
-            WalkEvent::Enter(_) => stack.push(),
-            WalkEvent::Leave(_) => stack.pop(),
-        };
-
         let event_range = match &event {
-            WalkEvent::Enter(it) => it.text_range(),
-            WalkEvent::Leave(it) => it.text_range(),
+            WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
         };
 
         // Element outside of the viewport, no need to highlight
@@ -138,15 +133,8 @@ pub(crate) fn highlight(
                 if ast::Attr::can_cast(node.kind()) {
                     inside_attribute = false
                 }
-                if let Some((doctest, range_mapping, new_comments)) =
-                    injection::extract_doc_comments(node)
-                {
-                    injection::highlight_doc_comment(
-                        doctest,
-                        range_mapping,
-                        new_comments,
-                        &mut stack,
-                    );
+                if let Some((new_comments, inj)) = injection::extract_doc_comments(node) {
+                    injection::highlight_doc_comment(new_comments, inj, &mut stack);
                 }
             }
             WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
@@ -217,7 +205,6 @@ pub(crate) fn highlight(
                 format_string_highlighter.highlight_format_string(&mut stack, &string, range);
                 // Highlight escape sequences
                 if let Some(char_ranges) = string.char_ranges() {
-                    stack.push();
                     for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
                         if string.text()[piece_range.start().into()..].starts_with('\\') {
                             stack.add(HighlightedRange {
@@ -227,177 +214,12 @@ pub(crate) fn highlight(
                             });
                         }
                     }
-                    stack.pop_and_inject(None);
-                }
-            }
-        }
-    }
-
-    stack.flattened()
-}
-
-#[derive(Debug)]
-struct HighlightedRangeStack {
-    stack: Vec<Vec<HighlightedRange>>,
-}
-
-/// We use a stack to implement the flattening logic for the highlighted
-/// syntax ranges.
-impl HighlightedRangeStack {
-    fn new() -> Self {
-        Self { stack: vec![Vec::new()] }
-    }
-
-    fn push(&mut self) {
-        self.stack.push(Vec::new());
-    }
-
-    /// Flattens the highlighted ranges.
-    ///
-    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
-    /// 1) parent-range: Attribute [0, 23)
-    /// 2) child-range: String [16, 21)
-    ///
-    /// The following code implements the flattening, for our example this results to:
-    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
-    fn pop(&mut self) {
-        let children = self.stack.pop().unwrap();
-        let prev = self.stack.last_mut().unwrap();
-        let needs_flattening = !children.is_empty()
-            && !prev.is_empty()
-            && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
-        if !needs_flattening {
-            prev.extend(children);
-        } else {
-            let mut parent = prev.pop().unwrap();
-            for ele in children {
-                assert!(parent.range.contains_range(ele.range));
-
-                let cloned = Self::intersect(&mut parent, &ele);
-                if !parent.range.is_empty() {
-                    prev.push(parent);
-                }
-                prev.push(ele);
-                parent = cloned;
-            }
-            if !parent.range.is_empty() {
-                prev.push(parent);
-            }
-        }
-    }
-
-    /// Intersects the `HighlightedRange` `parent` with `child`.
-    /// `parent` is mutated in place, becoming the range before `child`.
-    /// Returns the range (of the same type as `parent`) *after* `child`.
-    fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
-        assert!(parent.range.contains_range(child.range));
-
-        let mut cloned = parent.clone();
-        parent.range = TextRange::new(parent.range.start(), child.range.start());
-        cloned.range = TextRange::new(child.range.end(), cloned.range.end());
-
-        cloned
-    }
-
-    /// Remove the `HighlightRange` of `parent` that's currently covered by `child`.
-    fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) {
-        assert!(
-            parent.range.start() <= child.range.start()
-                && parent.range.end() >= child.range.start()
-                && child.range.end() > parent.range.end()
-        );
-
-        parent.range = TextRange::new(parent.range.start(), child.range.start());
-    }
-
-    /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
-    /// can only modify the last range currently on the stack.
-    /// Can be used to do injections that span multiple ranges, like the
-    /// doctest injection below.
-    /// If `overwrite_parent` is non-optional, the highlighting of the parent range
-    /// is overwritten with the argument.
-    ///
-    /// Note that `pop` can be simulated by `pop_and_inject(false)` but the
-    /// latter is computationally more expensive.
-    fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
-        let mut children = self.stack.pop().unwrap();
-        let prev = self.stack.last_mut().unwrap();
-        children.sort_by_key(|range| range.range.start());
-        prev.sort_by_key(|range| range.range.start());
-
-        for child in children {
-            if let Some(idx) =
-                prev.iter().position(|parent| parent.range.contains_range(child.range))
-            {
-                if let Some(tag) = overwrite_parent {
-                    prev[idx].highlight = tag;
-                }
-
-                let cloned = Self::intersect(&mut prev[idx], &child);
-                let insert_idx = if prev[idx].range.is_empty() {
-                    prev.remove(idx);
-                    idx
-                } else {
-                    idx + 1
-                };
-                prev.insert(insert_idx, child);
-                if !cloned.range.is_empty() {
-                    prev.insert(insert_idx + 1, cloned);
-                }
-            } else {
-                let maybe_idx =
-                    prev.iter().position(|parent| parent.range.contains(child.range.start()));
-                match (overwrite_parent, maybe_idx) {
-                    (Some(_), Some(idx)) => {
-                        Self::intersect_partial(&mut prev[idx], &child);
-                        let insert_idx = if prev[idx].range.is_empty() {
-                            prev.remove(idx);
-                            idx
-                        } else {
-                            idx + 1
-                        };
-                        prev.insert(insert_idx, child);
-                    }
-                    (_, None) => {
-                        let idx = prev
-                            .binary_search_by_key(&child.range.start(), |range| range.range.start())
-                            .unwrap_or_else(|x| x);
-                        prev.insert(idx, child);
-                    }
-                    _ => {
-                        unreachable!("child range should be completely contained in parent range");
-                    }
                 }
             }
         }
     }
 
-    fn add(&mut self, range: HighlightedRange) {
-        self.stack
-            .last_mut()
-            .expect("during DFS traversal, the stack must not be empty")
-            .push(range)
-    }
-
-    fn flattened(mut self) -> Vec<HighlightedRange> {
-        assert_eq!(
-            self.stack.len(),
-            1,
-            "after DFS traversal, the stack should only contain a single element"
-        );
-        let mut res = self.stack.pop().unwrap();
-        res.sort_by_key(|range| range.range.start());
-        // Check that ranges are sorted and disjoint
-        for (left, right) in res.iter().zip(res.iter().skip(1)) {
-            assert!(
-                left.range.end() <= right.range.start(),
-                "left: {:#?}, right: {:#?}",
-                left,
-                right
-            );
-        }
-        res
-    }
+    stack.to_vec()
 }
 
 fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index 26416022b8..ab66b406c8 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -4,9 +4,9 @@ use syntax::{
     AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
 };
 
-use crate::{
-    syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind,
-};
+use crate::{HighlightTag, HighlightedRange, SymbolKind};
+
+use super::highlights::Highlights;
 
 #[derive(Default)]
 pub(super) struct FormatStringHighlighter {
@@ -39,22 +39,20 @@ impl FormatStringHighlighter {
     }
     pub(super) fn highlight_format_string(
         &self,
-        range_stack: &mut HighlightedRangeStack,
+        stack: &mut Highlights,
         string: &impl HasFormatSpecifier,
         range: TextRange,
     ) {
         if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) {
-            range_stack.push();
             string.lex_format_specifier(|piece_range, kind| {
                 if let Some(highlight) = highlight_format_specifier(kind) {
-                    range_stack.add(HighlightedRange {
+                    stack.add(HighlightedRange {
                         range: piece_range + range.start(),
                         highlight: highlight.into(),
                         binding_hash: None,
                     });
                 }
             });
-            range_stack.pop();
         }
     }
 }
diff --git a/crates/ide/src/syntax_highlighting/highlights.rs b/crates/ide/src/syntax_highlighting/highlights.rs
new file mode 100644
index 0000000000..3e733c87c6
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/highlights.rs
@@ -0,0 +1,109 @@
+//! Collects a tree of highlighted ranges and flattens it.
+use std::{cmp::Ordering, iter};
+
+use stdx::equal_range_by;
+use syntax::TextRange;
+
+use crate::{HighlightTag, HighlightedRange};
+
+pub(super) struct Highlights {
+    root: Node,
+}
+
+struct Node {
+    highlighted_range: HighlightedRange,
+    nested: Vec<Node>,
+}
+
+impl Highlights {
+    pub(super) fn new(range: TextRange) -> Highlights {
+        Highlights {
+            root: Node::new(HighlightedRange {
+                range,
+                highlight: HighlightTag::Dummy.into(),
+                binding_hash: None,
+            }),
+        }
+    }
+
+    pub(super) fn add(&mut self, highlighted_range: HighlightedRange) {
+        self.root.add(highlighted_range);
+    }
+
+    pub(super) fn to_vec(self) -> Vec<HighlightedRange> {
+        let mut res = Vec::new();
+        self.root.flatten(&mut res);
+        res
+    }
+}
+
+impl Node {
+    fn new(highlighted_range: HighlightedRange) -> Node {
+        Node { highlighted_range, nested: Vec::new() }
+    }
+
+    fn add(&mut self, highlighted_range: HighlightedRange) {
+        assert!(self.highlighted_range.range.contains_range(highlighted_range.range));
+
+        // Fast path
+        if let Some(last) = self.nested.last_mut() {
+            if last.highlighted_range.range.contains_range(highlighted_range.range) {
+                return last.add(highlighted_range);
+            }
+            if last.highlighted_range.range.end() <= highlighted_range.range.start() {
+                return self.nested.push(Node::new(highlighted_range));
+            }
+        }
+
+        let (start, len) = equal_range_by(&self.nested, |n| {
+            ordering(n.highlighted_range.range, highlighted_range.range)
+        });
+
+        if len == 1
+            && self.nested[start].highlighted_range.range.contains_range(highlighted_range.range)
+        {
+            return self.nested[start].add(highlighted_range);
+        }
+
+        let nested = self
+            .nested
+            .splice(start..start + len, iter::once(Node::new(highlighted_range)))
+            .collect::<Vec<_>>();
+        self.nested[start].nested = nested;
+    }
+
+    fn flatten(&self, acc: &mut Vec<HighlightedRange>) {
+        let mut start = self.highlighted_range.range.start();
+        let mut nested = self.nested.iter();
+        loop {
+            let next = nested.next();
+            let end = next.map_or(self.highlighted_range.range.end(), |it| {
+                it.highlighted_range.range.start()
+            });
+            if start < end {
+                acc.push(HighlightedRange {
+                    range: TextRange::new(start, end),
+                    highlight: self.highlighted_range.highlight,
+                    binding_hash: self.highlighted_range.binding_hash,
+                });
+            }
+            start = match next {
+                Some(child) => {
+                    child.flatten(acc);
+                    child.highlighted_range.range.end()
+                }
+                None => break,
+            }
+        }
+    }
+}
+
+pub(super) fn ordering(r1: TextRange, r2: TextRange) -> Ordering {
+    if r1.end() <= r2.start() {
+        Ordering::Less
+    } else if r2.end() <= r1.start() {
+        Ordering::Greater
+    } else {
+        Ordering::Equal
+    }
+}
diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs
index 99ba3a59d3..44f611b25d 100644
--- a/crates/ide/src/syntax_highlighting/html.rs
+++ b/crates/ide/src/syntax_highlighting/html.rs
@@ -3,7 +3,7 @@
 use ide_db::base_db::SourceDatabase;
 use oorandom::Rand32;
 use stdx::format_to;
-use syntax::{AstNode, TextRange, TextSize};
+use syntax::AstNode;
 
 use crate::{syntax_highlighting::highlight, FileId, RootDatabase};
 
@@ -22,17 +22,15 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
 
     let ranges = highlight(db, file_id, None, false);
     let text = parse.tree().syntax().to_string();
-    let mut prev_pos = TextSize::from(0);
     let mut buf = String::new();
     buf.push_str(&STYLE);
     buf.push_str("<pre><code>");
     for range in &ranges {
-        if range.range.start() > prev_pos {
-            let curr = &text[TextRange::new(prev_pos, range.range.start())];
-            let text = html_escape(curr);
-            buf.push_str(&text);
+        let curr = &text[range.range];
+        if range.highlight.is_empty() {
+            format_to!(buf, "{}", html_escape(curr));
+            continue;
         }
-        let curr = &text[TextRange::new(range.range.start(), range.range.end())];
 
         let class = range.highlight.to_string().replace('.', " ");
         let color = match (rainbow, range.binding_hash) {
@@ -42,13 +40,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
             _ => "".into(),
         };
         format_to!(buf, "<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr));
-
-        prev_pos = range.range.end();
     }
-    // Add the remaining (non-highlighted) text
-    let curr = &text[TextRange::new(prev_pos, TextSize::of(&text))];
-    let text = html_escape(curr);
-    buf.push_str(&text);
     buf.push_str("</code></pre>");
     buf
 }
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs
index d6be9708df..98ee03e0d9 100644
--- a/crates/ide/src/syntax_highlighting/injection.rs
+++ b/crates/ide/src/syntax_highlighting/injection.rs
@@ -1,18 +1,18 @@
 //! Syntax highlighting injections such as highlighting of documentation tests.
 
-use std::{collections::BTreeMap, convert::TryFrom};
+use std::convert::TryFrom;
 
 use hir::Semantics;
 use ide_db::call_info::ActiveParameter;
 use itertools::Itertools;
 use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
 
-use crate::{Analysis, Highlight, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase};
+use crate::{Analysis, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase};
 
-use super::HighlightedRangeStack;
+use super::{highlights::Highlights, injector::Injector};
 
 pub(super) fn highlight_injection(
-    acc: &mut HighlightedRangeStack,
+    acc: &mut Highlights,
     sema: &Semantics<RootDatabase>,
     literal: ast::String,
     expanded: SyntaxToken,
@@ -98,9 +98,6 @@ impl MarkerInfo {
     }
 }
 
-/// Mapping from extracted documentation code to original code
-type RangesMap = BTreeMap<TextSize, TextSize>;
-
 const RUSTDOC_FENCE: &'static str = "```";
 const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
     "",
@@ -119,20 +116,20 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
 /// Lastly, a vector of new comment highlight ranges (spanning only the
 /// comment prefix) is returned which is used in the syntax highlighting
 /// injection to replace the previous (line-spanning) comment ranges.
-pub(super) fn extract_doc_comments(
-    node: &SyntaxNode,
-) -> Option<(String, RangesMap, Vec<HighlightedRange>)> {
+pub(super) fn extract_doc_comments(node: &SyntaxNode) -> Option<(Vec<HighlightedRange>, Injector)> {
+    let mut inj = Injector::default();
     // wrap the doctest into function body to get correct syntax highlighting
     let prefix = "fn doctest() {\n";
     let suffix = "}\n";
-    // Mapping from extracted documentation code to original code
-    let mut range_mapping: RangesMap = BTreeMap::new();
-    let mut line_start = TextSize::try_from(prefix.len()).unwrap();
+
+    let mut line_start = TextSize::of(prefix);
     let mut is_codeblock = false;
     let mut is_doctest = false;
     // Replace the original, line-spanning comment ranges by new, only comment-prefix
     // spanning comment ranges.
     let mut new_comments = Vec::new();
+
+    inj.add_unmapped(prefix);
     let doctest = node
         .children_with_tokens()
         .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
@@ -169,7 +166,6 @@ pub(super) fn extract_doc_comments(
                 pos
             };
 
-            range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap());
             new_comments.push(HighlightedRange {
                 range: TextRange::new(
                     range.start(),
@@ -179,62 +175,43 @@ pub(super) fn extract_doc_comments(
                 binding_hash: None,
             });
             line_start += range.len() - TextSize::try_from(pos).unwrap();
-            line_start += TextSize::try_from('\n'.len_utf8()).unwrap();
+            line_start += TextSize::of("\n");
 
+            inj.add(
+                &line[pos..],
+                TextRange::new(range.start() + TextSize::try_from(pos).unwrap(), range.end()),
+            );
+            inj.add_unmapped("\n");
             line[pos..].to_owned()
         })
         .join("\n");
+    inj.add_unmapped(suffix);
 
     if doctest.is_empty() {
         return None;
     }
 
-    let doctest = format!("{}{}{}", prefix, doctest, suffix);
-    Some((doctest, range_mapping, new_comments))
+    Some((new_comments, inj))
 }
 
 /// Injection of syntax highlighting of doctests.
 pub(super) fn highlight_doc_comment(
-    text: String,
-    range_mapping: RangesMap,
     new_comments: Vec<HighlightedRange>,
-    stack: &mut HighlightedRangeStack,
+    inj: Injector,
+    stack: &mut Highlights,
 ) {
-    let (analysis, tmp_file_id) = Analysis::from_single_file(text);
-
-    stack.push();
-    for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
-        // Determine start offset and end offset in case of multi-line ranges
-        let mut start_offset = None;
-        let mut end_offset = None;
-        for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
-            // It's possible for orig_line_start - line_start to be negative. Add h.range.start()
-            // here and remove it from the end range after the loop below so that the values are
-            // always non-negative.
-            let offset = h.range.start() + orig_line_start - line_start;
-            if line_start <= &h.range.start() {
-                start_offset.get_or_insert(offset);
-                break;
-            } else {
-                end_offset.get_or_insert(offset);
-            }
-        }
-        if let Some(start_offset) = start_offset {
-            h.range = TextRange::new(
-                start_offset,
-                h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(),
-            );
-
-            h.highlight |= HighlightModifier::Injected;
-            stack.add(h);
-        }
-    }
-
-    // Inject the comment prefix highlight ranges
-    stack.push();
+    let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string());
     for comment in new_comments {
         stack.add(comment);
     }
-    stack.pop_and_inject(None);
-    stack.pop_and_inject(Some(Highlight::from(HighlightTag::Dummy) | HighlightModifier::Injected));
+
+    for h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
+        for r in inj.map_range_up(h.range) {
+            stack.add(HighlightedRange {
+                range: r,
+                highlight: h.highlight | HighlightModifier::Injected,
+                binding_hash: h.binding_hash,
+            });
+        }
+    }
 }
diff --git a/crates/ide/src/syntax_highlighting/injector.rs b/crates/ide/src/syntax_highlighting/injector.rs
new file mode 100644
index 0000000000..0513a9fd63
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/injector.rs
@@ -0,0 +1,83 @@
+//! Extracts a subsequence of a text document, remembering the mapping of ranges
+//! between original and extracted texts.
+use std::ops::{self, Sub};
+
+use stdx::equal_range_by;
+use syntax::{TextRange, TextSize};
+
+use super::highlights::ordering;
+
+#[derive(Default)]
+pub(super) struct Injector {
+    buf: String,
+    ranges: Vec<(TextRange, Option<Delta<TextSize>>)>,
+}
+
+impl Injector {
+    pub(super) fn add(&mut self, text: &str, source_range: TextRange) {
+        let len = TextSize::of(text);
+        assert_eq!(len, source_range.len());
+
+        let target_range = TextRange::at(TextSize::of(&self.buf), len);
+        self.ranges
+            .push((target_range, Some(Delta::new(target_range.start(), source_range.start()))));
+        self.buf.push_str(text);
+    }
+    pub(super) fn add_unmapped(&mut self, text: &str) {
+        let len = TextSize::of(text);
+
+        let target_range = TextRange::at(TextSize::of(&self.buf), len);
+        self.ranges.push((target_range, None));
+        self.buf.push_str(text);
+    }
+
+    pub(super) fn text(&self) -> &str {
+        &self.buf
+    }
+    pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
+        let (start, len) = equal_range_by(&self.ranges, |&(r, _)| ordering(r, range));
+        (start..start + len).filter_map(move |i| {
+            let (target_range, delta) = self.ranges[i];
+            let intersection = target_range.intersect(range).unwrap();
+            Some(intersection + delta?)
+        })
+    }
+}
+
+#[derive(Clone, Copy)]
+enum Delta<T> {
+    Add(T),
+    Sub(T),
+}
+
+impl<T> Delta<T> {
+    fn new(from: T, to: T) -> Delta<T>
+    where
+        T: Ord + Sub<Output = T>,
+    {
+        if to >= from {
+            Delta::Add(to - from)
+        } else {
+            Delta::Sub(from - to)
+        }
+    }
+}
+
+impl ops::Add<Delta<TextSize>> for TextSize {
+    type Output = TextSize;
+
+    fn add(self, rhs: Delta<TextSize>) -> TextSize {
+        match rhs {
+            Delta::Add(it) => self + it,
+            Delta::Sub(it) => self - it,
+        }
+    }
+}
+
+impl ops::Add<Delta<TextSize>> for TextRange {
+    type Output = TextRange;
+
+    fn add(self, rhs: Delta<TextSize>) -> TextRange {
+        TextRange::at(self.start() + rhs, self.len())
+    }
+}
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 8b88670798..a0286b72d9 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -94,13 +94,13 @@ impl HighlightTag {
             HighlightTag::Comment => "comment",
             HighlightTag::EscapeSequence => "escape_sequence",
             HighlightTag::FormatSpecifier => "format_specifier",
-            HighlightTag::Dummy => "dummy",
             HighlightTag::Keyword => "keyword",
             HighlightTag::Punctuation => "punctuation",
             HighlightTag::NumericLiteral => "numeric_literal",
             HighlightTag::Operator => "operator",
             HighlightTag::StringLiteral => "string_literal",
             HighlightTag::UnresolvedReference => "unresolved_reference",
+            HighlightTag::Dummy => "dummy",
         }
     }
 }
@@ -173,6 +173,9 @@ impl Highlight {
     pub(crate) fn new(tag: HighlightTag) -> Highlight {
         Highlight { tag, modifiers: HighlightModifiers::default() }
     }
+    pub fn is_empty(&self) -> bool {
+        self.tag == HighlightTag::Dummy && self.modifiers == HighlightModifiers::default()
+    }
 }
 
 impl ops::BitOr<HighlightModifier> for HighlightTag {
diff --git a/crates/ide_db/src/apply_change.rs b/crates/ide_db/src/apply_change.rs
index c770a236b0..71c19ed138 100644
--- a/crates/ide_db/src/apply_change.rs
+++ b/crates/ide_db/src/apply_change.rs
@@ -145,7 +145,6 @@ impl RootDatabase {
             hir::db::MacroDefQuery
             hir::db::ParseMacroExpansionQuery
             hir::db::MacroExpandQuery
-            hir::db::HygieneFrameQuery
 
             // DefDatabase
             hir::db::ItemTreeQuery
diff --git a/crates/mbe/src/mbe_expander/matcher.rs b/crates/mbe/src/mbe_expander/matcher.rs
index c6d615c81b..fdc8844cef 100644
--- a/crates/mbe/src/mbe_expander/matcher.rs
+++ b/crates/mbe/src/mbe_expander/matcher.rs
@@ -150,7 +150,7 @@ fn match_subtree(
                     res.add_err(err!("leftover tokens"));
                 }
             }
-            Op::Var { name, kind, .. } => {
+            Op::Var { name, kind } => {
                 let kind = match kind {
                     Some(k) => k,
                     None => {
diff --git a/crates/mbe/src/mbe_expander/transcriber.rs b/crates/mbe/src/mbe_expander/transcriber.rs
index 27b2ac777a..7205312371 100644
--- a/crates/mbe/src/mbe_expander/transcriber.rs
+++ b/crates/mbe/src/mbe_expander/transcriber.rs
@@ -100,8 +100,8 @@ fn expand_subtree(
                 err = err.or(e);
                 arena.push(tt.into());
             }
-            Op::Var { name, id, .. } => {
-                let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name, *id);
+            Op::Var { name, .. } => {
+                let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name);
                 err = err.or(e);
                 push_fragment(arena, fragment);
             }
@@ -118,11 +118,14 @@ fn expand_subtree(
     ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err }
 }
 
-fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> {
-    // We already handle $crate case in mbe parser
-    debug_assert!(v != "crate");
-
-    if !ctx.bindings.contains(v) {
+fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> {
+    if v == "crate" {
+        // 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();
+        ExpandResult::ok(Fragment::Tokens(tt))
+    } else if !ctx.bindings.contains(v) {
         // Note that it is possible to have a `$var` inside a macro which is not bound.
         // For example:
         // ```
@@ -139,8 +142,14 @@ fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult
         let tt = tt::Subtree {
             delimiter: None,
             token_trees: vec![
-                tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(),
-                tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(),
+                tt::Leaf::from(tt::Punct {
+                    char: '$',
+                    spacing: tt::Spacing::Alone,
+                    id: tt::TokenId::unspecified(),
+                })
+                .into(),
+                tt::Leaf::from(tt::Ident { text: v.clone(), id: tt::TokenId::unspecified() })
+                    .into(),
             ],
         }
         .into();
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index f3047972dd..2f3ebc8313 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -8,7 +8,7 @@ use crate::{tt_iter::TtIter, ExpandError, MetaTemplate};
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum Op {
-    Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
+    Var { name: SmolStr, kind: Option<SmolStr> },
     Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
     Leaf(tt::Leaf),
     Subtree(MetaTemplate),
@@ -106,25 +106,18 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
                         }
                         let name = UNDERSCORE.clone();
                         let kind = eat_fragment_kind(src, mode)?;
-                        let id = punct.id;
-                        Op::Var { name, kind, id }
-                    }
-                    tt::Leaf::Ident(ident) if ident.text == "crate" => {
-                        // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
-                        Op::Leaf(tt::Leaf::from(tt::Ident { text: "$crate".into(), id: ident.id }))
+                        Op::Var { name, kind }
                     }
                     tt::Leaf::Ident(ident) => {
                         let name = ident.text.clone();
                         let kind = eat_fragment_kind(src, mode)?;
-                        let id = ident.id;
-                        Op::Var { name, kind, id }
+                        Op::Var { name, kind }
                     }
                     tt::Leaf::Literal(lit) => {
                         if is_boolean_literal(&lit) {
                             let name = lit.text.clone();
                             let kind = eat_fragment_kind(src, mode)?;
-                            let id = lit.id;
-                            Op::Var { name, kind, id }
+                            Op::Var { name, kind }
                         } else {
                             bail!("bad var 2");
                         }
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index bc9999ddc5..204cae2654 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -343,6 +343,9 @@ pub(crate) fn semantic_tokens(
     let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
 
     for highlight_range in highlights {
+        if highlight_range.highlight.is_empty() {
+            continue;
+        }
         let (type_, mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
         let token_index = semantic_tokens::type_index(type_);
         let modifier_bitset = mods.0;
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 5332edb09c..5aacdb16ed 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -1,5 +1,5 @@
 //! Missing batteries for standard libraries.
-use std::{ops, process, time::Instant};
+use std::{cmp::Ordering, ops, process, time::Instant};
 
 mod macros;
 pub mod panic_context;
@@ -117,7 +117,12 @@ impl<'a> Iterator for LinesWithEnds<'a> {
     }
 }
 
-// https://github.com/rust-lang/rust/issues/73831
+/// Returns `idx` such that:
+///
+///     ∀ x in slice[..idx]:  pred(x)
+///  && ∀ x in slice[idx..]: !pred(x)
+///
+/// https://github.com/rust-lang/rust/issues/73831
 pub fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
 where
     P: FnMut(&T) -> bool,
@@ -147,6 +152,15 @@ where
     left
 }
 
+pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> (usize, usize)
+where
+    F: FnMut(&T) -> Ordering,
+{
+    let start = partition_point(slice, |it| key(it) == Ordering::Less);
+    let len = partition_point(&slice[start..], |it| key(it) == Ordering::Equal);
+    (start, len)
+}
+
 pub struct JodChild(pub process::Child);
 
 impl ops::Deref for JodChild {


Test patch:
diff --git a/crates/hir_ty/src/tests/macros.rs b/crates/hir_ty/src/tests/macros.rs
index c64f0b5b53..1953da7beb 100644
--- a/crates/hir_ty/src/tests/macros.rs
+++ b/crates/hir_ty/src/tests/macros.rs
@@ -370,37 +370,6 @@ expand!();
     );
 }
 
-#[test]
-fn infer_macro_with_dollar_crate_in_def_site() {
-    check_types(
-        r#"
-//- /main.rs crate:main deps:foo
-use foo::expand;
-
-macro_rules! list {
-    ($($tt:tt)*) => { $($tt)* }
-}
-
-fn test() {
-    let r = expand!();
-    r;
-  //^ u128
-}
-
-//- /lib.rs crate:foo
-#[macro_export]
-macro_rules! expand {
-    () => { list!($crate::m!()) };
-}
-
-#[macro_export]
-macro_rules! m {
-    () => { 0u128 };
-}
-"#,
-    );
-}
-
 #[test]
 fn infer_type_value_non_legacy_macro_use_as() {
     check_infer(
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 4dd7413ba0..9d42b11c1e 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -37,13 +37,18 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
 </style>
 <pre><code><span class="comment documentation">/// ```</span>
-<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span><span class="punctuation injected">
-</span><span class="comment documentation">/// ```</span>
+<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span>
+<span class="comment documentation">/// ```</span>
 <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span>
     <span class="field declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span><span class="punctuation">,</span>
 <span class="punctuation">}</span>
 
 <span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
+    <span class="comment documentation">/// ```</span>
+    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="punctuation injected">_</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Call me</span>
+    <span class="comment">//    KILLER WHALE</span>
+    <span class="comment documentation">/// </span><span class="string_literal injected">    Ishmael."</span><span class="punctuation injected">;</span>
+    <span class="comment documentation">/// ```</span>
     <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="punctuation">;</span>
 
     <span class="comment documentation">/// Constructs a new `Foo`.</span>
@@ -52,8 +57,8 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```</span>
     <span class="comment documentation">/// #</span><span class="dummy injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span>
-    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="keyword injected">mut</span><span class="dummy injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
-</span>    <span class="comment documentation">/// ```</span>
+    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="keyword injected">mut</span><span class="dummy injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
+    <span class="comment documentation">/// ```</span>
     <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
         <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span>
     <span class="punctuation">}</span>
@@ -72,18 +77,18 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">bar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="dummy injected"> </span><span class="operator injected">||</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span>
     <span class="comment documentation">///</span>
-    <span class="comment documentation">/// </span><span class="comment injected">/* multi-line
-    </span><span class="comment documentation">/// </span><span class="comment injected">       comment */</span>
+    <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span>
+    <span class="comment documentation">/// </span><span class="comment injected">       comment */</span>
     <span class="comment documentation">///</span>
-    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Foo
-    </span><span class="comment documentation">/// </span><span class="string_literal injected">  bar
-    </span><span class="comment documentation">/// </span><span class="string_literal injected">         "</span><span class="punctuation injected">;</span>
+    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="string_literal injected">"Foo</span>
+    <span class="comment documentation">/// </span><span class="string_literal injected">  bar</span>
+    <span class="comment documentation">/// </span><span class="string_literal injected">         "</span><span class="punctuation injected">;</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```rust,no_run</span>
-    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">foobar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
-</span>    <span class="comment documentation">/// ```</span>
+    <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="dummy injected"> </span><span class="variable declaration injected">foobar</span><span class="dummy injected"> </span><span class="operator injected">=</span><span class="dummy injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
+    <span class="comment documentation">/// ```</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```sh</span>
     <span class="comment documentation">/// echo 1</span>
@@ -94,8 +99,8 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="punctuation">}</span>
 
 <span class="comment documentation">/// ```</span>
-<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
-</span><span class="comment documentation">/// ```</span>
+<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span>
+<span class="comment documentation">/// ```</span>
 <span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span>
     <span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
         <span class="punctuation">$</span>expr
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 9e1a3974cb..a62704c39e 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -446,6 +446,11 @@ struct Foo {
 }
 
 impl Foo {
+    /// ```
+    /// let _ = "Call me
+    //    KILLER WHALE
+    ///     Ishmael.";
+    /// ```
     pub const bar: bool = true;
 
     /// Constructs a new `Foo`.
diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs
index d854985c5b..1d9afb4fb8 100644
--- a/crates/mbe/src/tests.rs
+++ b/crates/mbe/src/tests.rs
@@ -1079,19 +1079,6 @@ fn test_vertical_bar_with_pat() {
     .assert_expand_items(r#"foo! { | x | }"#, r#"0"#);
 }
 
-#[test]
-fn test_dollar_crate_lhs_is_not_meta() {
-    parse_macro(
-        r#"
-macro_rules! foo {
-    ($crate) => {};
-    () => {0};
-}
-    "#,
-    )
-    .assert_expand_items(r#"foo!{}"#, r#"0"#);
-}
-
 #[test]
 fn test_lifetime() {
     parse_macro(