about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorCaleb Cartwright <caleb.cartwright@outlook.com>2022-08-06 19:07:02 -0500
committerCaleb Cartwright <caleb.cartwright@outlook.com>2022-08-06 19:07:02 -0500
commita392dd10cdc4b25850cc9cdda4889ed462c101ff (patch)
tree3284bd5335b830cecfb4e2c0cada8b98303c52e4 /src
parentaf72f7a17f01a2d61490556e32c91c2d98416c9b (diff)
parent662702eb54b12f16729d6559ce0478b00ef153a8 (diff)
Merge remote-tracking branch 'upstream/master' into subtree-sync-2022-08-06
Diffstat (limited to 'src')
-rw-r--r--src/attr.rs4
-rw-r--r--src/bin/main.rs2
-rw-r--r--src/chains.rs120
-rw-r--r--src/config/config_type.rs122
-rw-r--r--src/config/macro_names.rs118
-rw-r--r--src/config/mod.rs147
-rw-r--r--src/expr.rs9
-rw-r--r--src/imports.rs4
-rw-r--r--src/items.rs8
-rw-r--r--src/lib.rs1
-rw-r--r--src/macros.rs18
-rw-r--r--src/skip.rs79
-rw-r--r--src/test/configuration_snippet.rs7
-rw-r--r--src/test/mod.rs6
-rw-r--r--src/utils.rs5
-rw-r--r--src/visitor.rs13
16 files changed, 556 insertions, 107 deletions
diff --git a/src/attr.rs b/src/attr.rs
index 41ba9a847e6..b1efaa21f27 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -337,7 +337,7 @@ impl Rewrite for ast::Attribute {
         } else {
             let should_skip = self
                 .ident()
-                .map(|s| context.skip_context.skip_attribute(s.name.as_str()))
+                .map(|s| context.skip_context.attributes.skip(s.name.as_str()))
                 .unwrap_or(false);
             let prefix = attr_prefix(self);
 
@@ -391,7 +391,7 @@ impl Rewrite for [ast::Attribute] {
 
         // Determine if the source text is annotated with `#[rustfmt::skip::attributes(derive)]`
         // or `#![rustfmt::skip::attributes(derive)]`
-        let skip_derives = context.skip_context.skip_attribute("derive");
+        let skip_derives = context.skip_context.attributes.skip("derive");
 
         // This is not just a simple map because we need to handle doc comments
         // (where we take as many doc comment attributes as possible) and possibly
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 8e871e61f26..be64559e877 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -136,7 +136,7 @@ fn make_opts() -> Options {
         "l",
         "files-with-diff",
         "Prints the names of mismatched files that were formatted. Prints the names of \
-         files that would be formated when used with `--check` mode. ",
+         files that would be formatted when used with `--check` mode. ",
     );
     opts.optmulti(
         "",
diff --git a/src/chains.rs b/src/chains.rs
index e26e24ec55a..a2976bbe92a 100644
--- a/src/chains.rs
+++ b/src/chains.rs
@@ -70,10 +70,64 @@ use crate::rewrite::{Rewrite, RewriteContext};
 use crate::shape::Shape;
 use crate::source_map::SpanUtils;
 use crate::utils::{
-    self, first_line_width, last_line_extendable, last_line_width, mk_sp, rewrite_ident,
-    trimmed_last_line_width, wrap_str,
+    self, filtered_str_fits, first_line_width, last_line_extendable, last_line_width, mk_sp,
+    rewrite_ident, trimmed_last_line_width, wrap_str,
 };
 
+/// Provides the original input contents from the span
+/// of a chain element with trailing spaces trimmed.
+fn format_overflow_style(span: Span, context: &RewriteContext<'_>) -> Option<String> {
+    context.snippet_provider.span_to_snippet(span).map(|s| {
+        s.lines()
+            .map(|l| l.trim_end())
+            .collect::<Vec<_>>()
+            .join("\n")
+    })
+}
+
+fn format_chain_item(
+    item: &ChainItem,
+    context: &RewriteContext<'_>,
+    rewrite_shape: Shape,
+    allow_overflow: bool,
+) -> Option<String> {
+    if allow_overflow {
+        item.rewrite(context, rewrite_shape)
+            .or_else(|| format_overflow_style(item.span, context))
+    } else {
+        item.rewrite(context, rewrite_shape)
+    }
+}
+
+fn get_block_child_shape(
+    prev_ends_with_block: bool,
+    context: &RewriteContext<'_>,
+    shape: Shape,
+) -> Shape {
+    if prev_ends_with_block {
+        shape.block_indent(0)
+    } else {
+        shape.block_indent(context.config.tab_spaces())
+    }
+    .with_max_width(context.config)
+}
+
+fn get_visual_style_child_shape(
+    context: &RewriteContext<'_>,
+    shape: Shape,
+    offset: usize,
+    parent_overflowing: bool,
+) -> Option<Shape> {
+    if !parent_overflowing {
+        shape
+            .with_max_width(context.config)
+            .offset_left(offset)
+            .map(|s| s.visual_indent(0))
+    } else {
+        Some(shape.visual_indent(offset))
+    }
+}
+
 pub(crate) fn rewrite_chain(
     expr: &ast::Expr,
     context: &RewriteContext<'_>,
@@ -498,6 +552,8 @@ struct ChainFormatterShared<'a> {
     // The number of children in the chain. This is not equal to `self.children.len()`
     // because `self.children` will change size as we process the chain.
     child_count: usize,
+    // Whether elements are allowed to overflow past the max_width limit
+    allow_overflow: bool,
 }
 
 impl<'a> ChainFormatterShared<'a> {
@@ -507,6 +563,8 @@ impl<'a> ChainFormatterShared<'a> {
             rewrites: Vec::with_capacity(chain.children.len() + 1),
             fits_single_line: false,
             child_count: chain.children.len(),
+            // TODO(calebcartwright)
+            allow_overflow: false,
         }
     }
 
@@ -519,6 +577,14 @@ impl<'a> ChainFormatterShared<'a> {
         }
     }
 
+    fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
+        for item in &self.children[..self.children.len() - 1] {
+            let rewrite = format_chain_item(item, context, child_shape, self.allow_overflow)?;
+            self.rewrites.push(rewrite);
+        }
+        Some(())
+    }
+
     // Rewrite the last child. The last child of a chain requires special treatment. We need to
     // know whether 'overflowing' the last child make a better formatting:
     //
@@ -731,22 +797,12 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
     }
 
     fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
