Repository: rust-analyzer/rust-analyzer
Pull request #6488 (2020-11-06 22:51:39)

Textmate grammar: add `+=` to assignment operators
Fixes https://github.com/dustypomerleau/rust-syntax/issues/3.


Patch:
diff --git a/.github/ISSUE_TEMPLATE/blank-issue.md b/.github/ISSUE_TEMPLATE/blank-issue.md
deleted file mode 100644
index a08ad07cbf..0000000000
--- a/.github/ISSUE_TEMPLATE/blank-issue.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-name: Blank Issue
-about: Create a blank issue.
-title: ''
-labels: ''
-assignees: ''
-
----
-
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index dddc82f805..0000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-name: Bug report
-about: Create a bug report for rust-analyzer.
-title: ''
-labels: ''
-assignees: ''
-
----
-
-<!--
-Troubleshooting guide: https://rust-analyzer.github.io/manual.html#troubleshooting
-Forum for questions: https://users.rust-lang.org/c/ide/14
-
-Before submitting, please make sure that you're not running into one of these known issues:
-
- 1. local imports (`use` statements) don't work: #1165
- 2. local items don't work: #1559
- 3. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file)
- 4. some settings are required for procedural macro and build script support (`rust-analyzer.cargo.loadOutDirsFromCheck`, `rust-analyzer.procMacro.enable`): #6448
- 5. some platform-specific imports are not resolved: #6038
- 6. the official `rust-lang.rust` VS Code extension conflicts with `rust-analyzer`: #6463
-
-Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
--->
diff --git a/Cargo.lock b/Cargo.lock
index 1a4a63550d..f6b53d1882 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,9 +26,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.34"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
+checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
 
 [[package]]
 name = "anymap"
@@ -269,9 +269,9 @@ dependencies = [
 
 [[package]]
 name = "const_fn"
-version = "0.4.3"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
+checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2"
 
 [[package]]
 name = "crc32fast"
@@ -1793,9 +1793,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-subscriber"
-version = "0.2.15"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401"
+checksum = "2810660b9d5b18895d140caba6401765749a6a162e5d0736cfc44ea50db9d79d"
 dependencies = [
  "ansi_term",
  "chrono",
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index fcfe2d6ee4..d11fee196a 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -12,7 +12,7 @@ use ide_db::{
 };
 use syntax::{
     algo::{self, find_node_at_offset, SyntaxRewriter},
-    AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
+    AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
     TokenAtOffset,
 };
 use text_edit::{TextEdit, TextEditBuilder};
@@ -81,12 +81,9 @@ impl<'a> AssistContext<'a> {
     pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
         self.source_file.syntax().token_at_offset(self.offset())
     }
-    pub(crate) fn find_token_syntax_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
+    pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
         self.token_at_offset().find(|it| it.kind() == kind)
     }
-    pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
-        self.token_at_offset().find_map(T::cast)
-    }
     pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
         find_node_at_offset(self.source_file.syntax(), self.offset())
     }
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index 669dd9b217..8757fa33f2 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -1,16 +1,13 @@
-use ide_db::imports_locator;
 use itertools::Itertools;
 use syntax::{
-    ast::{self, make, AstNode},
+    ast::{self, AstNode},
     Direction, SmolStr,
     SyntaxKind::{IDENT, WHITESPACE},
     TextRange, TextSize,
 };
 
 use crate::{
-    assist_config::SnippetCap,
-    assist_context::{AssistBuilder, AssistContext, Assists},
-    utils::mod_path_to_ast,
+    assist_context::{AssistContext, Assists},
     AssistId, AssistKind,
 };
 
@@ -33,151 +30,78 @@ use crate::{
 // ```
 pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let attr = ctx.find_node_at_offset::<ast::Attr>()?;
+    let input = attr.token_tree()?;
 
     let attr_name = attr
         .syntax()
         .descendants_with_tokens()
         .filter(|t| t.kind() == IDENT)
-        .find_map(syntax::NodeOrToken::into_token)
-        .filter(|t| t.text() == "derive")?
+        .find_map(|i| i.into_token())
+        .filter(|t| *t.text() == "derive")?
         .text()
         .clone();
 
     let trait_token =
         ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
-    let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
 
     let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
     let annotated_name = annotated.syntax().text().to_string();
-    let insert_pos = annotated.syntax().parent()?.text_range().end();
-
-    let current_module = ctx.sema.scope(annotated.syntax()).module()?;
-    let current_crate = current_module.krate();
-
-    let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
-        .into_iter()
-        .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
-            either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
-            _ => None,
-        })
-        .flat_map(|trait_| {
-            current_module
-                .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
-                .as_ref()
-                .map(mod_path_to_ast)
-                .zip(Some(trait_))
-        });
+    let start_offset = annotated.syntax().parent()?.text_range().end();
 
-    let mut no_traits_found = true;
-    for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
-        add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
-    }
-    if no_traits_found {
-        add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
-    }
-    Some(())
-}
+    let label =
+        format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
 
-fn add_assist(
-    acc: &mut Assists,
-    snippet_cap: Option<SnippetCap>,
-    attr: &ast::Attr,
-    trait_path: &ast::Path,
-    annotated_name: &str,
-    insert_pos: TextSize,
-) -> Option<()> {
     let target = attr.syntax().text_range();
-    let input = attr.token_tree()?;
-    let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
-    let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
-
     acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
-        update_attribute(builder, &input, &trait_name, &attr);
-        match snippet_cap {
+        let new_attr_input = input
+            .syntax()
+            .descendants_with_tokens()
+            .filter(|t| t.kind() == IDENT)
+            .filter_map(|t| t.into_token().map(|t| t.text().clone()))
+            .filter(|t| t != trait_token.text())
+            .collect::<Vec<SmolStr>>();
+        let has_more_derives = !new_attr_input.is_empty();
+
+        if has_more_derives {
+            let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
+            builder.replace(input.syntax().text_range(), new_attr_input);
+        } else {
+            let attr_range = attr.syntax().text_range();
+            builder.delete(attr_range);
+
+            let line_break_range = attr
+                .syntax()
+                .next_sibling_or_token()
+                .filter(|t| t.kind() == WHITESPACE)
+                .map(|t| t.text_range())
+                .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
+            builder.delete(line_break_range);
+        }
+
+        match ctx.config.snippet_cap {
             Some(cap) => {
                 builder.insert_snippet(
                     cap,
-                    insert_pos,
-                    format!("\n\nimpl {} for {} {{\n    $0\n}}", trait_path, annotated_name),
+                    start_offset,
+                    format!("\n\nimpl {} for {} {{\n    $0\n}}", trait_token, annotated_name),
                 );
             }
             None => {
                 builder.insert(
-                    insert_pos,
-                    format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
+                    start_offset,
+                    format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
                 );
             }
         }
     })
 }
 
-fn update_attribute(
-    builder: &mut AssistBuilder,
-    input: &ast::TokenTree,
-    trait_name: &ast::NameRef,
-    attr: &ast::Attr,
-) {
-    let new_attr_input = input
-        .syntax()
-        .descendants_with_tokens()
-        .filter(|t| t.kind() == IDENT)
-        .filter_map(|t| t.into_token().map(|t| t.text().clone()))
-        .filter(|t| t != trait_name.text())
-        .collect::<Vec<SmolStr>>();
-    let has_more_derives = !new_attr_input.is_empty();
-
-    if has_more_derives {
-        let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
-        builder.replace(input.syntax().text_range(), new_attr_input);
-    } else {
-        let attr_range = attr.syntax().text_range();
-        builder.delete(attr_range);
-
-        let line_break_range = attr
-            .syntax()
-            .next_sibling_or_token()
-            .filter(|t| t.kind() == WHITESPACE)
-            .map(|t| t.text_range())
-            .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
-        builder.delete(line_break_range);
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_not_applicable};
 
     use super::*;
 
-    #[test]
-    fn add_custom_impl_qualified() {
-        check_assist(
-            add_custom_impl,
-            "
-mod fmt {
-    pub trait Debug {}
-}
-
-#[derive(Debu<|>g)]
-struct Foo {
-    bar: String,
-}
-",
-            "
-mod fmt {
-    pub trait Debug {}
-}
-
-struct Foo {
-    bar: String,
-}
-
-impl fmt::Debug for Foo {
-    $0
-}
-",
-        )
-    }
     #[test]
     fn add_custom_impl_for_unique_input() {
         check_assist(
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs
index 1f486c013c..e3d84d6986 100644
--- a/crates/assists/src/handlers/add_turbo_fish.rs
+++ b/crates/assists/src/handlers/add_turbo_fish.rs
@@ -25,7 +25,7 @@ use crate::{
 // }
 // ```
 pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
+    let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| {
         let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
         if arg_list.args().count() > 0 {
             return None;
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 37dd612661..e49e641b3d 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -99,6 +99,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
     let group = import_group_message(import_assets.import_candidate());
     let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
+    let syntax = scope.as_syntax_node();
     for (import, _) in proposed_imports {
         acc.add_group(
             &group,
@@ -106,9 +107,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
             format!("Import `{}`", &import),
             range,
             |builder| {
-                let rewriter =
+                let new_syntax =
                     insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
-                builder.rewrite(rewriter);
+                builder.replace(syntax.text_range(), new_syntax.to_string())
             },
         );
     }
diff --git a/crates/assists/src/handlers/change_return_type_to_result.rs b/crates/assists/src/handlers/change_return_type_to_result.rs
index 76f33a5b6d..be480943c5 100644
--- a/crates/assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/assists/src/handlers/change_return_type_to_result.rs
@@ -2,7 +2,7 @@ use std::iter;
 
 use syntax::{
     ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
-    match_ast, AstNode, SyntaxNode,
+    AstNode, SyntaxNode,
 };
 use test_utils::mark;
 
@@ -21,18 +21,8 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // ```
 pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
-    let parent = ret_type.syntax().parent()?;
-    let block_expr = match_ast! {
-        match parent {
-            ast::Fn(func) => func.body()?,
-            ast::ClosureExpr(closure) => match closure.body()? {
-                Expr::BlockExpr(block) => block,
-                // closures require a block when a return type is specified
-                _ => return None,
-            },
-            _ => return None,
-        }
-    };
+    // FIXME: extend to lambdas as well
+    let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
 
     let type_ref = &ret_type.ty()?;
     let ret_type_str = type_ref.syntax().text().to_string();
@@ -44,14 +34,16 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
         }
     }
 
+    let block_expr = &fn_def.body()?;
+
     acc.add(
         AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
         "Wrap return type in Result",
         type_ref.syntax().text_range(),
         |builder| {
             let mut tail_return_expr_collector = TailReturnCollector::new();
-            tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
-            tail_return_expr_collector.collect_tail_exprs(&block_expr);
+            tail_return_expr_collector.collect_jump_exprs(block_expr, false);
+            tail_return_expr_collector.collect_tail_exprs(block_expr);
 
             for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
                 let ok_wrapped = make::expr_call(
@@ -293,20 +285,16 @@ mod tests {
     }
 
     #[test]
-    fn change_return_type_to_result_simple_closure() {
+    fn change_return_type_to_result_simple_return_type() {
         check_assist(
             change_return_type_to_result,
-            r#"fn foo() {
-                || -> i32<|> {
-                    let test = "test";
-                    return 42i32;
-                };
+            r#"fn foo() -> i32<|> {
+                let test = "test";
+                return 42i32;
             }"#,
-            r#"fn foo() {
-                || -> Result<i32, ${0:_}> {
-                    let test = "test";
-                    return Ok(42i32);
-                };
+            r#"fn foo() -> Result<i32, ${0:_}> {
+                let test = "test";
+                return Ok(42i32);
             }"#,
         );
     }
@@ -322,29 +310,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn change_return_type_to_result_simple_return_type_bad_cursor_closure() {
-        check_assist_not_applicable(
-            change_return_type_to_result,
-            r#"fn foo() {
-                || -> i32 {
-                    let test = "test";<|>
-                    return 42i32;
-                };
-            }"#,
-        );
-    }
-
-    #[test]
-    fn change_return_type_to_result_closure_non_block() {
-        check_assist_not_applicable(
-            change_return_type_to_result,
-            r#"fn foo() {
-                || -> i<|>32 3;
-            }"#,
-        );
-    }
-
     #[test]
     fn change_return_type_to_result_simple_return_type_already_result_std() {
         check_assist_not_applicable(
@@ -368,19 +333,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn change_return_type_to_result_simple_return_type_already_result_closure() {
-        check_assist_not_applicable(
-            change_return_type_to_result,
-            r#"fn foo() {
-                || -> Result<i32<|>, String> {
-                    let test = "test";
-                    return 42i32;
-                };
-            }"#,
-        );
-    }
-
     #[test]
     fn change_return_type_to_result_simple_with_cursor() {
         check_assist(
@@ -411,25 +363,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn change_return_type_to_result_simple_with_tail_closure() {
-        check_assist(
-            change_return_type_to_result,
-            r#"fn foo() {
-                || -><|> i32 {
-                    let test = "test";
-                    42i32
-                };
-            }"#,
-            r#"fn foo() {
-                || -> Result<i32, ${0:_}> {
-                    let test = "test";
-                    Ok(42i32)
-                };
-            }"#,
-        );
-    }
-
     #[test]
     fn change_return_type_to_result_simple_with_tail_only() {
         check_assist(
@@ -442,7 +375,6 @@ mod tests {
             }"#,
         );
     }
-
     #[test]
     fn change_return_type_to_result_simple_with_tail_block_like() {
         check_assist(
@@ -464,31 +396,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn change_return_type_to_result_simple_without_block_closure() {
-        check_assist(
-            change_return_type_to_result,
-            r#"fn foo() {
-                || -> i32<|> {
-                    if true {
-                        42i32
-                    } else {
-                        24i32
-                    }
-                };
-            }"#,
-            r#"fn foo() {
-                || -> Result<i32, ${0:_}> {
-                    if true {
-                        Ok(42i32)
-                    } else {
-                        Ok(24i32)
-                    }
-                };
-            }"#,
-        );
-    }
-
     #[test]
     fn change_return_type_to_result_simple_with_nested_if() {
         check_assist(
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
index 1094ed3f3a..ea35e833a9 100644
--- a/crates/assists/src/handlers/convert_integer_literal.rs
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -1,4 +1,4 @@
-use syntax::{ast, ast::Radix, AstToken};
+use syntax::{ast, AstNode, SmolStr};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 
@@ -15,34 +15,40 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 // ```
 pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let literal = ctx.find_node_at_offset::<ast::Literal>()?;
-    let literal = match literal.kind() {
-        ast::LiteralKind::IntNumber(it) => it,
-        _ => return None,
-    };
-    let radix = literal.radix();
-    let value = literal.value()?;
-    let suffix = literal.suffix();
-
     let range = literal.syntax().text_range();
     let group_id = GroupLabel("Convert integer base".into());
 
-    for &target_radix in Radix::ALL {
-        if target_radix == radix {
+    let suffix = match literal.kind() {
+        ast::LiteralKind::IntNumber { suffix } => suffix,
+        _ => return None,
+    };
+    let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0);
+    let raw_literal_text = literal.syntax().to_string();
+
+    // Gets the literal's text without the type suffix and without underscores.
+    let literal_text = raw_literal_text
+        .chars()
+        .take(raw_literal_text.len() - suffix_len)
+        .filter(|c| *c != '_')
+        .collect::<SmolStr>();
+    let literal_base = IntegerLiteralBase::identify(&literal_text)?;
+
+    for base in IntegerLiteralBase::bases() {
+        if *base == literal_base {
             continue;
         }
 
-        let mut converted = match target_radix {
-            Radix::Binary => format!("0b{:b}", value),
-            Radix::Octal => format!("0o{:o}", value),
-            Radix::Decimal => value.to_string(),
-            Radix::Hexadecimal => format!("0x{:X}", value),
-        };
+        let mut converted = literal_base.convert(&literal_text, base);
 
-        let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
+        let label = if let Some(suffix) = &suffix {
+            format!("Convert {} ({}) to {}", &literal_text, suffix, &converted)
+        } else {
+            format!("Convert {} to {}", &literal_text, &converted)
+        };
 
         // Appends the type suffix back into the new literal if it exists.
-        if let Some(suffix) = suffix {
-            converted.push_str(suffix);
+        if let Some(suffix) = &suffix {
+            converted.push_str(&suffix);
         }
 
         acc.add_group(
@@ -57,11 +63,79 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) ->
     Some(())
 }
 
+#[derive(Debug, PartialEq, Eq)]
+enum IntegerLiteralBase {
+    Binary,
+    Octal,
+    Decimal,
+    Hexadecimal,
+}
+
+impl IntegerLiteralBase {
+    fn identify(literal_text: &str) -> Option<Self> {
+        // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
+        if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
+            return Some(Self::Decimal);
+        }
+
+        let base = match &literal_text[..2] {
+            "0b" => Self::Binary,
+            "0o" => Self::Octal,
+            "0x" => Self::Hexadecimal,
+            _ => Self::Decimal,
+        };
+
+        // Checks that all characters after the base prefix are all valid digits for that base.
+        if literal_text[base.prefix_len()..].chars().all(|c| c.is_digit(base.base())) {
+            Some(base)
+        } else {
+            None
+        }
+    }
+
+    fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String {
+        let digits = &literal_text[self.prefix_len()..];
+        let value = u128::from_str_radix(digits, self.base()).unwrap();
+
+        match to {
+            Self::Binary => format!("0b{:b}", value),
+            Self::Octal => format!("0o{:o}", value),
+            Self::Decimal => value.to_string(),
+            Self::Hexadecimal => format!("0x{:X}", value),
+        }
+    }
+
+    const fn base(&self) -> u32 {
+        match self {
+            Self::Binary => 2,
+            Self::Octal => 8,
+            Self::Decimal => 10,
+            Self::Hexadecimal => 16,
+        }
+    }
+
+    const fn prefix_len(&self) -> usize {
+        match self {
+            Self::Decimal => 0,
+            _ => 2,
+        }
+    }
+
+    const fn bases() -> &'static [IntegerLiteralBase] {
+        &[
+            IntegerLiteralBase::Binary,
+            IntegerLiteralBase::Octal,
+            IntegerLiteralBase::Decimal,
+            IntegerLiteralBase::Hexadecimal,
+        ]
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
 
     use super::*;
+    use crate::tests::{check_assist_by_label, check_assist_target};
 
     #[test]
     fn binary_target() {
@@ -243,21 +317,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b1111101000;",
-            "Convert 1_00_0 to 0b1111101000",
+            "Convert 1000 to 0b1111101000",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o1750;",
-            "Convert 1_00_0 to 0o1750",
+            "Convert 1000 to 0o1750",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0x3E8;",
-            "Convert 1_00_0 to 0x3E8",
+            "Convert 1000 to 0x3E8",
         );
     }
 
@@ -269,21 +343,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b1010;",
-            "Convert 1_0 to 0b1010",
+            "Convert 10 to 0b1010",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o12;",
-            "Convert 1_0 to 0o12",
+            "Convert 10 to 0o12",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xA;",
-            "Convert 1_0 to 0xA",
+            "Convert 10 to 0xA",
         );
     }
 
@@ -295,21 +369,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b11111111;",
-            "Convert 0x_F_F to 0b11111111",
+            "Convert 0xFF to 0b11111111",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o377;",
-            "Convert 0x_F_F to 0o377",
+            "Convert 0xFF to 0o377",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255;",
-            "Convert 0x_F_F to 255",
+            "Convert 0xFF to 255",
         );
     }
 
@@ -321,21 +395,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0o377;",
-            "Convert 0b1111_1111 to 0o377",
+            "Convert 0b11111111 to 0o377",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255;",
-            "Convert 0b1111_1111 to 255",
+            "Convert 0b11111111 to 255",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xFF;",
-            "Convert 0b1111_1111 to 0xFF",
+            "Convert 0b11111111 to 0xFF",
         );
     }
 
@@ -347,21 +421,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b11111111;",
-            "Convert 0o3_77 to 0b11111111",
+            "Convert 0o377 to 0b11111111",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255;",
-            "Convert 0o3_77 to 255",
+            "Convert 0o377 to 255",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xFF;",
-            "Convert 0o3_77 to 0xFF",
+            "Convert 0o377 to 0xFF",
         );
     }
 
