1#![cfg_attr(coverage, feature(coverage_attribute))]
16#![feature(iterator_try_collect)]
17
18use proc_macro::TokenStream;
19use proc_macro2::{Span, TokenStream as TokenStream2};
20use quote::{format_ident, quote};
21use syn::{Data, DataEnum, DataStruct, DeriveInput, Result, parse_macro_input};
22
23mod generate;
24
25#[proc_macro_derive(AnyPB)]
28pub fn any_pb(input: TokenStream) -> TokenStream {
29 let ast = parse_macro_input!(input as DeriveInput);
31
32 match produce(&ast) {
33 Ok(tokens) => tokens.into(),
34 Err(e) => e.to_compile_error().into(),
35 }
36}
37
38#[proc_macro_derive(StreamNodeBodyVariants)]
39pub fn stream_node_body_variants(input: TokenStream) -> TokenStream {
40 let ast = parse_macro_input!(input as DeriveInput);
41
42 match produce_stream_node_body_variants(&ast) {
43 Ok(tokens) => tokens.into(),
44 Err(e) => e.to_compile_error().into(),
45 }
46}
47
48fn produce_stream_node_body_variants(ast: &DeriveInput) -> Result<TokenStream2> {
49 if ast.ident != "NodeBody" {
50 return Err(syn::Error::new_spanned(
51 &ast.ident,
52 "StreamNodeBodyVariants can only be derived for stream_plan::stream_node::NodeBody",
53 ));
54 }
55
56 let Data::Enum(DataEnum { variants, .. }) = &ast.data else {
57 return Err(syn::Error::new_spanned(
58 ast,
59 "StreamNodeBodyVariants can only be derived for enums",
60 ));
61 };
62
63 let variants: Vec<_> = variants.iter().map(|variant| &variant.ident).collect();
64 let marker_types: Vec<_> = variants
65 .iter()
66 .map(|variant| format_ident!("{variant}Variant"))
67 .collect();
68 let dollar = quote!($);
69
70 Ok(quote! {
71 #(
72 #[derive(Debug, Clone, Copy)]
73 pub struct #marker_types;
74 )*
75
76 #[macro_export]
77 #[doc(hidden)]
78 macro_rules! __dispatch_stream_node_body {
79 (#dollar body:expr, #dollar node_body:ident, #dollar node:ident => #dollar call:expr) => {
80 match #dollar body {
81 #(
82 ::risingwave_pb::stream_plan::stream_node::NodeBody::#variants(#dollar node) => {
83 type #dollar node_body = ::risingwave_pb::stream_plan::stream_node::#marker_types;
84 #dollar call
85 }
86 )*
87 }
88 };
89 }
90 })
91}
92
93fn produce(ast: &DeriveInput) -> Result<TokenStream2> {
95 let name = &ast.ident;
96
97 let struct_get = if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data {
99 let generated: Vec<_> = fields.iter().map(generate::implement).try_collect()?;
100 quote! {
101 impl #name {
102 #(#generated)*
103 }
104 }
105 } else {
106 quote! {}
108 };
109
110 let pb_alias = {
113 let pb_name = format_ident!("Pb{name}");
114 quote! {
115 pub type #pb_name = #name;
116 }
117 };
118
119 Ok(quote! {
120 #pb_alias
121 #struct_get
122 })
123}
124
125#[proc_macro_derive(Version)]
126pub fn version(input: TokenStream) -> TokenStream {
127 fn version_inner(ast: &DeriveInput) -> syn::Result<TokenStream2> {
128 let last_variant = match &ast.data {
129 Data::Enum(v) => v.variants.iter().next_back().ok_or_else(|| {
130 syn::Error::new(
131 Span::call_site(),
132 "This macro requires at least one variant in the enum.",
133 )
134 })?,
135 _ => {
136 return Err(syn::Error::new(
137 Span::call_site(),
138 "This macro only supports enums.",
139 ));
140 }
141 };
142
143 let enum_name = &ast.ident;
144 let last_variant_name = &last_variant.ident;
145
146 Ok(quote! {
147 impl #enum_name {
148 pub const LATEST: Self = Self::#last_variant_name;
149 }
150 })
151 }
152
153 let ast = parse_macro_input!(input as DeriveInput);
154
155 match version_inner(&ast) {
156 Ok(tokens) => tokens.into(),
157 Err(e) => e.to_compile_error().into(),
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use quote::quote;
164 use syn::parse_quote;
165
166 use super::*;
167
168 fn normalize(tokens: TokenStream2) -> String {
169 tokens
170 .to_string()
171 .split_whitespace()
172 .collect::<Vec<_>>()
173 .join(" ")
174 }
175
176 #[test]
177 fn stream_node_body_variants_generates_markers_and_dispatch() {
178 let input = parse_quote! {
179 enum NodeBody {
180 Source(Box<SourceNode>),
181 Project(Box<ProjectNode>),
182 }
183 };
184
185 let dollar = quote!($);
186 let expected_code = quote! {
187 #[derive(Debug, Clone, Copy)]
188 pub struct SourceVariant;
189
190 #[derive(Debug, Clone, Copy)]
191 pub struct ProjectVariant;
192
193 #[macro_export]
194 #[doc(hidden)]
195 macro_rules! __dispatch_stream_node_body {
196 (#dollar body:expr, #dollar node_body:ident, #dollar node:ident => #dollar call:expr) => {
197 match #dollar body {
198 ::risingwave_pb::stream_plan::stream_node::NodeBody::Source(#dollar node) => {
199 type #dollar node_body = ::risingwave_pb::stream_plan::stream_node::SourceVariant;
200 #dollar call
201 }
202 ::risingwave_pb::stream_plan::stream_node::NodeBody::Project(#dollar node) => {
203 type #dollar node_body = ::risingwave_pb::stream_plan::stream_node::ProjectVariant;
204 #dollar call
205 }
206 }
207 };
208 }
209 };
210
211 assert_eq!(
212 normalize(produce_stream_node_body_variants(&input).unwrap()),
213 normalize(expected_code)
214 );
215 }
216
217 #[test]
218 fn stream_node_body_variants_rejects_unexpected_enum_name() {
219 let input = parse_quote! {
220 enum OtherBody {
221 Source(Box<SourceNode>),
222 }
223 };
224
225 let err = produce_stream_node_body_variants(&input).unwrap_err();
226 assert_eq!(
227 err.to_string(),
228 "StreamNodeBodyVariants can only be derived for stream_plan::stream_node::NodeBody"
229 );
230 }
231}