Skip to content

Commit 228fd49

Browse files
authored
@newtype custom getters (#240)
Now you can optionally specify a getter e.g. `foo = uint ; @newtype custom_getter`. Previously `@newtype` would always generate one named `get()` but this isn't always very helpful. Now you can explicitly name it, and if it's empty, no getter will be made, in which case users can choose to expose it however they want in their own `utils.rs` files.
1 parent 1ec516f commit 228fd49

File tree

8 files changed

+168
-121
lines changed

8 files changed

+168
-121
lines changed

docs/docs/comment_dsl.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ script = [
5353

5454
With code like `foo = uint` this creates an alias e.g. `pub type Foo = u64;` in rust. When we use `foo = uint ; @newtype` it instead creates a `pub struct Foo(u64);`.
5555

56+
`@newtype` can also optionally specify a getter function e.g. `foo = uint ; @newtype custom_getter` will generate:
57+
58+
```rust
59+
impl Foo {
60+
pub fn custom_getter(&self) -> u64 {
61+
self.0
62+
}
63+
}
64+
```
65+
5666
## @no_alias
5767

5868
```cddl

src/comment_ast.rs

Lines changed: 117 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use nom::{
99
#[derive(Clone, Default, Debug, PartialEq)]
1010
pub struct RuleMetadata {
1111
pub name: Option<String>,
12-
pub is_newtype: bool,
12+
/// None = not newtype, Some(Some) = generate getter, Some(None) = no getter
13+
pub newtype: Option<Option<String>>,
1314
pub no_alias: bool,
1415
pub used_as_key: bool,
1516
pub custom_json: bool,
@@ -18,56 +19,46 @@ pub struct RuleMetadata {
1819
pub comment: Option<String>,
1920
}
2021

21-
pub fn merge_metadata(r1: &RuleMetadata, r2: &RuleMetadata) -> RuleMetadata {
22-
let merged = RuleMetadata {
23-
name: match (r1.name.as_ref(), r2.name.as_ref()) {
24-
(Some(val1), Some(val2)) => {
25-
panic!("Key \"name\" specified twice: {:?} {:?}", val1, val2)
26-
}
27-
(val @ Some(_), _) => val.cloned(),
28-
(_, val) => val.cloned(),
29-
},
30-
is_newtype: r1.is_newtype || r2.is_newtype,
31-
no_alias: r1.no_alias || r2.no_alias,
32-
used_as_key: r1.used_as_key || r2.used_as_key,
33-
custom_json: r1.custom_json || r2.custom_json,
34-
custom_serialize: match (r1.custom_serialize.as_ref(), r2.custom_serialize.as_ref()) {
22+
macro_rules! merge_metadata_fields {
23+
($lhs:expr, $rhs:expr, $field_name:literal) => {
24+
match ($lhs.as_ref(), $rhs.as_ref()) {
3525
(Some(val1), Some(val2)) => {
3626
panic!(
37-
"Key \"custom_serialize\" specified twice: {:?} {:?}",
27+
concat!("Key \"", $field_name, "\" specified twice: {:?} {:?}"),
3828
val1, val2
3929
)
4030
}
4131
(val @ Some(_), _) => val.cloned(),
4232
(_, val) => val.cloned(),
43-
},
44-
custom_deserialize: match (
45-
r1.custom_deserialize.as_ref(),
46-
r2.custom_deserialize.as_ref(),
47-
) {
48-
(Some(val1), Some(val2)) => {
49-
panic!(
50-
"Key \"custom_deserialize\" specified twice: {:?} {:?}",
51-
val1, val2
52-
)
53-
}
54-
(val @ Some(_), _) => val.cloned(),
55-
(_, val) => val.cloned(),
56-
},
57-
comment: match (r1.comment.as_ref(), r2.comment.as_ref()) {
58-
(Some(val1), Some(val2)) => {
59-
panic!("Key \"comment\" specified twice: {:?} {:?}", val1, val2)
60-
}
61-
(val @ Some(_), _) => val.cloned(),
62-
(_, val) => val.cloned(),
63-
},
33+
}
34+
};
35+
}
36+
37+
pub fn merge_metadata(r1: &RuleMetadata, r2: &RuleMetadata) -> RuleMetadata {
38+
let merged = RuleMetadata {
39+
name: merge_metadata_fields!(r1.name, r2.name, "name"),
40+
newtype: merge_metadata_fields!(r1.newtype, r2.newtype, "newtype"),
41+
no_alias: r1.no_alias || r2.no_alias,
42+
used_as_key: r1.used_as_key || r2.used_as_key,
43+
custom_json: r1.custom_json || r2.custom_json,
44+
custom_serialize: merge_metadata_fields!(
45+
r1.custom_serialize,
46+
r2.custom_serialize,
47+
"custom_serialize"
48+
),
49+
custom_deserialize: merge_metadata_fields!(
50+
r1.custom_deserialize,
51+
r2.custom_deserialize,
52+
"custom_deserialize"
53+
),
54+
comment: merge_metadata_fields!(r1.comment, r2.comment, "comment"),
6455
};
6556
merged.verify();
6657
merged
6758
}
6859

6960
enum ParseResult {
70-
NewType,
61+
NewType(Option<String>),
7162
Name(String),
7263
DontGenAlias,
7364
UsedAsKey,
@@ -77,21 +68,30 @@ enum ParseResult {
7768
Comment(String),
7869
}
7970

71+
macro_rules! merge_parse_fields {
72+
($base:expr, $new:expr, $field_name:literal) => {
73+
match $base.as_ref() {
74+
Some(old) => {
75+
panic!(
76+
concat!("Key \"", $field_name, "\" specified twice: {:?} {:?}"),
77+
old, $new
78+
)
79+
}
80+
None => {
81+
$base = Some($new.to_owned());
82+
}
83+
}
84+
};
85+
}
86+
8087
impl RuleMetadata {
8188
fn from_parse_results(results: &[ParseResult]) -> RuleMetadata {
8289
let mut base = RuleMetadata::default();
8390
for result in results {
8491
match result {
85-
ParseResult::Name(name) => match base.name.as_ref() {
86-
Some(old_name) => {
87-
panic!("Key \"name\" specified twice: {:?} {:?}", old_name, name)
88-
}
89-
None => {
90-
base.name = Some(name.to_string());
91-
}
92-
},
93-
ParseResult::NewType => {
94-
base.is_newtype = true;
92+
ParseResult::Name(name) => merge_parse_fields!(base.name, name, "name"),
93+
ParseResult::NewType(newtype) => {
94+
merge_parse_fields!(base.newtype, newtype, "newtype")
9595
}
9696
ParseResult::DontGenAlias => {
9797
base.no_alias = true;
@@ -104,47 +104,24 @@ impl RuleMetadata {
104104
base.custom_json = true;
105105
}
106106
ParseResult::CustomSerialize(custom_serialize) => {
107-
match base.custom_serialize.as_ref() {
108-
Some(old) => {
109-
panic!(
110-
"Key \"custom_serialize\" specified twice: {:?} {:?}",
111-
old, custom_serialize
112-
)
113-
}
114-
None => {
115-
base.custom_serialize = Some(custom_serialize.to_string());
116-
}
117-
}
107+
merge_parse_fields!(base.custom_serialize, custom_serialize, "custom_serialize")
118108
}
119-
ParseResult::CustomDeserialize(custom_deserialize) => {
120-
match base.custom_deserialize.as_ref() {
121-
Some(old) => {
122-
panic!(
123-
"Key \"custom_deserialize\" specified twice: {:?} {:?}",
124-
old, custom_deserialize
125-
)
126-
}
127-
None => {
128-
base.custom_deserialize = Some(custom_deserialize.to_string());
129-
}
130-
}
109+
ParseResult::CustomDeserialize(custom_deserialize) => merge_parse_fields!(
110+
base.custom_deserialize,
111+
custom_deserialize,
112+
"custom_deserialize"
113+
),
114+
ParseResult::Comment(comment) => {
115+
merge_parse_fields!(base.comment, comment, "comment")
131116
}
132-
ParseResult::Comment(comment) => match base.comment.as_ref() {
133-
Some(old) => {
134-
panic!("Key \"comment\" specified twice: {:?} {:?}", old, comment)
135-
}
136-
None => {
137-
base.comment = Some(comment.to_string());
138-
}
139-
},
140117
}
141118
}
142119
base.verify();
143120
base
144121
}
145122

146123
fn verify(&self) {
147-
if self.is_newtype && self.no_alias {
124+
if self.newtype.is_some() && self.no_alias {
148125
// this would make no sense anyway as with newtype we're already not making an alias
149126
panic!("cannot use both @newtype and @no_alias on the same alias");
150127
}
@@ -161,8 +138,16 @@ fn tag_name(input: &str) -> IResult<&str, ParseResult> {
161138

162139
fn tag_newtype(input: &str) -> IResult<&str, ParseResult> {
163140
let (input, _) = tag("@newtype")(input)?;
164-
165-
Ok((input, ParseResult::NewType))
141+
// to get around type annotations
142+
fn parse_newtype(input: &str) -> IResult<&str, ParseResult> {
143+
let (input, _) = take_while(char::is_whitespace)(input)?;
144+
let (input, getter) = take_while1(|ch| !char::is_whitespace(ch) && ch != '@')(input)?;
145+
Ok((input, ParseResult::NewType(Some(getter.trim().to_owned()))))
146+
}
147+
match parse_newtype(input) {
148+
Ok(ret) => Ok(ret),
149+
Err(_) => Ok((input.trim_start(), ParseResult::NewType(None))),
150+
}
166151
}
167152

168153
fn tag_no_alias(input: &str) -> IResult<&str, ParseResult> {
@@ -261,7 +246,7 @@ fn parse_comment_name() {
261246
"",
262247
RuleMetadata {
263248
name: Some("foo".to_string()),
264-
is_newtype: false,
249+
newtype: None,
265250
no_alias: false,
266251
used_as_key: false,
267252
custom_json: false,
@@ -281,7 +266,7 @@ fn parse_comment_newtype() {
281266
"",
282267
RuleMetadata {
283268
name: None,
284-
is_newtype: true,
269+
newtype: Some(None),
285270
no_alias: false,
286271
used_as_key: false,
287272
custom_json: false,
@@ -293,6 +278,46 @@ fn parse_comment_newtype() {
293278
);
294279
}
295280

281+
#[test]
282+
fn parse_comment_newtype_getter_before() {
283+
assert_eq!(
284+
rule_metadata("@newtype custom_getter @used_as_key"),
285+
Ok((
286+
"",
287+
RuleMetadata {
288+
name: None,
289+
newtype: Some(Some("custom_getter".to_owned())),
290+
no_alias: false,
291+
used_as_key: true,
292+
custom_json: false,
293+
custom_serialize: None,
294+
custom_deserialize: None,
295+
comment: None,
296+
}
297+
))
298+
);
299+
}
300+
301+
#[test]
302+
fn parse_comment_newtype_getter_after() {
303+
assert_eq!(
304+
rule_metadata("@used_as_key @newtype custom_getter"),
305+
Ok((
306+
"",
307+
RuleMetadata {
308+
name: None,
309+
newtype: Some(Some("custom_getter".to_owned())),
310+
no_alias: false,
311+
used_as_key: true,
312+
custom_json: false,
313+
custom_serialize: None,
314+
custom_deserialize: None,
315+
comment: None,
316+
}
317+
))
318+
);
319+
}
320+
296321
#[test]
297322
fn parse_comment_newtype_and_name() {
298323
assert_eq!(
@@ -301,7 +326,7 @@ fn parse_comment_newtype_and_name() {
301326
"",
302327
RuleMetadata {
303328
name: Some("foo".to_string()),
304-
is_newtype: true,
329+
newtype: Some(None),
305330
no_alias: false,
306331
used_as_key: false,
307332
custom_json: false,
@@ -321,7 +346,7 @@ fn parse_comment_newtype_and_name_and_used_as_key() {
321346
"",
322347
RuleMetadata {
323348
name: Some("foo".to_string()),
324-
is_newtype: true,
349+
newtype: Some(None),
325350
no_alias: false,
326351
used_as_key: true,
327352
custom_json: false,
@@ -341,7 +366,7 @@ fn parse_comment_used_as_key() {
341366
"",
342367
RuleMetadata {
343368
name: None,
344-
is_newtype: false,
369+
newtype: None,
345370
no_alias: false,
346371
used_as_key: true,
347372
custom_json: false,
@@ -361,7 +386,7 @@ fn parse_comment_newtype_and_name_inverse() {
361386
"",
362387
RuleMetadata {
363388
name: Some("foo".to_string()),
364-
is_newtype: true,
389+
newtype: Some(None),
365390
no_alias: false,
366391
used_as_key: false,
367392
custom_json: false,
@@ -381,7 +406,7 @@ fn parse_comment_name_noalias() {
381406
"",
382407
RuleMetadata {
383408
name: Some("foo".to_string()),
384-
is_newtype: false,
409+
newtype: None,
385410
no_alias: true,
386411
used_as_key: false,
387412
custom_json: false,
@@ -401,7 +426,7 @@ fn parse_comment_newtype_and_custom_json() {
401426
"",
402427
RuleMetadata {
403428
name: None,
404-
is_newtype: true,
429+
newtype: Some(None),
405430
no_alias: false,
406431
used_as_key: false,
407432
custom_json: true,
@@ -427,7 +452,7 @@ fn parse_comment_custom_serialize_deserialize() {
427452
"",
428453
RuleMetadata {
429454
name: None,
430-
is_newtype: false,
455+
newtype: None,
431456
no_alias: false,
432457
used_as_key: false,
433458
custom_json: false,
@@ -448,7 +473,7 @@ fn parse_comment_all_except_no_alias() {
448473
"",
449474
RuleMetadata {
450475
name: Some("baz".to_string()),
451-
is_newtype: true,
476+
newtype: Some(None),
452477
no_alias: false,
453478
used_as_key: true,
454479
custom_json: true,

0 commit comments

Comments
 (0)