Revert "Replace some manual parsing code with darling helper crate"

This reverts commit e5af4f8efe.

The `darling` crate limits us to `syn::Meta` which is probably not
enough in the long run. There are a couple of other problems I have
with this crate and I think it's currently not worth. I'm considering
writing my own alternative...
This commit is contained in:
Lukas Kalbertodt
2021-11-03 14:58:23 +01:00
parent e5af4f8efe
commit a54042f8ca
4 changed files with 270 additions and 200 deletions

View File

@@ -12,8 +12,7 @@ license = "MIT/Apache-2.0"
proc-macro = true
[dependencies]
darling = "0.13.0"
heck = "0.3.2"
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"
heck = "0.3.2"

View File

@@ -1,5 +1,9 @@
//! Definition of the intermediate representation.
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned};
use crate::util::{is_option, unwrap_option};
/// The parsed input to the `gen_config` macro.
pub(crate) struct Input {
@@ -53,7 +57,6 @@ impl LeafKind {
/// The kinds of expressions (just literals) we allow for default or example
/// values.
#[derive(Debug)]
pub(crate) enum Expr {
Str(syn::LitStr),
Int(syn::LitInt),
@@ -61,3 +64,263 @@ pub(crate) enum Expr {
Bool(syn::LitBool),
// TODO: arrays?
}
impl Input {
pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result<Self, Error> {
let fields = match input.data {
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f,
_ => return Err(Error::new(
input.span(),
"`confique::Config` can only be derive for structs with named fields",
)),
};
let doc = extract_doc(&mut input.attrs);
let fields = fields.named.into_iter()
.map(Field::from_ast)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
doc,
visibility: input.vis,
name: input.ident,
fields,
})
}
}
impl Field {
fn from_ast(mut field: syn::Field) -> Result<Self, Error> {
let doc = extract_doc(&mut field.attrs);
let attrs = extract_internal_attrs(&mut field.attrs)?;
// TODO: check no other attributes are here
let kind = if attrs.nested {
if is_option(&field.ty) {
return Err(Error::new(
field.ident.span(),
"nested configurations cannot be optional (type `Option<_>`)",
));
}
if attrs.default.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `default` attributes at the same time",
));
}
if attrs.env.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `env` attributes at the same time",
));
}
FieldKind::Nested { ty: field.ty }
} else {
match unwrap_option(&field.ty) {
None => FieldKind::Leaf {
env: attrs.env,
kind: LeafKind::Required {
default: attrs.default,
ty: field.ty,
},
},
Some(inner) => {
if attrs.default.is_some() {
return Err(Error::new(
field.ident.span(),
"optional fields (type `Option<_>`) cannot have default \
values (`#[config(default = ...)]`)",
));
}
FieldKind::Leaf {
env: attrs.env,
kind: LeafKind::Optional {
inner_ty: inner.clone(),
},
}
}
}
};
Ok(Self {
doc,
name: field.ident.expect("bug: expected named field"),
kind,
})
}
pub(crate) fn is_leaf(&self) -> bool {
matches!(self.kind, FieldKind::Leaf { .. })
}
}
impl Expr {
fn from_lit(lit: syn::Lit) -> Result<Self, Error> {
match lit {
syn::Lit::Str(l) => Ok(Self::Str(l)),
syn::Lit::Int(l) => Ok(Self::Int(l)),
syn::Lit::Float(l) => Ok(Self::Float(l)),
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
_ => {
let msg = "only string, integer, float and bool literals are allowed here";
Err(Error::new(lit.span(), msg))
}
}
}
}
/// Extracts all doc string attributes from the list and return them as list of
/// strings (in order).
fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Vec<String> {
extract_attrs(attrs, |attr| {
match attr.parse_meta().ok()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
path,
..
}) if path.is_ident("doc") => Some(s.value()),
_ => None,
}
})
}
fn extract_attrs<P, O>(attrs: &mut Vec<syn::Attribute>, mut pred: P) -> Vec<O>
where
P: FnMut(&syn::Attribute) -> Option<O>,
{
// TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²).
let mut i = 0;
let mut out = Vec::new();
while i < attrs.len() {
match pred(&attrs[i]) {
Some(v) => {
out.push(v);
attrs.remove(i);
}
None => i += 1,
}
}
out
}
fn extract_internal_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> Result<InternalAttrs, Error> {
let internal_attrs = extract_attrs(attrs, |attr| {
if attr.path.is_ident("config") {
// TODO: clone not necessary once we use drain_filter
Some(attr.clone())
} else {
None
}
});
let mut out = InternalAttrs::default();
for attr in internal_attrs {
type AttrList = syn::punctuated::Punctuated<InternalAttr, Token![,]>;
let parsed_list = attr.parse_args_with(AttrList::parse_terminated)?;
for parsed in parsed_list {
let keyword = parsed.keyword();
macro_rules! duplicate_if {
($cond:expr) => {
if $cond {
let msg = format!("duplicate '{}' confique attribute", keyword);
return Err(Error::new(attr.tokens.span(), msg));
}
};
}
match parsed {
InternalAttr::Default(expr) => {
duplicate_if!(out.default.is_some());
out.default = Some(expr);
}
InternalAttr::Nested => {
duplicate_if!(out.nested);
out.nested = true;
}
InternalAttr::Env(key) => {
duplicate_if!(out.env.is_some());
out.env = Some(key);
}
}
}
}
Ok(out)
}
#[derive(Default)]
struct InternalAttrs {
nested: bool,
default: Option<Expr>,
env: Option<String>
}
enum InternalAttr {
Nested,
Default(Expr),
Env(String),
}
impl InternalAttr {
fn keyword(&self) -> &'static str {
match self {
Self::Nested => "nested",
Self::Default(_) => "default",
Self::Env(_) => "env",
}
}
}
impl Parse for InternalAttr {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let ident: syn::Ident = input.parse()?;
match &*ident.to_string() {
"nested" => {
assert_empty_or_comma(input)?;
Ok(Self::Nested)
}
"default" => {
let _: Token![=] = input.parse()?;
let expr = Expr::from_lit(input.parse()?)?;
assert_empty_or_comma(input)?;
Ok(Self::Default(expr))
}
"env" => {
let _: Token![=] = input.parse()?;
let key: syn::LitStr = input.parse()?;
assert_empty_or_comma(input)?;
let value = key.value();
if value.contains('=') || value.contains('\0') {
Err(syn::Error::new(
key.span(),
"environment variable key must not contain '=' or null bytes",
))
} else {
Ok(Self::Env(value))
}
}
_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
}
}
}
fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
if input.is_empty() || input.peek(Token![,]) {
Ok(())
} else {
Err(Error::new(input.span(), "unexpected tokens, expected no more tokens in this context"))
}
}

