Skip to content

Commit 2ae63f0

Browse files
tommilligancalebcartwright
authored andcommitted
config_type: add unstable_variant attribute
1 parent b3d4fb4 commit 2ae63f0

File tree

9 files changed

+244
-34
lines changed

9 files changed

+244
-34
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ unicode-segmentation = "1.9"
5757
unicode-width = "0.1"
5858
unicode_categories = "0.1"
5959

60-
rustfmt-config_proc_macro = { version = "0.2", path = "config_proc_macro" }
60+
rustfmt-config_proc_macro = { version = "0.3", path = "config_proc_macro" }
6161

6262
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
6363
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`

config_proc_macro/Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config_proc_macro/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rustfmt-config_proc_macro"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2018"
55
description = "A collection of procedural macros for rustfmt"
66
license = "Apache-2.0/MIT"

config_proc_macro/src/attrs.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
//! This module provides utilities for handling attributes on variants
2-
//! of `config_type` enum. Currently there are two types of attributes
3-
//! that could appear on the variants of `config_type` enum: `doc_hint`
4-
//! and `value`. Both comes in the form of name-value pair whose value
5-
//! is string literal.
2+
//! of `config_type` enum. Currently there are the following attributes
3+
//! that could appear on the variants of `config_type` enum:
4+
//!
5+
//! - `doc_hint`: name-value pair whose value is string literal
6+
//! - `value`: name-value pair whose value is string literal
7+
//! - `unstable_variant`: name only
68
79
/// Returns the value of the first `doc_hint` attribute in the given slice or
810
/// `None` if `doc_hint` attribute is not available.
@@ -27,6 +29,11 @@ pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> {
2729
attrs.iter().filter_map(config_value).next()
2830
}
2931

32+
/// Returns `true` if the there is at least one `unstable` attribute in the given slice.
33+
pub fn any_unstable_variant(attrs: &[syn::Attribute]) -> bool {
34+
attrs.iter().any(is_unstable_variant)
35+
}
36+
3037
/// Returns a string literal value if the given attribute is `value`
3138
/// attribute or `None` otherwise.
3239
pub fn config_value(attr: &syn::Attribute) -> Option<String> {
@@ -38,13 +45,25 @@ pub fn is_config_value(attr: &syn::Attribute) -> bool {
3845
is_attr_name_value(attr, "value")
3946
}
4047

48+
/// Returns `true` if the given attribute is an `unstable` attribute.
49+
pub fn is_unstable_variant(attr: &syn::Attribute) -> bool {
50+
is_attr_path(attr, "unstable_variant")
51+
}
52+
4153
fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool {
4254
attr.parse_meta().ok().map_or(false, |meta| match meta {
4355
syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) if path.is_ident(name) => true,
4456
_ => false,
4557
})
4658
}
4759

60+
fn is_attr_path(attr: &syn::Attribute, name: &str) -> bool {
61+
attr.parse_meta().ok().map_or(false, |meta| match meta {
62+
syn::Meta::Path(path) if path.is_ident(name) => true,
63+
_ => false,
64+
})
65+
}
66+
4867
fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> {
4968
attr.parse_meta().ok().and_then(|meta| match meta {
5069
syn::Meta::NameValue(syn::MetaNameValue {

config_proc_macro/src/item_enum.rs

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use proc_macro2::TokenStream;
2-
use quote::quote;
2+
use quote::{quote, quote_spanned};
3+
use syn::spanned::Spanned;
34

45
use crate::attrs::*;
56
use crate::utils::*;
@@ -47,25 +48,50 @@ fn process_variant(variant: &syn::Variant) -> TokenStream {
4748
let metas = variant
4849
.attrs
4950
.iter()
50-
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr));
51+
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr) && !is_unstable_variant(attr));
5152
let attrs = fold_quote(metas, |meta| quote!(#meta));
5253
let syn::Variant { ident, fields, .. } = variant;
5354
quote!(#attrs #ident #fields)
5455
}
5556

57+
/// Return the correct syntax to pattern match on the enum variant, discarding all
58+
/// internal field data.
59+
fn fields_in_variant(variant: &syn::Variant) -> TokenStream {
60+
// With thanks to https://stackoverflow.com/a/65182902
61+
match &variant.fields {
62+
syn::Fields::Unnamed(_) => quote_spanned! { variant.span() => (..) },
63+
syn::Fields::Unit => quote_spanned! { variant.span() => },
64+
syn::Fields::Named(_) => quote_spanned! { variant.span() => {..} },
65+
}
66+
}
67+
5668
fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
5769
let doc_hint = variants
5870
.iter()
5971
.map(doc_hint_of_variant)
6072
.collect::<Vec<_>>()
6173
.join("|");
6274
let doc_hint = format!("[{}]", doc_hint);
75+
76+
let variant_stables = variants
77+
.iter()
78+
.map(|v| (&v.ident, fields_in_variant(&v), !unstable_of_variant(v)));
79+
let match_patterns = fold_quote(variant_stables, |(v, fields, stable)| {
80+
quote! {
81+
#ident::#v #fields => #stable,
82+
}
83+
});
6384
quote! {
6485
use crate::config::ConfigType;
6586
impl ConfigType for #ident {
6687
fn doc_hint() -> String {
6788
#doc_hint.to_owned()
6889
}
90+
fn stable_variant(&self) -> bool {
91+
match self {
92+
#match_patterns
93+
}
94+
}
6995
}
7096
}
7197
}
@@ -123,13 +149,21 @@ fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream {
123149
}
124150

125151
fn doc_hint_of_variant(variant: &syn::Variant) -> String {
126-
find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string())
152+
let mut text = find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string());
153+
if unstable_of_variant(&variant) {
154+
text.push_str(" (unstable)")
155+
};
156+
text
127157
}
128158

129159
fn config_value_of_variant(variant: &syn::Variant) -> String {
130160
find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string())
131161
}
132162

163+
fn unstable_of_variant(variant: &syn::Variant) -> bool {
164+
any_unstable_variant(&variant.attrs)
165+
}
166+
133167
fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream {
134168
let arms = fold_quote(variants.iter(), |v| {
135169
let v_ident = &v.ident;

config_proc_macro/tests/smoke.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod config {
22
pub trait ConfigType: Sized {
33
fn doc_hint() -> String;
4+
fn stable_variant(&self) -> bool;
45
}
56
}
67

src/config/config_type.rs

+74-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ pub(crate) trait ConfigType: Sized {
66
/// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
77
/// pipe-separated list of variants; for other types it returns "<type>".
88
fn doc_hint() -> String;
9+
10+
/// Return `true` if the variant (i.e. value of this type) is stable.
11+
///
12+
/// By default, return true for all values. Enums annotated with `#[config_type]`
13+
/// are automatically implemented, based on the `#[unstable_variant]` annotation.
14+
fn stable_variant(&self) -> bool {
15+
true
16+
}
917
}
1018