-        Some(
-            if self.root_ends_with_block {
-                shape.block_indent(0)
-            } else {
-                shape.block_indent(context.config.tab_spaces())
-            }
-            .with_max_width(context.config),
-        )
+        let block_end = self.root_ends_with_block;
+        Some(get_block_child_shape(block_end, context, shape))
     }
 
     fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
-        for item in &self.shared.children[..self.shared.children.len() - 1] {
-            let rewrite = item.rewrite(context, child_shape)?;
-            self.shared.rewrites.push(rewrite);
-        }
-        Some(())
+        self.shared.format_children(context, child_shape)
     }
 
     fn format_last_child(
@@ -810,15 +866,14 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
                 .visual_indent(self.offset)
                 .sub_width(self.offset)?;
             let rewrite = item.rewrite(context, child_shape)?;
-            match wrap_str(rewrite, context.config.max_width(), shape) {
-                Some(rewrite) => root_rewrite.push_str(&rewrite),
-                None => {
-                    // We couldn't fit in at the visual indent, try the last
-                    // indent.
-                    let rewrite = item.rewrite(context, parent_shape)?;
-                    root_rewrite.push_str(&rewrite);
-                    self.offset = 0;
-                }
+            if filtered_str_fits(&rewrite, context.config.max_width(), shape) {
+                root_rewrite.push_str(&rewrite);
+            } else {
+                // We couldn't fit in at the visual indent, try the last
+                // indent.
+                let rewrite = item.rewrite(context, parent_shape)?;
+                root_rewrite.push_str(&rewrite);
+                self.offset = 0;
             }
 
             self.shared.children = &self.shared.children[1..];
@@ -829,18 +884,17 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
     }
 
     fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
-        shape
-            .with_max_width(context.config)
-            .offset_left(self.offset)
-            .map(|s| s.visual_indent(0))
+        get_visual_style_child_shape(
+            context,
+            shape,
+            self.offset,
+            // TODO(calebcartwright): self.shared.permissibly_overflowing_parent,
+            false,
+        )
     }
 
     fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