@@ -373,21 +447,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b1111101000i32;",
-            "Convert 1000i32 to 0b1111101000i32",
+            "Convert 1000 (i32) to 0b1111101000",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o1750i32;",
-            "Convert 1000i32 to 0o1750i32",
+            "Convert 1000 (i32) to 0o1750",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0x3E8i32;",
-            "Convert 1000i32 to 0x3E8i32",
+            "Convert 1000 (i32) to 0x3E8",
         );
     }
 
@@ -399,21 +473,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b1010i32;",
-            "Convert 10i32 to 0b1010i32",
+            "Convert 10 (i32) to 0b1010",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o12i32;",
-            "Convert 10i32 to 0o12i32",
+            "Convert 10 (i32) to 0o12",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xAi32;",
-            "Convert 10i32 to 0xAi32",
+            "Convert 10 (i32) to 0xA",
         );
     }
 
@@ -425,21 +499,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b11111111i32;",
-            "Convert 0xFFi32 to 0b11111111i32",
+            "Convert 0xFF (i32) to 0b11111111",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o377i32;",
-            "Convert 0xFFi32 to 0o377i32",
+            "Convert 0xFF (i32) to 0o377",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255i32;",
-            "Convert 0xFFi32 to 255i32",
+            "Convert 0xFF (i32) to 255",
         );
     }
 
@@ -451,21 +525,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0o377i32;",
-            "Convert 0b11111111i32 to 0o377i32",
+            "Convert 0b11111111 (i32) to 0o377",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255i32;",
-            "Convert 0b11111111i32 to 255i32",
+            "Convert 0b11111111 (i32) to 255",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xFFi32;",
-            "Convert 0b11111111i32 to 0xFFi32",
+            "Convert 0b11111111 (i32) to 0xFF",
         );
     }
 
@@ -477,21 +551,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b11111111i32;",
-            "Convert 0o377i32 to 0b11111111i32",
+            "Convert 0o377 (i32) to 0b11111111",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255i32;",
-            "Convert 0o377i32 to 255i32",
+            "Convert 0o377 (i32) to 255",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xFFi32;",
-            "Convert 0o377i32 to 0xFFi32",
+            "Convert 0o377 (i32) to 0xFF",
         );
     }
 
@@ -503,21 +577,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b1111101000i32;",
-            "Convert 1_00_0i32 to 0b1111101000i32",
+            "Convert 1000 (i32) to 0b1111101000",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o1750i32;",
-            "Convert 1_00_0i32 to 0o1750i32",
+            "Convert 1000 (i32) to 0o1750",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0x3E8i32;",
-            "Convert 1_00_0i32 to 0x3E8i32",
+            "Convert 1000 (i32) to 0x3E8",
         );
     }
 
@@ -529,21 +603,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b1010i32;",
-            "Convert 1_0i32 to 0b1010i32",
+            "Convert 10 (i32) to 0b1010",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o12i32;",
-            "Convert 1_0i32 to 0o12i32",
+            "Convert 10 (i32) to 0o12",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xAi32;",
-            "Convert 1_0i32 to 0xAi32",
+            "Convert 10 (i32) to 0xA",
         );
     }
 
@@ -555,21 +629,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b11111111i32;",
-            "Convert 0x_F_Fi32 to 0b11111111i32",
+            "Convert 0xFF (i32) to 0b11111111",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0o377i32;",
-            "Convert 0x_F_Fi32 to 0o377i32",
+            "Convert 0xFF (i32) to 0o377",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255i32;",
-            "Convert 0x_F_Fi32 to 255i32",
+            "Convert 0xFF (i32) to 255",
         );
     }
 
@@ -581,21 +655,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0o377i32;",
-            "Convert 0b1111_1111i32 to 0o377i32",
+            "Convert 0b11111111 (i32) to 0o377",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255i32;",
-            "Convert 0b1111_1111i32 to 255i32",
+            "Convert 0b11111111 (i32) to 255",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xFFi32;",
-            "Convert 0b1111_1111i32 to 0xFFi32",
+            "Convert 0b11111111 (i32) to 0xFF",
         );
     }
 
@@ -607,28 +681,21 @@ mod tests {
             convert_integer_literal,
             before,
             "const _: i32 = 0b11111111i32;",
-            "Convert 0o3_77i32 to 0b11111111i32",
+            "Convert 0o377 (i32) to 0b11111111",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 255i32;",
-            "Convert 0o3_77i32 to 255i32",
+            "Convert 0o377 (i32) to 255",
         );
 
         check_assist_by_label(
             convert_integer_literal,
             before,
             "const _: i32 = 0xFFi32;",
-            "Convert 0o3_77i32 to 0xFFi32",
+            "Convert 0o377 (i32) to 0xFF",
         );
     }
