@@ -11,6 +11,7 @@ extern crate proc_macro;
1111
1212use proc_macro:: TokenStream ;
1313use std:: collections:: HashSet ;
14+ use std:: ffi:: CString ;
1415
1516use proc_macro2:: Ident ;
1617use quote:: { format_ident, quote, ToTokens } ;
@@ -1064,91 +1065,108 @@ fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
10641065}
10651066
10661067/// Derives the `GucEnum` trait, so that normal Rust enums can be used as a GUC.
1067- #[ proc_macro_derive( PostgresGucEnum , attributes( hidden) ) ]
1068+ #[ proc_macro_derive( PostgresGucEnum , attributes( name , hidden) ) ]
10681069pub fn postgres_guc_enum ( input : TokenStream ) -> TokenStream {
10691070 let ast = parse_macro_input ! ( input as syn:: DeriveInput ) ;
10701071
10711072 impl_guc_enum ( ast) . unwrap_or_else ( |e| e. into_compile_error ( ) ) . into ( )
10721073}
10731074
10741075fn impl_guc_enum ( ast : DeriveInput ) -> syn:: Result < proc_macro2:: TokenStream > {
1075- let mut stream = proc_macro2:: TokenStream :: new ( ) ;
1076+ use std:: str:: FromStr ;
1077+ use syn:: parse:: Parse ;
10761078
1077- // validate that we're only operating on an enum
1078- let enum_data = match ast. data {
1079- Data :: Enum ( e) => e,
1080- _ => {
1081- return Err ( syn:: Error :: new (
1082- ast. span ( ) ,
1083- "#[derive(PostgresGucEnum)] can only be applied to enums" ,
1084- ) )
1085- }
1086- } ;
1087- let enum_name = ast. ident ;
1088- let enum_len = enum_data. variants . len ( ) ;
1089-
1090- let mut from_match_arms = proc_macro2:: TokenStream :: new ( ) ;
1091- for ( idx, e) in enum_data. variants . iter ( ) . enumerate ( ) {
1092- let label = & e. ident ;
1093- let idx = idx as i32 ;
1094- from_match_arms. extend ( quote ! { #idx => #enum_name:: #label, } )
1079+ enum GucEnumAttribute {
1080+ Name ( CString ) ,
1081+ Hidden ( bool ) ,
10951082 }
1096- from_match_arms. extend ( quote ! { _ => panic!( "Unrecognized ordinal " ) } ) ;
10971083
1098- let mut ordinal_match_arms = proc_macro2:: TokenStream :: new ( ) ;
1099- for ( idx, e) in enum_data. variants . iter ( ) . enumerate ( ) {
1100- let label = & e. ident ;
1101- let idx = idx as i32 ;
1102- ordinal_match_arms. extend ( quote ! { #enum_name:: #label => #idx, } ) ;
1084+ impl Parse for GucEnumAttribute {
1085+ fn parse ( input : syn:: parse:: ParseStream ) -> syn:: Result < Self > {
1086+ let ident: Ident = input. parse ( ) ?;
1087+ let _: syn:: token:: Eq = input. parse ( ) ?;
1088+ match ident. to_string ( ) . as_str ( ) {
1089+ "name" => input. parse :: < syn:: LitCStr > ( ) . map ( |val| Self :: Name ( val. value ( ) ) ) ,
1090+ "hidden" => input. parse :: < syn:: LitBool > ( ) . map ( |val| Self :: Hidden ( val. value ( ) ) ) ,
1091+ x => Err ( syn:: Error :: new ( input. span ( ) , format ! ( "unknown attribute {x}" ) ) ) ,
1092+ }
1093+ }
11031094 }
11041095
1105- let mut build_array_body = proc_macro2:: TokenStream :: new ( ) ;
1106- for ( idx, e) in enum_data. variants . iter ( ) . enumerate ( ) {
1107- let label = e. ident . to_string ( ) ;
1108- let mut hidden = false ;
1109-
1110- for att in e. attrs . iter ( ) {
1111- let att = quote ! { #att} . to_string ( ) ;
1112- if att == "# [hidden]" {
1113- hidden = true ;
1114- break ;
1096+ // validate that we're only operating on an enum
1097+ let Data :: Enum ( data) = ast. data . clone ( ) else {
1098+ return Err ( syn:: Error :: new (
1099+ ast. span ( ) ,
1100+ "#[derive(PostgresGucEnum)] can only be applied to enums" ,
1101+ ) ) ;
1102+ } ;
1103+ let ident = ast. ident . clone ( ) ;
1104+ let mut config = Vec :: new ( ) ;
1105+ for ( index, variant) in data. variants . iter ( ) . enumerate ( ) {
1106+ let default_name = CString :: from_str ( & variant. ident . to_string ( ) )
1107+ . expect ( "the identifier contains a null character." ) ;
1108+ let default_val = index as i32 ;
1109+ let default_hidden = false ;
1110+ let mut name = None ;
1111+ let mut hidden = None ;
1112+ for attr in variant. attrs . iter ( ) {
1113+ let tokens = attr. meta . require_name_value ( ) ?. to_token_stream ( ) ;
1114+ let pair: GucEnumAttribute = syn:: parse2 ( tokens) ?;
1115+ match pair {
1116+ GucEnumAttribute :: Name ( value) => {
1117+ if name. replace ( value) . is_some ( ) {
1118+ return Err ( syn:: Error :: new ( ast. span ( ) , "too many #[name] attributes" ) ) ;
1119+ }
1120+ }
1121+ GucEnumAttribute :: Hidden ( value) => {
1122+ if hidden. replace ( value) . is_some ( ) {
1123+ return Err ( syn:: Error :: new ( ast. span ( ) , "too many #[hidden] attributes" ) ) ;
1124+ }
1125+ }
11151126 }
11161127 }
1117-
1118- build_array_body. extend ( quote ! {
1119- :: pgrx:: pgbox:: PgBox :: <_, :: pgrx:: pgbox:: AllocatedByPostgres >:: with( & mut slice[ #idx] , |v| {
1120- v. name = :: pgrx:: memcxt:: PgMemoryContexts :: TopMemoryContext . pstrdup( #label) ;
1121- v. val = #idx as i32 ;
1122- v. hidden = #hidden;
1123- } ) ;
1124- } ) ;
1128+ let ident = variant. ident . clone ( ) ;
1129+ let name = name. unwrap_or ( default_name) ;
1130+ let val = default_val;
1131+ let hidden = hidden. unwrap_or ( default_hidden) ;
1132+ config. push ( ( ident, name, val, hidden) ) ;
11251133 }
1126-
1127- stream. extend ( quote ! {
1128- unsafe impl :: pgrx:: guc:: GucEnum <#enum_name> for #enum_name {
1129- fn from_ordinal( ordinal: i32 ) -> #enum_name {
1134+ let config_idents = config. iter ( ) . map ( |x| & x. 0 ) . collect :: < Vec < _ > > ( ) ;
1135+ let config_names = config. iter ( ) . map ( |x| & x. 1 ) . collect :: < Vec < _ > > ( ) ;
1136+ let config_vals = config. iter ( ) . map ( |x| & x. 2 ) . collect :: < Vec < _ > > ( ) ;
1137+ let config_hiddens = config. iter ( ) . map ( |x| & x. 3 ) . collect :: < Vec < _ > > ( ) ;
1138+
1139+ Ok ( quote ! {
1140+ unsafe impl :: pgrx:: guc:: GucEnum for #ident {
1141+ fn from_ordinal( ordinal: i32 ) -> Self {
11301142 match ordinal {
1131- #from_match_arms
1143+ #( #config_vals => Self :: #config_idents, ) *
1144+ _ => panic!( "Unrecognized ordinal" ) ,
11321145 }
11331146 }
11341147
11351148 fn to_ordinal( & self ) -> i32 {
1136- match * self {
1137- #ordinal_match_arms
1149+ match self {
1150+ #( Self :: #config_idents => #config_vals , ) *
11381151 }
11391152 }
11401153
1141- fn config_matrix( ) -> * const :: pgrx:: pg_sys:: config_enum_entry {
1142- unsafe {
1143- let slice = :: pgrx:: memcxt:: PgMemoryContexts :: TopMemoryContext . palloc0_slice:: <:: pgrx:: pg_sys:: config_enum_entry>( #enum_len + 1usize ) ;
1144- #build_array_body
1145- slice. as_ptr( )
1146- }
1147- }
1154+ const CONFIG_ENUM_ENTRY : * const :: pgrx:: pg_sys:: config_enum_entry = [
1155+ #(
1156+ :: pgrx:: pg_sys:: config_enum_entry {
1157+ name: #config_names. as_ptr( ) ,
1158+ val: #config_vals,
1159+ hidden: #config_hiddens,
1160+ } ,
1161+ ) *
1162+ :: pgrx:: pg_sys:: config_enum_entry {
1163+ name: core:: ptr:: null( ) ,
1164+ val: 0 ,
1165+ hidden: false ,
1166+ } ,
1167+ ] . as_ptr( ) ;
11481168 }
1149- } ) ;
1150-
1151- Ok ( stream)
1169+ } )
11521170}
11531171
11541172#[ derive( Debug , Hash , Ord , PartialOrd , Eq , PartialEq ) ]
0 commit comments