Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive enhancements #174

Merged
merged 2 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions derive/test/compile_fail/union.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use sval_derive::*;

#[derive(Value)]
pub union Union {
a: i32,
b: u32,
}

fn main() {

}
7 changes: 7 additions & 0 deletions derive/test/compile_fail/union.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error: proc-macro derive panicked
--> compile_fail/union.rs:3:10
|
3 | #[derive(Value)]
| ^^^^^
|
= help: message: unsupported container type
35 changes: 30 additions & 5 deletions derive/test/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,35 @@ mod derive_struct {
})
}

#[test]
fn generic() {
#[derive(Value)]
struct RecordTuple<S> {
a: S,
}

assert_tokens(&RecordTuple { a: 42 }, {
use sval_test::Token::*;

&[
RecordTupleBegin(None, Some(sval::Label::new("RecordTuple")), None, Some(1)),
RecordTupleValueBegin(None, sval::Label::new("a"), sval::Index::new(0)),
I32(42),
RecordTupleValueEnd(None, sval::Label::new("a"), sval::Index::new(0)),
RecordTupleEnd(None, Some(sval::Label::new("RecordTuple")), None),
]
})
}

#[test]
fn indexed() {
const B_INDEX: sval::Index = sval::Index::new(3);

#[derive(Value)]
struct RecordTuple {
#[sval(index = 1)]
a: i32,
#[sval(index = B_INDEX)]
b: i32,
}

Expand All @@ -43,9 +66,9 @@ mod derive_struct {
RecordTupleValueBegin(None, sval::Label::new("a"), sval::Index::new(1)),
I32(42),
RecordTupleValueEnd(None, sval::Label::new("a"), sval::Index::new(1)),
RecordTupleValueBegin(None, sval::Label::new("b"), sval::Index::new(2)),
RecordTupleValueBegin(None, sval::Label::new("b"), sval::Index::new(3)),
I32(57),
RecordTupleValueEnd(None, sval::Label::new("b"), sval::Index::new(2)),
RecordTupleValueEnd(None, sval::Label::new("b"), sval::Index::new(3)),
RecordTupleEnd(None, Some(sval::Label::new("RecordTuple")), None),
]
})
Expand Down Expand Up @@ -120,7 +143,7 @@ mod derive_struct {
fn data_tagged() {
#[derive(Value)]
struct RecordTuple {
#[sval(data_tag = "sval::tags::NUMBER")]
#[sval(data_tag = sval::tags::NUMBER)]
a: i32,
}

Expand Down Expand Up @@ -216,7 +239,7 @@ mod derive_struct {
const FIELD: sval::Tag = sval::Tag::new("field");

#[derive(Value)]
#[sval(tag = "CONTAINER", label = "record", index = 0)]
#[sval(tag = CONTAINER, label = "record", index = 0)]
struct Record {
#[sval(tag = "FIELD", label = "field0")]
a: i32,
Expand Down Expand Up @@ -309,8 +332,10 @@ mod derive_tuple {

#[test]
fn labeled() {
const B_LABEL: sval::Label<'static> = sval::Label::new("B");

#[derive(Value)]
struct RecordTuple(#[sval(label = "A")] i32, #[sval(label = "B")] i32);
struct RecordTuple(#[sval(label = "A")] i32, #[sval(label = B_LABEL)] i32);

assert_tokens(&RecordTuple(42, 43), {
use sval_test::Token::*;
Expand Down
69 changes: 51 additions & 18 deletions derive_macros/src/attr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::HashSet;

use syn::{spanned::Spanned, Attribute, Expr, ExprUnary, Lit, LitBool, Path, UnOp};
use syn::{Attribute, Expr, ExprUnary, Lit, Path, UnOp};

use crate::{index::IndexValue, label::LabelValue};

/**
The `tag` attribute.
Expand All @@ -13,6 +15,14 @@ pub(crate) struct TagAttr;
impl SvalAttribute for TagAttr {
type Result = syn::Path;

fn try_from_expr(&self, expr: &Expr) -> Option<Self::Result> {
match expr {
Expr::Lit(lit) => Some(self.from_lit(&lit.lit)),
Expr::Path(path) => Some(path.path.clone()),
_ => None,
}
}

fn from_lit(&self, lit: &Lit) -> Self::Result {
if let Lit::Str(ref s) = lit {
s.parse().expect("invalid value")
Expand All @@ -39,6 +49,14 @@ pub(crate) struct DataTagAttr;
impl SvalAttribute for DataTagAttr {
type Result = syn::Path;

fn try_from_expr(&self, expr: &Expr) -> Option<Self::Result> {
match expr {
Expr::Lit(lit) => Some(self.from_lit(&lit.lit)),
Expr::Path(path) => Some(path.path.clone()),
_ => None,
}
}

fn from_lit(&self, lit: &Lit) -> Self::Result {
if let Lit::Str(ref s) = lit {
s.parse().expect("invalid value")
Expand All @@ -63,11 +81,19 @@ to use for the annotated item.
pub(crate) struct LabelAttr;

impl SvalAttribute for LabelAttr {
type Result = String;
type Result = LabelValue;

fn try_from_expr(&self, expr: &Expr) -> Option<Self::Result> {
match expr {
Expr::Lit(lit) => Some(self.from_lit(&lit.lit)),
Expr::Path(path) => Some(LabelValue::Ident(quote!(#path))),
_ => None,
}
}

fn from_lit(&self, lit: &Lit) -> Self::Result {
if let Lit::Str(ref s) = lit {
s.value()
LabelValue::Const(s.value())
} else {
panic!("unexpected value")
}
Expand All @@ -88,10 +114,20 @@ to use for the annotated item.
*/
pub(crate) struct IndexAttr;

impl IndexAttr {
fn const_from_lit(&self, lit: &Lit) -> isize {
if let Lit::Int(ref n) = lit {
n.base10_parse().expect("invalid value")
} else {
panic!("unexpected value")
}
}
}

impl SvalAttribute for IndexAttr {
type Result = isize;
type Result = IndexValue;

fn from_expr(&self, expr: &Expr) -> Option<Self::Result> {
fn try_from_expr(&self, expr: &Expr) -> Option<Self::Result> {
match expr {
// Take `-` into account
Expr::Unary(ExprUnary {
Expand All @@ -100,22 +136,19 @@ impl SvalAttribute for IndexAttr {
..
}) => {
if let Expr::Lit(ref lit) = **expr {
Some(-(self.from_lit(&lit.lit)))
Some(IndexValue::Const(-(self.const_from_lit(&lit.lit))))
} else {
None
}
}
Expr::Lit(lit) => Some(self.from_lit(&lit.lit)),
Expr::Lit(lit) => Some(IndexValue::Const(self.const_from_lit(&lit.lit))),
Expr::Path(path) => Some(IndexValue::Ident(quote!(#path))),
_ => None,
}
}

fn from_lit(&self, lit: &Lit) -> Self::Result {
if let Lit::Int(ref n) = lit {
n.base10_parse().expect("invalid value")
} else {
panic!("unexpected value")
}
IndexValue::Const(self.const_from_lit(lit))
}
}

Expand Down Expand Up @@ -292,7 +325,7 @@ pub(crate) trait RawAttribute {
pub(crate) trait SvalAttribute: RawAttribute {
type Result: 'static;

fn from_expr(&self, expr: &Expr) -> Option<Self::Result> {
fn try_from_expr(&self, expr: &Expr) -> Option<Self::Result> {
if let Expr::Lit(lit) = expr {
Some(self.from_lit(&lit.lit))
} else {
Expand Down Expand Up @@ -363,7 +396,7 @@ pub(crate) fn get_unchecked<T: SvalAttribute>(
.flatten()
{
if value_key.is_ident(request_key) {
return Some(request.from_lit(&value));
return Some(request.try_from_expr(&value).expect("unexpected value"));
}
}

Expand All @@ -373,23 +406,23 @@ pub(crate) fn get_unchecked<T: SvalAttribute>(
fn sval_attr<'a>(
ctxt: &'a str,
attr: &'_ Attribute,
) -> Option<impl IntoIterator<Item = (Path, Lit)> + 'a> {
) -> Option<impl IntoIterator<Item = (Path, Expr)> + 'a> {
if !attr.path().is_ident("sval") {
return None;
}

let mut results = Vec::new();
attr.parse_nested_meta(|meta| {
let lit: Lit = match meta.value() {
let expr: Expr = match meta.value() {
Ok(value) => value.parse()?,
// If there isn't a value associated with the item
// then use the boolean `true`
Err(_) => Lit::Bool(LitBool::new(true, meta.path.span())),
Err(_) => syn::parse_quote!(true),
};

let path = meta.path;

results.push((path, lit));
results.push((path, expr));

Ok(())
})
Expand Down
39 changes: 38 additions & 1 deletion derive_macros/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,43 @@ pub(crate) fn derive(input: DeriveInput) -> proc_macro2::TokenStream {

derive_enum(&input.ident, &input.generics, variants.iter(), &attrs)
}
_ => panic!("unimplemented"),
_ => panic!("unsupported container type"),
}
}

fn impl_tokens(
impl_generics: syn::ImplGenerics,
ident: &syn::Ident,
ty_generics: syn::TypeGenerics,
bounded_where_clause: &syn::WhereClause,
stream_body: proc_macro2::TokenStream,
tag_body: Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let stream_fn = quote!(
fn stream<'sval, __SvalStream: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut __SvalStream) -> sval::Result {
#stream_body
}
);

let tag_fn = if let Some(tag_body) = tag_body {
quote!(
fn tag(&self) -> Option<sval::Tag> {
#tag_body
}
)
} else {
quote!()
};

quote! {
const _: () = {
extern crate sval;

impl #impl_generics sval::Value for #ident #ty_generics #bounded_where_clause {
#stream_fn

#tag_fn
}
};
}
}