-
-    #[test]
-    fn convert_overflowing_literal() {
-        let before = "const _: i32 =
-            111111111111111111111111111111111111111111111111111111111111111111111111<|>;";
-        check_assist_not_applicable(convert_integer_literal, before);
-    }
 }
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 853266395d..316a58d883 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -41,7 +41,7 @@ use crate::{
 // fn qux(bar: Bar, baz: Baz) {}
 // ```
 pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let star = ctx.find_token_syntax_at_offset(T![*])?;
+    let star = ctx.find_token_at_offset(T![*])?;
     let (parent, mod_path) = find_parent_and_path(&star)?;
     let target_module = match ctx.sema.resolve_path(&mod_path)? {
         PathResolution::Def(ModuleDef::Module(it)) => it,
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 14209b7714..178718c5e1 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -1,17 +1,16 @@
-use std::iter;
-
-use either::Either;
-use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
+use hir::{EnumVariant, Module, ModuleDef, Name};
+use ide_db::base_db::FileId;
 use ide_db::{defs::Definition, search::Reference, RootDatabase};
-use rustc_hash::{FxHashMap, FxHashSet};
+use itertools::Itertools;
+use rustc_hash::FxHashSet;
 use syntax::{
     algo::find_node_at_offset,
-    algo::SyntaxRewriter,
-    ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
-    SourceFile, SyntaxElement,
+    ast::{self, edit::IndentLevel, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
+    SourceFile, TextRange, TextSize,
 };
 
 use crate::{
+    assist_context::AssistBuilder,
     utils::{insert_use, mod_path_to_ast, ImportScope},
     AssistContext, AssistId, AssistKind, Assists,
 };
@@ -34,39 +33,43 @@ pub(crate) fn extract_struct_from_enum_variant(
     ctx: &AssistContext,
 ) -> Option<()> {
     let variant = ctx.find_node_at_offset::<ast::Variant>()?;
-    let field_list = extract_field_list_if_applicable(&variant)?;
+    let field_list = match variant.kind() {
+        ast::StructKind::Tuple(field_list) => field_list,
+        _ => return None,
+    };
 
-    let variant_name = variant.name()?;
-    let variant_hir = ctx.sema.to_def(&variant)?;
-    if existing_definition(ctx.db(), &variant_name, &variant_hir) {
+    // skip 1-tuple variants
+    if field_list.fields().count() == 1 {
         return None;
     }
 
+    let variant_name = variant.name()?.to_string();
+    let variant_hir = ctx.sema.to_def(&variant)?;
+    if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
+        return None;
+    }
     let enum_ast = variant.parent_enum();
+    let visibility = enum_ast.visibility();
     let enum_hir = ctx.sema.to_def(&enum_ast)?;
+    let variant_hir_name = variant_hir.name(ctx.db());
+    let enum_module_def = ModuleDef::from(enum_hir);
+    let current_module = enum_hir.module(ctx.db());
     let target = variant.syntax().text_range();
     acc.add(
         AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
         "Extract struct from enum variant",
         target,
         |builder| {
-            let variant_hir_name = variant_hir.name(ctx.db());
-            let enum_module_def = ModuleDef::from(enum_hir);
-            let usages =
-                Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)).usages(&ctx.sema).all();
-
+            let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
+            let res = definition.usages(&ctx.sema).all();
+            let start_offset = variant.parent_enum().syntax().text_range().start();
             let mut visited_modules_set = FxHashSet::default();
-            let current_module = enum_hir.module(ctx.db());
             visited_modules_set.insert(current_module);
-            let mut rewriters = FxHashMap::default();
-            for reference in usages {
-                let rewriter = rewriters
-                    .entry(reference.file_range.file_id)
-                    .or_insert_with(SyntaxRewriter::default);
+            for reference in res {
                 let source_file = ctx.sema.parse(reference.file_range.file_id);
                 update_reference(
                     ctx,
-                    rewriter,
+                    builder,
                     reference,
                     &source_file,
                     &enum_module_def,
@@ -74,62 +77,34 @@ pub(crate) fn extract_struct_from_enum_variant(
                     &mut visited_modules_set,
                 );
             }
-            let mut rewriter =
-                rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default);
-            for (file_id, rewriter) in rewriters {
-                builder.edit_file(file_id);
-                builder.rewrite(rewriter);
-            }
-            builder.edit_file(ctx.frange.file_id);
-            update_variant(&mut rewriter, &variant);
             extract_struct_def(
-                &mut rewriter,
+                builder,
                 &enum_ast,
-                variant_name.clone(),
-                &field_list,
-                &variant.parent_enum().syntax().clone().into(),
-                enum_ast.visibility(),
+                &variant_name,
+                &field_list.to_string(),
+                start_offset,
+                ctx.frange.file_id,
+                &visibility,
             );
-            builder.rewrite(rewriter);
+            let list_range = field_list.syntax().text_range();
+            update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
         },
     )
 }
 
-fn extract_field_list_if_applicable(
-    variant: &ast::Variant,
-) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
-    match variant.kind() {
-        ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
-            Some(Either::Left(field_list))
-        }
-        ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
-            Some(Either::Right(field_list))
-        }
-        _ => None,
-    }
-}
-
-fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool {
+fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
     variant
         .parent_enum(db)
         .module(db)
         .scope(db, None)
         .into_iter()
-        .filter(|(_, def)| match def {
-            // only check type-namespace
-            hir::ScopeDef::ModuleDef(def) => matches!(def,
-                ModuleDef::Module(_) | ModuleDef::Adt(_) |
-                ModuleDef::EnumVariant(_) | ModuleDef::Trait(_) |
-                ModuleDef::TypeAlias(_) | ModuleDef::BuiltinType(_)
-            ),
-            _ => false,
-        })
-        .any(|(name, _)| name == variant_name.as_name())
+        .any(|(name, _)| name.to_string() == variant_name)
 }
 
+#[allow(dead_code)]
 fn insert_import(
     ctx: &AssistContext,
-    rewriter: &mut SyntaxRewriter,
+    builder: &mut AssistBuilder,
     path: &ast::PathExpr,
     module: &Module,
     enum_module_def: &ModuleDef,
@@ -141,68 +116,69 @@ fn insert_import(
         mod_path.segments.pop();
         mod_path.segments.push(variant_hir_name.clone());
         let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
+        let syntax = scope.as_syntax_node();
 
-        *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
+        let new_syntax =
+            insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
+        // FIXME: this will currently panic as multiple imports will have overlapping text ranges
+        builder.replace(syntax.text_range(), new_syntax.to_string())
     }
     Some(())
 }
 
+// FIXME: this should use strongly-typed `make`, rather than string manipulation.
 fn extract_struct_def(
-    rewriter: &mut SyntaxRewriter,
+    builder: &mut AssistBuilder,
     enum_: &ast::Enum,
-    variant_name: ast::Name,
-    field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
-    start_offset: &SyntaxElement,
-    visibility: Option<ast::Visibility>,
+    variant_name: &str,
+    variant_list: &str,
+    start_offset: TextSize,
+    file_id: FileId,
+    visibility: &Option<ast::Visibility>,
 ) -> Option<()> {
-    let pub_vis = Some(make::visibility_pub());
-    let field_list = match field_list {
-        Either::Left(field_list) => {
-            make::record_field_list(field_list.fields().flat_map(|field| {
-                Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
-            }))
-            .into()
-        }
-        Either::Right(field_list) => make::tuple_field_list(
-            field_list
-                .fields()
-                .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
-        )
-        .into(),
+    let visibility_string = if let Some(visibility) = visibility {
+        format!("{} ", visibility.to_string())
+    } else {
+        "".to_string()
     };
-
-    rewriter.insert_before(
-        start_offset,
-        make::struct_(visibility, variant_name, None, field_list).syntax(),
+    let indent = IndentLevel::from_node(enum_.syntax());
+    let struct_def = format!(
+        r#"{}struct {}{};
+
+{}"#,
+        visibility_string,
+        variant_name,
+        list_with_visibility(variant_list),
+        indent
     );
-    rewriter.insert_before(start_offset, &make::tokens::blank_line());
-
-    if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
-        rewriter
-            .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
-    }
+    builder.edit_file(file_id);
+    builder.insert(start_offset, struct_def);
     Some(())
 }
 
-fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
-    let name = variant.name()?;
-    let tuple_field = make::tuple_field(None, make::ty(name.text()));
-    let replacement = make::variant(
-        name,
-        Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
+fn update_variant(
+    builder: &mut AssistBuilder,
+    variant_name: &str,
+    file_id: FileId,
+    list_range: TextRange,
+) -> Option<()> {
+    let inside_variant_range = TextRange::new(
+        list_range.start().checked_add(TextSize::from(1))?,
+        list_range.end().checked_sub(TextSize::from(1))?,
     );
-    rewriter.replace(variant.syntax(), replacement.syntax());
+    builder.edit_file(file_id);
+    builder.replace(inside_variant_range, variant_name);
     Some(())
 }
 
 fn update_reference(
     ctx: &AssistContext,
-    rewriter: &mut SyntaxRewriter,
+    builder: &mut AssistBuilder,
     reference: Reference,
     source_file: &SourceFile,
-    enum_module_def: &ModuleDef,
-    variant_hir_name: &Name,
-    visited_modules_set: &mut FxHashSet<Module>,
+    _enum_module_def: &ModuleDef,
+    _variant_hir_name: &Name,
+    _visited_modules_set: &mut FxHashSet<Module>,
 ) -> Option<()> {
     let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
         source_file.syntax(),
@@ -211,23 +187,37 @@ fn update_reference(
     let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
     let list = call.arg_list()?;
     let segment = path_expr.path()?.segment()?;
-    let module = ctx.sema.scope(&path_expr.syntax()).module()?;
+    let _module = ctx.sema.scope(&path_expr.syntax()).module()?;
+    let list_range = list.syntax().text_range();
+    let inside_list_range = TextRange::new(
+        list_range.start().checked_add(TextSize::from(1))?,
+        list_range.end().checked_sub(TextSize::from(1))?,
+    );
+    builder.edit_file(reference.file_range.file_id);
+    /* FIXME: this most likely requires AST-based editing, see `insert_import`
     if !visited_modules_set.contains(&module) {
-        if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
+        if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
             .is_some()
         {
             visited_modules_set.insert(module);
         }
     }
-
-    let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
-    let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
-    rewriter.insert_after(&lparen, segment.syntax());
-    rewriter.insert_after(&lparen, &lparen);
-    rewriter.insert_before(&rparen, &rparen);
+    */
+    builder.replace(inside_list_range, format!("{}{}", segment, list));
     Some(())
 }
 
+fn list_with_visibility(list: &str) -> String {
+    list.split(',')
+        .map(|part| {
+            let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
+            let mut mod_part = part.trim().to_string();
+            mod_part.insert_str(index, "pub ");
+            mod_part
+        })
+        .join(", ")
+}
+
 #[cfg(test)]
 mod tests {
     use crate::{
@@ -238,47 +228,12 @@ mod tests {
     use super::*;
 
     #[test]
-    fn test_extract_struct_several_fields_tuple() {
+    fn test_extract_struct_several_fields() {
         check_assist(
             extract_struct_from_enum_variant,
             "enum A { <|>One(u32, u32) }",
             r#"struct One(pub u32, pub u32);
 
-enum A { One(One) }"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_struct_several_fields_named() {
-        check_assist(
-            extract_struct_from_enum_variant,
-            "enum A { <|>One { foo: u32, bar: u32 } }",
-            r#"struct One{ pub foo: u32, pub bar: u32 }
-
-enum A { One(One) }"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_struct_one_field_named() {
-        check_assist(
-            extract_struct_from_enum_variant,
-            "enum A { <|>One { foo: u32 } }",
-            r#"struct One{ pub foo: u32 }
-
-enum A { One(One) }"#,
-        );
-    }
-
-    #[test]
-    fn test_extract_enum_variant_name_value_namespace() {
-        check_assist(
-            extract_struct_from_enum_variant,
-            r#"const One: () = ();
-enum A { <|>One(u32, u32) }"#,
-            r#"const One: () = ();
-struct One(pub u32, pub u32);
-
 enum A { One(One) }"#,
         );
     }
@@ -295,6 +250,7 @@ pub enum A { One(One) }"#,
     }
 
     #[test]
+    #[ignore] // FIXME: this currently panics if `insert_import` is used
     fn test_extract_struct_with_complex_imports() {
         check_assist(
             extract_struct_from_enum_variant,
@@ -360,7 +316,7 @@ fn another_fn() {
     fn test_extract_enum_not_applicable_if_struct_exists() {
         check_not_applicable(
             r#"struct One;
-        enum A { <|>One(u8, u32) }"#,
+        enum A { <|>One(u8) }"#,
         );
     }
 
@@ -368,14 +324,4 @@ fn another_fn() {
     fn test_extract_not_applicable_one_field() {
         check_not_applicable(r"enum A { <|>One(u32) }");
     }
-
-    #[test]
-    fn test_extract_not_applicable_no_field_tuple() {
-        check_not_applicable(r"enum A { <|>None() }");
-    }
-
-    #[test]
-    fn test_extract_not_applicable_no_field_named() {
-        check_not_applicable(r"enum A { <|>None {} }");
-    }
 }
diff --git a/crates/assists/src/handlers/flip_comma.rs b/crates/assists/src/handlers/flip_comma.rs
index 64b4b1a76a..5c69db53e5 100644
--- a/crates/assists/src/handlers/flip_comma.rs
+++ b/crates/assists/src/handlers/flip_comma.rs
@@ -18,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // }
 // ```
 pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let comma = ctx.find_token_syntax_at_offset(T![,])?;
+    let comma = ctx.find_token_at_offset(T![,])?;
     let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
     let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
 
diff --git a/crates/assists/src/handlers/flip_trait_bound.rs b/crates/assists/src/handlers/flip_trait_bound.rs
index 92ee421810..347e79b1da 100644
--- a/crates/assists/src/handlers/flip_trait_bound.rs
+++ b/crates/assists/src/handlers/flip_trait_bound.rs
@@ -20,7 +20,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     // We want to replicate the behavior of `flip_binexpr` by only suggesting
     // the assist when the cursor is on a `+`
-    let plus = ctx.find_token_syntax_at_offset(T![+])?;
+    let plus = ctx.find_token_at_offset(T![+])?;
 
     // Make sure we're in a `TypeBoundList`
     if ast::TypeBoundList::cast(plus.parent()).is_none() {
diff --git a/crates/assists/src/handlers/infer_function_return_type.rs b/crates/assists/src/handlers/infer_function_return_type.rs
deleted file mode 100644
index 520d07ae06..0000000000
--- a/crates/assists/src/handlers/infer_function_return_type.rs
+++ /dev/null
@@ -1,337 +0,0 @@
-use hir::HirDisplay;
-use syntax::{ast, AstNode, TextRange, TextSize};
-use test_utils::mark;
-
-use crate::{AssistContext, AssistId, AssistKind, Assists};
-
-// Assist: infer_function_return_type
-//
-// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
-// type specified. This assists is useable in a functions or closures tail expression or return type position.
-//
-// ```
-// fn foo() { 4<|>2i32 }
-// ```
-// ->
-// ```
-// fn foo() -> i32 { 42i32 }
-// ```
-pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let (tail_expr, builder_edit_pos, wrap_expr) = extract_tail(ctx)?;
-    let module = ctx.sema.scope(tail_expr.syntax()).module()?;
-    let ty = ctx.sema.type_of_expr(&tail_expr)?;
-    if ty.is_unit() {
-        return None;
-    }
-    let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
-
-    acc.add(
-        AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
-        "Add this function's return type",
-        tail_expr.syntax().text_range(),
-        |builder| {
-            match builder_edit_pos {
-                InsertOrReplace::Insert(insert_pos) => {
-                    builder.insert(insert_pos, &format!("-> {} ", ty))
-                }
-                InsertOrReplace::Replace(text_range) => {
-                    builder.replace(text_range, &format!("-> {}", ty))
-                }
-            }
-            if wrap_expr {
-                mark::hit!(wrap_closure_non_block_expr);
-                // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
-                builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
-            }
-        },
-    )
-}
-
-enum InsertOrReplace {
-    Insert(TextSize),
-    Replace(TextRange),
-}
-
-/// Check the potentially already specified return type and reject it or turn it into a builder command
-/// if allowed.
-fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
-    match ret_ty {
-        Some(ret_ty) => match ret_ty.ty() {
-            Some(ast::Type::InferType(_)) | None => {
-                mark::hit!(existing_infer_ret_type);
-                mark::hit!(existing_infer_ret_type_closure);
-                Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
-            }
-            _ => {
-                mark::hit!(existing_ret_type);
-                mark::hit!(existing_ret_type_closure);
-                None
-            }
-        },
-        None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
-    }
-}
-
-fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool)> {
-    let (tail_expr, return_type_range, action, wrap_expr) =
-        if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
-            let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
-            let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
-
-            let body = closure.body()?;
-            let body_start = body.syntax().first_token()?.text_range().start();
-            let (tail_expr, wrap_expr) = match body {
-                ast::Expr::BlockExpr(block) => (block.expr()?, false),
-                body => (body, true),
-            };
-
-            let ret_range = TextRange::new(rpipe_pos, body_start);
-            (tail_expr, ret_range, action, wrap_expr)
-        } else {
-            let func = ctx.find_node_at_offset::<ast::Fn>()?;
-            let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
-            let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
-
-            let body = func.body()?;
-            let tail_expr = body.expr()?;
-
-            let ret_range_end = body.l_curly_token()?.text_range().start();
-            let ret_range = TextRange::new(rparen_pos, ret_range_end);
-            (tail_expr, ret_range, action, false)
-        };
-    let frange = ctx.frange.range;
-    if return_type_range.contains_range(frange) {
-        mark::hit!(cursor_in_ret_position);
-        mark::hit!(cursor_in_ret_position_closure);
-    } else if tail_expr.syntax().text_range().contains_range(frange) {
-        mark::hit!(cursor_on_tail);
-        mark::hit!(cursor_on_tail_closure);
-    } else {
-        return None;
-    }
-    Some((tail_expr, action, wrap_expr))
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::{check_assist, check_assist_not_applicable};
-
-    use super::*;
-
-    #[test]
-    fn infer_return_type_specified_inferred() {
-        mark::check!(existing_infer_ret_type);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() -> <|>_ {
-    45
-}"#,
-            r#"fn foo() -> i32 {
-    45
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_specified_inferred_closure() {
-        mark::check!(existing_infer_ret_type_closure);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    || -> _ {<|>45};
-}"#,
-            r#"fn foo() {
-    || -> i32 {45};
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_cursor_at_return_type_pos() {
-        mark::check!(cursor_in_ret_position);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() <|>{
-    45
-}"#,
-            r#"fn foo() -> i32 {
-    45
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_cursor_at_return_type_pos_closure() {
-        mark::check!(cursor_in_ret_position_closure);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    || <|>45
-}"#,
-            r#"fn foo() {
-    || -> i32 {45}
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type() {
-        mark::check!(cursor_on_tail);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    45<|>
-}"#,
-            r#"fn foo() -> i32 {
-    45
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_nested() {
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    if true {
-        3<|>
-    } else {
-        5
-    }
-}"#,
-            r#"fn foo() -> i32 {
-    if true {
-        3
-    } else {
-        5
-    }
-}"#,
-        );
-    }
-
-    #[test]
-    fn not_applicable_ret_type_specified() {
-        mark::check!(existing_ret_type);
-        check_assist_not_applicable(
-            infer_function_return_type,
-            r#"fn foo() -> i32 {
-    ( 45<|> + 32 ) * 123
-}"#,
-        );
-    }
-
-    #[test]
-    fn not_applicable_non_tail_expr() {
-        check_assist_not_applicable(
-            infer_function_return_type,
-            r#"fn foo() {
-    let x = <|>3;
-    ( 45 + 32 ) * 123
-}"#,
-        );
-    }
-
-    #[test]
-    fn not_applicable_unit_return_type() {
-        check_assist_not_applicable(
-            infer_function_return_type,
-            r#"fn foo() {
-    (<|>)
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_closure_block() {
-        mark::check!(cursor_on_tail_closure);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    |x: i32| {
-        x<|>
-    };
-}"#,
-            r#"fn foo() {
-    |x: i32| -> i32 {
-        x
-    };
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_closure() {
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    |x: i32| { x<|> };
-}"#,
-            r#"fn foo() {
-    |x: i32| -> i32 { x };
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_closure_wrap() {
-        mark::check!(wrap_closure_non_block_expr);
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    |x: i32| x<|>;
-}"#,
-            r#"fn foo() {
-    |x: i32| -> i32 {x};
-}"#,
-        );
-    }
-
-    #[test]
-    fn infer_return_type_nested_closure() {
-        check_assist(
-            infer_function_return_type,
-            r#"fn foo() {
-    || {
-        if true {
-            3<|>
-        } else {
-            5
-        }
-    }
-}"#,
-            r#"fn foo() {
-    || -> i32 {
-        if true {
-            3
-        } else {
-            5
-        }
-    }
-}"#,
-        );
-    }
-
-    #[test]
-    fn not_applicable_ret_type_specified_closure() {
-        mark::check!(existing_ret_type_closure);
-        check_assist_not_applicable(
-            infer_function_return_type,
-            r#"fn foo() {
-    || -> i32 { 3<|> }
-}"#,
-        );
-    }
-
-    #[test]
-    fn not_applicable_non_tail_expr_closure() {
-        check_assist_not_applicable(
-            infer_function_return_type,
-            r#"fn foo() {
-    || -> i32 {
-        let x = 3<|>;
-        6
-    }
-}"#,
-        );
-    }
-}
diff --git a/crates/assists/src/handlers/introduce_named_lifetime.rs b/crates/assists/src/handlers/introduce_named_lifetime.rs
index 4cc8dae650..5f623e5f7a 100644
--- a/crates/assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/assists/src/handlers/introduce_named_lifetime.rs
@@ -36,7 +36,7 @@ static ASSIST_LABEL: &str = "Introduce named lifetime";
 // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
 pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let lifetime_token = ctx