1119
impl ConfigType for bool {
@@ -51,6 +59,13 @@ impl ConfigType for IgnoreList {
5159
}
5260

5361
macro_rules! create_config {
62+
// Options passed in to the macro.
63+
//
64+
// - $i: the ident name of the option
65+
// - $ty: the type of the option value
66+
// - $def: the default value of the option
67+
// - $stb: true if the option is stable
68+
// - $dstring: description of the option
5469
($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
5570
#[cfg(test)]
5671
use std::collections::HashSet;
@@ -61,9 +76,12 @@ macro_rules! create_config {
6176
#[derive(Clone)]
6277
#[allow(unreachable_pub)]
6378
pub struct Config {
64-
// For each config item, we store a bool indicating whether it has
65-
// been accessed and the value, and a bool whether the option was
66-
// manually initialised, or taken from the default,
79+
// For each config item, we store:
80+
//
81+
// - 0: true if the value has been access
82+
// - 1: true if the option was manually initialized
83+
// - 2: the option value
84+
// - 3: true if the option is unstable
6785
$($i: (Cell<bool>, bool, $ty, bool)),+
6886
}
6987

@@ -143,18 +161,13 @@ macro_rules! create_config {
143161

144162
fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
145163
$(
146-
if let Some(val) = parsed.$i {
147-
if self.$i.3 {
164+
if let Some(option_value) = parsed.$i {
165+
let option_stable = self.$i.3;
166+
if $crate::config::config_type::is_stable_option_and_value(
167+
stringify!($i), option_stable, &option_value
168+
) {
148169
self.$i.1 = true;
149-
self.$i.2 = val;
150-
} else {
151-
if crate::is_nightly_channel!() {
152-
self.$i.1 = true;
153-
self.$i.2 = val;
154-
} else {
155-
eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \
156-
available in nightly channel.", stringify!($i), val);
157-
}
170+
self.$i.2 = option_value;
158171
}
159172
}
160173
)+
@@ -221,12 +234,22 @@ macro_rules! create_config {
221234
match key {
222235
$(
223236
stringify!($i) => {
224-
self.$i.1 = true;
225-
self.$i.2 = val.parse::<$ty>()
237+
let option_value = val.parse::<$ty>()
226238
.expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
227239
stringify!($i),
228240
val,
229241
stringify!($ty)));
242+
243+
// Users are currently allowed to set unstable
244+
// options/variants via the `--config` options override.
245+
//
246+
// There is ongoing discussion about how to move forward here:
247+
// https://github.com/rust-lang/rustfmt/pull/5379
248+
//
249+
// For now, do not validate whether the option or value is stable,
250+
// just always set it.
251+
self.$i.1 = true;
252+
self.$i.2 = option_value;
230253
}
231254
)+
232255
_ => panic!("Unknown config key in override: {}", key)
@@ -424,3 +447,38 @@ macro_rules! create_config {
424447
}
425448
)
426449
}
450+
451+
pub(crate) fn is_stable_option_and_value<T>(
452+
option_name: &str,
453+
option_stable: bool,
454+
option_value: &T,
455+
) -> bool
456+
where
457+
T: PartialEq + std::fmt::Debug + ConfigType,
458+
{
459+
let nightly = crate::is_nightly_channel!();
460+
let variant_stable = option_value.stable_variant();
461+
match (nightly, option_stable, variant_stable) {
462+
// Stable with an unstable option
463+
(false, false, _) => {
464+
eprintln!(
465+
"Warning: can't set `{} = {:?}`, unstable features are only \
466+
available in nightly channel.",
467+
option_name, option_value
468+
);
469+
false
470+
}
471+
// Stable with a stable option, but an unstable variant
472+
(false, true, false) => {
473+
eprintln!(
474+
"Warning: can't set `{} = {:?}`, unstable variants are only \
475+
available in nightly channel.",
476+
option_name, option_value
477+
);
478+
false
479+
}
480+
// Nightly: everything allowed
481+
// Stable with stable option and variant: allowed
482+
(true, _, _) | (false, true, true) => true,
483+
}
484+
}

0 commit comments

Comments
 (0)