Skip to content

Commit

Permalink
Merge pull request #6 from k9withabone/services
Browse files Browse the repository at this point in the history
Services
  • Loading branch information
k9withabone authored Mar 20, 2024
2 parents bee57a0 + 5bdf9de commit a17bcb8
Show file tree
Hide file tree
Showing 50 changed files with 9,284 additions and 418 deletions.
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ workspace = true
[dependencies]
compose_spec_macros.workspace = true
indexmap = { version = "2", features = ["serde"] }
itoa = "1"
serde = { workspace = true, features = ["derive"] }
serde-untagged = "0.1"
serde_yaml.workspace = true
thiserror = "1.0.7"
thiserror = "1.0.28"
url = { version = "2.3", features = ["serde"] }

[dev-dependencies]
proptest = "1"
pomsky-macro = "0.11"
proptest = "1.3.1"
3 changes: 3 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Clippy configuration

doc-valid-idents = ["SELinux", ".."]
89 changes: 73 additions & 16 deletions compose_spec_macros/src/as_short.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct Input<'a> {
generics: &'a Generics,

/// The field marked with `#[as_short(short)]`.
short_field: (&'a Ident, &'a Type),
short_field: ShortField<'a>,

/// [`Field`]s not marked with `#[as_short(short)]`.
other_fields: Vec<Field<'a>>,
Expand Down Expand Up @@ -66,7 +66,7 @@ impl<'a> Input<'a> {
if short_field.is_some() {
return Some(Err(Error::new(span, "duplicate `short`")));
}
short_field = Some((ident, &field.ty));
short_field = Some(ShortField::new(ident, &field.ty));
None
}
Attribute::Other { if_fn, default_fn } => Some(Ok(Field {
Expand All @@ -93,7 +93,12 @@ impl<'a> Input<'a> {
let Self {
ident,
generics,
short_field: (short, short_ty),
short_field:
ShortField {
ident: short,
ty: short_ty,
optional,
},
ref other_fields,
} = *self;

Expand All @@ -104,7 +109,7 @@ impl<'a> Input<'a> {
let ty = field.ty;
let if_fn = field.if_fn.as_ref().map_or_else(
|| {
if is_option(ty) {
if option_type(ty).is_some() {
quote!(::std::option::Option::is_none)
} else if is_bool(ty) {
quote!(::std::ops::Not::not)
Expand All @@ -117,13 +122,19 @@ impl<'a> Input<'a> {
quote!((#if_fn)(&self.#ident))
});

let short = if optional {
quote!(self.#short.as_ref())
} else {
quote!(::std::option::Option::Some(&self.#short))
};

quote! {
impl #impl_generics crate::AsShort for #ident #ty_generics #where_clause {
type Short = #short_ty;

fn as_short(&self) -> Option<&Self::Short> {
fn as_short(&self) -> ::std::option::Option<&Self::Short> {
if #(#if_expressions)&&* {
Some(&self.#short)
#short
} else {
None
}
Expand All @@ -137,12 +148,23 @@ impl<'a> Input<'a> {
let Self {
ident,
generics,
short_field: (short, short_ty),
short_field:
ShortField {
ident: short,
ty: short_ty,
optional,
},
ref other_fields,
} = *self;

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let short_field = if optional {
quote!(#short: ::std::option::Option::Some(#short))
} else {
short.to_token_stream()
};

let other_fields = other_fields.iter().map(|field| {
let ident = field.ident;
let default_fn = field.default_fn.as_ref().map_or_else(
Expand All @@ -158,7 +180,7 @@ impl<'a> Input<'a> {
{
fn from(#short: #short_ty) -> Self {
Self {
#short,
#short_field,
#(#other_fields,)*
}
}
Expand All @@ -167,28 +189,32 @@ impl<'a> Input<'a> {
}
}

/// Returns `true` if the given [`Type`] is an [`Option`].
fn is_option(ty: &Type) -> bool {
/// Returns the inner [`Type`] if `ty` is [`Option<Type>`].
fn option_type(ty: &Type) -> Option<&Type> {
let Type::Path(TypePath { qself: None, path }) = ty else {
return false;
return None;
};

if path.segments.len() != 1 {
return false;
return None;
}
let segment = &path.segments[0];

let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) =
&segment.arguments
else {
return false;
return None;
};

if args.len() != 1 {
return false;
if args.len() != 1 || segment.ident != "Option" {
return None;
}

segment.ident == "Option" && matches!(&args[0], GenericArgument::Type(_))
if let GenericArgument::Type(ty) = &args[0] {
Some(ty)
} else {
None
}
}

/// Returns `true` if the given [`Type`] is a [`bool`].
Expand All @@ -200,6 +226,37 @@ fn is_bool(ty: &Type) -> bool {
path.is_ident("bool")
}

/// Field marked with `#[as_short(short)]`.
struct ShortField<'a> {
/// Field name.
ident: &'a Ident,

/// Field type, or inner [`Option`] type if `optional` is true.
ty: &'a Type,

/// Whether the field is optional.
optional: bool,
}

impl<'a> ShortField<'a> {
/// Create a [`ShortField`].
fn new(ident: &'a Ident, ty: &'a Type) -> Self {
if let Some(ty) = option_type(ty) {
Self {
ident,
ty,
optional: true,
}
} else {
Self {
ident,
ty,
optional: false,
}
}
}
}

/// Field with an optional `#[as_short([default = {default_fn},][if_fn = {if_fn}])]` attribute.
struct Field<'a> {
/// Function used to determine if the [`Input`] struct can be represented as its short form.
Expand Down
130 changes: 130 additions & 0 deletions compose_spec_macros/src/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! Implementation of the [`Default`](super::default()) derive macro.
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
spanned::Spanned, Data, DataStruct, DeriveInput, Error, Expr, Fields, FieldsNamed, Generics,
Ident, Result,
};

/// [`Default`](super::default()) derive macro input.
///
/// Created with [`Input::from_syn()`].
pub struct Input<'a> {
/// Name of the input struct.
ident: &'a Ident,

/// Input struct generics.
generics: &'a Generics,

/// Fields of the input struct.
fields: Vec<Field<'a>>,
}

impl<'a> Input<'a> {
/// Create an [`Input`] from [`DeriveInput`].
///
/// # Errors
///
/// Returns an error if the input is not a struct with named fields or a `default` attribute has
/// an incorrect format or duplicates.
pub fn from_syn(
DeriveInput {
ident,
generics,
data,
..
}: &'a DeriveInput,
) -> Result<Self> {
let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { named: fields, .. }),
..
}) = data
else {
return Err(Error::new(
ident.span(),
"must be used on structs with named fields",
));
};

let fields = fields.iter().map(Field::from_syn).collect::<Result<_>>()?;

Ok(Self {
ident,
generics,
fields,
})
}

/// Expand the input into a [`Default`] implementation.
pub fn expand(self) -> TokenStream {
let Self {
ident,
generics,
fields,
} = self;

let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

let fields = fields.into_iter().map(Field::expand);

quote! {
impl #impl_generics ::std::default::Default for #ident #type_generics #where_clause {
fn default() -> Self {
Self {
#(#fields,)*
}
}
}
}
}
}

/// Field with an optional `#[default = {default}]` attribute.
struct Field<'a> {
/// Expression to use as the default value for the field.
default: Option<&'a Expr>,

/// Field name.
ident: &'a Ident,
}

impl<'a> Field<'a> {
/// Create a [`Field`] from a [`syn::Field`].
///
/// # Errors
///
/// Returns an error if a `default` attribute has an incorrect format or duplicates.
fn from_syn(syn::Field { attrs, ident, .. }: &'a syn::Field) -> Result<Self> {
let mut default = None;

for attribute in attrs {
if attribute.path().is_ident("default") {
if default.is_some() {
return Err(Error::new(
attribute.span(),
"duplicate `default` attribute",
));
}

default = Some(&attribute.meta.require_name_value()?.value);
}
}

Ok(Self {
default,
ident: ident.as_ref().expect("named field"),
})
}

/// Expand into a struct field for a [`Default`] implementation.
fn expand(self) -> TokenStream {
let Self { default, ident } = self;

if let Some(default) = default {
quote!(#ident: #default)
} else {
quote!(#ident: ::std::default::Default::default())
}
}
}
Loading

0 comments on commit a17bcb8

Please sign in to comment.