Skip to content

Commit b774777

Browse files
committed
refactor
1 parent 636e22f commit b774777

File tree

13 files changed

+1179
-481
lines changed

13 files changed

+1179
-481
lines changed

CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [0.4.0] - 2023-03-02
8+
### Changed
9+
- **Breaking Change**: Moved some syn types behind feature `full`
10+
- **Breaking Change**: Refactored attributes
11+
- Use [interpolator](https://docs.rs/interpolator) for error messages
12+
- **Breaking Change**: Compile time verify if ident is given through helper
13+
trait
14+
15+
[unreleased]: https://github.com/ModProg/attribute-derive/compare/v0.4.0...HEAD
16+
[0.4.0]: https://github.com/ModProg/attribute-derive/compare/v0.3.1...v0.4.0

Cargo.toml

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
categories = ["rust-patterns", "development-tools::procedural-macro-helpers", "parsing"]
33
description = "Clap for proc macro attributes"
44
documentation = "https://docs.rs/attribute-derive"
5-
include = ["src/**/*", "Cargo.toml", "LICENSE", "README.md"]
5+
include = ["src/**/*", "LICENSE", "README.md"]
66
keywords = ["derive", "macro"]
77
license = "MIT"
88
readme = "README.md"
99
repository = "https://github.com/ModProg/attribute-derive"
1010
name = "attribute-derive"
11-
version = "0.3.1"
11+
version = "0.4.0"
1212
edition = "2021"
1313

1414
[lib]
1515

1616
[dependencies]
1717
proc-macro2 = "1"
18-
quote = "1.0.18"
19-
syn = { version = "1", features = ["full"] }
18+
quote = "1"
19+
syn = "1"
20+
21+
[features]
22+
# default = ["syn-full"]
23+
syn-full = ["syn/full"]
2024

2125
[dependencies.attribute-derive-macro]
22-
version = "0.3.1"
26+
version = "0.4.0"
2327
path = "macro"
2428

2529
[workspace]
26-
members = ["macro"]
27-
28-
[dev-dependencies.syn]
29-
version = "1"
30-
features = ["full"]
30+
members = ["example", "macro"]

example/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
attribute-derive = { path = "..", features = ["syn-full"] }
12+
syn = { version = "1", features = ["full"] }
13+
trybuild = "1"

example/src/lib.rs

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#![allow(unused)]
2+
use std::fmt::Debug;
3+
4+
use attribute_derive::{Attribute, AttributeIdent};
5+
use proc_macro::TokenStream;
6+
use syn::{Block, DeriveInput, Result};
7+
8+
// #[derive(Attribute)]
9+
// #[attribute(ident = "positional")]
10+
// struct PositionalAttr {
11+
// #[attribute(positional)]
12+
// a: u8,
13+
// #[attribute(positional)]
14+
// b: String,
15+
// #[attribute(positional)]
16+
// c: bool,
17+
// }
18+
//
19+
// #[proc_macro_derive(Positional, attributes(attribute))]
20+
// pub fn positional_derive(input: TokenStream) -> proc_macro::TokenStream {
21+
// all_attrs::<PositionalAttr>(input).unwrap_or_else(|e|
22+
// e.to_compile_error().into()) }
23+
#[derive(Attribute)]
24+
#[attribute(ident = empty)]
25+
struct Empty {}
26+
27+
#[derive(Attribute)]
28+
#[attribute(ident = single)]
29+
struct Single {
30+
field: bool,
31+
}
32+
33+
#[derive(Attribute, Debug)]
34+
#[attribute(ident = ident, aliases = [a, b])]
35+
struct Normal {
36+
optional_implicit: Option<u8>,
37+
#[attribute(optional)]
38+
optional_explicit: u8,
39+
#[attribute(optional, default = 2 * 5)]
40+
optional_default: u8,
41+
#[attribute(default = 33)]
42+
default: u8,
43+
#[attribute(conflicts = [conflict_b])]
44+
conflict_a: Option<String>,
45+
conflict_b: Option<String>,
46+
#[attribute(example = "2.5")]
47+
example: f32,
48+
flag: bool,
49+
#[attribute(optional = false)]
50+
mandatory_flag: bool,
51+
}
52+
#[proc_macro_derive(Normal, attributes(ident, a, b, empty, single))]
53+
pub fn normal_derive(input: TokenStream) -> proc_macro::TokenStream {
54+
let mut tokens =
55+
all_attrs::<Normal>(input.clone()).unwrap_or_else(|e| e.to_compile_error().into());
56+
tokens
57+
.extend(all_attrs::<Empty>(input.clone()).unwrap_or_else(|e| e.to_compile_error().into()));
58+
tokens.extend(all_attrs::<Single>(input).unwrap_or_else(|e| e.to_compile_error().into()));
59+
tokens
60+
}
61+
62+
#[derive(Attribute, Debug)]
63+
#[attribute(ident = ident, aliases = [a, b])]
64+
#[attribute(error(
65+
unknown_field = "found `{found_field}` but expected one of {expected_fields:i(`{}`)(, )}",
66+
duplicate_field = "duplicate `{field}`",
67+
missing_field = "missing field `{field}`",
68+
field_help = "try {attribute}: {field}={example}",
69+
missing_flag = "missing flag `{flag}`",
70+
flag_help = "try {attribute}: {flag}",
71+
conflict = "{first} !!! {second}"
72+
))]
73+
struct Custom {
74+
optional_implicit: Option<Block>,
75+
#[attribute(optional)]
76+
optional_explicit: u8,
77+
#[attribute(optional, default = 2 * 5)]
78+
optional_default: u8,
79+
#[attribute(default = 33)]
80+
default: u8,
81+
#[attribute(conflicts = [conflict_b])]
82+
conflict_a: Option<String>,
83+
conflict_b: Option<String>,
84+
#[attribute(example = "2.5")]
85+
example: f32,
86+
flag: bool,
87+
#[attribute(optional = false)]
88+
mandatory_flag: bool,
89+
}
90+
#[derive(Attribute)]
91+
#[attribute(ident = empty, error(unknown_field_empty = "found {found_field}, but expected none"))]
92+
struct EmptyCustom {}
93+
94+
#[derive(Attribute)]
95+
#[attribute(ident = single, error(unknown_field_single = "found {found_field}, but expected {expected_field}"))]
96+
struct SingleCustom {
97+
field: bool,
98+
}
99+
100+
#[proc_macro_derive(Custom, attributes(ident, a, b, empty, single))]
101+
pub fn custom_derive(input: TokenStream) -> proc_macro::TokenStream {
102+
let mut tokens =
103+
all_attrs::<Custom>(input.clone()).unwrap_or_else(|e| e.to_compile_error().into());
104+
tokens.extend(
105+
all_attrs::<EmptyCustom>(input.clone()).unwrap_or_else(|e| e.to_compile_error().into()),
106+
);
107+
tokens.extend(all_attrs::<SingleCustom>(input).unwrap_or_else(|e| e.to_compile_error().into()));
108+
tokens
109+
}
110+
111+
fn all_attrs<T: Attribute + AttributeIdent>(input: TokenStream) -> Result<TokenStream> {
112+
let DeriveInput { attrs, data, .. } = syn::parse(input)?;
113+
T::from_attributes(&attrs)?;
114+
match data {
115+
syn::Data::Struct(data) => {
116+
for field in data.fields {
117+
T::from_attributes(&field.attrs)?;
118+
}
119+
}
120+
syn::Data::Enum(data) => {
121+
for variant in data.variants {
122+
T::from_attributes(&variant.attrs)?;
123+
for field in variant.fields {
124+
T::from_attributes(&field.attrs)?;
125+
}
126+
}
127+
}
128+
syn::Data::Union(data) => {
129+
for field in data.fields.named {
130+
T::from_attributes(&field.attrs)?;
131+
}
132+
}
133+
}
134+
Ok(TokenStream::new())
135+
}

