config_type: add unstable_variant attribute
This commit is contained in:
parent
b3d4fb448c
commit
2ae63f0018
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -476,7 +476,7 @@ checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
|
||||
|
||||
[[package]]
|
||||
name = "rustfmt-config_proc_macro"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -57,7 +57,7 @@ unicode-segmentation = "1.9"
|
||||
unicode-width = "0.1"
|
||||
unicode_categories = "0.1"
|
||||
|
||||
rustfmt-config_proc_macro = { version = "0.2", path = "config_proc_macro" }
|
||||
rustfmt-config_proc_macro = { version = "0.3", path = "config_proc_macro" }
|
||||
|
||||
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
|
||||
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
|
||||
|
2
config_proc_macro/Cargo.lock
generated
2
config_proc_macro/Cargo.lock
generated
@ -22,7 +22,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfmt-config_proc_macro"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustfmt-config_proc_macro"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2018"
|
||||
description = "A collection of procedural macros for rustfmt"
|
||||
license = "Apache-2.0/MIT"
|
||||
|
@ -1,8 +1,10 @@
|
||||
//! This module provides utilities for handling attributes on variants
|
||||
//! of `config_type` enum. Currently there are two types of attributes
|
||||
//! that could appear on the variants of `config_type` enum: `doc_hint`
|
||||
//! and `value`. Both comes in the form of name-value pair whose value
|
||||
//! is string literal.
|
||||
//! of `config_type` enum. Currently there are the following attributes
|
||||
//! that could appear on the variants of `config_type` enum:
|
||||
//!
|
||||
//! - `doc_hint`: name-value pair whose value is string literal
|
||||
//! - `value`: name-value pair whose value is string literal
|
||||
//! - `unstable_variant`: name only
|
||||
|
||||
/// Returns the value of the first `doc_hint` attribute in the given slice or
|
||||
/// `None` if `doc_hint` attribute is not available.
|
||||
@ -27,6 +29,11 @@ pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> {
|
||||
attrs.iter().filter_map(config_value).next()
|
||||
}
|
||||
|
||||
/// Returns `true` if the there is at least one `unstable` attribute in the given slice.
|
||||
pub fn any_unstable_variant(attrs: &[syn::Attribute]) -> bool {
|
||||
attrs.iter().any(is_unstable_variant)
|
||||
}
|
||||
|
||||
/// Returns a string literal value if the given attribute is `value`
|
||||
/// attribute or `None` otherwise.
|
||||
pub fn config_value(attr: &syn::Attribute) -> Option<String> {
|
||||
@ -38,6 +45,11 @@ pub fn is_config_value(attr: &syn::Attribute) -> bool {
|
||||
is_attr_name_value(attr, "value")
|
||||
}
|
||||
|
||||
/// Returns `true` if the given attribute is an `unstable` attribute.
|
||||
pub fn is_unstable_variant(attr: &syn::Attribute) -> bool {
|
||||
is_attr_path(attr, "unstable_variant")
|
||||
}
|
||||
|
||||
fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool {
|
||||
attr.parse_meta().ok().map_or(false, |meta| match meta {
|
||||
syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) if path.is_ident(name) => true,
|
||||
@ -45,6 +57,13 @@ fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn is_attr_path(attr: &syn::Attribute, name: &str) -> bool {
|
||||
attr.parse_meta().ok().map_or(false, |meta| match meta {
|
||||
syn::Meta::Path(path) if path.is_ident(name) => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> {
|
||||
attr.parse_meta().ok().and_then(|meta| match meta {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::attrs::*;
|
||||
use crate::utils::*;
|
||||
@ -47,12 +48,23 @@ fn process_variant(variant: &syn::Variant) -> TokenStream {
|
||||
let metas = variant
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr));
|
||||
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr) && !is_unstable_variant(attr));
|
||||
let attrs = fold_quote(metas, |meta| quote!(#meta));
|
||||
let syn::Variant { ident, fields, .. } = variant;
|
||||
quote!(#attrs #ident #fields)
|
||||
}
|
||||
|
||||
/// Return the correct syntax to pattern match on the enum variant, discarding all
|
||||
/// internal field data.
|
||||
fn fields_in_variant(variant: &syn::Variant) -> TokenStream {
|
||||
// With thanks to https://stackoverflow.com/a/65182902
|
||||
match &variant.fields {
|
||||
syn::Fields::Unnamed(_) => quote_spanned! { variant.span() => (..) },
|
||||
syn::Fields::Unit => quote_spanned! { variant.span() => },
|
||||
syn::Fields::Named(_) => quote_spanned! { variant.span() => {..} },
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let doc_hint = variants
|
||||
.iter()
|
||||
@ -60,12 +72,26 @@ fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
.collect::<Vec<_>>()
|
||||
.join("|");
|
||||
let doc_hint = format!("[{}]", doc_hint);
|
||||
|
||||
let variant_stables = variants
|
||||
.iter()
|
||||
.map(|v| (&v.ident, fields_in_variant(&v), !unstable_of_variant(v)));
|
||||
let match_patterns = fold_quote(variant_stables, |(v, fields, stable)| {
|
||||
quote! {
|
||||
#ident::#v #fields => #stable,
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
use crate::config::ConfigType;
|
||||
impl ConfigType for #ident {
|
||||
fn doc_hint() -> String {
|
||||
#doc_hint.to_owned()
|
||||
}
|
||||
fn stable_variant(&self) -> bool {
|
||||
match self {
|
||||
#match_patterns
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,13 +149,21 @@ fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
}
|
||||
|
||||
fn doc_hint_of_variant(variant: &syn::Variant) -> String {
|
||||
find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string())
|
||||
let mut text = find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string());
|
||||
if unstable_of_variant(&variant) {
|
||||
text.push_str(" (unstable)")
|
||||
};
|
||||
text
|
||||
}
|
||||
|
||||
fn config_value_of_variant(variant: &syn::Variant) -> String {
|
||||
find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string())
|
||||
}
|
||||
|
||||
fn unstable_of_variant(variant: &syn::Variant) -> bool {
|
||||
any_unstable_variant(&variant.attrs)
|
||||
}
|
||||
|
||||
fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let arms = fold_quote(variants.iter(), |v| {
|
||||
let v_ident = &v.ident;
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod config {
|
||||
pub trait ConfigType: Sized {
|
||||
fn doc_hint() -> String;
|
||||
fn stable_variant(&self) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,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 {
|
||||
@ -51,6 +59,13 @@ fn doc_hint() -> String {
|
||||
}
|
||||
|
||||
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 +76,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)),+
|
||||
}
|
||||
|
||||
@ -143,18 +161,13 @@ pub fn was_set(&self) -> ConfigWasSet<'_> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
)+
|
||||
@ -221,12 +234,22 @@ pub fn override_value(&mut self, key: &str, val: &str)
|
||||
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)
|
||||
@ -424,3 +447,38 @@ fn default() -> 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,
|
||||
}
|
||||
}
|
||||
|
@ -408,6 +408,15 @@ mod 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
|
||||
@ -451,6 +460,63 @@ mod mock {
|
||||
// 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 +555,11 @@ fn test_was_set() {
|
||||
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 +568,9 @@ fn test_print_docs_exclude_unstable() {
|
||||
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 +581,9 @@ fn test_print_docs_include_unstable() {
|
||||
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]
|
||||
@ -921,4 +991,32 @@ fn test_override_single_line_if_else_max_width_exceeds_max_width() {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user