Skip to content

Commit 1b06662

Browse files
Stable ordering & fix copy partials (#7)
1 parent 16377a8 commit 1b06662

File tree

4 files changed

+144
-45
lines changed

4 files changed

+144
-45
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ itertools = "0.10.0"
1717
proc-macro2 = "1.0.26"
1818
quote = "1.0.9"
1919
syn = "1.0.70"
20+
21+
[dev-dependencies]
22+
serde = { version = "1.0", features = ["derive"] }
23+
serde_json = "1.0"

src/attributes.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Utilities to help with parsing configuration attributes.
2+
use darling::{Error, FromMeta};
3+
use syn::{Ident, NestedMeta};
4+
5+
/// Parse a list of nested meta items.
6+
///
7+
/// Useful for passing through attributes intended for other macros.
8+
#[derive(Debug)]
9+
pub struct NestedMetaList {
10+
pub metas: Vec<NestedMeta>,
11+
}
12+
13+
impl FromMeta for NestedMetaList {
14+
fn from_list(items: &[NestedMeta]) -> Result<Self, Error> {
15+
Ok(Self {
16+
metas: items.iter().cloned().collect(),
17+
})
18+
}
19+
}
20+
21+
/// List of identifiers implementing `FromMeta`.
22+
///
23+
/// Useful for imposing ordering, unlike the `HashMap` options provided by `darling`.
24+
#[derive(Debug)]
25+
pub struct IdentList {
26+
pub idents: Vec<Ident>,
27+
}
28+
29+
impl FromMeta for IdentList {
30+
fn from_list(items: &[NestedMeta]) -> Result<Self, Error> {
31+
let idents = items
32+
.iter()
33+
.map(|nested_meta| {
34+
let meta = match nested_meta {
35+
NestedMeta::Meta(m) => m,
36+
NestedMeta::Lit(l) => {
37+
return Err(Error::custom(format!(
38+
"expected ident, got literal: {:?}",
39+
l
40+
)))
41+
}
42+
};
43+
let path = meta.path();
44+
path.get_ident()
45+
.cloned()
46+
.ok_or(Error::custom(format!("can't parse as ident: {:?}", path)))
47+
})
48+
.collect::<Result<_, _>>()?;
49+
Ok(Self { idents })
50+
}
51+
}

src/lib.rs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use attributes::{IdentList, NestedMetaList};
12
use darling::FromMeta;
23
use itertools::Itertools;
34
use proc_macro::TokenStream;
@@ -7,14 +8,16 @@ use std::collections::HashMap;
78
use std::iter::{self, FromIterator};
89
use syn::{
910
parse_macro_input, Attribute, AttributeArgs, Expr, Field, GenericParam, Ident, ItemStruct,
10-
Lifetime, LifetimeDef, NestedMeta, Type, TypeGenerics,
11+
Lifetime, LifetimeDef, Type, TypeGenerics,
1112
};
1213

14+
mod attributes;
15+
1316
/// Top-level configuration via the `superstruct` attribute.
1417
#[derive(Debug, FromMeta)]
1518
struct StructOpts {
1619
/// List of variant names of the superstruct being derived.
17-
variants: HashMap<Ident, ()>,
20+
variants: IdentList,
1821
/// List of attributes to apply to the variant structs.
1922
#[darling(default)]
2023
variant_attributes: Option<NestedMetaList>,
@@ -102,19 +105,6 @@ impl FieldData {
102105
}
103106
}
104107

105-
#[derive(Debug)]
106-
struct NestedMetaList {
107-
metas: Vec<NestedMeta>,
108-
}
109-
110-
impl FromMeta for NestedMetaList {
111-
fn from_list(items: &[NestedMeta]) -> Result<Self, darling::Error> {
112-
Ok(Self {
113-
metas: items.iter().cloned().collect(),
114-
})
115-
}
116-
}
117-
118108
#[proc_macro_attribute]
119109
pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream {
120110
let attr_args = parse_macro_input!(args as AttributeArgs);
@@ -133,7 +123,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream {
133123

134124
let mk_struct_name = |variant_name: &Ident| format_ident!("{}{}", type_name, variant_name);
135125

136-
let variant_names = opts.variants.keys().cloned().collect_vec();
126+
let variant_names = &opts.variants.idents;
137127
let struct_names = variant_names.iter().map(mk_struct_name).collect_vec();
138128

139129
// Vec of field data.
@@ -520,9 +510,12 @@ fn make_self_arg(mutable: bool) -> proc_macro2::TokenStream {
520510
}
521511
}
522512

523-
fn make_type_ref(ty: &Type, mutable: bool) -> proc_macro2::TokenStream {
513+
fn make_type_ref(ty: &Type, mutable: bool, copy: bool) -> proc_macro2::TokenStream {
514+
// XXX: bit hacky, ignore `copy` if `mutable` is set
524515
if mutable {
525516
quote! { &mut #ty }
517+
} else if copy {
518+
quote! { #ty }
526519
} else {
527520
quote! { &#ty }
528521
}
@@ -547,10 +540,13 @@ fn make_partial_getter(
547540
} else {
548541
renamed_field.clone()
549542
};
543+
let copy = field_data.partial_getter_opts.copy;
550544
let self_arg = make_self_arg(mutable);
551-
let ret_ty = make_type_ref(&field_data.field.ty, mutable);
545+
let ret_ty = make_type_ref(&field_data.field.ty, mutable, copy);
552546
let ret_expr = if mutable {
553547
quote! { &mut inner.#field_name }
548+
} else if copy {
549+
quote! { inner.#field_name }
554550
} else {
555551
quote! { &inner.#field_name }
556552
};

tests/basic.rs

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,93 @@
1+
use serde::Deserialize;
12
use superstruct::superstruct;
23

3-
#[superstruct(
4-
variants(Base, Ext),
5-
variant_attributes(derive(Debug, PartialEq)),
6-
cast_error(ty = "BlockError", expr = "BlockError::WrongVariant")
7-
)]
8-
#[derive(Debug, PartialEq)]
9-
pub struct Block {
10-
#[superstruct(getter(copy))]
11-
slot: u64,
12-
data: Vec<u8>,
13-
#[superstruct(only(Ext))]
14-
description: &'static str,
15-
}
16-
17-
pub enum BlockError {
18-
WrongVariant,
19-
}
20-
214
#[test]
225
fn basic() {
6+
#[superstruct(
7+
variants(Base, Ext),
8+
variant_attributes(derive(Debug, PartialEq, Clone)),
9+
cast_error(ty = "BlockError", expr = "BlockError::WrongVariant"),
10+
partial_getter_error(ty = "BlockError", expr = "BlockError::WrongVariant")
11+
)]
12+
#[derive(Debug, PartialEq, Clone)]
13+
pub struct Block {
14+
#[superstruct(getter(copy))]
15+
slot: u64,
16+
data: Vec<u8>,
17+
#[superstruct(only(Ext), partial_getter(copy))]
18+
description: &'static str,
19+
}
20+
21+
#[derive(Debug, PartialEq)]
22+
pub enum BlockError {
23+
WrongVariant,
24+
}
25+
2326
let base = BlockBase {
2427
slot: 10,
2528
data: vec![],
2629
};
27-
let lmao = BlockExt {
30+
let ext = BlockExt {
2831
slot: 11,
2932
data: vec![10],
3033
description: "oooeee look at this",
3134
};
3235

33-
let mut block1 = Block::Base(base);
34-
let block2 = Block::Ext(lmao);
36+
let mut block1 = Block::Base(base.clone());
37+
let mut block2 = Block::Ext(ext.clone());
38+
39+
// Test basic getters.
40+
assert_eq!(block1.slot(), 10);
41+
assert_eq!(block2.slot(), 11);
3542

36-
println!("{:?}", block1);
37-
println!("{:?}", block2);
38-
println!("{}", block1.slot());
43+
// Check ref getters.
44+
let block1_ref = block1.to_ref();
45+
assert_eq!(block1_ref.slot(), 10);
3946

40-
let block_ref = block1.to_ref();
41-
println!("{:?}", block_ref.slot());
47+
// Check casting
48+
assert_eq!(block1.as_base(), Ok(&base));
49+
assert_eq!(block1.as_ext(), Err(BlockError::WrongVariant));
50+
assert_eq!(block2.as_ext(), Ok(&ext));
51+
assert_eq!(block2.as_base(), Err(BlockError::WrongVariant));
4252

53+
// Check mutable reference mutators.
4354
let mut block_mut_ref = block1.to_mut();
44-
println!("{:?}", block_mut_ref.slot_mut());
55+
*block_mut_ref.slot_mut() = 1000;
56+
assert_eq!(block1.slot(), 1000);
57+
*block1.slot_mut() = 1001;
58+
assert_eq!(block1.slot(), 1001);
59+
60+
// Check partial getters.
61+
assert_eq!(block1.description(), Err(BlockError::WrongVariant));
62+
assert_eq!(block2.description().unwrap(), ext.description);
63+
*block2.description_mut().unwrap() = "updated";
64+
assert_eq!(block2.description().unwrap(), "updated");
65+
}
66+
67+
// Test that superstruct's enum ordering is based on the ordering in `variants(...)`.
68+
// This test fails with variant order (A, B) because A is a subset of B and we're not
69+
// using `serde(deny_unknown_fields)`.
70+
#[test]
71+
fn serde_deserialise_order() {
72+
#[superstruct(
73+
variants(B, A),
74+
variant_attributes(derive(Debug, Deserialize, PartialEq))
75+
)]
76+
#[serde(untagged)]
77+
#[derive(Debug, Deserialize, PartialEq)]
78+
struct Message {
79+
common: String,
80+
#[superstruct(only(B))]
81+
exclusive: String,
82+
}
83+
84+
let message_str = r#"{"common": "hello", "exclusive": "world"}"#;
85+
let message: Message = serde_json::from_str(&message_str).unwrap();
86+
87+
let expected = Message::B(MessageB {
88+
common: "hello".into(),
89+
exclusive: "world".into(),
90+
});
91+
92+
assert_eq!(message, expected);
4593
}

0 commit comments

Comments
 (0)