View File

@@ -3,19 +3,14 @@ use proc_macro::TokenStream as TokenStream1;
mod gen;
mod ir;
mod parse;
mod util;
#[proc_macro_derive(Config, attributes(config))]
pub fn config(input: TokenStream1) -> TokenStream1 {
let input = match syn::parse2::<syn::DeriveInput>(input.into()) {
Err(e) => return e.to_compile_error().into(),
Ok(i) => i,
};
ir::Input::from_ast(input)
syn::parse2::<syn::DeriveInput>(input.into())
.and_then(ir::Input::from_ast)
.map(gen::gen)
.unwrap_or_else(|e| e.write_errors())
.unwrap_or_else(|e| e.to_compile_error())
.into()
}

View File

@@ -1,187 +0,0 @@
use darling::Error;
use syn::spanned::Spanned;
use crate::{ir, util::{is_option, unwrap_option}};
macro_rules! bail {
($span:expr, $msg:expr $(,)?) => {
return Err(Error::custom($msg).with_span(&$span))
};
}
impl ir::Input {
pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result<Self, Error> {
let struct_fields = match input.data {
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f,
_ => bail!(
input.span(),
"`confique::Config` can only be derive for structs with named fields",
),
};
let doc = extract_doc(&mut input.attrs);
let mut errors = Vec::new();
let mut fields = Vec::new();
for field in struct_fields.named {
match ir::Field::from_ast(field) {
Ok(f) => fields.push(f),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
return Err(Error::multiple(errors));
}
Ok(Self {
doc,
visibility: input.vis,
name: input.ident,
fields,
})
}
}
impl ir::Field {
fn from_ast(field: syn::Field) -> Result<Self, darling::Error> {
use darling::FromField;
let mut field = Field::from_field(&field)?;
let doc = extract_doc(&mut field.attrs);
let kind = if field.nested {
// Nested field.
if is_option(&field.ty) {
bail!(
field.ident.span(),
"nested configurations cannot be optional (type `Option<_>`)",
);
}
if field.default.is_some() {
bail!(
field.ident.span(),
"cannot specify `nested` and `default` attributes at the same time",
);
}
if field.env.is_some() {
bail!(
field.ident.span(),
"cannot specify `nested` and `env` attributes at the same time",
);
}
ir::FieldKind::Nested { ty: field.ty }
} else {
// Leaf field.
match unwrap_option(&field.ty) {
None => ir::FieldKind::Leaf {
env: field.env,
kind: ir::LeafKind::Required {
default: field.default,
ty: field.ty,
},
},
Some(inner) => {
if field.default.is_some() {
bail!(
field.ident.span(),
"optional fields (type `Option<_>`) cannot have default \
values (`#[config(default = ...)]`)",
);
}
ir::FieldKind::Leaf {
env: field.env,
kind: ir::LeafKind::Optional {
inner_ty: inner.clone(),
},
}
}
}
};
Ok(Self {
doc,
name: field.ident.expect("bug: expected named field"),
kind,
})
}
pub(crate) fn is_leaf(&self) -> bool {
matches!(self.kind, ir::FieldKind::Leaf { .. })
}
}
impl darling::FromMeta for ir::Expr {
fn from_value(lit: &syn::Lit) -> Result<Self, darling::Error> {
match lit {
syn::Lit::Str(l) => Ok(Self::Str(l.clone())),
syn::Lit::Int(l) => Ok(Self::Int(l.clone())),
syn::Lit::Float(l) => Ok(Self::Float(l.clone())),
syn::Lit::Bool(l) => Ok(Self::Bool(l.clone())),
_ => {
// let msg = "only string, integer, float and bool literals are allowed here";
// Err(Error::new(lit.span(), msg))
Err(darling::Error::unexpected_lit_type(lit))
}
}
}
}
#[derive(Debug, darling::FromField)]
#[darling(attributes(config), forward_attrs(doc))]
struct Field {
ident: Option<syn::Ident>,
ty: syn::Type,
attrs: Vec<syn::Attribute>,
#[darling(default)]
nested: bool,
#[darling(default)]
env: Option<String>,
#[darling(default)]
default: Option<ir::Expr>,
}
/// Extracts all doc string attributes from the list and return them as list of
/// strings (in order).
fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Vec<String> {
extract_attrs(attrs, |attr| {
match attr.parse_meta().ok()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
path,
..
}) if path.is_ident("doc") => Some(s.value()),
_ => None,
}
})
}
fn extract_attrs<P, O>(attrs: &mut Vec<syn::Attribute>, mut pred: P) -> Vec<O>
where
P: FnMut(&syn::Attribute) -> Option<O>,
{
// TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²).
let mut i = 0;
let mut out = Vec::new();
while i < attrs.len() {
match pred(&attrs[i]) {
Some(v) => {
out.push(v);
attrs.remove(i);
}
None => i += 1,
}
}
out
}