Repository: rust-analyzer/rust-analyzer
Pull request #3753 (2020-03-28 10:21:04)

Introduce stdx crate
bors r+
🤖


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 3bb1df05b8..8d81c48398 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -353,12 +353,6 @@ version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
 
-[[package]]
-name = "format-buf"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7aea5a5909a74969507051a3b17adc84737e31a5f910559892aedce026f4d53"
-
 [[package]]
 name = "fs_extra"
 version = "1.1.0"
@@ -573,12 +567,6 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2f52a11f73b88fab829a0e4d9e13ea5982c7ac457c72eb3541d82a4afdfce4ff"
 
-[[package]]
-name = "join_to_string"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dc7a5290e8c2606ce2be49f456d50f69173cb96d1541e4f66e34ac8b331a98f"
-
 [[package]]
 name = "kernel32-sys"
 version = "0.2.2"
@@ -885,9 +873,7 @@ name = "ra_assists"
 version = "0.1.0"
 dependencies = [
  "either",
- "format-buf",
  "itertools 0.9.0",
- "join_to_string",
  "ra_db",
  "ra_fmt",
  "ra_hir",
@@ -896,6 +882,7 @@ dependencies = [
  "ra_syntax",
  "ra_text_edit",
  "rustc-hash",
+ "stdx",
  "test_utils",
 ]
 
@@ -979,6 +966,7 @@ dependencies = [
  "ra_syntax",
  "ra_tt",
  "rustc-hash",
+ "stdx",
  "test_utils",
 ]
 
@@ -1015,6 +1003,7 @@ dependencies = [
  "ra_prof",
  "ra_syntax",
  "rustc-hash",
+ "stdx",
  "test_utils",
 ]
 
@@ -1023,11 +1012,9 @@ name = "ra_ide"
 version = "0.1.0"
 dependencies = [
  "either",
- "format-buf",
  "indexmap",
  "insta",
  "itertools 0.9.0",
- "join_to_string",
  "log",
  "ra_assists",
  "ra_cfg",
@@ -1040,6 +1027,7 @@ dependencies = [
  "ra_text_edit",
  "rand",
  "rustc-hash",
+ "stdx",
  "test_utils",
 ]
 
@@ -1130,6 +1118,7 @@ dependencies = [
  "rustc_lexer",
  "serde",
  "smol_str",
+ "stdx",
  "test_utils",
  "walkdir",
 ]
@@ -1321,6 +1310,7 @@ dependencies = [
  "rustc-hash",
  "serde",
  "serde_json",
+ "stdx",
  "tempfile",
  "test_utils",
  "threadpool",
@@ -1488,6 +1478,10 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "stdx"
+version = "0.1.0"
+
 [[package]]
 name = "superslice"
 version = "1.0.0"
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index a87f4052aa..3bcf58ba4b 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -8,12 +8,12 @@ authors = ["rust-analyzer developers"]
 doctest = false
 
 [dependencies]
-format-buf = "1.0.0"
-join_to_string = "0.1.3"
 rustc-hash = "1.1.0"
 itertools = "0.9.0"
 either = "1.5.3"
 
+stdx = { path = "../stdx" }
+
 ra_syntax = { path = "../ra_syntax" }
 ra_text_edit = { path = "../ra_text_edit" }
 ra_fmt = { path = "../ra_fmt" }
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index dd2bed25a8..15f9b216b6 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -1,17 +1,13 @@
-//! FIXME: write short doc here
-
-use join_to_string::join;
 use ra_syntax::{
     ast::{self, AstNode},
     Direction, SmolStr,
     SyntaxKind::{IDENT, WHITESPACE},
     TextRange, TextUnit,
 };
+use stdx::SepBy;
 
 use crate::{Assist, AssistCtx, AssistId};
 
-const DERIVE_TRAIT: &str = "derive";
-
 // Assist: add_custom_impl
 //
 // Adds impl block for derived trait.
@@ -38,7 +34,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
         .descendants_with_tokens()
         .filter(|t| t.kind() == IDENT)
         .find_map(|i| i.into_token())
-        .filter(|t| *t.text() == DERIVE_TRAIT)?
+        .filter(|t| *t.text() == "derive")?
         .text()
         .clone();
 
@@ -63,8 +59,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
             .filter(|t| t != trait_token.text())
             .collect::<Vec<SmolStr>>();
         let has_more_derives = !new_attr_input.is_empty();
-        let new_attr_input =
-            join(new_attr_input.iter()).separator(", ").surround_with("(", ")").to_string();
+        let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
         let new_attr_input_len = new_attr_input.len();
 
         let mut buf = String::new();
@@ -100,9 +95,10 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
 
 #[cfg(test)]
 mod tests {
-    use super::*;
     use crate::helpers::{check_assist, check_assist_not_applicable};
 
+    use super::*;
+
     #[test]
     fn add_custom_impl_for_unique_input() {
         check_assist(
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index afae7d3850..6622eadb2d 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -1,9 +1,8 @@
-use format_buf::format;
-use join_to_string::join;
 use ra_syntax::{
     ast::{self, AstNode, NameOwner, TypeParamsOwner},
     TextUnit,
 };
+use stdx::{format_to, SepBy};
 
 use crate::{Assist, AssistCtx, AssistId};
 
@@ -36,7 +35,7 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
         let mut buf = String::new();
         buf.push_str("\n\nimpl");
         if let Some(type_params) = &type_params {
-            format!(buf, "{}", type_params.syntax());
+            format_to!(buf, "{}", type_params.syntax());
         }
         buf.push_str(" ");
         buf.push_str(name.text().as_str());
@@ -47,7 +46,9 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
                 .map(|it| it.text().clone());
             let type_params =
                 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
-            join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf);
+
+            let generic_params = lifetime_params.chain(type_params).sep_by(", ");
+            format_to!(buf, "<{}>", generic_params)
         }
         buf.push_str(" {\n");
         edit.set_cursor(start_offset + TextUnit::of_str(&buf));
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index 729a223e0e..240b19fa37 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -1,14 +1,11 @@
-use std::fmt::Write;
-
-use format_buf::format;
 use hir::Adt;
-use join_to_string::join;
 use ra_syntax::{
     ast::{
         self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
     },
     TextUnit, T,
 };
+use stdx::{format_to, SepBy};
 
 use crate::{Assist, AssistCtx, AssistId};
 
@@ -53,24 +50,22 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
             buf.push('\n');
         }
 
-        let vis = strukt.visibility().map(|v| format!("{} ", v.syntax()));
+        let vis = strukt.visibility().map(|v| format!("{} ", v));
         let vis = vis.as_deref().unwrap_or("");
-        write!(&mut buf, "    {}fn new(", vis).unwrap();
-
-        join(field_list.fields().filter_map(|f| {
-            Some(format!("{}: {}", f.name()?.syntax().text(), f.ascribed_type()?.syntax().text()))
-        }))
-        .separator(", ")
-        .to_buf(&mut buf);
 
-        buf.push_str(") -> Self { Self {");
-
-        join(field_list.fields().filter_map(|f| Some(f.name()?.syntax().text())))
-            .separator(", ")
-            .surround_with(" ", " ")
-            .to_buf(&mut buf);
+        let params = field_list
+            .fields()
+            .filter_map(|f| {
+                Some(format!(
+                    "{}: {}",
+                    f.name()?.syntax().text(),
+                    f.ascribed_type()?.syntax().text()
+                ))
+            })
+            .sep_by(", ");
+        let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
 
-        buf.push_str("} }");
+        format_to!(buf, "    {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
 
         let (start_offset, end_offset) = impl_def
             .and_then(|impl_def| {
@@ -103,7 +98,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
     let mut buf = String::with_capacity(code.len());
     buf.push_str("\n\nimpl");
     if let Some(type_params) = &type_params {
-        format!(buf, "{}", type_params.syntax());
+        format_to!(buf, "{}", type_params.syntax());
     }
     buf.push_str(" ");
     buf.push_str(strukt.name().unwrap().text().as_str());
@@ -114,10 +109,10 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
             .map(|it| it.text().clone());
         let type_params =
             type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
-        join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf);
+        format_to!(buf, "<{}>", lifetime_params.chain(type_params).sep_by(", "))
     }
 
-    format!(&mut buf, " {{\n{}\n}}\n", code);
+    format_to!(buf, " {{\n{}\n}}\n", code);
 
     buf
 }
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index b453c51fbc..1edbdc14c7 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -1,4 +1,3 @@
-use format_buf::format;
 use ra_syntax::{
     ast::{self, AstNode},
     SyntaxKind::{
@@ -7,6 +6,7 @@ use ra_syntax::{
     },
     SyntaxNode, TextUnit,
 };
+use stdx::format_to;
 use test_utils::tested_by;
 
 use crate::{Assist, AssistCtx, AssistId};
@@ -52,7 +52,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
             buf.push_str("let var_name = ");
             TextUnit::of_str("let ")
         };
-        format!(buf, "{}", expr.syntax());
+        format_to!(buf, "{}", expr.syntax());
         let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
         let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
             Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml
index 30a12337ec..56e791e3e3 100644
--- a/crates/ra_hir_def/Cargo.toml
+++ b/crates/ra_hir_def/Cargo.toml
@@ -15,6 +15,8 @@ either = "1.5.3"
 anymap = "0.12.1"
 drop_bomb = "0.1.4"
 
+stdx = { path = "../stdx" }
+
 ra_arena = { path = "../ra_arena" }
 ra_db = { path = "../ra_db" }
 ra_syntax = { path = "../ra_syntax" }
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs
index 40bdc34f56..f279c2ad4a 100644
--- a/crates/ra_hir_def/src/nameres.rs
+++ b/crates/ra_hir_def/src/nameres.rs
@@ -63,6 +63,7 @@ use ra_db::{CrateId, Edition, FileId};
 use ra_prof::profile;
 use ra_syntax::ast;
 use rustc_hash::FxHashMap;
+use stdx::format_to;
 
 use crate::{
     db::DefDatabase,
@@ -246,7 +247,7 @@ impl CrateDefMap {
             entries.sort_by_key(|(name, _)| name.clone());
 
             for (name, def) in entries {
-                *buf += &format!("{}:", name);
+                format_to!(buf, "{}:", name);
 
                 if def.types.is_some() {
                     *buf += " t";
@@ -265,7 +266,7 @@ impl CrateDefMap {
             }
 
             for (name, child) in map.modules[module].children.iter() {
-                let path = path.to_string() + &format!("::{}", name);
+                let path = &format!("{}::{}", path, name);
                 go(buf, map, &path, *child);
             }
         }
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index 9962112dbd..5a58d70cf0 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -13,6 +13,8 @@ ena = "0.13.1"
 log = "0.4.8"
 rustc-hash = "1.1.0"
 
+stdx = { path = "../stdx" }
+
 hir_def = { path = "../ra_hir_def", package = "ra_hir_def" }
 hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" }
 ra_arena = { path = "../ra_arena" }
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 6eafdc8f6d..0f8522021e 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -4,6 +4,7 @@ use std::any::Any;
 
 use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile};
 use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
+use stdx::format_to;
 
 pub use hir_def::diagnostics::UnresolvedModule;
 pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
@@ -37,12 +38,11 @@ pub struct MissingFields {
 
 impl Diagnostic for MissingFields {
     fn message(&self) -> String {
-        use std::fmt::Write;
-        let mut message = String::from("Missing structure fields:\n");
+        let mut buf = String::from("Missing structure fields:\n");
         for field in &self.missed_fields {
-            writeln!(message, "- {}", field).unwrap();
+            format_to!(buf, "- {}", field);
         }
-        message
+        buf
     }
     fn source(&self) -> InFile<SyntaxNodePtr> {
         InFile { file_id: self.file, value: self.field_list.into() }
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 36eec0e60a..b4a29b81b3 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -12,14 +12,14 @@ wasm = []
 
 [dependencies]
 either = "1.5.3"
-format-buf = "1.0.0"
 indexmap = "1.3.2"
 itertools = "0.9.0"
-join_to_string = "0.1.3"
 log = "0.4.8"
 rustc-hash = "1.1.0"
 rand = { version = "0.7.3", features = ["small_rng"] }
 
+stdx = { path = "../stdx" }
+
 ra_syntax = { path = "../ra_syntax" }
 ra_text_edit = { path = "../ra_text_edit" }
 ra_db = { path = "../ra_db" }
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 2538486029..60f1b83f33 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -1,15 +1,14 @@
 //! This modules takes care of rendering various definitions as completion items.
 
 use hir::{Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type};
-use join_to_string::join;
 use ra_syntax::ast::NameOwner;
+use stdx::SepBy;
 use test_utils::tested_by;
 
-use crate::completion::{
-    CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
-};
-
 use crate::{
+    completion::{
+        CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
+    },
     display::{const_label, macro_label, type_label, FunctionSignature},
     RootDatabase,
 };
@@ -221,13 +220,13 @@ impl Completions {
                 builder = builder.trigger_call_info();
                 let snippet = if ctx.options.add_call_argument_snippets {
                     let to_skip = if has_self_param { 1 } else { 0 };
-                    let function_params_snippet = join(
-                        function_signature.parameter_names.iter().skip(to_skip).enumerate().map(
-                            |(index, param_name)| format!("${{{}:{}}}", index + 1, param_name),
-                        ),
-                    )
-                    .separator(", ")
-                    .to_string();
+                    let function_params_snippet = function_signature
+                        .parameter_names
+                        .iter()
+                        .skip(to_skip)
+                        .enumerate()
+                        .map(|(index, param_name)| format!("${{{}:{}}}", index + 1, param_name))
+                        .sep_by(", ");
                     format!("{}({})$0", name, function_params_snippet)
                 } else {
                     format!("{}($0)", name)
@@ -281,18 +280,16 @@ impl Completions {
             .into_iter()
             .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db)));
         let detail = match variant.kind(ctx.db) {
-            StructKind::Tuple | StructKind::Unit => {
-                join(detail_types.map(|(_, t)| t.display(ctx.db).to_string()))
-                    .separator(", ")
-                    .surround_with("(", ")")
-                    .to_string()
-            }
-            StructKind::Record => {
-                join(detail_types.map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())))
-                    .separator(", ")
-                    .surround_with("{ ", " }")
-                    .to_string()
-            }
+            StructKind::Tuple | StructKind::Unit => detail_types
+                .map(|(_, t)| t.display(ctx.db).to_string())
+                .sep_by(", ")
+                .surround_with("(", ")")
+                .to_string(),
+            StructKind::Record => detail_types
+                .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string()))
+                .sep_by(", ")
+                .surround_with("{ ", " }")
+                .to_string(),
         };
         CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
             .kind(CompletionItemKind::EnumVariant)
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index a10e642dba..c1d7ddaf23 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -200,8 +200,8 @@ fn check_struct_shorthand_initialization(
 #[cfg(test)]
 mod tests {
     use insta::assert_debug_snapshot;
-    use join_to_string::join;
     use ra_syntax::SourceFile;
+    use stdx::SepBy;
     use test_utils::assert_eq_text;
 
     use crate::mock_analysis::{analysis_and_position, single_file};
@@ -254,16 +254,12 @@ mod tests {
             .map(|it| it.len() - it.trim_start().len())
             .next()
             .expect("empty fixture");
-        let after = join(after.lines().filter_map(|line| {
-            if line.len() > margin {
-                Some(&line[margin..])
-            } else {
-                None
-            }
-        }))
-        .separator("\n")
-        .suffix("\n")
-        .to_string();
+        let after = after
+            .lines()
+            .filter_map(|line| if line.len() > margin { Some(&line[margin..]) } else { None })
+            .sep_by("\n")
+            .suffix("\n")
+            .to_string();
 
         assert_eq_text!(&after, &actual);
         assert!(
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs
index c395057a79..722092de97 100644
--- a/crates/ra_ide/src/display.rs
+++ b/crates/ra_ide/src/display.rs
@@ -6,12 +6,13 @@ mod navigation_target;
 mod structure;
 mod short_label;
 
-use std::fmt::{Display, Write};
+use std::fmt::Display;
 
 use ra_syntax::{
     ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
     SyntaxKind::{ATTR, COMMENT},
 };
+use stdx::format_to;
 
 pub use function_signature::FunctionSignature;
 pub use navigation_target::NavigationTarget;
@@ -78,18 +79,18 @@ pub(crate) fn rust_code_markup_with_doc(
     doc: Option<&str>,
     mod_path: Option<&str>,
 ) -> String {
-    let mut markup = "```rust\n".to_owned();
+    let mut buf = "```rust\n".to_owned();
 
     if let Some(mod_path) = mod_path {
         if !mod_path.is_empty() {
-            write!(markup, "{}\n", mod_path).unwrap();
+            format_to!(buf, "{}\n", mod_path);
         }
     }
-    write!(markup, "{}\n```", code).unwrap();
+    format_to!(buf, "{}\n```", code);
 
     if let Some(doc) = doc {
-        write!(markup, "\n\n{}", doc).unwrap();
+        format_to!(buf, "\n\n{}", doc);
     }
 
-    markup
+    buf
 }
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index ec1bbd5a03..b967a68168 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -1,12 +1,14 @@
 //! FIXME: write short doc here
 
-use std::fmt::{self, Display};
+use std::{
+    convert::From,
+    fmt::{self, Display},
+};
 
 use hir::{Docs, Documentation, HasSource, HirDisplay};
-use join_to_string::join;
 use ra_ide_db::RootDatabase;
 use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
-use std::convert::From;
+use stdx::SepBy;
 
 use crate::display::{generic_parameters, where_predicates};
 
@@ -227,21 +229,17 @@ impl Display for FunctionSignature {
         }
 
         if !self.generic_parameters.is_empty() {
-            join(self.generic_parameters.iter())
-                .separator(", ")
-                .surround_with("<", ">")
-                .to_fmt(f)?;
+            write!(f, "{}", self.generic_parameters.iter().sep_by(", ").surround_with("<", ">"))?;
         }
 
-        join(self.parameters.iter()).separator(", ").surround_with("(", ")").to_fmt(f)?;
+        write!(f, "{}", self.parameters.iter().sep_by(", ").surround_with("(", ")"))?;
 
         if let Some(t) = &self.ret_type {
             write!(f, " -> {}", t)?;
         }
 
         if !self.where_predicates.is_empty() {
-            write!(f, "\nwhere ")?;
-            join(self.where_predicates.iter()).separator(",\n      ").to_fmt(f)?;
+            write!(f, "\nwhere {}", self.where_predicates.iter().sep_by(",\n      "))?;
         }
 
         Ok(())
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs
index 9ffc9b9801..4b081bf6c6 100644
--- a/crates/ra_ide/src/display/short_label.rs
+++ b/crates/ra_ide/src/display/short_label.rs
@@ -1,7 +1,7 @@
 //! FIXME: write short doc here
 
-use format_buf::format;
 use ra_syntax::ast::{self, AstNode, NameOwner, TypeAscriptionOwner, VisibilityOwner};
+use stdx::format_to;
 
 pub(crate) trait ShortLabel {
     fn short_label(&self) -> Option<String>;
@@ -80,7 +80,7 @@ where
     let mut buf = short_label_from_node(node, prefix)?;
 
     if let Some(type_ref) = node.ascribed_type() {
-        format!(buf, ": {}", type_ref.syntax());
+        format_to!(buf, ": {}", type_ref.syntax());
     }
 
     Some(buf)
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml
index 6fccc23038..3c6ae77e46 100644
--- a/crates/ra_syntax/Cargo.toml
+++ b/crates/ra_syntax/Cargo.toml
@@ -18,6 +18,8 @@ rustc-hash = "1.1.0"
 arrayvec = "0.5.1"
 once_cell = "1.3.1"
 
+stdx = { path = "../stdx" }
+
 ra_text_edit = { path = "../ra_text_edit" }
 ra_parser = { path = "../ra_parser" }
 
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index dbf8e63704..0c908573d9 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -1,6 +1,7 @@
 //! This module contains free-standing functions for creating AST fragments out
 //! of smaller pieces.
 use itertools::Itertools;
+use stdx::format_to;
 
 use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken};
 
@@ -34,14 +35,14 @@ pub fn use_tree(
     let mut buf = "use ".to_string();
     buf += &path.syntax().to_string();
     if let Some(use_tree_list) = use_tree_list {
-        buf += &format!("::{}", use_tree_list);
+        format_to!(buf, "::{}", use_tree_list);
     }
     if add_star {
         buf += "::*";
     }
 
     if let Some(alias) = alias {
-        buf += &format!(" {}", alias);
+        format_to!(buf, " {}", alias);
     }
     ast_from_text(&buf)
 }
@@ -70,15 +71,15 @@ pub fn block_expr(
     stmts: impl IntoIterator<Item = ast::Stmt>,
     tail_expr: Option<ast::Expr>,
 ) -> ast::BlockExpr {
-    let mut text = "{\n".to_string();
+    let mut buf = "{\n".to_string();
     for stmt in stmts.into_iter() {
-        text += &format!("    {}\n", stmt);
+        format_to!(buf, "    {}\n", stmt);
     }
     if let Some(tail_expr) = tail_expr {
-        text += &format!("    {}\n", tail_expr)
+        format_to!(buf, "    {}\n", tail_expr)
     }
-    text += "}";
-    ast_from_text(&format!("fn f() {}", text))
+    buf += "}";
+    ast_from_text(&format!("fn f() {}", buf))
 }
 
 pub fn block_from_expr(e: ast::Expr) -> ast::Block {
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs
index cef926ed3b..f0e16dc2bf 100644
--- a/crates/ra_syntax/src/lib.rs
+++ b/crates/ra_syntax/src/lib.rs
@@ -32,9 +32,10 @@ pub mod ast;
 #[doc(hidden)]
 pub mod fuzz;
 
-use std::{fmt::Write, marker::PhantomData, sync::Arc};
+use std::{marker::PhantomData, sync::Arc};
 
 use ra_text_edit::AtomTextEdit;
+use stdx::format_to;
 
 use crate::syntax_node::GreenNode;
 
@@ -115,7 +116,7 @@ impl Parse<SourceFile> {
     pub fn debug_dump(&self) -> String {
         let mut buf = format!("{:#?}", self.tree().syntax());
         for err in self.errors.iter() {
-            writeln!(buf, "error {:?}: {}", err.range(), err).unwrap();
+            format_to!(buf, "error {:?}: {}\n", err.range(), err);
         }
         buf
     }
@@ -296,7 +297,7 @@ fn api_walkthrough() {
                     NodeOrToken::Node(it) => it.text().to_string(),
                     NodeOrToken::Token(it) => it.text().to_string(),
                 };
-                buf += &format!("{:indent$}{:?} {:?}\n", " ", text, node.kind(), indent = indent);
+                format_to!(buf, "{:indent$}{:?} {:?}\n", " ", text, node.kind(), indent = indent);
                 indent += 2;
             }
             WalkEvent::Leave(_) => indent -= 2,
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index e071e9b8d1..8fe6799d23 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -30,6 +30,8 @@ serde = { version = "1.0.104", features = ["derive"] }
 serde_json = "1.0.48"
 threadpool = "1.7.1"
 
+stdx = { path = "../stdx" }
+
 lsp-server = "0.3.1"
 ra_cargo_watch = { path = "../ra_cargo_watch" }
 ra_ide = { path = "../ra_ide" }
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 27459be8c8..75cf2dae50 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -1,7 +1,7 @@
 //! Fully type-check project and print various stats, like the number of type
 //! errors.
 
-use std::{collections::HashSet, fmt::Write, path::Path, time::Instant};
+use std::{collections::HashSet, path::Path, time::Instant};
 
 use hir::{
     db::{AstDatabase, DefDatabase, HirDatabase},
@@ -13,6 +13,7 @@ use itertools::Itertools;
 use ra_db::SourceDatabaseExt;
 use ra_syntax::AstNode;
 use rand::{seq::SliceRandom, thread_rng};
+use stdx::format_to;
 
 use crate::cli::{load_cargo::load_cargo, progress_report::ProgressReport, Result, Verbosity};
 
@@ -128,7 +129,7 @@ pub fn analysis_stats(
             let original_file = src.file_id.original_file(db);
             let path = db.file_relative_path(original_file);
             let syntax_range = src.value.syntax().text_range();
-            write!(msg, " ({:?} {})", path, syntax_range).unwrap();
+            format_to!(msg, " ({:?} {})", path, syntax_range);
         }
         if verbosity.is_spammy() {
             bar.println(msg.to_string());
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 1033d6de94..12f8ca2976 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -3,7 +3,6 @@
 //! `ra_ide` crate.
 
 use std::{
-    fmt::Write as _,
     io::Write as _,
     process::{self, Stdio},
 };
@@ -28,6 +27,7 @@ use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
 use rustc_hash::FxHashMap;
 use serde::{Deserialize, Serialize};
 use serde_json::to_value;
+use stdx::format_to;
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
@@ -46,11 +46,11 @@ use crate::{
 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
     let _p = profile("handle_analyzer_status");
     let mut buf = world.status();
-    writeln!(buf, "\n\nrequests:").unwrap();
+    format_to!(buf, "\n\nrequests:");
     let requests = world.latest_requests.read();
     for (is_last, r) in requests.iter() {
         let mark = if is_last { "*" } else { " " };
-        writeln!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis()).unwrap();
+        format_to!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis());
     }
     Ok(buf)
 }
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index de85bb0178..64a7b907ed 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -19,6 +19,7 @@ use ra_ide::{
 use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace};
 use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
 use relative_path::RelativePathBuf;
+use stdx::format_to;
 
 use crate::{
     diagnostics::{CheckFixes, DiagnosticCollection},
@@ -319,23 +320,23 @@ impl WorldSnapshot {
     }
 
     pub fn status(&self) -> String {
-        let mut res = String::new();
+        let mut buf = String::new();
         if self.workspaces.is_empty() {
-            res.push_str("no workspaces\n")
+            buf.push_str("no workspaces\n")
         } else {
-            res.push_str("workspaces:\n");
+            buf.push_str("workspaces:\n");
             for w in self.workspaces.iter() {
-                res += &format!("{} packages loaded\n", w.n_packages());
+                format_to!(buf, "{} packages loaded\n", w.n_packages());
             }
         }
-        res.push_str("\nanalysis:\n");
-        res.push_str(
+        buf.push_str("\nanalysis:\n");
+        buf.push_str(
             &self
                 .analysis
                 .status()
                 .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
         );
-        res
+        buf
     }
 
     pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml
new file mode 100644
index 0000000000..f9e380c105
--- /dev/null
+++ b/crates/stdx/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "stdx"
+version = "0.1.0"
+authors = ["rust-analyzer developers"]
+edition = "2018"
+
+[lib]
+doctest = false
+
+[dependencies]
+# Think twice before adding anything here
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
new file mode 100644
index 0000000000..8492c17af2
--- /dev/null
+++ b/crates/stdx/src/lib.rs
@@ -0,0 +1,75 @@
+//! Missing batteries for standard libraries.
+
+use std::{cell::Cell, fmt};
+
+/// Appends formatted string to a `String`.
+#[macro_export]
+macro_rules! format_to {
+    (&buf:expr) => ();
+    ($buf:expr, $lit:literal $($arg:tt)*) => {
+        { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); }
+    };
+}
+
+pub trait SepBy: Sized {
+    /// Returns an `impl fmt::Display`, which joins elements via a separator.
+    fn sep_by<'a>(self, sep: &'a str) -> SepByBuilder<'a, Self>;
+}
+
+impl<I> SepBy for I
+where
+    I: Iterator,
+    I::Item: fmt::Display,
+{
+    fn sep_by<'a>(self, sep: &'a str) -> SepByBuilder<'a, Self> {
+        SepByBuilder::new(sep, self)
+    }
+}
+
+pub struct SepByBuilder<'a, I> {
+    sep: &'a str,
+    prefix: &'a str,
+    suffix: &'a str,
+    iter: Cell<Option<I>>,
+}
+
+impl<'a, I> SepByBuilder<'a, I> {
+    fn new(sep: &'a str, iter: I) -> SepByBuilder<'a, I> {
+        SepByBuilder { sep, prefix: "", suffix: "", iter: Cell::new(Some(iter)) }
+    }
+
+    pub fn prefix(mut self, prefix: &'a str) -> Self {
+        self.prefix = prefix;
+        self
+    }
+
+    pub fn suffix(mut self, suffix: &'a str) -> Self {
+        self.suffix = suffix;
+        self
+    }
+
+    /// Set both suffix and prefix.
+    pub fn surround_with(self, prefix: &'a str, suffix: &'a str) -> Self {
+        self.prefix(prefix).suffix(suffix)
+    }
+}
+
+impl<I> fmt::Display for SepByBuilder<'_, I>
+where
+    I: Iterator,
+    I::Item: fmt::Display,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(self.prefix)?;
+        let mut first = true;
+        for item in self.iter.take().unwrap() {
+            if !first {
+                f.write_str(self.sep)?;
+            }
+            first = false;
+            fmt::Display::fmt(&item, f)?;
+        }
+        f.write_str(self.suffix)?;
+        Ok(())
+    }
+}


Test patch:
diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs
index 5bbeabf519..208096aab6 100644
--- a/crates/ra_hir_ty/src/test_db.rs
+++ b/crates/ra_hir_ty/src/test_db.rs
@@ -10,6 +10,7 @@ use hir_expand::{db::AstDatabase, diagnostics::DiagnosticSink};
 use ra_db::{
     salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, SourceDatabase, Upcast,
 };
+use stdx::format_to;
 
 use crate::{db::HirDatabase, expr::ExprValidator};
 
@@ -131,7 +132,7 @@ impl TestDB {
             for f in fns {
                 let infer = self.infer(f.into());
                 let mut sink = DiagnosticSink::new(|d| {
-                    buf += &format!("{:?}: {}\n", d.syntax_node(self).text(), d.message());
+                    format_to!(buf, "{:?}: {}\n", d.syntax_node(self).text(), d.message());
                 });
                 infer.add_diagnostics(self, f, &mut sink);
                 let mut validator = ExprValidator::new(f, infer, &mut sink);
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs
index 7e95473408..027e5a8f88 100644
--- a/crates/ra_hir_ty/src/tests.rs
+++ b/crates/ra_hir_ty/src/tests.rs
@@ -7,7 +7,6 @@ mod traits;
 mod method_resolution;
 mod macros;
 
-use std::fmt::Write;
 use std::sync::Arc;
 
 use hir_def::{
@@ -26,6 +25,7 @@ use ra_syntax::{
     algo,
     ast::{self, AstNode},
 };
+use stdx::format_to;
 
 use crate::{db::HirDatabase, display::HirDisplay, test_db::TestDB, InferenceResult};
 
@@ -63,7 +63,7 @@ fn infer(ra_fixture: &str) -> String {
 fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
     let (db, file_id) = TestDB::with_single_file(content);
 
-    let mut acc = String::new();
+    let mut buf = String::new();
 
     let mut infer_def = |inference_result: Arc<InferenceResult>,
                          body_source_map: Arc<BodySourceMap>| {
@@ -106,15 +106,14 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
                 (src_ptr.value.range(), node.text().to_string().replace("\n", " "))
             };
             let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" };
-            writeln!(
-                acc,
-                "{}{} '{}': {}",
+            format_to!(
+                buf,
+                "{}{} '{}': {}\n",
                 macro_prefix,
                 range,
                 ellipsize(text, 15),
                 ty.display(&db)
-            )
-            .unwrap();
+            );
         }
         if include_mismatches {
             mismatches.sort_by_key(|(src_ptr, _)| {
@@ -123,15 +122,14 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
             for (src_ptr, mismatch) in &mismatches {
                 let range = src_ptr.value.range();
                 let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" };
-                writeln!(
-                    acc,
-                    "{}{}: expected {}, got {}",
+                format_to!(
+                    buf,
+                    "{}{}: expected {}, got {}\n",
                     macro_prefix,
                     range,
                     mismatch.expected.display(&db),
                     mismatch.actual.display(&db),
-                )
-                .unwrap();
+                );
             }
         }
     };
@@ -158,8 +156,8 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
         infer_def(infer, source_map);
     }
 
-    acc.truncate(acc.trim_end().len());
-    acc
+    buf.truncate(buf.trim_end().len());
+    buf
 }
 
 fn visit_module(