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)); +}