-        .find_token_syntax_at_offset(SyntaxKind::LIFETIME)
+        .find_token_at_offset(SyntaxKind::LIFETIME)
         .filter(|lifetime| lifetime.text() == "'_")?;
     if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) {
         generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
diff --git a/crates/assists/src/handlers/invert_if.rs b/crates/assists/src/handlers/invert_if.rs
index ea722b91b2..461fcf8626 100644
--- a/crates/assists/src/handlers/invert_if.rs
+++ b/crates/assists/src/handlers/invert_if.rs
@@ -29,7 +29,7 @@ use crate::{
 // ```
 
 pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
+    let if_keyword = ctx.find_token_at_offset(T![if])?;
     let expr = ast::IfExpr::cast(if_keyword.parent())?;
     let if_range = if_keyword.text_range();
     let cursor_in_range = if_range.contains_range(ctx.frange.range);
diff --git a/crates/assists/src/handlers/raw_string.rs b/crates/assists/src/handlers/raw_string.rs
index 4c759cc25e..9ddd116e01 100644
--- a/crates/assists/src/handlers/raw_string.rs
+++ b/crates/assists/src/handlers/raw_string.rs
@@ -1,6 +1,11 @@
 use std::borrow::Cow;
 
-use syntax::{ast, AstToken, TextRange, TextSize};
+use syntax::{
+    ast::{self, HasQuotes, HasStringValue},
+    AstToken,
+    SyntaxKind::{RAW_STRING, STRING},
+    TextRange, TextSize,
+};
 use test_utils::mark;
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -21,10 +26,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // }
 // ```
 pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let token = ctx.find_token_at_offset::<ast::String>()?;
-    if token.is_raw() {
-        return None;
-    }
+    let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
     let value = token.value()?;
     let target = token.syntax().text_range();
     acc.add(
@@ -63,10 +65,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
 // }
 // ```
 pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let token = ctx.find_token_at_offset::<ast::String>()?;
-    if !token.is_raw() {
-        return None;
-    }
+    let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
     let value = token.value()?;
     let target = token.syntax().text_range();
     acc.add(
@@ -105,15 +104,11 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
 // }
 // ```
 pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let token = ctx.find_token_at_offset::<ast::String>()?;
-    if !token.is_raw() {
-        return None;
-    }
-    let text_range = token.syntax().text_range();
-    let target = text_range;
+    let token = ctx.find_token_at_offset(RAW_STRING)?;
+    let target = token.text_range();
     acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
-        edit.insert(text_range.start() + TextSize::of('r'), "#");
-        edit.insert(text_range.end(), "#");
+        edit.insert(token.text_range().start() + TextSize::of('r'), "#");
+        edit.insert(token.text_range().end(), "#");
     })
 }
 
@@ -133,10 +128,7 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
 // }
 // ```
 pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let token = ctx.find_token_at_offset::<ast::String>()?;
-    if !token.is_raw() {
-        return None;
-    }
+    let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
 
     let text = token.text().as_str();
     if !text.starts_with("r#") && text.ends_with('#') {
diff --git a/crates/assists/src/handlers/remove_mut.rs b/crates/assists/src/handlers/remove_mut.rs
index 575b271f71..44f41daa92 100644
--- a/crates/assists/src/handlers/remove_mut.rs
+++ b/crates/assists/src/handlers/remove_mut.rs
@@ -18,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // }
 // ```
 pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let mut_token = ctx.find_token_syntax_at_offset(T![mut])?;
+    let mut_token = ctx.find_token_at_offset(T![mut])?;
     let delete_from = mut_token.text_range().start();
     let delete_to = match mut_token.next_token() {
         Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index 69d3b08d36..a5bcbda24e 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -37,7 +37,7 @@ use ide_db::ty_filter::TryEnum;
 // fn compute() -> Option<i32> { None }
 // ```
 pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
+    let let_kw = ctx.find_token_at_offset(T![let])?;
     let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
     let init = let_stmt.initializer()?;
     let original_pat = let_stmt.pat()?;
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index d7e1d95805..c50bc76040 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -45,9 +45,10 @@ pub(crate) fn replace_qualified_name_with_use(
             // affected (that is, all paths inside the node we added the `use` to).
             let mut rewriter = SyntaxRewriter::default();
             shorten_paths(&mut rewriter, syntax.clone(), &path);
-            if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
-                rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
-                builder.rewrite(rewriter);
+            let rewritten_syntax = rewriter.rewrite(&syntax);
+            if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) {
+                let new_syntax = insert_use(import_scope, path, ctx.config.insert_use.merge);
+                builder.replace(syntax.text_range(), new_syntax.to_string())
             }
         },
     )
diff --git a/crates/assists/src/handlers/replace_string_with_char.rs b/crates/assists/src/handlers/replace_string_with_char.rs
index b4b8988461..4ca87a8ecc 100644
--- a/crates/assists/src/handlers/replace_string_with_char.rs
+++ b/crates/assists/src/handlers/replace_string_with_char.rs
@@ -1,4 +1,8 @@
-use syntax::{ast, AstToken, SyntaxKind::STRING};
+use syntax::{
+    ast::{self, HasStringValue},
+    AstToken,
+    SyntaxKind::STRING,
+};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
 
@@ -18,7 +22,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // }
 // ```
 pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
+    let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
     let value = token.value()?;
     let target = token.syntax().text_range();
 
diff --git a/crates/assists/src/handlers/split_import.rs b/crates/assists/src/handlers/split_import.rs
index ef1f6b8a1a..15e67eaa19 100644
--- a/crates/assists/src/handlers/split_import.rs
+++ b/crates/assists/src/handlers/split_import.rs
@@ -16,7 +16,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // use std::{collections::HashMap};
 // ```
 pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let colon_colon = ctx.find_token_syntax_at_offset(T![::])?;
+    let colon_colon = ctx.find_token_at_offset(T![::])?;
     let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
     let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
 
diff --git a/crates/assists/src/handlers/unwrap_block.rs b/crates/assists/src/handlers/unwrap_block.rs
index 36ef871b93..3851aeb3e7 100644
--- a/crates/assists/src/handlers/unwrap_block.rs
+++ b/crates/assists/src/handlers/unwrap_block.rs
@@ -29,7 +29,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
     let assist_label = "Unwrap block";
 
-    let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
+    let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
     let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
     let mut parent = block.syntax().parent()?;
     if ast::MatchArm::can_cast(parent.kind()) {
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index af88b34374..b804e495d0 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -143,7 +143,6 @@ mod handlers {
     mod generate_function;
     mod generate_impl;
     mod generate_new;
-    mod infer_function_return_type;
     mod inline_local_variable;
     mod introduce_named_lifetime;
     mod invert_if;
@@ -191,7 +190,6 @@ mod handlers {
             generate_function::generate_function,
             generate_impl::generate_impl,
             generate_new::generate_new,
-            infer_function_return_type::infer_function_return_type,
             inline_local_variable::inline_local_variable,
             introduce_named_lifetime::introduce_named_lifetime,
             invert_if::invert_if,
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 84a0dffdd4..a76bd5ebf6 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,9 +1,12 @@
 //! Handle syntactic aspects of inserting a new `use`.
-use std::{cmp::Ordering, iter::successors};
+use std::{
+    cmp::Ordering,
+    iter::{self, successors},
+};
 
 use itertools::{EitherOrBoth, Itertools};
 use syntax::{
-    algo::SyntaxRewriter,
+    algo,
     ast::{
         self,
         edit::{AstNodeEdit, IndentLevel},
@@ -85,19 +88,20 @@ impl ImportScope {
 }
 
 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
-pub(crate) fn insert_use<'a>(
+pub(crate) fn insert_use(
     scope: &ImportScope,
     path: ast::Path,
     merge: Option<MergeBehaviour>,
-) -> SyntaxRewriter<'a> {
-    let mut rewriter = SyntaxRewriter::default();
+) -> SyntaxNode {
     let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
     // merge into existing imports if possible
     if let Some(mb) = merge {
         for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
             if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
-                rewriter.replace(existing_use.syntax(), merged.syntax());
-                return rewriter;
+                let to_delete: SyntaxElement = existing_use.syntax().clone().into();
+                let to_delete = to_delete.clone()..=to_delete;
+                let to_insert = iter::once(merged.syntax().clone().into());
+                return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
             }
         }
     }
@@ -153,15 +157,7 @@ pub(crate) fn insert_use<'a>(
         buf
     };
 
-    match insert_position {
-        InsertPosition::First => {
-            rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
-        }
-        InsertPosition::Last => return rewriter, // actually unreachable
-        InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
-        InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
-    }
-    rewriter
+    algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
 }
 
 fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -1105,8 +1101,7 @@ use foo::bar::baz::Qux;",
             .find_map(ast::Path::cast)
             .unwrap();
 
-        let rewriter = insert_use(&file, path, mb);
-        let result = rewriter.rewrite(file.as_syntax_node()).to_string();
+        let result = insert_use(&file, path, mb).to_string();
         assert_eq_text!(&result, ra_fixture_after);
     }
 
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index cd79587461..1deaa90f21 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -953,19 +953,18 @@ impl From<ast::BinOp> for BinaryOp {
 impl From<ast::LiteralKind> for Literal {
     fn from(ast_lit_kind: ast::LiteralKind) -> Self {
         match ast_lit_kind {
-            LiteralKind::IntNumber(lit) => {
-                if let Some(float_suffix) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
-                    return Literal::Float(Default::default(), Some(float_suffix));
-                }
-                let ty = lit.suffix().and_then(|it| BuiltinInt::from_suffix(&it));
-                Literal::Int(Default::default(), ty)
+            LiteralKind::IntNumber { suffix } => {
+                let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it));
+
+                Literal::Int(Default::default(), known_name)
             }
-            LiteralKind::FloatNumber(lit) => {
-                let ty = lit.suffix().and_then(|it| BuiltinFloat::from_suffix(&it));
-                Literal::Float(Default::default(), ty)
+            LiteralKind::FloatNumber { suffix } => {
+                let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it));
+
+                Literal::Float(Default::default(), known_name)
             }
-            LiteralKind::ByteString(_) => Literal::ByteString(Default::default()),
-            LiteralKind::String(_) => Literal::String(Default::default()),
+            LiteralKind::ByteString => Literal::ByteString(Default::default()),
+            LiteralKind::String => Literal::String(Default::default()),
             LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
             LiteralKind::Bool(val) => Literal::Bool(val),
             LiteralKind::Char => Literal::Char(Default::default()),
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 386287518a..59b6644c38 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1116,20 +1116,17 @@ impl ModCollector<'_, '_> {
                     &self.item_tree[module.visibility],
                 );
 
-                if let Some(mod_dir) = self.mod_dir.descend_into_definition(&module.name, path_attr)
-                {
-                    ModCollector {
-                        def_collector: &mut *self.def_collector,
-                        macro_depth: self.macro_depth,
-                        module_id,
-                        file_id: self.file_id,
-                        item_tree: self.item_tree,
-                        mod_dir,
-                    }
-                    .collect(&*items);
-                    if is_macro_use {
-                        self.import_all_legacy_macros(module_id);
-                    }
+                ModCollector {
+                    def_collector: &mut *self.def_collector,
+                    macro_depth: self.macro_depth,
+                    module_id,
+                    file_id: self.file_id,
+                    item_tree: self.item_tree,
+                    mod_dir: self.mod_dir.descend_into_definition(&module.name, path_attr),
+                }
+                .collect(&*items);
+                if is_macro_use {
+                    self.import_all_legacy_macros(module_id);
                 }
             }
             // out of line module, resolve, parse and recurse
diff --git a/crates/hir_def/src/nameres/mod_resolution.rs b/crates/hir_def/src/nameres/mod_resolution.rs
index c0c789caef..e8389b4846 100644
--- a/crates/hir_def/src/nameres/mod_resolution.rs
+++ b/crates/hir_def/src/nameres/mod_resolution.rs
@@ -2,12 +2,9 @@
 use base_db::FileId;
 use hir_expand::name::Name;
 use syntax::SmolStr;
-use test_utils::mark;
 
 use crate::{db::DefDatabase, HirFileId};
 
-const MOD_DEPTH_LIMIT: u32 = 32;
-
 #[derive(Clone, Debug)]
 pub(super) struct ModDir {
     /// `` for `mod.rs`, `lib.rs`
@@ -17,28 +14,18 @@ pub(super) struct ModDir {
     dir_path: DirPath,
     /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/`
     root_non_dir_owner: bool,
-    depth: u32,
 }
 
 impl ModDir {
     pub(super) fn root() -> ModDir {
-        ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false, depth: 0 }
-    }
-    fn child(&self, dir_path: DirPath, root_non_dir_owner: bool) -> Option<ModDir> {
-        let depth = self.depth + 1;
-        if depth > MOD_DEPTH_LIMIT {
-            log::error!("MOD_DEPTH_LIMIT exceeded");
-            mark::hit!(circular_mods);
-            return None;
-        }
-        Some(ModDir { dir_path, root_non_dir_owner, depth })
+        ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false }
     }
 
     pub(super) fn descend_into_definition(
         &self,
         name: &Name,
         attr_path: Option<&SmolStr>,
-    ) -> Option<ModDir> {
+    ) -> ModDir {
         let path = match attr_path.map(|it| it.as_str()) {
             None => {
                 let mut path = self.dir_path.clone();
@@ -53,7 +40,7 @@ impl ModDir {
                 DirPath::new(path)
             }
         };
-        self.child(path, false)
+        ModDir { dir_path: path, root_non_dir_owner: false }
     }
 
     pub(super) fn resolve_declaration(
@@ -85,9 +72,7 @@ impl ModDir {
                 } else {
                     (DirPath::new(format!("{}/", name)), true)
                 };
-                if let Some(mod_dir) = self.child(dir_path, root_non_dir_owner) {
-                    return Ok((file_id, is_mod_rs, mod_dir));
-                }
+                return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner }));
             }
         }
         Err(candidate_files.remove(0))
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index aebbfc4dfe..86918b6269 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -8,7 +8,7 @@ use base_db::FileId;
 use either::Either;
 use mbe::parse_to_token_tree;
 use parser::FragmentKind;
-use syntax::ast::{self, AstToken};
+use syntax::ast::{self, AstToken, HasStringValue};
 
 macro_rules! register_builtin {
     ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),*  ) => {
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index d275dd75ba..02e17ba437 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -157,8 +157,7 @@ fn missing_record_expr_field_fix(
         return None;
     }
     let new_field = make::record_field(
-        None,
-        make::name(record_expr_field.field_name()?.text()),
+        record_expr_field.field_name()?,
         make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
     );
 
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 0971f77015..3ee0af8ad4 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -35,7 +35,7 @@ fn try_extend_selection(
 ) -> Option<TextRange> {
     let range = frange.range;
 
-    let string_kinds = [COMMENT, STRING, BYTE_STRING];
+    let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
     let list_kinds = [
         RECORD_PAT_FIELD_LIST,
         MATCH_ARM_LIST,
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 6cfb22e13e..ac704ae217 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -162,7 +162,7 @@ fn get_param_name_hints(
         .zip(args)
         .filter_map(|((param, _ty), arg)| {
             let param_name = match param? {
-                Either::Left(_) => "self".to_string(),
+                Either::Left(self_param) => self_param.to_string(),
                 Either::Right(pat) => match pat {
                     ast::Pat::IdentPat(it) => it.name()?.to_string(),
                     _ => return None,
@@ -809,7 +809,7 @@ fn main() {
     t.method(123);
            //^^^ param
     Test::method(&t,      3456);
-               //^^ self  ^^^^ param
+               //^^ &self ^^^^ param
     Test::from_syntax(
         FileId {},
       //^^^^^^^^^ file_id
@@ -1360,25 +1360,4 @@ fn main() {
             "#,
         );
     }
-
-    #[test]
-    fn self_param_hints() {
-        check(
-            r#"
-struct Foo;
-
-impl Foo {
-    fn foo(self: Self) {}
-    fn bar(self: &Self) {}
-}
-
-fn main() {
-    Foo::foo(Foo);
-           //^^^ self
-    Foo::bar(&Foo);
-           //^^^^ self
-}
-"#,
-        )
-    }
 }
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 05bafe9c89..efcc8ecfe6 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -179,12 +179,10 @@ pub(crate) fn highlight(
             element.clone()
         };
 
-        if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) {
-            if token.is_raw() {
-                let expanded = element_to_highlight.as_token().unwrap().clone();
-                if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
-                    continue;
-                }
+        if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
+            let expanded = element_to_highlight.as_token().unwrap().clone();
+            if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
+                continue;
             }
         }
 
@@ -216,6 +214,10 @@ pub(crate) fn highlight(
                     }
                     stack.pop_and_inject(None);
                 }
+            } else if let Some(string) =
+                element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
+            {
+                format_string_highlighter.highlight_format_string(&mut stack, &string, range);
             }
         }
     }
@@ -530,7 +532,7 @@ fn highlight_element(
                 None => h.into(),
             }
         }
-        STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
+        STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
         ATTR => HighlightTag::Attribute.into(),
         INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
         BYTE => HighlightTag::ByteLiteral.into(),
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index 42f27df5d9..71bde24f08 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -29,7 +29,9 @@ impl FormatStringHighlighter {
                         .children_with_tokens()
                         .filter(|t| t.kind() != SyntaxKind::WHITESPACE)
                         .nth(1)
-                        .filter(|e| ast::String::can_cast(e.kind()))
+                        .filter(|e| {
+                            ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind())
+                        })
                 }
                 _ => {}
             }
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs
index e97d1be1aa..59a74bc02e 100644
--- a/crates/ide/src/syntax_highlighting/injection.rs
+++ b/crates/ide/src/syntax_highlighting/injection.rs
@@ -2,6 +2,7 @@
 
 use std::{collections::BTreeMap, convert::TryFrom};
 
+use ast::{HasQuotes, HasStringValue};
 use hir::Semantics;
 use ide_db::call_info::ActiveParameter;
 use itertools::Itertools;
@@ -14,7 +15,7 @@ use super::HighlightedRangeStack;
 pub(super) fn highlight_injection(
     acc: &mut HighlightedRangeStack,
     sema: &Semantics<RootDatabase>,
-    literal: ast::String,
+    literal: ast::RawString,
     expanded: SyntaxToken,
 ) -> Option<()> {
     let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 6dd05c05de..7941610d6b 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -1,7 +1,9 @@
 use ide_db::base_db::{FileId, SourceDatabase};
 use ide_db::RootDatabase;
 use syntax::{
-    algo, AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
+    algo, AstNode, NodeOrToken, SourceFile,
+    SyntaxKind::{RAW_STRING, STRING},
+    SyntaxToken, TextRange, TextSize,
 };
 
 // Feature: Show Syntax Tree
@@ -44,7 +46,7 @@ fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<
     // we'll attempt parsing it as rust syntax
     // to provide the syntax tree of the contents of the string
     match token.kind() {
-        STRING => syntax_tree_for_token(token, text_range),
+        STRING | RAW_STRING => syntax_tree_for_token(token, text_range),
         _ => None,
     }
 }
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 116b991a82..4ab206a838 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -236,7 +236,10 @@ fn abi(p: &mut Parser) {
     assert!(p.at(T![extern]));
     let abi = p.start();
     p.bump(T![extern]);
-    p.eat(STRING);
+    match p.current() {
+        STRING | RAW_STRING => p.bump_any(),
+        _ => (),
+    }
     abi.complete(p, ABI);
 }
 
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 31f42f1618..66a92a4e11 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -15,8 +15,18 @@ use super::*;
 //     let _ = b"e";
 //     let _ = br"f";
 // }
-pub(crate) const LITERAL_FIRST: TokenSet =
-    TokenSet::new(&[TRUE_KW, FALSE_KW, INT_NUMBER, FLOAT_NUMBER, BYTE, CHAR, STRING, BYTE_STRING]);
+pub(crate) const LITERAL_FIRST: TokenSet = TokenSet::new(&[
+    TRUE_KW,
+    FALSE_KW,
+    INT_NUMBER,
+    FLOAT_NUMBER,
+    BYTE,
+    CHAR,
+    STRING,
+    RAW_STRING,
+    BYTE_STRING,
+    RAW_BYTE_STRING,
+]);
 
 pub(crate) fn literal(p: &mut Parser) -> Option<CompletedMarker> {
     if !p.at_ts(LITERAL_FIRST) {
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 780bc470a4..22810e6fb8 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -239,7 +239,9 @@ fn items_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> {
         T![static] => consts::static_(p, m),
         // test extern_block
         // extern {}
-        T![extern] if la == T!['{'] || (la == STRING && p.nth(2) == T!['{']) => {
+        T![extern]
+            if la == T!['{'] || ((la == STRING || la == RAW_STRING) && p.nth(2) == T!['{']) =>
+        {
             abi(p);
             extern_item_list(p);
             m.complete(p, EXTERN_BLOCK);
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 8bc6688f3c..935bd2c5e9 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -111,7 +111,9 @@ pub enum SyntaxKind {
     CHAR,
     BYTE,
     STRING,
+    RAW_STRING,
     BYTE_STRING,
+    RAW_BYTE_STRING,
     ERROR,
     IDENT,
     WHITESPACE,
@@ -275,7 +277,8 @@ impl SyntaxKind {
     }
     pub fn is_literal(self) -> bool {
         match self {
-            INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE | STRING | BYTE_STRING => true,
+            INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE | STRING | RAW_STRING | BYTE_STRING
+            | RAW_BYTE_STRING => true,
             _ => false,
         }
     }
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index b0e8863f6a..3fe494729b 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -114,12 +114,8 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
     if let Ok(path) = env::var("RUST_SRC_PATH") {
         let path = AbsPathBuf::try_from(path.as_str())
             .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
-        let core = path.join("core");
-        if core.exists() {
-            log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
-            return Ok(path);
-        }
-        log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
+        log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
+        return Ok(path);
     }
 
     let sysroot_path = {
diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs
index 9253c97d08..f5ba872233 100644
--- a/crates/syntax/src/ast/expr_ext.rs
+++ b/crates/syntax/src/ast/expr_ext.rs
@@ -2,7 +2,7 @@
 
 use crate::{
     ast::{self, support, AstChildren, AstNode},
-    AstToken,
+    SmolStr,
     SyntaxKind::*,
     SyntaxToken, T,
 };
@@ -298,12 +298,12 @@ impl ast::ArrayExpr {
 
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum LiteralKind {
-    String(ast::String),
-    ByteString(ast::ByteString),
-    IntNumber(ast::IntNumber),
-    FloatNumber(ast::FloatNumber),
+    String,
+    ByteString,
     Char,
     Byte,
+    IntNumber { suffix: Option<SmolStr> },
+    FloatNumber { suffix: Option<SmolStr> },
     Bool(bool),
 }
 
@@ -315,25 +315,44 @@ impl ast::Literal {
             .and_then(|e| e.into_token())
             .unwrap()
     }
+
+    fn find_suffix(text: &str, possible_suffixes: &[&str]) -> Option<SmolStr> {
+        possible_suffixes
+            .iter()
+            .find(|&suffix| text.ends_with(suffix))
+            .map(|&suffix| SmolStr::new(suffix))
+    }
+
     pub fn kind(&self) -> LiteralKind {
-        let token = self.token();
+        const INT_SUFFIXES: [&str; 12] = [
+            "u64", "u32", "u16", "u8", "usize", "isize", "i64", "i32", "i16", "i8", "u128", "i128",
+        ];
+        const FLOAT_SUFFIXES: [&str; 2] = ["f32", "f64"];
 
-        if let Some(t) = ast::IntNumber::cast(token.clone()) {
-            return LiteralKind::IntNumber(t);
-        }
-        if let Some(t) = ast::FloatNumber::cast(token.clone()) {
-            return LiteralKind::FloatNumber(t);
-        }
-        if let Some(t) = ast::String::cast(token.clone()) {
-            return LiteralKind::String(t);
-        }
-        if let Some(t) = ast::ByteString::cast(token.clone()) {
-            return LiteralKind::ByteString(t);
-        }
+        let token = self.token();
 
         match token.kind() {
+            INT_NUMBER => {
+                // FYI: there was a bug here previously, thus the if statement below is necessary.
+                // The lexer treats e.g. `1f64` as an integer literal. See
+                // https://github.com/rust-analyzer/rust-analyzer/issues/1592
+                // and the comments on the linked PR.
+
+                let text = token.text();
+                if let suffix @ Some(_) = Self::find_suffix(&text, &FLOAT_SUFFIXES) {
+                    LiteralKind::FloatNumber { suffix }
+                } else {
+                    LiteralKind::IntNumber { suffix: Self::find_suffix(&text, &INT_SUFFIXES) }
+                }
+            }
+            FLOAT_NUMBER => {
+                let text = token.text();
+                LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) }
+            }
+            STRING | RAW_STRING => LiteralKind::String,
             T![true] => LiteralKind::Bool(true),
             T![false] => LiteralKind::Bool(false),
+            BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString,
             CHAR => LiteralKind::Char,
             BYTE => LiteralKind::Byte,
             _ => unreachable!(),
diff --git a/crates/syntax/src/ast/generated/tokens.rs b/crates/syntax/src/ast/generated/tokens.rs
index 728b72cd77..abadd0b61c 100644
--- a/crates/syntax/src/ast/generated/tokens.rs
+++ b/crates/syntax/src/ast/generated/tokens.rs
@@ -70,58 +70,16 @@ impl AstToken for String {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ByteString {
+pub struct RawString {
     pub(crate) syntax: SyntaxToken,
 }
-impl std::fmt::Display for ByteString {
+impl std::fmt::Display for RawString {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(&self.syntax, f)
     }
 }
-impl AstToken for ByteString {
-    fn can_cast(kind: SyntaxKind) -> bool { kind == BYTE_STRING }
-    fn cast(syntax: SyntaxToken) -> Option<Self> {
-        if Self::can_cast(syntax.kind()) {
-            Some(Self { syntax })
-        } else {
-            None
-        }
-    }
-    fn syntax(&self) -> &SyntaxToken { &self.syntax }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct IntNumber {
-    pub(crate) syntax: SyntaxToken,
-}
-impl std::fmt::Display for IntNumber {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Display::fmt(&self.syntax, f)
-    }
-}
-impl AstToken for IntNumber {
-    fn can_cast(kind: SyntaxKind) -> bool { kind == INT_NUMBER }
-    fn cast(syntax: SyntaxToken) -> Option<Self> {
-        if Self::can_cast(syntax.kind()) {
-            Some(Self { syntax })
-        } else {
-            None
-        }
-    }
-    fn syntax(&self) -> &SyntaxToken { &self.syntax }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct FloatNumber {
-    pub(crate) syntax: SyntaxToken,
-}
-impl std::fmt::Display for FloatNumber {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Display::fmt(&self.syntax, f)
-    }
-}
-impl AstToken for FloatNumber {
-    fn can_cast(kind: SyntaxKind) -> bool { kind == FLOAT_NUMBER }
+impl AstToken for RawString {
+    fn can_cast(kind: SyntaxKind) -> bool { kind == RAW_STRING }
     fn cast(syntax: SyntaxToken) -> Option<Self> {
         if Self::can_cast(syntax.kind()) {
             Some(Self { syntax })
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index b1578820fe..5b06cb7671 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -110,16 +110,8 @@ pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::Re
     }
 }
 
-pub fn record_field(
-    visibility: Option<ast::Visibility>,
-    name: ast::Name,
-    ty: ast::Type,
-) -> ast::RecordField {
-    let visibility = match visibility {
-        None => String::new(),
-        Some(it) => format!("{} ", it),
-    };
-    ast_from_text(&format!("struct S {{ {}{}: {}, }}", visibility, name, ty))
+pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField {
+    ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty))
 }
 
 pub fn block_expr(
@@ -359,38 +351,6 @@ pub fn visibility_pub_crate() -> ast::Visibility {
     ast_from_text("pub(crate) struct S")
 }
 
-pub fn visibility_pub() -> ast::Visibility {
-    ast_from_text("pub struct S")
-}
-
-pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> ast::TupleFieldList {
-    let fields = fields.into_iter().join(", ");
-    ast_from_text(&format!("struct f({});", fields))
-}
-
-pub fn record_field_list(
-    fields: impl IntoIterator<Item = ast::RecordField>,
-) -> ast::RecordFieldList {
-    let fields = fields.into_iter().join(", ");
-    ast_from_text(&format!("struct f {{ {} }}", fields))
-}
-
-pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField {
-    let visibility = match visibility {
-        None => String::new(),
-        Some(it) => format!("{} ", it),
-    };
-    ast_from_text(&format!("struct f({}{});", visibility, ty))
-}
-
-pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant {
-    let field_list = match field_list {
-        None => String::new(),
-        Some(it) => format!("{}", it),
-    };
-    ast_from_text(&format!("enum f {{ {}{} }}", name, field_list))
-}
-
 pub fn fn_(
     visibility: Option<ast::Visibility>,
     fn_name: ast::Name,
@@ -413,26 +373,6 @@ pub fn fn_(
     ))
 }
 
-pub fn struct_(
-    visibility: Option<ast::Visibility>,
-    strukt_name: ast::Name,
-    type_params: Option<ast::GenericParamList>,
-    field_list: ast::FieldList,
-) -> ast::Struct {
-    let semicolon = if matches!(field_list, ast::FieldList::TupleFieldList(_)) { ";" } else { "" };
-    let type_params =
-        if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
-    let visibility = match visibility {
-        None => String::new(),
-        Some(it) => format!("{} ", it),
-    };
-
-    ast_from_text(&format!(
-        "{}struct {}{}{}{}",
-        visibility, strukt_name, type_params, field_list, semicolon
-    ))
-}
-
 fn ast_from_text<N: AstNode>(text: &str) -> N {
     let parse = SourceFile::parse(text);
     let node = match parse.tree().syntax().descendants().find_map(N::cast) {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index ce35ac01af..c5cd1c504d 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -7,7 +7,7 @@ use itertools::Itertools;
 use parser::SyntaxKind;
 
 use crate::{
-    ast::{self, support, AstNode, AstToken, NameOwner, SyntaxNode},
+    ast::{self, support, token_ext::HasStringValue, AstNode, AstToken, NameOwner, SyntaxNode},
     SmolStr, SyntaxElement, SyntaxToken, T,
 };
 
@@ -55,7 +55,13 @@ impl ast::Attr {
         let key = self.simple_name()?;
         let value_token = lit.syntax().first_token()?;
 
-        let value: SmolStr = ast::String::cast(value_token.clone())?.value()?.into();
+        let value: SmolStr = if let Some(s) = ast::String::cast(value_token.clone()) {
+            s.value()?.into()
+        } else if let Some(s) = ast::RawString::cast(value_token) {
+            s.value()?.into()
+        } else {
+            return None;
+        };
 
         Some((key, value))
     }
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index e4e512f2e0..c5ef92733d 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -8,11 +8,11 @@ use std::{
 use rustc_lexer::unescape::{unescape_literal, Mode};
 
 use crate::{
-    ast::{self, AstToken},
+    ast::{AstToken, Comment, RawString, String, Whitespace},
     TextRange, TextSize,
 };
 
-impl ast::Comment {
+impl Comment {
     pub fn kind(&self) -> CommentKind {
         kind_by_prefix(self.text())
     }
@@ -80,7 +80,7 @@ fn kind_by_prefix(text: &str) -> CommentKind {
     panic!("bad comment text: {:?}", text)
 }
 
-impl ast::Whitespace {
+impl Whitespace {
     pub fn spans_multiple_lines(&self) -> bool {
         let text = self.text();
         text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
@@ -114,28 +114,43 @@ impl QuoteOffsets {
     }
 }
 
-impl ast::String {
-    pub fn is_raw(&self) -> bool {
-        self.text().starts_with('r')
+pub trait HasQuotes: AstToken {
+    fn quote_offsets(&self) -> Option<QuoteOffsets> {
+        let text = self.text().as_str();
+        let offsets = QuoteOffsets::new(text)?;
+        let o = self.syntax().text_range().start();
+        let offsets = QuoteOffsets {
+            quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
+            contents: offsets.contents + o,
+        };
+        Some(offsets)
     }
-    pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
-        let contents_range = self.text_range_between_quotes()?;
-        assert!(TextRange::up_to(contents_range.len()).contains_range(range));
-        Some(range + contents_range.start())
+    fn open_quote_text_range(&self) -> Option<TextRange> {
+        self.quote_offsets().map(|it| it.quotes.0)
     }
 
-    pub fn value(&self) -> Option<Cow<'_, str>> {
-        if self.is_raw() {
-            let text = self.text().as_str();
-            let text =
-                &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
-            return Some(Cow::Borrowed(text));
-        }
+    fn close_quote_text_range(&self) -> Option<TextRange> {
+        self.quote_offsets().map(|it| it.quotes.1)
+    }
 
+    fn text_range_between_quotes(&self) -> Option<TextRange> {
+        self.quote_offsets().map(|it| it.contents)
+    }
+}
+
+impl HasQuotes for String {}
+impl HasQuotes for RawString {}
+
+pub trait HasStringValue: HasQuotes {
+    fn value(&self) -> Option<Cow<'_, str>>;
+}
+
+impl HasStringValue for String {
+    fn value(&self) -> Option<Cow<'_, str>> {
         let text = self.text().as_str();
         let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
 
-        let mut buf = String::with_capacity(text.len());
+        let mut buf = std::string::String::with_capacity(text.len());
         let mut has_error = false;
         unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char {
             Ok(c) => buf.push(c),
@@ -149,31 +164,21 @@ impl ast::String {
         let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) };
         Some(res)
     }
+}
 
-    pub fn quote_offsets(&self) -> Option<QuoteOffsets> {
+impl HasStringValue for RawString {
+    fn value(&self) -> Option<Cow<'_, str>> {
         let text = self.text().as_str();
-        let offsets = QuoteOffsets::new(text)?;
-        let o = self.syntax().text_range().start();
-        let offsets = QuoteOffsets {
-            quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
-            contents: offsets.contents + o,
-        };
-        Some(offsets)
-    }
-    pub fn text_range_between_quotes(&self) -> Option<TextRange> {
-        self.quote_offsets().map(|it| it.contents)
-    }
-    pub fn open_quote_text_range(&self) -> Option<TextRange> {
-        self.quote_offsets().map(|it| it.quotes.0)
-    }
-    pub fn close_quote_text_range(&self) -> Option<TextRange> {
-        self.quote_offsets().map(|it| it.quotes.1)
+        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+        Some(Cow::Borrowed(text))
     }
 }
 
-impl ast::ByteString {
-    pub fn is_raw(&self) -> bool {
-        self.text().starts_with("br")
+impl RawString {
+    pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
+        let contents_range = self.text_range_between_quotes()?;
+        assert!(TextRange::up_to(contents_range.len()).contains_range(range));
+        Some(range + contents_range.start())
     }
 }
 
@@ -495,7 +500,7 @@ pub trait HasFormatSpecifier: AstToken {
     }
 }
 
-impl HasFormatSpecifier for ast::String {
+impl HasFormatSpecifier for String {
     fn char_ranges(
         &self,
     ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
@@ -516,86 +521,18 @@ impl HasFormatSpecifier for ast::String {
     }
 }
 
-impl ast::IntNumber {
-    const SUFFIXES: &'static [&'static str] = &[
-        "u8", "u16", "u32", "u64", "u128", "usize", // Unsigned.
-        "i8", "i16", "i32", "i64", "i128", "isize", // Signed.
-    ];
-
-    pub fn radix(&self) -> Radix {
-        match self.text().get(..2).unwrap_or_default() {
-            "0b" => Radix::Binary,
-            "0o" => Radix::Octal,
-            "0x" => Radix::Hexadecimal,
-            _ => Radix::Decimal,
-        }
-    }
-
-    pub fn value(&self) -> Option<u128> {
-        let token = self.syntax();
-
-        let mut text = token.text().as_str();
-        if let Some(suffix) = self.suffix() {
-            text = &text[..text.len() - suffix.len()]
-        }
-
-        let radix = self.radix();
-        text = &text[radix.prefix_len()..];
-
-        let buf;
-        if text.contains("_") {
-            buf = text.replace('_', "");
-            text = buf.as_str();
-        };
-
-        let value = u128::from_str_radix(text, radix as u32).ok()?;
-        Some(value)
-    }
-
-    pub fn suffix(&self) -> Option<&str> {
-        let text = self.text();
-        // FIXME: don't check a fixed set of suffixes, `1_0_1_l_o_l` is valid
-        // syntax, suffix is `l_o_l`.
-        ast::IntNumber::SUFFIXES.iter().chain(ast::FloatNumber::SUFFIXES.iter()).find_map(
-            |suffix| {
-                if text.ends_with(suffix) {
-                    return Some(&text[text.len() - suffix.len()..]);
-                }
-                None
-            },
-        )
-    }
-}
-
-impl ast::FloatNumber {
-    const SUFFIXES: &'static [&'static str] = &["f32", "f64"];
-    pub fn suffix(&self) -> Option<&str> {
-        let text = self.text();
-        ast::FloatNumber::SUFFIXES.iter().find_map(|suffix| {
-            if text.ends_with(suffix) {
-                return Some(&text[text.len() - suffix.len()..]);
-            }
-            None
-        })
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-pub enum Radix {
-    Binary = 2,
-    Octal = 8,
-    Decimal = 10,
-    Hexadecimal = 16,
-}
-
-impl Radix {
-    pub const ALL: &'static [Radix] =
-        &[Radix::Binary, Radix::Octal, Radix::Decimal, Radix::Hexadecimal];
+impl HasFormatSpecifier for RawString {
+    fn char_ranges(
+        &self,
+    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
+        let text = self.text().as_str();
+        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
+        let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
 
-    const fn prefix_len(&self) -> usize {
-        match self {
-            Self::Decimal => 0,
-            _ => 2,
+        let mut res = Vec::with_capacity(text.len());
+        for (idx, c) in text.char_indices() {
+            res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c)));
         }
+        Some(res)
     }
 }
diff --git a/crates/syntax/src/parsing/lexer.rs b/crates/syntax/src/parsing/lexer.rs
index 8afd7e53bc..7e38c32cc5 100644
--- a/crates/syntax/src/parsing/lexer.rs
+++ b/crates/syntax/src/parsing/lexer.rs
@@ -3,7 +3,7 @@
 
 use std::convert::TryInto;
 
-use rustc_lexer::RawStrError;
+use rustc_lexer::{LiteralKind as LK, RawStrError};
 
 use crate::{
     SyntaxError,
@@ -185,77 +185,63 @@ fn rustc_token_kind_to_syntax_kind(
     return (syntax_kind, None);
 
     fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<&'static str>) {
-        let mut err = "";
+        #[rustfmt::skip]
         let syntax_kind = match *kind {
-            rustc_lexer::LiteralKind::Int { empty_int, base: _ } => {
-                if empty_int {
-                    err = "Missing digits after the integer base prefix";
-                }
-                INT_NUMBER
-            }
-            rustc_lexer::LiteralKind::Float { empty_exponent, base: _ } => {
-                if empty_exponent {
-                    err = "Missing digits after the exponent symbol";
-                }
-                FLOAT_NUMBER
-            }
-            rustc_lexer::LiteralKind::Char { terminated } => {
-                if !terminated {
-                    err = "Missing trailing `'` symbol to terminate the character literal";
-                }
-                CHAR
+            LK::Int { empty_int: false, .. } => INT_NUMBER,
+            LK::Int { empty_int: true, .. } => {
+                return (INT_NUMBER, Some("Missing digits after the integer base prefix"))
             }
-            rustc_lexer::LiteralKind::Byte { terminated } => {
-                if !terminated {
-                    err = "Missing trailing `'` symbol to terminate the byte literal";
-                }
-                BYTE
+
+            LK::Float { empty_exponent: false, .. } => FLOAT_NUMBER,
+            LK::Float { empty_exponent: true, .. } => {
+                return (FLOAT_NUMBER, Some("Missing digits after the exponent symbol"))
             }
-            rustc_lexer::LiteralKind::Str { terminated } => {
-                if !terminated {
-                    err = "Missing trailing `\"` symbol to terminate the string literal";
-                }
-                STRING
+
+            LK::Char { terminated: true } => CHAR,
+            LK::Char { terminated: false } => {
+                return (CHAR, Some("Missing trailing `'` symbol to terminate the character literal"))
             }
-            rustc_lexer::LiteralKind::ByteStr { terminated } => {
-                if !terminated {
-                    err = "Missing trailing `\"` symbol to terminate the byte string literal";
-                }
-                BYTE_STRING
+
+            LK::Byte { terminated: true } => BYTE,
+            LK::Byte { terminated: false } => {
+                return (BYTE, Some("Missing trailing `'` symbol to terminate the byte literal"))
             }
-            rustc_lexer::LiteralKind::RawStr { err: raw_str_err, .. } => {
-                if let Some(raw_str_err) = raw_str_err {
-                    err = match raw_str_err {
-                        RawStrError::InvalidStarter { .. } => "Missing `\"` symbol after `#` symbols to begin the raw string literal",
-                        RawStrError::NoTerminator { expected, found, .. } => if expected == found {
-                            "Missing trailing `\"` to terminate the raw string literal"
-                        } else {
-                            "Missing trailing `\"` with `#` symbols to terminate the raw string literal"
-                        },
-                        RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols",
-                    };
-                };
-                STRING
+
+            LK::Str { terminated: true } => STRING,
+            LK::Str { terminated: false } => {
+                return (STRING, Some("Missing trailing `\"` symbol to terminate the string literal"))
             }
-            rustc_lexer::LiteralKind::RawByteStr { err: raw_str_err, .. } => {
-                if let Some(raw_str_err) = raw_str_err {
-                    err = match raw_str_err {
-                        RawStrError::InvalidStarter { .. } => "Missing `\"` symbol after `#` symbols to begin the raw byte string literal",
-                        RawStrError::NoTerminator { expected, found, .. } => if expected == found {
-                            "Missing trailing `\"` to terminate the raw byte string literal"
-                        } else {
-                            "Missing trailing `\"` with `#` symbols to terminate the raw byte string literal"
-                        },
-                        RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw byte strings may be delimited by up to 65535 `#` symbols",
-                    };
-                };
-
-                BYTE_STRING
+
+
+            LK::ByteStr { terminated: true } => BYTE_STRING,
+            LK::ByteStr { terminated: false } => {
+                return (BYTE_STRING, Some("Missing trailing `\"` symbol to terminate the byte string literal"))
             }
-        };
 
-        let err = if err.is_empty() { None } else { Some(err) };
+            LK::RawStr { err, .. } => match err {
+                None => RAW_STRING,
+                Some(RawStrError::InvalidStarter { .. }) => return (RAW_STRING, Some("Missing `\"` symbol after `#` symbols to begin the raw string literal")),
+                Some(RawStrError::NoTerminator { expected, found, .. }) => if expected == found {
+                    return (RAW_STRING, Some("Missing trailing `\"` to terminate the raw string literal"))
+                } else {
+                    return (RAW_STRING, Some("Missing trailing `\"` with `#` symbols to terminate the raw string literal"))
+
+                },
+                Some(RawStrError::TooManyDelimiters { .. }) => return (RAW_STRING, Some("Too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols")),
+            },
+            LK::RawByteStr { err, .. } => match err {
+                None => RAW_BYTE_STRING,
+                Some(RawStrError::InvalidStarter { .. }) => return (RAW_BYTE_STRING, Some("Missing `\"` symbol after `#` symbols to begin the raw byte string literal")),
+                Some(RawStrError::NoTerminator { expected, found, .. }) => if expected == found {
+                    return (RAW_BYTE_STRING, Some("Missing trailing `\"` to terminate the raw byte string literal"))
+                } else {
+                    return (RAW_BYTE_STRING, Some("Missing trailing `\"` with `#` symbols to terminate the raw byte string literal"))
+
+                },
+                Some(RawStrError::TooManyDelimiters { .. }) => return (RAW_BYTE_STRING, Some("Too many `#` symbols: raw byte strings may be delimited by up to 65535 `#` symbols")),
+            },
+        };
 
-        (syntax_kind, err)
+        (syntax_kind, None)
     }
 }
diff --git a/crates/syntax/src/parsing/reparsing.rs b/crates/syntax/src/parsing/reparsing.rs
index 190f5f67a4..4149f856a8 100644
--- a/crates/syntax/src/parsing/reparsing.rs
+++ b/crates/syntax/src/parsing/reparsing.rs
@@ -44,7 +44,7 @@ fn reparse_token<'node>(
     let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
     let prev_token_kind = prev_token.kind();
     match prev_token_kind {
-        WHITESPACE | COMMENT | IDENT | STRING => {
+        WHITESPACE | COMMENT | IDENT | STRING | RAW_STRING => {
             if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT {
                 // removing a new line may extends previous token
                 let deleted_range = edit.delete - prev_token.text_range().start();
diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs
index 6f45149bf6..0f9a5e8ae7 100644
--- a/crates/syntax/src/validation.rs
+++ b/crates/syntax/src/validation.rs
@@ -4,7 +4,7 @@ mod block;
 
 use crate::{
     algo, ast, match_ast, AstNode, SyntaxError,
-    SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
+    SyntaxKind::{BYTE, BYTE_STRING, CHAR, CONST, FN, INT_NUMBER, STRING, TYPE_ALIAS},
     SyntaxNode, SyntaxToken, TextSize, T,
 };
 use rowan::Direction;
@@ -121,42 +121,36 @@ fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
         acc.push(SyntaxError::new_at_offset(rustc_unescape_error_to_string(err), off));
     };
 
-    match literal.kind() {
-        ast::LiteralKind::String(s) => {
-            if !s.is_raw() {
-                if let Some(without_quotes) = unquote(text, 1, '"') {
-                    unescape_literal(without_quotes, Mode::Str, &mut |range, char| {
-                        if let Err(err) = char {
-                            push_err(1, (range.start, err));
-                        }
-                    })
-                }
-            }
-        }
-        ast::LiteralKind::ByteString(s) => {
-            if !s.is_raw() {
-                if let Some(without_quotes) = unquote(text, 2, '"') {
-                    unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| {
-                        if let Err(err) = char {
-                            push_err(2, (range.start, err));
-                        }
-                    })
-                }
+    match token.kind() {
+        BYTE => {
+            if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) {
+                push_err(2, e);
             }
         }
-        ast::LiteralKind::Char => {
+        CHAR => {
             if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) {
                 push_err(1, e);
             }
         }
-        ast::LiteralKind::Byte => {
-            if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) {
-                push_err(2, e);
+        BYTE_STRING => {
+            if let Some(without_quotes) = unquote(text, 2, '"') {
+                unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| {
+                    if let Err(err) = char {
+                        push_err(2, (range.start, err));
+                    }
+                })
+            }
+        }
+        STRING => {
+            if let Some(without_quotes) = unquote(text, 1, '"') {
+                unescape_literal(without_quotes, Mode::Str, &mut |range, char| {
+                    if let Err(err) = char {
+                        push_err(1, (range.start, err));
+                    }
+                })
             }
         }
-        ast::LiteralKind::IntNumber(_)
-        | ast::LiteralKind::FloatNumber(_)
-        | ast::LiteralKind::Bool(_) => {}
+        _ => (),
     }
 }
 
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 1a952197fe..8d57fc0498 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -89,32 +89,6 @@ There are many benefits to this:
 It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
 as long as they are still readable.
 
-When using multiline fixtures, use unindented raw string literals:
-
-```rust
-    #[test]
-    fn inline_field_shorthand() {
-        check_assist(
-            inline_local_variable,
-            r"
-struct S { foo: i32}
-fn main() {
-    let <|>foo = 92;
-    S { foo }
-}
-",
-            r"
-struct S { foo: i32}
-fn main() {
-    S { foo: 92 }
-}
-",
-        );
-    }
-```
-
-That way, you can use your editor's "number of selected characters" feature to correlate offsets with test's source code.
-
 ## Preconditions
 
 Express function preconditions in types and force the caller to provide them (rather than checking in callee):
diff --git a/editors/code/rust.tmGrammar.json b/editors/code/rust.tmGrammar.json
index 77595aa006..7475aadbb9 100644
--- a/editors/code/rust.tmGrammar.json
+++ b/editors/code/rust.tmGrammar.json
@@ -693,7 +693,7 @@
                 {
                     "comment": "assignment operators",
                     "name": "keyword.operator.assignment.rust",
-                    "match": "(-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)"
+                    "match": "(\\+=|-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)"
                 },
                 {
                     "comment": "single equal",
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index d032b45b71..1ba2352ee0 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -2,7 +2,6 @@ import * as lc from 'vscode-languageclient/node';
 import * as vscode from 'vscode';
 import * as ra from '../src/lsp_ext';
 import * as Is from 'vscode-languageclient/lib/common/utils/is';
-import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens';
 import { assert } from './util';
 
 function renderCommand(cmd: ra.CommandLink) {
@@ -19,13 +18,6 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri
     return result;
 }
 
-// Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576
-async function semanticHighlightingWorkaround<R, F extends (...args: any[]) => vscode.ProviderResult<R>>(next: F, ...args: Parameters<F>): Promise<R> {
-    const res = await next(...args);
-    if (res == null) throw new Error('busy');
-    return res;
-}
-
 export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
     // '.' Is the fallback if no folder is open
     // TODO?: Workspace folders support Uri's (eg: file://test.txt).
@@ -49,15 +41,6 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
         diagnosticCollectionName: "rustc",
         traceOutputChannel,
         middleware: {
-            provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken, next: DocumentSemanticsTokensSignature): vscode.ProviderResult<vscode.SemanticTokens> {
-                return semanticHighlightingWorkaround(next, document, token);
-            },
-            provideDocumentSemanticTokensEdits(document: vscode.TextDocument, previousResultId: string, token: vscode.CancellationToken, next: DocumentSemanticsTokensEditsSignature): vscode.ProviderResult<vscode.SemanticTokensEdits | vscode.SemanticTokens> {
-                return semanticHighlightingWorkaround(next, document, previousResultId, token);
-            },
-            provideDocumentRangeSemanticTokens(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken, next: DocumentRangeSemanticTokensSignature): vscode.ProviderResult<vscode.SemanticTokens> {
-                return semanticHighlightingWorkaround(next, document, range, token);
-            },
             async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
                 return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then(
                     (result) => {
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs
index 8ceaaf60eb..adc191254d 100644
--- a/xtask/src/ast_src.rs
+++ b/xtask/src/ast_src.rs
@@ -71,7 +71,16 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "trait", "true", "try", "type", "unsafe", "use", "where", "while",
     ],
     contextual_keywords: &["auto", "default", "existential", "union", "raw"],
-    literals: &["INT_NUMBER", "FLOAT_NUMBER", "CHAR", "BYTE", "STRING", "BYTE_STRING"],
+    literals: &[
+        "INT_NUMBER",
+        "FLOAT_NUMBER",
+        "CHAR",
+        "BYTE",
+        "STRING",
+        "RAW_STRING",
+        "BYTE_STRING",
+        "RAW_BYTE_STRING",
+    ],
     tokens: &[
         "ERROR",
         "IDENT",
diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs
index 44460effa8..02f4095ce8 100644
--- a/xtask/src/codegen/gen_syntax.rs
+++ b/xtask/src/codegen/gen_syntax.rs
@@ -504,11 +504,7 @@ impl Field {
 
 fn lower(grammar: &Grammar) -> AstSrc {
     let mut res = AstSrc::default();
-
-    res.tokens = "Whitespace Comment String ByteString IntNumber FloatNumber"
-        .split_ascii_whitespace()
-        .map(|it| it.to_string())
-        .collect::<Vec<_>>();
+    res.tokens = vec!["Whitespace".into(), "Comment".into(), "String".into(), "RawString".into()];
 
     let nodes = grammar.iter().collect::<Vec<_>>();
 


Test patch:
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 168e1626ab..acbf5b6522 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -505,19 +505,6 @@ impl<T: Clone> Ctx<T> {
     )
 }
 
-#[test]
-fn doctest_infer_function_return_type() {
-    check_doc_test(
-        "infer_function_return_type",
-        r#####"
-fn foo() { 4<|>2i32 }
-"#####,
-        r#####"
-fn foo() -> i32 { 42i32 }
-"#####,
-    )
-}
-
 #[test]
 fn doctest_inline_local_variable() {
     check_doc_test(
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index a4d1fb8f31..9c19bf572f 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -20,8 +20,9 @@ fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> {
 }
 
 fn check(ra_fixture: &str, expect: Expect) {
-    let def_map = compute_crate_def_map(ra_fixture);
-    let actual = def_map.dump();
+    let db = TestDB::with_files(ra_fixture);
+    let krate = db.crate_graph().iter().next().unwrap();
+    let actual = db.crate_def_map(krate).dump();
     expect.assert_eq(&actual);
 }
 
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs
index ba295fd9e0..f93337a6ea 100644
--- a/crates/hir_def/src/nameres/tests/mod_resolution.rs
+++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs
@@ -323,26 +323,13 @@ pub struct Baz;
 fn module_resolution_relative_path_outside_root() {
     check(
         r#"
-//- /a/b/c/d/e/main.rs crate:main
+//- /main.rs
 #[path="../../../../../outside.rs"]
 mod foo;
-
-//- /outside.rs
-mod bar;
-
-//- /bar.rs
-pub struct Baz;
 "#,
         expect![[r#"
             crate
-            foo: t
-
-            crate::foo
-            bar: t
-
-            crate::foo::bar
-            Baz: t v
-"#]],
+        "#]],
     );
 }
 
@@ -771,30 +758,3 @@ struct X;
         "#]],
     );
 }
-
-#[test]
-fn circular_mods() {
-    mark::check!(circular_mods);
-    compute_crate_def_map(
-        r#"
-//- /lib.rs
-mod foo;
-//- /foo.rs
-#[path = "./foo.rs"]
-mod foo;
-"#,
-    );
-
-    compute_crate_def_map(
-        r#"
-//- /lib.rs
-mod foo;
-//- /foo.rs
-#[path = "./bar.rs"]
-mod bar;
-//- /bar.rs
-#[path = "./foo.rs"]
-mod foo;
-"#,
-    );
-}
diff --git a/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt
index 54e707b736..6fd59ccc02 100644
--- a/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0033_unclosed_raw_string_at_eof.txt
@@ -1,2 +1,2 @@
-STRING 4 "r##\""
+RAW_STRING 4 "r##\""
 > error0..4 token("r##\"") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt b/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt
index 1f9889775f..8d9ca0e8f1 100644
--- a/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt
+++ b/crates/syntax/test_data/lexer/err/0034_unclosed_raw_string_with_ferris.txt
@@ -1,2 +1,2 @@
-STRING 8 "r##\"🦀"
+RAW_STRING 8 "r##\"🦀"
 > error0..8 token("r##\"🦀") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt b/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt
index 93f6f72ae6..a906380c71 100644
--- a/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0035_unclosed_raw_string_with_ascii_escape.txt
@@ -1,2 +1,2 @@
-STRING 8 "r##\"\\x7f"
+RAW_STRING 8 "r##\"\\x7f"
 > error0..8 token("r##\"\\x7f") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt b/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt
index 1d2ebc60fd..5667c61499 100644
--- a/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0036_unclosed_raw_string_with_unicode_escape.txt
@@ -1,2 +1,2 @@
-STRING 12 "r##\"\\u{20AA}"
+RAW_STRING 12 "r##\"\\u{20AA}"
 > error0..12 token("r##\"\\u{20AA}") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt b/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt
index c567ab7e28..141c8268e8 100644
--- a/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt
+++ b/crates/syntax/test_data/lexer/err/0037_unclosed_raw_string_with_space.txt
@@ -1,2 +1,2 @@
-STRING 5 "r##\" "
+RAW_STRING 5 "r##\" "
 > error0..5 token("r##\" ") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt b/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt
index 343b20323a..f61d4cc915 100644
--- a/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt
+++ b/crates/syntax/test_data/lexer/err/0038_unclosed_raw_string_with_slash.txt
@@ -1,2 +1,2 @@
-STRING 5 "r##\"\\"
+RAW_STRING 5 "r##\"\\"
 > error0..5 token("r##\"\\") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt b/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt
index 041a427375..12e2c0fc03 100644
--- a/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt
+++ b/crates/syntax/test_data/lexer/err/0039_unclosed_raw_string_with_slash_n.txt
@@ -1,2 +1,2 @@
-STRING 6 "r##\"\\n"
+RAW_STRING 6 "r##\"\\n"
 > error0..6 token("r##\"\\n") msg(Missing trailing `"` with `#` symbols to terminate the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt
index efaa1cafd9..fe12cb5fc6 100644
--- a/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0040_unclosed_raw_byte_string_at_eof.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 5 "br##\""
+RAW_BYTE_STRING 5 "br##\""
 > error0..5 token("br##\"") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt b/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt
index b6c938f94e..5be2a7861b 100644
--- a/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt
+++ b/crates/syntax/test_data/lexer/err/0041_unclosed_raw_byte_string_with_ferris.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 9 "br##\"🦀"
+RAW_BYTE_STRING 9 "br##\"🦀"
 > error0..9 token("br##\"🦀") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt b/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt
index f82efe49ac..6cbe08d07e 100644
--- a/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0042_unclosed_raw_byte_string_with_ascii_escape.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 9 "br##\"\\x7f"
+RAW_BYTE_STRING 9 "br##\"\\x7f"
 > error0..9 token("br##\"\\x7f") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt b/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt
index 4e4a576961..f56a4f9848 100644
--- a/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt
+++ b/crates/syntax/test_data/lexer/err/0043_unclosed_raw_byte_string_with_unicode_escape.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 13 "br##\"\\u{20AA}"
+RAW_BYTE_STRING 13 "br##\"\\u{20AA}"
 > error0..13 token("br##\"\\u{20AA}") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt b/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt
index 0018c8623c..3d32ce34ea 100644
--- a/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt
+++ b/crates/syntax/test_data/lexer/err/0044_unclosed_raw_byte_string_with_space.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 6 "br##\" "
+RAW_BYTE_STRING 6 "br##\" "
 > error0..6 token("br##\" ") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt b/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt
index c3ba4ae82b..320fea1770 100644
--- a/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt
+++ b/crates/syntax/test_data/lexer/err/0045_unclosed_raw_byte_string_with_slash.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 6 "br##\"\\"
+RAW_BYTE_STRING 6 "br##\"\\"
 > error0..6 token("br##\"\\") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt b/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt
index 7bda72276f..b3a56380cb 100644
--- a/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt
+++ b/crates/syntax/test_data/lexer/err/0046_unclosed_raw_byte_string_with_slash_n.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 7 "br##\"\\n"
+RAW_BYTE_STRING 7 "br##\"\\n"
 > error0..7 token("br##\"\\n") msg(Missing trailing `"` with `#` symbols to terminate the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt
index ce92d2ff75..5af1e2d97f 100644
--- a/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0047_unstarted_raw_string_at_eof.txt
@@ -1,2 +1,2 @@
-STRING 3 "r##"
+RAW_STRING 3 "r##"
 > error0..3 token("r##") msg(Missing `"` symbol after `#` symbols to begin the raw string literal)
diff --git a/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt b/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt
index a75d9030c4..aec7afd927 100644
--- a/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt
+++ b/crates/syntax/test_data/lexer/err/0048_unstarted_raw_byte_string_at_eof.txt
@@ -1,2 +1,2 @@
-BYTE_STRING 4 "br##"
+RAW_BYTE_STRING 4 "br##"
 > error0..4 token("br##") msg(Missing `"` symbol after `#` symbols to begin the raw byte string literal)
diff --git a/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt b/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt
index 516e0b78ed..e22fe5374f 100644
--- a/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt
+++ b/crates/syntax/test_data/lexer/err/0049_unstarted_raw_string_with_ascii.txt
@@ -1,4 +1,4 @@
-STRING 4 "r## "
+RAW_STRING 4 "r## "
 IDENT 1 "I"
 WHITESPACE 1 " "
 IDENT 4 "lack"
diff --git a/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt b/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt
index 2f8a6f5f29..d74ea4c279 100644
--- a/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt
+++ b/crates/syntax/test_data/lexer/err/0050_unstarted_raw_byte_string_with_ascii.txt
@@ -1,4 +1,4 @@
-BYTE_STRING 5 "br## "
+RAW_BYTE_STRING 5 "br## "
 IDENT 1 "I"
 WHITESPACE 1 " "
 IDENT 4 "lack"
diff --git a/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt b/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt
index e61ad99bef..bc03b51a8a 100644
--- a/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt
+++ b/crates/syntax/test_data/lexer/ok/0008_byte_strings.txt
@@ -4,13 +4,13 @@ BYTE 4 "b\'x\'"
 WHITESPACE 1 " "
 BYTE_STRING 6 "b\"foo\""
 WHITESPACE 1 " "
-BYTE_STRING 4 "br\"\""
+RAW_BYTE_STRING 4 "br\"\""
 WHITESPACE 1 "\n"
 BYTE 6 "b\'\'suf"
 WHITESPACE 1 " "
 BYTE_STRING 5 "b\"\"ix"
 WHITESPACE 1 " "
-BYTE_STRING 6 "br\"\"br"
+RAW_BYTE_STRING 6 "br\"\"br"
 WHITESPACE 1 "\n"
 BYTE 5 "b\'\\n\'"
 WHITESPACE 1 " "
diff --git a/crates/syntax/test_data/lexer/ok/0009_strings.txt b/crates/syntax/test_data/lexer/ok/0009_strings.txt
index 988a8877bd..4cb4d711d6 100644
--- a/crates/syntax/test_data/lexer/ok/0009_strings.txt
+++ b/crates/syntax/test_data/lexer/ok/0009_strings.txt
@@ -1,6 +1,6 @@
 STRING 7 "\"hello\""
 WHITESPACE 1 " "
-STRING 8 "r\"world\""
+RAW_STRING 8 "r\"world\""
 WHITESPACE 1 " "
 STRING 17 "\"\\n\\\"\\\\no escape\""
 WHITESPACE 1 " "
diff --git a/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt b/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt
index db0d5ffd14..9cf0957d1e 100644
--- a/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt
+++ b/crates/syntax/test_data/lexer/ok/0013_raw_strings.txt
@@ -1,2 +1,2 @@
-STRING 36 "r###\"this is a r##\"raw\"## string\"###"
+RAW_STRING 36 "r###\"this is a r##\"raw\"## string\"###"
 WHITESPACE 1 "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast b/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast
index ae838105d0..9a87b5b936 100644
--- a/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0085_expr_literals.rast
@@ -104,7 +104,7 @@ SOURCE_FILE@0..189
         EQ@142..143 "="
         WHITESPACE@143..144 " "
         LITERAL@144..148
-          STRING@144..148 "r\"d\""
+          RAW_STRING@144..148 "r\"d\""
         SEMICOLON@148..149 ";"
       WHITESPACE@149..154 "\n    "
       LET_STMT@154..167
@@ -128,7 +128,7 @@ SOURCE_FILE@0..189
         EQ@178..179 "="
         WHITESPACE@179..180 " "
         LITERAL@180..185
-          BYTE_STRING@180..185 "br\"f\""
+          RAW_BYTE_STRING@180..185 "br\"f\""
         SEMICOLON@185..186 ";"
       WHITESPACE@186..187 "\n"
       R_CURLY@187..188 "}"