dao_dao_macros/lib.rs
1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
4
5use quote::quote;
6use syn::{parse_macro_input, AttributeArgs, DataEnum, DeriveInput, Path};
7
8// Merges the variants of two enums.
9fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) -> TokenStream {
10 use syn::Data::Enum;
11
12 let args = parse_macro_input!(metadata as AttributeArgs);
13 if let Some(first_arg) = args.first() {
14 return syn::Error::new_spanned(first_arg, "macro takes no arguments")
15 .to_compile_error()
16 .into();
17 }
18
19 let mut left: DeriveInput = parse_macro_input!(left);
20 let right: DeriveInput = parse_macro_input!(right);
21
22 if let (
23 Enum(DataEnum { variants, .. }),
24 Enum(DataEnum {
25 variants: to_add, ..
26 }),
27 ) = (&mut left.data, right.data)
28 {
29 variants.extend(to_add);
30
31 quote! { #left }.into()
32 } else {
33 syn::Error::new(left.ident.span(), "variants may only be added for enums")
34 .to_compile_error()
35 .into()
36 }
37}
38
39/// Gets the dao_interface path for something exported by
40/// dao_interface. If we are currently compiling the dao-interface
41/// crate, `crate::{internal}` is returned. If we are not,
42/// `::dao_interface::{internal}` is returned.
43///
44/// The this is needed is that dao_interface both defines types used
45/// in the interfaces here, and uses the macros exported here. At some
46/// point we'll be in a compilation context where we're inside of a
47/// crate as the macro is being expanded, and we need to use types
48/// local to the crate.
49fn dao_interface_path(inside: &str) -> Path {
50 let pkg = std::env::var("CARGO_PKG_NAME").unwrap();
51 let base = if pkg == "dao-interface" {
52 "crate"
53 } else {
54 "::dao_interface"
55 };
56 let path = format!("{base}::{inside}");
57 let path: Path = syn::parse_str(&path).unwrap();
58 path
59}
60
61/// Adds the necessary fields to an enum such that the enum implements the
62/// interface needed to be a voting module.
63///
64/// For example:
65///
66/// ```
67/// use dao_dao_macros::voting_module_query;
68/// use cosmwasm_schema::{cw_serde, QueryResponses};
69/// use dao_interface::voting::TotalPowerAtHeightResponse;
70/// use dao_interface::voting::VotingPowerAtHeightResponse;
71///
72/// #[voting_module_query]
73/// #[cw_serde]
74/// #[derive(QueryResponses)]
75/// enum QueryMsg {}
76///
77/// ```
78/// Will transform the enum to:
79///
80/// ```
81///
82/// enum QueryMsg {
83/// VotingPowerAtHeight {
84/// address: String,
85/// height: Option<u64>
86/// },
87/// TotalPowerAtHeight {
88/// height: Option<u64>
89/// },
90/// Dao {},
91/// Info {},
92/// }
93/// ```
94///
95/// Note that other derive macro invocations must occur after this
96/// procedural macro as they may depend on the new fields. For
97/// example, the following will fail becase the `Clone` derivation
98/// occurs before the addition of the field.
99///
100/// ```compile_fail
101/// use dao_dao_macros::voting_module_query;
102/// use cosmwasm_schema::{cw_serde, QueryResponses};
103/// use dao_interface::voting::TotalPowerAtHeightResponse;
104/// use dao_interface::voting::VotingPowerAtHeightResponse;
105///
106/// #[derive(Clone)]
107/// #[voting_module_query]
108/// #[allow(dead_code)]
109/// #[cw_serde]
110/// #[derive(QueryResponses)]
111/// enum Test {
112/// #[returns(String)]
113/// Foo,
114/// #[returns(String)]
115/// Bar(u64),
116/// #[returns(String)]
117/// Baz { foo: u64 },
118/// }
119/// ```
120#[proc_macro_attribute]
121pub fn voting_module_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
122 let i = dao_interface_path("voting::InfoResponse");
123 let vp = dao_interface_path("voting::VotingPowerAtHeightResponse");
124 let tp = dao_interface_path("voting::TotalPowerAtHeightResponse");
125
126 merge_variants(
127 metadata,
128 input,
129 quote! {
130 enum Right {
131 /// Returns the voting power for an address at a given height.
132 #[returns(#vp)]
133 VotingPowerAtHeight {
134 address: ::std::string::String,
135 height: ::std::option::Option<::std::primitive::u64>
136 },
137 /// Returns the total voting power at a given block heigh.
138 #[returns(#tp)]
139 TotalPowerAtHeight {
140 height: ::std::option::Option<::std::primitive::u64>
141 },
142 /// Returns the address of the DAO this module belongs to.
143 #[returns(cosmwasm_std::Addr)]
144 Dao {},
145 /// Returns contract version info.
146 #[returns(#i)]
147 Info {}
148 }
149 }
150 .into(),
151 )
152}
153
154/// Adds the necessary fields to an enum such that it implements the
155/// interface needed to be a voting module with a cw20 token.
156///
157/// For example:
158///
159/// ```
160/// use dao_dao_macros::cw20_token_query;
161/// use cosmwasm_schema::{cw_serde, QueryResponses};
162/// use cosmwasm_std::Addr;
163///
164/// #[cw20_token_query]
165/// #[cw_serde]
166/// #[derive(QueryResponses)]
167/// enum QueryMsg {}
168/// ```
169///
170/// Will transform the enum to:
171///
172/// ```
173/// enum QueryMsg {
174/// TokenContract {},
175/// }
176/// ```
177///
178/// Note that other derive macro invocations must occur after this
179/// procedural macro as they may depend on the new fields. For
180/// example, the following will fail becase the `Clone` derivation
181/// occurs before the addition of the field.
182///
183/// ```compile_fail
184/// use dao_dao_macros::cw20_token_query;
185/// use cosmwasm_schema::{cw_serde, QueryResponses};
186///
187/// #[derive(Clone)]
188/// #[cw20_token_query]
189/// #[allow(dead_code)]
190/// #[cw_serde]
191/// #[derive(QueryResponses)]
192/// enum Test {
193/// Foo,
194/// Bar(u64),
195/// Baz { foo: u64 },
196/// }
197/// ```
198#[proc_macro_attribute]
199pub fn cw20_token_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
200 merge_variants(
201 metadata,
202 input,
203 quote! {
204 enum Right {
205 #[returns(::cosmwasm_std::Addr)]
206 TokenContract {}
207 }
208 }
209 .into(),
210 )
211}
212
213/// Adds the necessary fields to an enum such that it implements the
214/// interface needed to be a voting module with a native token.
215///
216/// For example:
217///
218/// ```
219/// use dao_dao_macros::native_token_query;
220/// use cosmwasm_schema::{cw_serde, QueryResponses};
221/// use cosmwasm_std::Addr;
222///
223/// #[native_token_query]
224/// #[cw_serde]
225/// #[derive(QueryResponses)]
226/// enum QueryMsg {}
227/// ```
228///
229/// Will transform the enum to:
230///
231/// ```
232/// enum QueryMsg {
233/// Denom {},
234/// }
235/// ```
236///
237/// Note that other derive macro invocations must occur after this
238/// procedural macro as they may depend on the new fields. For
239/// example, the following will fail becase the `Clone` derivation
240/// occurs before the addition of the field.
241///
242/// ```compile_fail
243/// use dao_dao_macros::native_token_query;
244/// use cosmwasm_schema::{cw_serde, QueryResponses};
245///
246/// #[derive(Clone)]
247/// #[native_token_query]
248/// #[allow(dead_code)]
249/// #[cw_serde]
250/// #[derive(QueryResponses)]
251/// enum Test {
252/// Foo,
253/// Bar(u64),
254/// Baz { foo: u64 },
255/// }
256/// ```
257#[proc_macro_attribute]
258pub fn native_token_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
259 merge_variants(
260 metadata,
261 input,
262 quote! {
263 enum Right {
264 #[returns(::dao_interface::voting::DenomResponse)]
265 Denom {}
266 }
267 }
268 .into(),
269 )
270}
271
272/// Adds the necessary fields to an enum such that it implements the
273/// interface needed to be a voting module that has an
274/// active check threshold.
275///
276/// For example:
277///
278/// ```
279/// use dao_dao_macros::active_query;
280/// use cosmwasm_schema::{cw_serde, QueryResponses};
281///
282/// #[active_query]
283/// #[cw_serde]
284/// #[derive(QueryResponses)]
285/// enum QueryMsg {}
286/// ```
287///
288/// Will transform the enum to:
289///
290/// ```
291/// enum QueryMsg {
292/// IsActive {},
293/// }
294/// ```
295///
296/// Note that other derive macro invocations must occur after this
297/// procedural macro as they may depend on the new fields. For
298/// example, the following will fail becase the `Clone` derivation
299/// occurs before the addition of the field.
300///
301/// ```compile_fail
302/// use dao_dao_macros::active_query;
303///
304/// #[derive(Clone)]
305/// #[active_query]
306/// #[allow(dead_code)]
307/// enum Test {
308/// Foo,
309/// Bar(u64),
310/// Baz { foo: u64 },
311/// }
312/// ```
313#[proc_macro_attribute]
314pub fn active_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
315 merge_variants(
316 metadata,
317 input,
318 quote! {
319 enum Right {
320 #[returns(::std::primitive::bool)]
321 IsActive {}
322 }
323 }
324 .into(),
325 )
326}
327
328/// Adds the necessary fields to an enum such that it implements the
329/// interface needed to be a proposal module.
330///
331/// For example:
332///
333/// ```
334/// use dao_dao_macros::proposal_module_query;
335/// use cosmwasm_schema::{cw_serde, QueryResponses};
336/// use cosmwasm_std::Addr;
337///
338/// #[proposal_module_query]
339/// #[cw_serde]
340/// #[derive(QueryResponses)]
341/// enum QueryMsg {}
342/// ```
343///
344/// Will transform the enum to:
345///
346/// ```
347/// enum QueryMsg {
348/// Dao {},
349/// Info {},
350/// ProposalCreationPolicy {},
351/// ProposalHooks {},
352/// }
353/// ```
354///
355/// Note that other derive macro invocations must occur after this
356/// procedural macro as they may depend on the new fields. For
357/// example, the following will fail becase the `Clone` derivation
358/// occurs before the addition of the field.
359///
360/// ```compile_fail
361/// use dao_dao_macros::proposal_module_query;
362/// use cosmwasm_schema::{cw_serde, QueryResponses};
363/// use cosmwasm_std::Addr;
364///
365/// #[derive(Clone)]
366/// #[proposal_module_query]
367/// #[allow(dead_code)]
368/// #[cw_serde]
369/// #[derive(QueryResponses)]
370/// enum Test {
371/// Foo,
372/// Bar(u64),
373/// Baz { foo: u64 },
374/// }
375/// ```
376#[proc_macro_attribute]
377pub fn proposal_module_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
378 let i = dao_interface_path("voting::InfoResponse");
379
380 merge_variants(
381 metadata,
382 input,
383 quote! {
384 enum Right {
385 /// Returns the address of the DAO this module belongs to
386 #[returns(::cosmwasm_std::Addr)]
387 Dao {},
388 /// Returns contract version info
389 #[returns(#i)]
390 Info { },
391 /// Returns the proposal ID that will be assigned to the
392 /// next proposal created.
393 #[returns(::std::primitive::u64)]
394 NextProposalId {},
395 }
396 }
397 .into(),
398 )
399}
400
401/// Limits the number of variants allowed on an enum at compile
402/// time. For example, the following will not compile:
403///
404/// ```compile_fail
405/// use dao_dao_macros::limit_variant_count;
406///
407/// #[limit_variant_count(1)]
408/// enum Two {
409/// One {},
410/// Two {},
411/// }
412/// ```
413#[proc_macro_attribute]
414pub fn limit_variant_count(metadata: TokenStream, input: TokenStream) -> TokenStream {
415 let args = parse_macro_input!(metadata as AttributeArgs);
416 if args.len() != 1 {
417 panic!("macro takes one argument. ex: `#[limit_variant_count(4)]`")
418 }
419
420 let limit: usize = if let syn::NestedMeta::Lit(syn::Lit::Int(unparsed)) = args.first().unwrap()
421 {
422 match unparsed.base10_parse() {
423 Ok(limit) => limit,
424 Err(e) => return e.to_compile_error().into(),
425 }
426 } else {
427 return syn::Error::new_spanned(args[0].clone(), "argument should be an integer literal")
428 .to_compile_error()
429 .into();
430 };
431
432 let ast: DeriveInput = parse_macro_input!(input);
433 match ast.data {
434 syn::Data::Enum(DataEnum { ref variants, .. }) => {
435 if variants.len() > limit {
436 return syn::Error::new_spanned(
437 variants[limit].clone(),
438 format!("this enum's variant count is limited to {limit}"),
439 )
440 .to_compile_error()
441 .into();
442 }
443 }
444 _ => {
445 return syn::Error::new(
446 ast.ident.span(),
447 "limit_variant_count may only be derived for enums",
448 )
449 .to_compile_error()
450 .into()
451 }
452 };
453
454 quote! {
455 #ast
456 }
457 .into()
458}