From e74ad4d1de2ddd1d2c8b752674c37875c071aa6d Mon Sep 17 00:00:00 2001 From: Sage Griffin Date: Thu, 25 Jun 2026 10:40:56 -0600 Subject: [PATCH] Print more useful debug output Prior to this commit, we were deriving Debug, which would just print the address of any pointers. Since the vast majority of interesting fields on nodes are pointers, this wasn't especially useful. We now write a manual debug output, printing the result of our generated method calls as if they were the field values. We also skip fields like `parseloc` and `type_`, which are only useful to PG internally and aren't useful in consuming code. --- build.rs | 61 ++++++++++++++++++------ src/const_val.rs | 19 +++++--- src/nodes.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 171 insertions(+), 28 deletions(-) diff --git a/build.rs b/build.rs index c911225..60d8b0d 100644 --- a/build.rs +++ b/build.rs @@ -52,6 +52,7 @@ fn main() { .blocklist_type("JsonTablePlan") // Yes, we want doc comments .clang_arg("-fparse-all-comments") + .derive_debug(false) .generate() .unwrap() // SAFETY: YOLO @@ -213,12 +214,15 @@ fn generate_node_structs( let sname = &s.ident; let mut impl_: syn::ItemImpl = parse_quote!(impl #sname {}); + let mut debug_expr: syn::Expr = parse_quote!(f.debug_struct(stringify!(#sname))); for field in s.fields.iter_mut() { clean_doc_comments(&mut field.attrs); let fname = &field.ident; let fattrs = &field.attrs; + let debug_kind; + if field.ty == ty(parse_quote!(NodeTag)) || field.ty == ty(parse_quote!(Expr)) || field.ty == ty(parse_quote!(ValUnion)) @@ -235,7 +239,6 @@ fn generate_node_structs( field.ty = parse_quote!(*mut Node); } else if field.ty == ty(parse_quote!(Expr)) { field.ty = parse_quote!(NodeTag); - s.attrs.push(parse_quote!(#[derive(Debug)])); } if let syn::Type::Ptr(ty) = &field.ty @@ -249,9 +252,8 @@ fn generate_node_structs( unsafe { self.#fname.as_ref() } } }); - } - - if field.ty == ty(parse_quote!(*mut List)) { + debug_kind = DebugKind::Method; + } else if field.ty == ty(parse_quote!(*mut List)) { let return_ty: syn::Type; let mut list_expr: syn::Expr = parse_quote! { // SAFETY: The lifetime is not longer than self @@ -308,9 +310,8 @@ fn generate_node_structs( crate::util::to_flat_iter(#list_expr) } }); - } - - if field.ty == ty(parse_quote!(*mut Node)) { + debug_kind = DebugKind::List; + } else if field.ty == ty(parse_quote!(*mut Node)) { impl_.items.push(parse_quote! { #(#fattrs)* pub fn #fname(&self) -> crate::Node<'_> { @@ -318,9 +319,8 @@ fn generate_node_structs( unsafe { crate::Node::from_ptr(self.#fname) } } }); - } - - if is_c_string(&field.ty) { + debug_kind = DebugKind::Method; + } else if is_c_string(&field.ty) { impl_.items.push(parse_quote! { #(#fattrs)* pub fn #fname(&self) -> Option<&str> { @@ -335,10 +335,9 @@ fn generate_node_structs( ) } } - }) - } - - if field.ty == ty(parse_quote!(ValUnion)) { + }); + debug_kind = DebugKind::Method; + } else if field.ty == ty(parse_quote!(ValUnion)) { impl_.items.push(parse_quote! { #(#fattrs)* pub fn #fname(&self) -> Option> { @@ -348,10 +347,35 @@ fn generate_node_structs( Some(ConstValue(&self.val)) } } - }) + }); + debug_kind = DebugKind::Method; + } else if field.ty == parse_quote!(NodeTag) || field.ty == parse_quote!(ParseLoc) { + debug_kind = DebugKind::Skip; + } else { + debug_kind = DebugKind::Field; + } + + let debug_value: Option = match debug_kind { + DebugKind::Method => Some(parse_quote!(&self.#fname())), + DebugKind::List => Some(parse_quote!(&__DebugIterator(|| self.#fname()))), + DebugKind::Field => Some(parse_quote!(&self.#fname)), + DebugKind::Skip => None, + }; + + if let Some(debug_value) = debug_value { + debug_expr = parse_quote! { + #debug_expr.field(stringify!(#fname), #debug_value) + }; } } + out_file.items.push(parse_quote! { + impl fmt::Debug for #sname { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #debug_expr.finish_non_exhaustive() + } + } + }); out_file.items.push(s.into()); out_file.items.push(impl_.into()); } @@ -539,3 +563,10 @@ fn doc_comments<'a>( fn ty(ty: syn::Type) -> syn::Type { ty } + +enum DebugKind { + Method, + List, + Field, + Skip, +} diff --git a/src/const_val.rs b/src/const_val.rs index f2d0447..21f5967 100644 --- a/src/const_val.rs +++ b/src/const_val.rs @@ -56,13 +56,18 @@ impl ConstValue<'_> { impl fmt::Debug for ConstValue<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.tag() { - NodeTag_T_Integer => write!(f, "{:?}", self.numeric_value::().unwrap()), - NodeTag_T_Float => write!(f, "{:?}", self.numeric_value::().unwrap()), - NodeTag_T_String => write!(f, "{:?}", self.str_value().unwrap()), - NodeTag_T_Boolean => write!(f, "{:?}", self.bool_value().unwrap()), - _ => write!(f, "{{unknown type}}"), - } + f.debug_tuple("ConstValue") + // SAFETY: We're checking the tag + .field(unsafe { + match self.tag() { + NodeTag_T_Integer => self.0.ival.as_ref(), + NodeTag_T_Float => self.0.fval.as_ref(), + NodeTag_T_Boolean => self.0.boolval.as_ref(), + NodeTag_T_String => self.0.sval.as_ref(), + _ => return Ok(()), + } + }) + .finish() } } diff --git a/src/nodes.rs b/src/nodes.rs index 9b404f4..d30edff 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -12,12 +12,119 @@ impl Bitmapset { } } -impl fmt::Debug for A_Const { +struct __DebugIterator(F); + +impl fmt::Debug for __DebugIterator +where + F: Fn() -> I, + I: IntoIterator, + I::Item: fmt::Debug, +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("A_Const") - .field("val", &self.val()) - .field("isnull", &self.isnull) - .field("location", &self.location) - .finish_non_exhaustive() + f.debug_list().entries(self.0()).finish() } } + +#[test] +// If this test begins failing due to a change in the standard library's +// formatter, update this test as long as the output continues to look +// reasonable +fn test_debug_output() { + let result = crate::parse("SELECT * FROM users WHERE id = 1").unwrap(); + let stmt = result.stmts().next().unwrap(); + let expected = r#"SelectStmt( + SelectStmt { + distinctClause: [], + intoClause: None, + targetList: [ + ResTarget { + name: None, + indirection: [], + val: ColumnRef( + ColumnRef { + fields: [ + A_Star( + A_Star { .. }, + ), + ], + .. + }, + ), + .. + }, + ], + fromClause: [ + RangeVar( + RangeVar { + catalogname: None, + schemaname: None, + relname: Some( + "users", + ), + inh: true, + relpersistence: 112, + alias: None, + .. + }, + ), + ], + whereClause: A_Expr( + A_Expr { + kind: 0, + name: [ + String( + String { + sval: Some( + "=", + ), + .. + }, + ), + ], + lexpr: ColumnRef( + ColumnRef { + fields: [ + String( + String { + sval: Some( + "id", + ), + .. + }, + ), + ], + .. + }, + ), + rexpr: A_Const( + A_Const { + val: Some( + 1, + ), + isnull: false, + .. + }, + ), + .. + }, + ), + groupClause: [], + groupDistinct: false, + havingClause: None, + windowClause: [], + valuesLists: [], + sortClause: [], + limitOffset: None, + limitCount: None, + limitOption: 0, + lockingClause: [], + withClause: None, + op: 0, + all: false, + larg: None, + rarg: None, + .. + }, +)"#; + assert_eq!(expected, format!("{:#?}", stmt)); +}