example/tests/ui.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[test]
2+
fn ui() {
3+
let t = trybuild::TestCases::new();
4+
t.compile_fail("tests/ui/*.rs");
5+
}

example/tests/ui/custom.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use example::Custom;
2+
3+
#[derive(Custom)]
4+
struct None;
5+
6+
#[derive(Custom)]
7+
#[ident(example = 2.)]
8+
struct Example;
9+
10+
#[derive(Custom)]
11+
#[ident(
12+
example = 2.,
13+
optional_implicit = {10},
14+
optional_default = 3,
15+
default = 2,
16+
conflict_a = "hello",
17+
mandatory_flag
18+
)]
19+
struct ExampleOI;
20+
21+
#[derive(Custom)]
22+
#[ident(example = 1., mandatory_flag)]
23+
#[a(conflict_a = "hey")]
24+
#[b(conflict_b = "hi")]
25+
struct Conflict;
26+
27+
#[derive(Custom)]
28+
#[ident(hello)]
29+
#[single(hello)]
30+
#[empty(hello)]
31+
struct UnknownField;
32+
33+
fn main() {}

example/tests/ui/custom.stderr

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
error: missing field `example`
2+
3+
= help: try ident: example=2.5
4+
--> tests/ui/custom.rs:3:10
5+
|
6+
3 | #[derive(Custom)]
7+
| ^^^^^^
8+
|
9+
= note: this error originates in the derive macro `Custom` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error: missing flag `mandatory_flag`
12+
13+
= help: try ident: mandatory_flag
14+
--> tests/ui/custom.rs:6:10
15+
|
16+
6 | #[derive(Custom)]
17+
| ^^^^^^
18+
|
19+
= note: this error originates in the derive macro `Custom` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error: conflict_a !!! conflict_b
22+
--> tests/ui/custom.rs:23:18
23+
|
24+
23 | #[a(conflict_a = "hey")]
25+
| ^^^^^
26+
27+
error: conflict_b !!! conflict_a
28+
--> tests/ui/custom.rs:24:18
29+
|
30+
24 | #[b(conflict_b = "hi")]
31+
| ^^^^
32+
33+
error: found `hello` but expected one of `optional_implicit`, `optional_explicit`, `optional_default`, `default`, `conflict_a`, `conflict_b`, `example`, `flag`, `mandatory_flag`
34+
--> tests/ui/custom.rs:28:9
35+
|
36+
28 | #[ident(hello)]
37+
| ^^^^^
38+
39+
error: found hello, but expected none
40+
--> tests/ui/custom.rs:30:9
41+
|
42+
30 | #[empty(hello)]
43+
| ^^^^^
44+
45+
error: found hello, but expected field
46+
--> tests/ui/custom.rs:29:10
47+
|
48+
29 | #[single(hello)]
49+
| ^^^^^

