Skip to content

Commit

Permalink
Refactor customise impl
Browse files Browse the repository at this point in the history
  • Loading branch information
cyqsimon committed Oct 19, 2024
1 parent 1f9f745 commit 3add3be
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 70 deletions.
28 changes: 14 additions & 14 deletions documented-macros/src/config/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use syn::{
use syn::{Expr, Visibility};

#[cfg(feature = "customise")]
use crate::config::customise_core::{ensure_unique_options, ConfigOption};
use crate::config::customise_core::{ensure_unique_options, ConfigOption, ConfigOptionData};

/// Configurable arguments for attribute macros.
///
Expand Down Expand Up @@ -53,30 +53,30 @@ impl AttrConfig {
#[cfg(feature = "customise")]
impl Parse for AttrCustomisations {
fn parse(input: ParseStream) -> syn::Result<Self> {
use ConfigOption as O;
use ConfigOptionData as Data;

let args = Punctuated::<ConfigOption, Token![,]>::parse_terminated(input)?
let opts = Punctuated::<ConfigOption, Token![,]>::parse_terminated(input)?
.into_iter()
.collect::<Vec<_>>();

ensure_unique_options(&args)?;
ensure_unique_options(&opts)?;

let mut config = Self::default();
for arg in args {
for opt in opts {
// I'd love to macro this if declarative macros can expand to a full match arm,
// but no: https://github.com/rust-lang/rfcs/issues/2654
match arg {
O::Vis(_, val) => {
config.custom_vis.replace(val);
match opt.data {
Data::Vis(vis) => {
config.custom_vis.replace(vis);
}
O::Name(_, val) => {
config.custom_name.replace(val);
Data::Name(name) => {
config.custom_name.replace(name.value());
}
O::Default(_, mode) => {
config.default_value.replace(mode);
Data::Default(expr) => {
config.default_value.replace(expr);
}
O::Trim(_, val) => {
config.trim.replace(val);
Data::Trim(trim) => {
config.trim.replace(trim.value());
}
}
}
Expand Down
101 changes: 56 additions & 45 deletions documented-macros/src/config/customise_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use itertools::Itertools;
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Error, Expr, LitBool, LitStr, Token, Visibility,
};

Expand All @@ -15,96 +14,108 @@ mod kw {
custom_keyword!(trim);
}

/// All known configuration options. Each variant of config struct may choose to
/// accept or reject any of them in their `Parse` implementation.
/// A configuration option that includes the span info. Each kind of
/// customisation struct may choose to accept or reject any of them.
///
/// Expected parse stream format: `<KW> = <VAL>`.
#[derive(Clone, Debug)]
pub struct ConfigOption {
/// The whole config span, from the keyword to the value.
pub span: Span,

/// The config key-value pair.
pub data: ConfigOptionData,
}
impl Parse for ConfigOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
use ConfigOptionData as Data;
use ConfigOptionKind as Kind;

let span = input.span();

let kind = input.parse::<ConfigOptionKind>()?;
input.parse::<Token![=]>()?;
let data = match kind {
Kind::Vis => Data::Vis(input.parse()?),
Kind::Name => Data::Name(input.parse()?),
Kind::Default => Data::Default(input.parse()?),
Kind::Trim => Data::Trim(input.parse()?),
};

Ok(Self { span, data })
}
}

/// The data of all known configuration options.
#[derive(Clone, Debug, PartialEq, Eq, strum::EnumDiscriminants)]
#[strum_discriminants(
vis(pub(self)),
name(ConfigOptionType),
name(ConfigOptionKind),
derive(strum::Display, Hash),
strum(serialize_all = "snake_case")
)]
pub enum ConfigOption {
pub enum ConfigOptionData {
/// Custom visibility for the generated constant.
///
/// E.g. `vis = pub(crate)`.
Vis(kw::vis, Visibility),
Vis(Visibility),

/// Custom name for generated constant.
///
/// E.g. `name = "CUSTOM_NAME_DOCS"`.
Name(kw::name, String),
Name(LitStr),

/// Use some default value when doc comments are absent.
///
/// E.g. `default = "not documented"`.
Default(kw::default, Expr),
Default(Expr),

/// Trim each line or not.
///
/// E.g. `trim = false`.
Trim(kw::trim, bool),
Trim(LitBool),
}
impl Parse for ConfigOption {

impl Parse for ConfigOptionKind {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();

if lookahead.peek(kw::vis) {
let kw = input.parse()?;
input.parse::<Token![=]>()?;
let vis = input.parse::<Visibility>()?;
Ok(Self::Vis(kw, vis))
let ty = if lookahead.peek(kw::vis) {
input.parse::<kw::vis>()?;
Self::Vis
} else if lookahead.peek(kw::name) {
let kw = input.parse()?;
input.parse::<Token![=]>()?;
let name = input.parse::<LitStr>()?;
Ok(Self::Name(kw, name.value()))
input.parse::<kw::name>()?;
Self::Name
} else if lookahead.peek(kw::default) {
let kw = input.parse()?;
input.parse::<Token![=]>()?;
let default = input.parse::<Expr>()?;
Ok(Self::Default(kw, default))
input.parse::<kw::default>()?;
Self::Default
} else if lookahead.peek(kw::trim) {
let kw = input.parse()?;
input.parse::<Token![=]>()?;
let trim = input.parse::<LitBool>()?;
Ok(Self::Trim(kw, trim.value))
input.parse::<kw::trim>()?;
Self::Trim
} else {
Err(lookahead.error())
}
}
}
impl ConfigOption {
pub fn kw_span(&self) -> Span {
match self {
Self::Vis(kw, _) => kw.span(),
Self::Name(kw, _) => kw.span(),
Self::Default(kw, _) => kw.span(),
Self::Trim(kw, _) => kw.span(),
}
Err(lookahead.error())?
};
Ok(ty)
}
}

/// Make sure there are no duplicate options.
/// Otherwise produces an error with detailed span info.
pub fn ensure_unique_options(opts: &[ConfigOption]) -> syn::Result<()> {
for (ty, opts) in opts
for (kind, opts) in opts
.iter()
.into_group_map_by(|opt| ConfigOptionType::from(*opt))
.into_group_map_by(|opt| ConfigOptionKind::from(&opt.data))
.into_iter()
{
match &opts[..] {
[] => unreachable!(), // guaranteed by `into_group_map_by`
[_unique] => continue,
[first, rest @ ..] => {
let initial_error = Error::new(
first.kw_span(),
format!("Option {ty} can only be declaration once"),
first.span,
format!("Option {kind} can only be declaration once"),
);
let final_error = rest.iter().fold(initial_error, |mut err, opt| {
err.combine(Error::new(opt.kw_span(), "Duplicate declaration here"));
err.combine(Error::new(opt.span, "Duplicate declaration here"));
err
});
Err(final_error)?
Expand Down
22 changes: 11 additions & 11 deletions documented-macros/src/config/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use syn::Expr;
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Error, Meta, Token};

#[cfg(feature = "customise")]
use crate::config::customise_core::{ensure_unique_options, ConfigOption};
use crate::config::customise_core::{ensure_unique_options, ConfigOption, ConfigOptionData};

/// Configurable options for derive macros via helper attributes.
///
Expand Down Expand Up @@ -47,21 +47,21 @@ impl TryFrom<Vec<ConfigOption>> for DeriveCustomisations {
type Error = syn::Error;

/// Duplicate option rejection should be handled upstream.
fn try_from(args: Vec<ConfigOption>) -> Result<Self, Self::Error> {
use ConfigOption as O;
fn try_from(opts: Vec<ConfigOption>) -> Result<Self, Self::Error> {
use ConfigOptionData as Data;

let mut config = Self::default();
for arg in args {
match arg {
O::Vis(..) | O::Name(..) => Err(Error::new(
arg.kw_span(),
for opt in opts {
match opt.data {
Data::Vis(..) | Data::Name(..) => Err(Error::new(
opt.span,
"This config option is not applicable to derive macros",
))?,
O::Default(_, mode) => {
config.default_value.replace(mode);
Data::Default(expr) => {
config.default_value.replace(expr);
}
O::Trim(_, val) => {
config.trim.replace(val);
Data::Trim(trim) => {
config.trim.replace(trim.value());
}
}
}
Expand Down

0 comments on commit 3add3be

Please sign in to comment.