-        for item in &self.shared.children[..self.shared.children.len() - 1] {
-            let rewrite = item.rewrite(context, child_shape)?;
-            self.shared.rewrites.push(rewrite);
-        }
-        Some(())
+        self.shared.format_children(context, child_shape)
     }
 
     fn format_last_child(
diff --git a/src/config/config_type.rs b/src/config/config_type.rs
index e37ed798cb5..90a67e12c5a 100644
--- a/src/config/config_type.rs
+++ b/src/config/config_type.rs
@@ -1,4 +1,5 @@
 use crate::config::file_lines::FileLines;
+use crate::config::macro_names::MacroSelectors;
 use crate::config::options::{IgnoreList, WidthHeuristics};
 
 /// Trait for types that can be used in `Config`.
@@ -6,6 +7,14 @@ pub(crate) trait ConfigType: Sized {
     /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
     /// pipe-separated list of variants; for other types it returns "<type>".
     fn doc_hint() -> String;
+
+    /// Return `true` if the variant (i.e. value of this type) is stable.
+    ///
+    /// By default, return true for all values. Enums annotated with `#[config_type]`
+    /// are automatically implemented, based on the `#[unstable_variant]` annotation.
+    fn stable_variant(&self) -> bool {
+        true
+    }
 }
 
 impl ConfigType for bool {
@@ -38,6 +47,12 @@ impl ConfigType for FileLines {
     }
 }
 
+impl ConfigType for MacroSelectors {
+    fn doc_hint() -> String {
+        String::from("[<string>, ...]")
+    }
+}
+
 impl ConfigType for WidthHeuristics {
     fn doc_hint() -> String {
         String::new()
@@ -51,6 +66,13 @@ impl ConfigType for IgnoreList {
 }
 
 macro_rules! create_config {
+    // Options passed in to the macro.
+    //
+    // - $i: the ident name of the option
+    // - $ty: the type of the option value
+    // - $def: the default value of the option
+    // - $stb: true if the option is stable
+    // - $dstring: description of the option
     ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
         #[cfg(test)]
         use std::collections::HashSet;
@@ -61,9 +83,12 @@ macro_rules! create_config {
         #[derive(Clone)]
         #[allow(unreachable_pub)]
         pub struct Config {
-            // For each config item, we store a bool indicating whether it has
-            // been accessed and the value, and a bool whether the option was
-            // manually initialised, or taken from the default,
+            // For each config item, we store:
+            //
+            // - 0: true if the value has been access
+            // - 1: true if the option was manually initialized
+            // - 2: the option value
+            // - 3: true if the option is unstable
             $($i: (Cell<bool>, bool, $ty, bool)),+
         }
 
@@ -102,6 +127,7 @@ macro_rules! create_config {
                     | "array_width"
                     | "chain_width" => self.0.set_heuristics(),
                     "merge_imports" => self.0.set_merge_imports(),
+                    "fn_args_layout" => self.0.set_fn_args_layout(),
                     &_ => (),
                 }
             }
@@ -143,24 +169,20 @@ macro_rules! create_config {
 
             fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
             $(
-                if let Some(val) = parsed.$i {
-                    if self.$i.3 {
+                if let Some(option_value) = parsed.$i {
+                    let option_stable = self.$i.3;
+                    if $crate::config::config_type::is_stable_option_and_value(
+                        stringify!($i), option_stable, &option_value
+                    ) {
                         self.$i.1 = true;
-                        self.$i.2 = val;
-                    } else {
-                        if crate::is_nightly_channel!() {
-                            self.$i.1 = true;
-                            self.$i.2 = val;
-                        } else {
-                            eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \
-                                       available in nightly channel.", stringify!($i), val);
-                        }
+                        self.$i.2 = option_value;
                     }
                 }
             )+
                 self.set_heuristics();
                 self.set_ignore(dir);
                 self.set_merge_imports();
+                self.set_fn_args_layout();
                 self
             }
 
@@ -221,12 +243,22 @@ macro_rules! create_config {
                 match key {
                     $(
                         stringify!($i) => {
-                            self.$i.1 = true;
-                            self.$i.2 = val.parse::<$ty>()
+                            let option_value = val.parse::<$ty>()
                                 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
                                                  stringify!($i),
                                                  val,
                                                  stringify!($ty)));
+
+                            // Users are currently allowed to set unstable
+                            // options/variants via the `--config` options override.
+                            //
+                            // There is ongoing discussion about how to move forward here:
+                            // https://github.com/rust-lang/rustfmt/pull/5379
+                            //
+                            // For now, do not validate whether the option or value is stable,
+                            // just always set it.
+                            self.$i.1 = true;
+                            self.$i.2 = option_value;
                         }
                     )+
                     _ => panic!("Unknown config key in override: {}", key)
@@ -243,14 +275,21 @@ macro_rules! create_config {
                     | "array_width"
                     | "chain_width" => self.set_heuristics(),
                     "merge_imports" => self.set_merge_imports(),
+                    "fn_args_layout" => self.set_fn_args_layout(),
                     &_ => (),
                 }
             }
 
             #[allow(unreachable_pub)]
             pub fn is_hidden_option(name: &str) -> bool {
-                const HIDE_OPTIONS: [&str; 5] =
-                    ["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports"];
+                const HIDE_OPTIONS: [&str; 6] = [
+                    "verbose",
+                    "verbose_diff",
+                    "file_lines",
+                    "width_heuristics",
+                    "merge_imports",
+                    "fn_args_layout"
+                ];
                 HIDE_OPTIONS.contains(&name)
             }
 
@@ -400,6 +439,18 @@ macro_rules! create_config {
                 }
             }
 
+            fn set_fn_args_layout(&mut self) {
+                if self.was_set().fn_args_layout() {
+                    eprintln!(
+                        "Warning: the `fn_args_layout` option is deprecated. \
+                        Use `fn_params_layout`. instead"
+                    );
+                    if !self.was_set().fn_params_layout() {
+                        self.fn_params_layout.2 = self.fn_args_layout();
+                    }
+                }
+            }
+
             #[allow(unreachable_pub)]
             /// Returns `true` if the config key was explicitly set and is the default value.
             pub fn is_default(&self, key: &str) -> bool {
@@ -424,3 +475,38 @@ macro_rules! create_config {
         }
     )
 }
+
+pub(crate) fn is_stable_option_and_value<T>(
+    option_name: &str,
+    option_stable: bool,
+    option_value: &T,
+) -> bool
+where
+    T: PartialEq + std::fmt::Debug + ConfigType,
+{
+    let nightly = crate::is_nightly_channel!();
+    let variant_stable = option_value.stable_variant();
+    match (nightly, option_stable, variant_stable) {
+        // Stable with an unstable option
+        (false, false, _) => {
+            eprintln!(
+                "Warning: can't set `{} = {:?}`, unstable features are only \
+                       available in nightly channel.",
+                option_name, option_value
+            );
+            false
+        }
+        // Stable with a stable option, but an unstable variant
+        (false, true, false) => {
+            eprintln!(
+                "Warning: can't set `{} = {:?}`, unstable variants are only \
+                       available in nightly channel.",
+                option_name, option_value
+            );
+            false
+        }
+        // Nightly: everything allowed
+        // Stable with stable option and variant: allowed
+        (true, _, _) | (false, true, true) => true,
+    }
+}
diff --git a/src/config/macro_names.rs b/src/config/macro_names.rs
new file mode 100644
index 00000000000..26ad78d6dca
--- /dev/null
+++ b/src/config/macro_names.rs
@@ -0,0 +1,118 @@
+//! This module contains types and functions to support formatting specific macros.
+
+use itertools::Itertools;
+use std::{fmt, str};
+
+use serde::{Deserialize, Serialize};
+use serde_json as json;
+use thiserror::Error;
+
+/// Defines the name of a macro.
+#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
+pub struct MacroName(String);
+
+impl MacroName {
+    pub fn new(other: String) -> Self {
+        Self(other)
+    }
+}
+
+impl fmt::Display for MacroName {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl From<MacroName> for String {
+    fn from(other: MacroName) -> Self {
+        other.0
+    }
+}
+
+/// Defines a selector to match against a macro.
+#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
+pub enum MacroSelector {
+    Name(MacroName),
+    All,
+}
+
+impl fmt::Display for MacroSelector {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Name(name) => name.fmt(f),
+            Self::All => write!(f, "*"),
+        }
+    }
+}
+
+impl str::FromStr for MacroSelector {
+    type Err = std::convert::Infallible;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s {
+            "*" => MacroSelector::All,
+            name => MacroSelector::Name(MacroName(name.to_owned())),
+        })
+    }
+}
+
+/// A set of macro selectors.
+#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
+pub struct MacroSelectors(pub Vec<MacroSelector>);
+
+impl fmt::Display for MacroSelectors {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0.iter().format(", "))
+    }
+}
+
+#[derive(Error, Debug)]
+pub enum MacroSelectorsError {
+    #[error("{0}")]
+    Json(json::Error),
+}
+
+// This impl is needed for `Config::override_value` to work for use in tests.
+impl str::FromStr for MacroSelectors {
+    type Err = MacroSelectorsError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let raw: Vec<&str> = json::from_str(s).map_err(MacroSelectorsError::Json)?;
+        Ok(Self(
+            raw.into_iter()
+                .map(|raw| {
+                    MacroSelector::from_str(raw).expect("MacroSelector from_str is infallible")
+                })
+                .collect(),
+        ))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::str::FromStr;
+
+    #[test]
+    fn macro_names_from_str() {
+        let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
+        assert_eq!(
+            macro_names,
+            MacroSelectors(
+                [
+                    MacroSelector::Name(MacroName("foo".to_owned())),
+                    MacroSelector::All,
+                    MacroSelector::Name(MacroName("bar".to_owned()))
+                ]
+                .into_iter()
+                .collect()
+            )
+        );
+    }
+
+    #[test]
+    fn macro_names_display() {
+        let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
+        assert_eq!(format!("{}", macro_names), "foo, *, bar");
+    }
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index f49c18d3a46..4ec7e924bf6 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -13,6 +13,8 @@ pub use crate::config::file_lines::{FileLines, FileName, Range};
 #[allow(unreachable_pub)]
 pub use crate::config::lists::*;
 #[allow(unreachable_pub)]
+pub use crate::config::macro_names::{MacroSelector, MacroSelectors};
+#[allow(unreachable_pub)]
 pub use crate::config::options::*;
 
 #[macro_use]
@@ -22,6 +24,7 @@ pub(crate) mod options;
 
 pub(crate) mod file_lines;
 pub(crate) mod lists;
+pub(crate) mod macro_names;
 
 // This macro defines configuration options used in rustfmt. Each option
 // is defined as follows:
@@ -67,6 +70,8 @@ create_config! {
     format_macro_matchers: bool, false, false,
         "Format the metavariable matching patterns in macros";
     format_macro_bodies: bool, true, false, "Format the bodies of macros";
+    skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false,
+        "Skip formatting the bodies of macros invoked with the following names.";
     hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
         "Format hexadecimal integer literals";
 
@@ -119,7 +124,9 @@ create_config! {
     force_multiline_blocks: bool, false, false,
         "Force multiline closure bodies and match arms to be wrapped in a block";
     fn_args_layout: Density, Density::Tall, true,
-        "Control the layout of arguments in a function";
+        "(deprecated: use fn_params_layout instead)";
+    fn_params_layout: Density, Density::Tall, true,
+        "Control the layout of parameters in function signatures.";
     brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for items";
     control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false,
         "Brace style for control flow constructs";
@@ -175,7 +182,7 @@ create_config! {
     make_backup: bool, false, false, "Backup changed files";
     print_misformatted_file_names: bool, false, true,
         "Prints the names of mismatched files that were formatted. Prints the names of \
-         files that would be formated when used with `--check` mode. ";
+         files that would be formatted when used with `--check` mode. ";
 }
 
 #[derive(Error, Debug)]
@@ -191,6 +198,7 @@ impl PartialConfig {
         cloned.width_heuristics = None;
         cloned.print_misformatted_file_names = None;
         cloned.merge_imports = None;
+        cloned.fn_args_layout = None;
 
         ::toml::to_string(&cloned).map_err(ToTomlError)
     }
@@ -403,11 +411,21 @@ mod test {
     use super::*;
     use std::str;
 
+    use crate::config::macro_names::MacroName;
     use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
 
     #[allow(dead_code)]
     mod mock {
         use super::super::*;
+        use rustfmt_config_proc_macro::config_type;
+
+        #[config_type]
+        pub enum PartiallyUnstableOption {
+            V1,
+            V2,
+            #[unstable_variant]
+            V3,
+        }
 
         create_config! {
             // Options that are used by the generated functions
@@ -427,6 +445,12 @@ mod test {
                 "Merge imports";
             merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)";
 
+            // fn_args_layout renamed to fn_params_layout
+            fn_args_layout: Density, Density::Tall, true,
+                "(deprecated: use fn_params_layout instead)";
+            fn_params_layout: Density, Density::Tall, true,
+                "Control the layout of parameters in a function signatures.";
+
             // Width Heuristics
             use_small_heuristics: Heuristics, Heuristics::Default, true,
                 "Whether to use different formatting for items and \
@@ -451,6 +475,63 @@ mod test {
             // Options that are used by the tests
             stable_option: bool, false, true, "A stable option";
             unstable_option: bool, false, false, "An unstable option";
+            partially_unstable_option: PartiallyUnstableOption, PartiallyUnstableOption::V1, true,
+                "A partially unstable option";
+        }
+
+        #[cfg(test)]
+        mod partially_unstable_option {
+            use super::{Config, PartialConfig, PartiallyUnstableOption};
+            use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
+            use std::path::Path;
+
+            /// From the config file, we can fill with a stable variant
+            #[test]
+            fn test_from_toml_stable_value() {
+                let toml = r#"
+                    partially_unstable_option = "V2"
+                "#;
+                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
+                let config = Config::default();
+                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
+                assert_eq!(
+                    config.partially_unstable_option(),
+                    PartiallyUnstableOption::V2
+                );
+            }
+
+            /// From the config file, we cannot fill with an unstable variant (stable only)
+            #[stable_only_test]
+            #[test]
+            fn test_from_toml_unstable_value_on_stable() {
+                let toml = r#"
+                    partially_unstable_option = "V3"
+                "#;
+                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
+                let config = Config::default();
+                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
+                assert_eq!(
+                    config.partially_unstable_option(),
+                    // default value from config, i.e. fill failed
+                    PartiallyUnstableOption::V1
+                );
+            }
+
+            /// From the config file, we can fill with an unstable variant (nightly only)
+            #[nightly_only_test]
+            #[test]
+            fn test_from_toml_unstable_value_on_nightly() {
+                let toml = r#"
+                    partially_unstable_option = "V3"
+                "#;
+                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
+                let config = Config::default();
+                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
+                assert_eq!(
+                    config.partially_unstable_option(),
+                    PartiallyUnstableOption::V3
+                );
+            }
         }
     }
 
@@ -489,6 +570,11 @@ mod test {
         assert_eq!(config.was_set().verbose(), false);
     }
 
+    const PRINT_DOCS_STABLE_OPTION: &str = "stable_option <boolean> Default: false";
+    const PRINT_DOCS_UNSTABLE_OPTION: &str = "unstable_option <boolean> Default: false (unstable)";
+    const PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION: &str =
+        "partially_unstable_option [V1|V2|V3 (unstable)] Default: V1";
+
     #[test]
     fn test_print_docs_exclude_unstable() {
         use self::mock::Config;
@@ -497,10 +583,9 @@ mod test {
         Config::print_docs(&mut output, false);
 
         let s = str::from_utf8(&output).unwrap();
-
-        assert_eq!(s.contains("stable_option"), true);
-        assert_eq!(s.contains("unstable_option"), false);
-        assert_eq!(s.contains("(unstable)"), false);
+        assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
+        assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), false);
+        assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
     }
 
     #[test]
@@ -511,9 +596,9 @@ mod test {
         Config::print_docs(&mut output, true);
 
         let s = str::from_utf8(&output).unwrap();
-        assert_eq!(s.contains("stable_option"), true);
-        assert_eq!(s.contains("unstable_option"), true);
-        assert_eq!(s.contains("(unstable)"), true);
+        assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
+        assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), true);
+        assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
     }
 
     #[test]
@@ -541,6 +626,7 @@ normalize_doc_attributes = false
 format_strings = false
 format_macro_matchers = false
 format_macro_bodies = true
+skip_macro_invocations = []
 hex_literal_case = "Preserve"
 empty_item_single_line = true
 struct_lit_single_line = true
@@ -567,7 +653,7 @@ enum_discrim_align_threshold = 0
 match_arm_blocks = true
 match_arm_leading_pipes = "Never"
 force_multiline_blocks = false
-fn_args_layout = "Tall"
+fn_params_layout = "Tall"
 brace_style = "SameLineWhere"
 control_brace_style = "AlwaysSameLine"
 trailing_semicolon = true
@@ -921,4 +1007,45 @@ make_backup = false
             assert_eq!(config.single_line_if_else_max_width(), 100);
         }
     }