example/tests/ui/normal.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use example::Normal;
2+
3+
#[derive(Normal)]
4+
struct None;
5+
6+
#[derive(Normal)]
7+
#[ident(example = 2.)]
8+
struct Example;
9+
10+
#[derive(Normal)]
11+
#[ident(
12+
example = 2.,
13+
optional_implicit = 10,
14+
optional_default = 3,
15+
default = 2,
16+
conflict_a = "hello",
17+
mandatory_flag
18+
)]
19+
struct ExampleOI;
20+
21+
#[derive(Normal)]
22+
#[ident(example = 1., mandatory_flag)]
23+
#[a(conflict_a = "hey")]
24+
#[b(conflict_b = "hi")]
25+
struct Conflict;
26+
27+
#[derive(Normal)]
28+
#[ident(hello)]
29+
#[single(hello)]
30+
#[empty(hello)]
31+
struct UnknownField;
32+
33+
fn main() {}

example/tests/ui/normal.stderr

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
error: required `example` is not specified
2+
3+
= help: try `#[ident(example=2.5)]`
4+
--> tests/ui/normal.rs:3:10
5+
|
6+
3 | #[derive(Normal)]
7+
| ^^^^^^
8+
|
9+
= note: this error originates in the derive macro `Normal` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error: required `mandatory_flag` is not specified
12+
13+
= help: try `#[ident(mandatory_flag)]`
14+
--> tests/ui/normal.rs:6:10
15+
|
16+
6 | #[derive(Normal)]
17+
| ^^^^^^
18+
|
19+
= note: this error originates in the derive macro `Normal` (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error: `conflict_a` conflicts with mutually exclusive `conflict_b`
22+
--> tests/ui/normal.rs:23:18
23+
|
24+
23 | #[a(conflict_a = "hey")]
25+
| ^^^^^
26+
27+
error: `conflict_b` conflicts with mutually exclusive `conflict_a`
28+
--> tests/ui/normal.rs:24:18
29+
|
30+
24 | #[b(conflict_b = "hi")]
31+
| ^^^^
32+
33+
error: supported fields are `optional_implicit`, `optional_explicit`, `optional_default`, `default`, `conflict_a`, `conflict_b`, `example`, `flag` and `mandatory_flag`
34+
--> tests/ui/normal.rs:28:9
35+
|
36+
28 | #[ident(hello)]
37+
| ^^^^^
38+
39+
error: expected empty attribute
40+
--> tests/ui/normal.rs:30:9
41+
|
42+
30 | #[empty(hello)]
43+
| ^^^^^
44+
45+
error: expected supported field `field`
46+
--> tests/ui/normal.rs:29:10
47+
|
48+
29 | #[single(hello)]
49+
| ^^^^^

0 commit comments

Comments
 (0)