risingwave_common_proc_macro/
config.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use bae::FromAttributes;
16use proc_macro_error::{ResultExt, abort};
17use proc_macro2::TokenStream;
18use quote::quote;
19use syn::DeriveInput;
20
21#[derive(FromAttributes)]
22pub struct OverrideOpts {
23    /// The path to the field to override.
24    pub path: syn::Expr,
25
26    /// Whether to override the field only if it is absent in the config.
27    ///
28    /// This requires the field to be an `Option`.
29    pub if_absent: Option<()>,
30}
31
32// TODO(bugen): the implementation is not robust but it works for now.
33fn type_is_option(ty: &syn::Type) -> bool {
34    if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
35        if let Some(segment) = path.segments.last() {
36            if segment.ident == "Option" {
37                return true;
38            }
39        }
40    }
41    false
42}
43
44pub fn produce_override_config(input: DeriveInput) -> TokenStream {
45    let syn::Data::Struct(syn::DataStruct { fields, .. }) = input.data else {
46        abort!(input, "Only struct is supported");
47    };
48
49    let mut override_stmts = Vec::new();
50
51    for field in fields {
52        let field_type_is_option = type_is_option(&field.ty);
53        let field_ident = field.ident;
54
55        // Allow multiple `override_opts` attributes on a field.
56        for attr in &field.attrs {
57            let attributes = OverrideOpts::try_from_attributes(std::slice::from_ref(attr))
58                .expect_or_abort("Failed to parse attribute");
59            let Some(OverrideOpts { path, if_absent }) = attributes else {
60                // Ignore attributes that are not `override_opts`.
61                continue;
62            };
63
64            // Use `into` to support `Option` target fields.
65            let mut override_stmt = if field_type_is_option {
66                quote! {
67                    if let Some(v) = self.#field_ident.clone() {
68                        config.#path = v.into();
69                    }
70                }
71            } else {
72                quote! {
73                    config.#path = self.#field_ident.clone().into();
74                }
75            };
76
77            if if_absent.is_some() {
78                override_stmt = quote! {
79                    if config.#path.is_none() {
80                        #override_stmt
81                    }
82                }
83            }
84
85            override_stmts.push(override_stmt);
86        }
87    }
88
89    let struct_ident = input.ident;
90
91    quote! {
92        impl ::risingwave_common::config::OverrideConfig for #struct_ident {
93            fn r#override(&self, config: &mut ::risingwave_common::config::RwConfig) {
94                #(#override_stmts)*
95            }
96        }
97    }
98}