+
+    #[cfg(test)]
+    mod partially_unstable_option {
+        use super::mock::{Config, PartiallyUnstableOption};
+        use super::*;
+
+        /// From the command line, we can override with a stable variant.
+        #[test]
+        fn test_override_stable_value() {
+            let mut config = Config::default();
+            config.override_value("partially_unstable_option", "V2");
+            assert_eq!(
+                config.partially_unstable_option(),
+                PartiallyUnstableOption::V2
+            );
+        }
+
+        /// From the command line, we can override with an unstable variant.
+        #[test]
+        fn test_override_unstable_value() {
+            let mut config = Config::default();
+            config.override_value("partially_unstable_option", "V3");
+            assert_eq!(
+                config.partially_unstable_option(),
+                PartiallyUnstableOption::V3
+            );
+        }
+    }
+
+    #[test]
+    fn test_override_skip_macro_invocations() {
+        let mut config = Config::default();
+        config.override_value("skip_macro_invocations", r#"["*", "println"]"#);
+        assert_eq!(
+            config.skip_macro_invocations(),
+            MacroSelectors(vec![
+                MacroSelector::All,
+                MacroSelector::Name(MacroName::new("println".to_owned()))
+            ])
+        );
+    }
 }
diff --git a/src/expr.rs b/src/expr.rs
index a7b73ba78c5..c9d704765c2 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -29,9 +29,9 @@ use crate::spanned::Spanned;
 use crate::string::{rewrite_string, StringFormat};
 use crate::types::{rewrite_path, PathContext};
 use crate::utils::{
-    colon_spaces, contains_skip, count_newlines, first_line_ends_with, inner_attributes,
-    last_line_extendable, last_line_width, mk_sp, outer_attributes, semicolon_for_expr,
-    unicode_str_width, wrap_str,
+    colon_spaces, contains_skip, count_newlines, filtered_str_fits, first_line_ends_with,
+    inner_attributes, last_line_extendable, last_line_width, mk_sp, outer_attributes,
+    semicolon_for_expr, unicode_str_width, wrap_str,
 };
 use crate::vertical::rewrite_with_alignment;
 use crate::visitor::FmtVisitor;
