Skip to content

Commit 4f15d67

Browse files
committed
Merge branch 'develop'
Signed-off-by: Toralf Wittner <tw@dtex.org>
2 parents 35ece21 + c921040 commit 4f15d67

File tree

11 files changed

+462
-212
lines changed

11 files changed

+462
-212
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
# minicbor
99

10+
## `2.2.0`
11+
12+
- Depends on `minicbor-derive-0.19.0`.
13+
1014
## `2.1.3`
1115

1216
- Depends on `minicbor-derive-0.18.3`.
@@ -437,6 +441,11 @@
437441

438442
# minicbor-derive
439443

444+
## `0.19.0`
445+
446+
- Adds attribute `skip_if`. See issue [#43](https://github.com/twittner/minicbor/issues/43) for
447+
details.
448+
440449
## `0.18.3`
441450

442451
- Fixes feature-handling, see pull request [#48](https://github.com/twittner/minicbor/pull/48) by

minicbor-derive/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "minicbor-derive"
3-
version = "0.18.3"
3+
version = "0.19.0"
44
authors = ["Toralf Wittner <tw@dtex.org>"]
55
license = "BlueOak-1.0.0"
66
edition = "2024"
@@ -17,9 +17,9 @@ alloc = [] # unused
1717
std = [] # unused
1818

1919
[dependencies]
20-
proc-macro2 = "1.0.79"
21-
quote = "1.0.35"
22-
syn = { version = "2.0.58", features = ["derive", "extra-traits", "visit"] }
20+
proc-macro2 = "1.0.106"
21+
quote = "1.0.44"
22+
syn = { version = "2.0.114", features = ["derive", "extra-traits", "full", "visit"] }
2323

2424
[dev-dependencies]
2525
minicbor = { path = "../minicbor", features = ["std", "derive"] }

minicbor-derive/src/attrs.rs

Lines changed: 165 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ use std::fmt;
1010
use std::hash::Hash;
1111
use std::iter;
1212

13-
use syn::{LitInt, LitStr};
13+
use syn::{parse_quote, LitInt, LitStr};
1414
use syn::spanned::Spanned;
1515

1616
pub use typeparam::TypeParams;
1717
pub use codec::CustomCodec;
1818
pub use encoding::Encoding;
1919
pub use idx::Idx;
2020

21+
use crate::attrs::codec::{Decode, Encode, PathOrClosure};
22+
use crate::{is_decode_bound, is_encode_bound, is_length_bound};
23+
2124
/// Recognised attributes.
2225
#[derive(Debug, Clone)]
2326
pub struct Attributes {
@@ -41,6 +44,7 @@ pub enum Kind {
4144
CborLen,
4245
Tag,
4346
Skip,
47+
SkipIf,
4448
Flat,
4549
Default
4650
}
@@ -61,6 +65,7 @@ enum Value {
6165
CborLen(syn::ExprPath, proc_macro2::Span),
6266
Tag(u64, proc_macro2::Span),
6367
Skip(proc_macro2::Span),
68+
SkipIf(Option<syn::ExprPath>, proc_macro2::Span),
6469
Flat(proc_macro2::Span),
6570
Default(proc_macro2::Span)
6671
}
@@ -120,14 +125,100 @@ impl Attributes {
120125
return Err(syn::Error::new(*s, "`tag` and `transparent` are mutually exclusive"))
121126
}
122127
}
123-
if let Some(Value::Skip(s)) = this.get(Kind::Skip) {
124-
if this.attrs.len() > 1 {
125-
return Err(syn::Error::new(*s, "`skip` does not allow other attributes"))
126-
}
128+
if this.contains_key(Kind::Skip)
129+
&& let Some((_, a)) = this.attrs
130+
.iter()
131+
.find(|(k, _)| !matches!(k, Kind::Skip | Kind::TypeParam))
132+
{
133+
return Err(syn::Error::new(a.span(), "`skip` can not be used with this attribute"))
134+
}
135+
if this.contains_key(Kind::Flat)
136+
&& let Some(Value::Encoding(Encoding::Map, s)) = this.get(Kind::Encoding)
137+
{
138+
return Err(syn::Error::new(*s, "flat enum does not support map encoding"))
127139
}
128-
if let Some(Value::Flat(_)) = this.get(Kind::Flat) {
129-
if let Some(Value::Encoding(Encoding::Map, s)) = this.get(Kind::Encoding) {
130-
return Err(syn::Error::new(*s, "flat enum does not support map encoding"))
140+
// `skip_if` triggers the creation of a custom codec where `encode` and `decode`
141+
// correspond to the default routines, `is_nil` is defined via `skip_if`'s
142+
// predicate and `nil` points to an internal helper that matches the signature
143+
// of `nil` and uses `Default::default` to create a default value.
144+
//
145+
// If a custom codec is already defined, `is_nil` and `nil` are set to `skip_if`'s
146+
// predicate and the internal default helper. If `is_nil` or `nil` already exist
147+
// in a pre-existing custom codec, an error is returned.
148+
if let Some(Value::SkipIf(is_nil@Some(_), skip_if_span)) = this.get_mut(Kind::SkipIf) {
149+
let is_nil = is_nil.take().expect("some is_nil");
150+
let encode = parse_quote!(minicbor::Encode::encode);
151+
let decode = parse_quote!(minicbor::Decode::decode);
152+
let default = PathOrClosure::Closure(parse_quote!(|| Some(Default::default())));
153+
let skip_if_span = *skip_if_span;
154+
match this.remove(Kind::Codec) {
155+
None => {
156+
let e = Encode { encode, is_nil: Some(is_nil), require_bound: true };
157+
let d = Decode { decode, nil: Some(default), require_bound: true };
158+
let c = CustomCodec::Both(Box::new(e), Box::new(d));
159+
this.attrs.insert(Kind::Codec, Value::Codec(c, skip_if_span));
160+
}
161+
Some(Value::Codec(CustomCodec::Encode(mut e), span)) => {
162+
if e.is_nil.is_some() {
163+
let msg = "skip_if and `is_nil` are mutually exclusive";
164+
return Err(syn::Error::new(skip_if_span, msg))
165+
}
166+
let c = {
167+
let d = Decode { decode, nil: Some(default), require_bound: true };
168+
e.is_nil = Some(is_nil);
169+
CustomCodec::Both(Box::new(e), Box::new(d))
170+
};
171+
this.attrs.insert(Kind::Codec, Value::Codec(c, span));
172+
}
173+
Some(Value::Codec(CustomCodec::Decode(mut d), span)) => {
174+
if d.nil.is_some() {
175+
let msg = "skip_if and `nil` are mutually exclusive";
176+
return Err(syn::Error::new(skip_if_span, msg))
177+
}
178+
let c = {
179+
let e = Encode { encode, is_nil: Some(is_nil), require_bound: true };
180+
d.nil = Some(default);
181+
CustomCodec::Both(Box::new(e), Box::new(d))
182+
};
183+
this.attrs.insert(Kind::Codec, Value::Codec(c, span));
184+
}
185+
Some(Value::Codec(CustomCodec::Both(mut e, mut d), span)) => {
186+
if e.is_nil.is_some() {
187+
let msg = "skip_if and `is_nil` are mutually exclusive";
188+
return Err(syn::Error::new(skip_if_span, msg))
189+
}
190+
if d.nil.is_some() {
191+
let msg = "skip_if and `nil` are mutually exclusive";
192+
return Err(syn::Error::new(skip_if_span, msg))
193+
}
194+
let c = {
195+
e.is_nil = Some(is_nil);
196+
d.nil = Some(default);
197+
CustomCodec::Both(e, d)
198+
};
199+
this.attrs.insert(Kind::Codec, Value::Codec(c, span));
200+
}
201+
Some(Value::Codec(m@CustomCodec::Module(_, has_nil), span)) => {
202+
if has_nil {
203+
let msg = "skip_if and `has_nil` are mutually exclusive";
204+
return Err(syn::Error::new(skip_if_span, msg))
205+
}
206+
let c = {
207+
let e = Encode {
208+
encode: m.to_encode_path().expect("module => encode path"),
209+
is_nil: Some(is_nil),
210+
require_bound: false,
211+
};
212+
let d = Decode {
213+
decode: m.to_decode_path().expect("module => decode path"),
214+
nil: Some(default),
215+
require_bound: false
216+
};
217+
CustomCodec::Both(Box::new(e), Box::new(d))
218+
};
219+
this.attrs.insert(Kind::Codec, Value::Codec(c, span));
220+
}
221+
_ => {}
131222
}
132223
}
133224
Ok(this)
@@ -168,18 +259,29 @@ impl Attributes {
168259
attrs.try_insert(Kind::HasNil, Value::HasNil(meta.path.span()))?
169260
} else if meta.path.is_ident("encode_with") {
170261
let s: LitStr = meta.value()?.parse()?;
171-
let c = CustomCodec::Encode(codec::Encode { encode: s.parse()?, is_nil: None });
262+
let c = CustomCodec::Encode(codec::Encode {
263+
encode: s.parse()?,
264+
is_nil: None,
265+
require_bound: false
266+
});
172267
attrs.try_insert(Kind::Codec, Value::Codec(c, meta.path.span()))?
173268
} else if meta.path.is_ident("is_nil") {
174269
let s: LitStr = meta.value()?.parse()?;
175270
attrs.try_insert(Kind::IsNil, Value::IsNil(s.parse()?, meta.path.span()))?
176271
} else if meta.path.is_ident("decode_with") {
177272
let s: LitStr = meta.value()?.parse()?;
178-
let c = CustomCodec::Decode(codec::Decode { decode: s.parse()?, nil: None });
273+
let c = CustomCodec::Decode(codec::Decode {
274+
decode: s.parse()?,
275+
nil: None,
276+
require_bound: false
277+
});
179278
attrs.try_insert(Kind::Codec, Value::Codec(c, meta.path.span()))?
180279
} else if meta.path.is_ident("nil") {
181280
let s: LitStr = meta.value()?.parse()?;
182281
attrs.try_insert(Kind::Nil, Value::Nil(s.parse()?, meta.path.span()))?
282+
} else if meta.path.is_ident("skip_if") {
283+
let s: LitStr = meta.value()?.parse()?;
284+
attrs.try_insert(Kind::SkipIf, Value::SkipIf(Some(s.parse()?), meta.path.span()))?;
183285
} else if meta.path.is_ident("with") {
184286
let s: LitStr = meta.value()?.parse()?;
185287
let c = CustomCodec::Module(s.parse()?, false);
@@ -211,8 +313,32 @@ impl Attributes {
211313
} else if meta.path.is_ident("bound") {
212314
let s: LitStr = meta.value()?.parse()?;
213315
let t: syn::TypeParam = s.parse()?;
214-
let m = iter::once((t.ident.clone(), t)).collect::<HashMap<_, _>>();
215-
let b = TypeParams::All { encode: m.clone(), length: m.clone(), decode: m };
316+
let b = TypeParams::All {
317+
encode: {
318+
let mut e = t.clone();
319+
e.bounds = e.bounds
320+
.into_iter()
321+
.filter(|b| !(is_decode_bound(b) || is_length_bound(b)))
322+
.collect();
323+
HashMap::from_iter([(e.ident.clone(), e)])
324+
},
325+
length: {
326+
let mut e = t.clone();
327+
e.bounds = e.bounds
328+
.into_iter()
329+
.filter(|b| !(is_encode_bound(b) || is_decode_bound(b)))
330+
.collect();
331+
HashMap::from_iter([(e.ident.clone(), e)])
332+
},
333+
decode: {
334+
let mut d = t;
335+
d.bounds = d.bounds
336+
.into_iter()
337+
.filter(|b| !(is_encode_bound(b) || is_length_bound(b)))
338+
.collect();
339+
HashMap::from_iter([(d.ident.clone(), d)])
340+
}
341+
};
216342
attrs.try_insert(Kind::TypeParam, Value::TypeParam(b, meta.path.span()))?
217343
} else if meta.path.is_ident("context_bound") {
218344
let s: LitStr = meta.value()?.parse()?;
@@ -305,6 +431,10 @@ impl Attributes {
305431
self.contains_key(Kind::Skip)
306432
}
307433

434+
pub fn skip_if_codec(&self) -> bool {
435+
self.contains_key(Kind::SkipIf)
436+
}
437+
308438
pub fn flat(&self) -> bool {
309439
self.contains_key(Kind::Flat)
310440
}
@@ -347,6 +477,7 @@ impl Attributes {
347477
| Kind::HasNil
348478
| Kind::CborLen
349479
| Kind::Skip
480+
| Kind::SkipIf
350481
| Kind::Flat
351482
| Kind::Default
352483
=> {
@@ -365,6 +496,7 @@ impl Attributes {
365496
| Kind::CborLen
366497
| Kind::Tag
367498
| Kind::Skip
499+
| Kind::SkipIf
368500
| Kind::Default
369501
=> {}
370502
| Kind::Encoding
@@ -394,6 +526,7 @@ impl Attributes {
394526
| Kind::HasNil
395527
| Kind::CborLen
396528
| Kind::Skip
529+
| Kind::SkipIf
397530
| Kind::Default
398531
=> {
399532
let msg = format!("attribute is not supported on {}-level", self.level);
@@ -416,6 +549,7 @@ impl Attributes {
416549
| Kind::ContextBound
417550
| Kind::CborLen
418551
| Kind::Skip
552+
| Kind::SkipIf
419553
| Kind::Flat
420554
| Kind::Default
421555
=> {
@@ -429,12 +563,20 @@ impl Attributes {
429563
let s = val.span();
430564
match (val, &cc) {
431565
(Value::Codec(CustomCodec::Encode(e), _), CustomCodec::Decode(d)) => {
432-
let d = codec::Decode { decode: d.decode.clone(), nil: d.nil.clone() };
566+
let d = codec::Decode {
567+
decode: d.decode.clone(),
568+
nil: d.nil.clone(),
569+
require_bound: d.require_bound
570+
};
433571
*cc = CustomCodec::Both(Box::new(e), Box::new(d));
434572
return Ok(())
435573
}
436574
(Value::Codec(CustomCodec::Decode(d), _), CustomCodec::Encode(e)) => {
437-
let e = codec::Encode { encode: e.encode.clone(), is_nil: e.is_nil.clone() };
575+
let e = codec::Encode {
576+
encode: e.encode.clone(),
577+
is_nil: e.is_nil.clone(),
578+
require_bound: e.require_bound
579+
};
438580
*cc = CustomCodec::Both(Box::new(e), Box::new(d));
439581
return Ok(())
440582
}
@@ -484,14 +626,14 @@ impl Attributes {
484626
if d.nil.is_some() {
485627
return Err(syn::Error::new(*s, "duplicate attribute"))
486628
}
487-
d.nil = Some(nil.clone());
629+
d.nil = Some(PathOrClosure::Path(nil.clone()));
488630
return Ok(())
489631
}
490632
Some(Value::Codec(CustomCodec::Both(_, d), _)) => {
491633
if d.nil.is_some() {
492634
return Err(syn::Error::new(*s, "duplicate attribute"))
493635
}
494-
d.nil = Some(nil.clone());
636+
d.nil = Some(PathOrClosure::Path(nil.clone()));
495637
return Ok(())
496638
}
497639
_ => {}
@@ -519,7 +661,7 @@ impl Attributes {
519661
if d.nil.is_some() {
520662
return Err(syn::Error::new(*s, "duplicate attribute"))
521663
}
522-
d.nil = Some(nil)
664+
d.nil = Some(PathOrClosure::Path(nil))
523665
}
524666
}
525667
Value::Codec(CustomCodec::Both(e, d), s) => {
@@ -533,7 +675,7 @@ impl Attributes {
533675
if d.nil.is_some() {
534676
return Err(syn::Error::new(*s, "duplicate attribute"))
535677
}
536-
d.nil = Some(nil)
678+
d.nil = Some(PathOrClosure::Path(nil))
537679
}
538680
}
539681
Value::Codec(CustomCodec::Module(_, b), s) => {
@@ -548,17 +690,13 @@ impl Attributes {
548690
}
549691
}
550692
Value::CborLen(_, s) => {
551-
if let Some(Value::Codec(c, _)) = self.get(Kind::Codec) {
552-
if c.is_module() {
553-
return Err(syn::Error::new(*s, "`cbor_len` and `with` are mutually exclusive"))
554-
}
693+
if let Some(Value::Codec(c, _)) = self.get(Kind::Codec) && c.is_module() {
694+
return Err(syn::Error::new(*s, "`cbor_len` and `with` are mutually exclusive"))
555695
}
556696
}
557697
Value::Borrow(_, s) => {
558-
if let Some(idx) = self.index() {
559-
if idx.is_b() {
560-
return Err(syn::Error::new(*s, "`borrow` and `b` are mutually exclusive"))
561-
}
698+
if let Some(idx) = self.index() && idx.is_b() {
699+
return Err(syn::Error::new(*s, "`borrow` and `b` are mutually exclusive"))
562700
}
563701
}
564702
Value::Index(idx, s) if idx.is_b() => {
@@ -590,6 +728,7 @@ impl Value {
590728
Value::CborLen(_, s) => *s,
591729
Value::Tag(_, s) => *s,
592730
Value::Skip(s) => *s,
731+
Value::SkipIf(_, s) => *s,
593732
Value::Flat(s) => *s,
594733
Value::Default(s) => *s
595734
}

0 commit comments

Comments
 (0)