@@ -2043,8 +2043,7 @@ fn choose_rhs<R: Rewrite>(
 
             match (orig_rhs, new_rhs) {
                 (Some(ref orig_rhs), Some(ref new_rhs))
-                    if wrap_str(new_rhs.clone(), context.config.max_width(), new_shape)
-                        .is_none() =>
+                    if !filtered_str_fits(&new_rhs, context.config.max_width(), new_shape) =>
                 {
                     Some(format!("{}{}", before_space_str, orig_rhs))
                 }
diff --git a/src/imports.rs b/src/imports.rs
index 8d41c881589..140d8953abc 100644
--- a/src/imports.rs
+++ b/src/imports.rs
@@ -251,8 +251,8 @@ fn flatten_use_trees(
     use_trees: Vec<UseTree>,
     import_granularity: ImportGranularity,
 ) -> Vec<UseTree> {
-    // Return non-sorted single occurance of the use-trees text string;
-    // order is by first occurance of the use-tree.
+    // Return non-sorted single occurrence of the use-trees text string;
+    // order is by first occurrence of the use-tree.
     use_trees
         .into_iter()
         .flat_map(|tree| tree.flatten(import_granularity))
diff --git a/src/items.rs b/src/items.rs
index 8f35068e35f..755a41f6bf0 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -1084,7 +1084,11 @@ pub(crate) fn format_trait(
             let item_snippet = context.snippet(item.span);
             if let Some(lo) = item_snippet.find('/') {
                 // 1 = `{`
-                let comment_hi = body_lo - BytePos(1);
+                let comment_hi = if generics.params.len() > 0 {
+                    generics.span.lo() - BytePos(1)
+                } else {
+                    body_lo - BytePos(1)
+                };
                 let comment_lo = item.span.lo() + BytePos(lo as u32);
                 if comment_lo < comment_hi {
                     match recover_missing_comment_in_span(
@@ -2602,7 +2606,7 @@ fn rewrite_params(
         &param_items,
         context
             .config
-            .fn_args_layout()
+            .fn_params_layout()
             .to_list_tactic(param_items.len()),
         Separator::Comma,
         one_line_budget,
diff --git a/src/lib.rs b/src/lib.rs
index 1d1ef525f23..495010a297d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,7 +3,6 @@
 #![warn(unreachable_pub)]
 #![recursion_limit = "256"]
 #![allow(clippy::match_like_matches_macro)]
-#![allow(unreachable_pub)]
 
 #[macro_use]
 extern crate derive_new;
diff --git a/src/macros.rs b/src/macros.rs
index 3a641fab5d6..1bcb7774c94 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -35,8 +35,8 @@ use crate::shape::{Indent, Shape};
 use crate::source_map::SpanUtils;
 use crate::spanned::Spanned;
 use crate::utils::{
-    format_visibility, indent_next_line, is_empty_line, mk_sp, remove_trailing_white_spaces,
-    rewrite_ident, trim_left_preserve_layout, wrap_str, NodeIdExt,
+    filtered_str_fits, format_visibility, indent_next_line, is_empty_line, mk_sp,
+    remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout, NodeIdExt,
 };
 use crate::visitor::FmtVisitor;
 
@@ -157,7 +157,8 @@ pub(crate) fn rewrite_macro(
 ) -> Option<String> {
     let should_skip = context
         .skip_context
-        .skip_macro(context.snippet(mac.path.span));
+        .macros
+        .skip(context.snippet(mac.path.span));
     if should_skip {
         None
     } else {
@@ -1265,15 +1266,14 @@ impl MacroBranch {
                 }
             }
         };
-        let new_body = wrap_str(
-            new_body_snippet.snippet.to_string(),
-            config.max_width(),
-            shape,
-        )?;
+
+        if !filtered_str_fits(&new_body_snippet.snippet, config.max_width(), shape) {
+            return None;
+        }
 
         // Indent the body since it is in a block.
         let indent_str = body_indent.to_string(&config);
-        let mut new_body = LineClasses::new(new_body.trim_end())
+        let mut new_body = LineClasses::new(new_body_snippet.snippet.trim_end())
             .enumerate()
             .fold(
                 (String::new(), true),
diff --git a/src/skip.rs b/src/skip.rs
index 0fdc097efc2..59d6d84c964 100644
--- a/src/skip.rs
+++ b/src/skip.rs
@@ -2,33 +2,84 @@
 
 use rustc_ast::ast;
 use rustc_ast_pretty::pprust;
+use std::collections::HashSet;
 
-/// Take care of skip name stack. You can update it by attributes slice or
-/// by other context. Query this context to know if you need skip a block.
+/// Track which blocks of code are to be skipped when formatting.
+///
+/// You can update it by:
+///
+/// - attributes slice
+/// - manually feeding values into the underlying contexts
+///
+/// Query this context to know if you need to skip a block.
 #[derive(Default, Clone)]
 pub(crate) struct SkipContext {
-    macros: Vec<String>,
-    attributes: Vec<String>,
+    pub(crate) macros: SkipNameContext,
+    pub(crate) attributes: SkipNameContext,
 }
 
 impl SkipContext {
     pub(crate) fn update_with_attrs(&mut self, attrs: &[ast::Attribute]) {
-        self.macros.append(&mut get_skip_names("macros", attrs));
-        self.attributes
-            .append(&mut get_skip_names("attributes", attrs));
+        self.macros.extend(get_skip_names("macros", attrs));
+        self.attributes.extend(get_skip_names("attributes", attrs));
     }
 
-    pub(crate) fn update(&mut self, mut other: SkipContext) {
-        self.macros.append(&mut other.macros);
-        self.attributes.append(&mut other.attributes);
+    pub(crate) fn update(&mut self, other: SkipContext) {
+        let SkipContext { macros, attributes } = other;
+        self.macros.update(macros);
+        self.attributes.update(attributes);
+    }
+}
+
+/// Track which names to skip.
+///
+/// Query this context with a string to know whether to skip it.
+#[derive(Clone)]
+pub(crate) enum SkipNameContext {
+    All,
+    Values(HashSet<String>),
+}
+
+impl Default for SkipNameContext {
+    fn default() -> Self {
+        Self::Values(Default::default())
+    }
+}
+
+impl Extend<String> for SkipNameContext {
+    fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
+        match self {
+            Self::All => {}
+            Self::Values(values) => values.extend(iter),
+        }
+    }
+}
+
+impl SkipNameContext {
+    pub(crate) fn update(&mut self, other: Self) {
+        match (self, other) {
+            // If we're already skipping everything, nothing more can be added
+            (Self::All, _) => {}
+            // If we want to skip all, set it
+            (this, Self::All) => {
+                *this = Self::All;
+            }
+            // If we have some new values to skip, add them
+            (Self::Values(existing_values), Self::Values(new_values)) => {
+                existing_values.extend(new_values)
+            }
+        }
     }
 
-    pub(crate) fn skip_macro(&self, name: &str) -> bool {
-        self.macros.iter().any(|n| n == name)
+    pub(crate) fn skip(&self, name: &str) -> bool {
+        match self {
+            Self::All => true,
+            Self::Values(values) => values.contains(name),
+        }
     }
 
-    pub(crate) fn skip_attribute(&self, name: &str) -> bool {
-        self.attributes.iter().any(|n| n == name)
+    pub(crate) fn skip_all(&mut self) {
+        *self = Self::All;
     }
 }
 
diff --git a/src/test/configuration_snippet.rs b/src/test/configuration_snippet.rs
index c8fda7c8556..c70b3c5facd 100644
--- a/src/test/configuration_snippet.rs
+++ b/src/test/configuration_snippet.rs
@@ -27,8 +27,13 @@ impl ConfigurationSection {
         lazy_static! {
             static ref CONFIG_NAME_REGEX: regex::Regex =
                 regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern");
+            // Configuration values, which will be passed to `from_str`:
+            //
+            // - must be prefixed with `####`
+            // - must be wrapped in backticks
+            // - may by wrapped in double quotes (which will be stripped)
             static ref CONFIG_VALUE_REGEX: regex::Regex =
-                regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#)
+                regex::Regex::new(r#"^#### `"?([^`]+?)"?`"#)
                     .expect("failed creating configuration value pattern");
         }
 
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 6b5bc2b30dd..cfad4a8ed0e 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -982,11 +982,7 @@ fn rustfmt() -> PathBuf {
     assert!(
         me.is_file() || me.with_extension("exe").is_file(),
         "{}",
-        if cfg!(release) {
-            "no rustfmt bin, try running `cargo build --release` before testing"
-        } else {
-            "no rustfmt bin, try running `cargo build` before testing"
-        }
+        "no rustfmt bin, try running `cargo build` or `cargo build --release` before testing"
     );
     me
 }
diff --git a/src/utils.rs b/src/utils.rs
index cd852855602..a97bcba0bd2 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -384,14 +384,15 @@ macro_rules! skip_out_of_file_lines_range_visitor {
 // Wraps String in an Option. Returns Some when the string adheres to the
 // Rewrite constraints defined for the Rewrite trait and None otherwise.
 pub(crate) fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> {
-    if is_valid_str(&filter_normal_code(&s), max_width, shape) {
+    if filtered_str_fits(&s, max_width, shape) {
         Some(s)
     } else {
         None
     }
 }
 
-fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool {
+pub(crate) fn filtered_str_fits(snippet: &str, max_width: usize, shape: Shape) -> bool {
+    let snippet = &filter_normal_code(snippet);
     if !snippet.is_empty() {
         // First line must fits with `shape.width`.
         if first_line_width(snippet) > shape.width {
diff --git a/src/visitor.rs b/src/visitor.rs
index 9a0e0752c12..c0fc37eaaa8 100644
--- a/src/visitor.rs
+++ b/src/visitor.rs
@@ -8,7 +8,7 @@ use rustc_span::{symbol, BytePos, Pos, Span};
 use crate::attr::*;
 use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices};
 use crate::config::Version;
-use crate::config::{BraceStyle, Config};
+use crate::config::{BraceStyle, Config, MacroSelector};
 use crate::coverage::transform_missing_snippet;
 use crate::items::{
     format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, rewrite_extern_crate,
@@ -770,6 +770,15 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
         snippet_provider: &'a SnippetProvider,
         report: FormatReport,
     ) -> FmtVisitor<'a> {
+        let mut skip_context = SkipContext::default();
+        let mut macro_names = Vec::new();
+        for macro_selector in config.skip_macro_invocations().0 {
+            match macro_selector {
+                MacroSelector::Name(name) => macro_names.push(name.to_string()),
+                MacroSelector::All => skip_context.macros.skip_all(),
+            }
+        }
+        skip_context.macros.extend(macro_names);
         FmtVisitor {
             parent_context: None,
             parse_sess: parse_session,
@@ -784,7 +793,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
             is_macro_def: false,
             macro_rewrite_failure: false,
             report,
-            skip_context: Default::default(),
+            skip_context,
         }
     }