From 9d66fcc54893388cc21543717ad634432eb1db6b Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 1 Oct 2024 00:05:39 -0700 Subject: [PATCH] improve ExportOptions support in `assert_export_firrtl!` --- crates/fayalite/src/firrtl.rs | 174 ++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 19 deletions(-) diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index 79db858..e1c9c5e 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -2378,15 +2378,17 @@ impl FileBackendTrait for FileBackend { } #[doc(hidden)] -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub struct TestBackendPrivate { pub module_var_name: &'static str, + pub included_fields: &'static [&'static str], } impl Default for TestBackendPrivate { fn default() -> Self { Self { module_var_name: "m", + included_fields: &[], } } } @@ -2407,7 +2409,11 @@ impl fmt::Debug for TestBackend { files, error_after, options, - __private: TestBackendPrivate { module_var_name }, + __private: + TestBackendPrivate { + module_var_name, + included_fields, + }, } = self; writeln!( f, @@ -2415,14 +2421,43 @@ impl fmt::Debug for TestBackend { )?; writeln!(f, " assert_export_firrtl! {{")?; writeln!(f, " {module_var_name} =>")?; - for (file, content) in files { - writeln!(f, " {file:?}: {:?},", DebugAsRawString(content))?; - } - if *error_after != Option::default() { + if *error_after != Option::default() || included_fields.contains(&"error_after") { writeln!(f, " error_after: {error_after:?},")?; } - if *options != ExportOptions::default() { - writeln!(f, " options: {options:?},")?; + if *options != ExportOptions::default() || included_fields.contains(&"options") { + struct DebugWithForceIncludeFields<'a> { + options: ExportOptions, + included_fields: &'a [&'a str], + } + impl fmt::Debug for DebugWithForceIncludeFields<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.options.debug_fmt(f, |field| { + self.included_fields.iter().any(|included_field| { + if let Some(("options", suffix)) = included_field.split_once(".") { + suffix == field + } else { + false + } + }) + }) + } + } + let options_str = format!( + "{:#?}", + DebugWithForceIncludeFields { + options: *options, + included_fields + } + ); + let mut sep = " options: "; + for line in options_str.lines() { + write!(f, "{sep}{line}")?; + sep = "\n "; + } + writeln!(f, ",")?; + } + for (file, content) in files { + writeln!(f, " {file:?}: {:?},", DebugAsRawString(content))?; } write!(f, " }};") } @@ -2507,6 +2542,7 @@ fn export_impl( let ExportOptions { simplify_memories: do_simplify_memories, simplify_enums: do_simplify_enums, + __private: _, } = options; if let Some(kind) = do_simplify_enums { top_module = @@ -2573,30 +2609,71 @@ impl clap::builder::TypedValueParser for OptionSimplifyEnumsKindValueParser { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct ExportOptionsPrivate(()); + #[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] pub struct ExportOptions { #[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)] pub simplify_memories: bool, #[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = OptionSimplifyEnumsKindValueParser::NONE_NAME)] pub simplify_enums: std::option::Option, + #[doc(hidden)] + #[clap(skip = ExportOptionsPrivate(()))] + /// `#[non_exhaustive]` except allowing struct update syntax + pub __private: ExportOptionsPrivate, } impl fmt::Debug for ExportOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt(f, |_| false) + } +} + +impl ExportOptions { + fn debug_fmt( + &self, + f: &mut fmt::Formatter<'_>, + force_include_field: impl Fn(&str) -> bool, + ) -> fmt::Result { + let Self { + simplify_memories, + simplify_enums, + __private: _, + } = *self; f.write_str("ExportOptions {")?; - if f.alternate() { - f.write_str("\n")?; - } let mut sep = if f.alternate() { "\n " } else { " " }; let comma_sep = if f.alternate() { ",\n " } else { ", " }; let default = ExportOptions::default(); - if self.simplify_memories != default.simplify_memories { - write!(f, "{sep}simplify_memories: {:?}", self.simplify_memories)?; + if simplify_memories != default.simplify_memories + || force_include_field("simplify_memories") + { + write!(f, "{sep}simplify_memories: {:?}", simplify_memories)?; sep = comma_sep; } - if self.simplify_enums != default.simplify_enums { - write!(f, "{sep}simplify_enums: {:?}", self.simplify_enums)?; + if simplify_enums != default.simplify_enums || force_include_field("simplify_enums") { + write!(f, "{sep}simplify_enums: ")?; + macro_rules! debug_cases { + ($($ident:ident $(($($args:tt)*))?,)*) => { + match simplify_enums { + // use more complex stringify to avoid the compiler inserting spaces + $($ident $(($($args)*))? => { + f.write_str(concat!( + stringify!($ident), + $("(", + $(stringify!($args),)* + ")")? + ))?; + })* + } + }; + } + debug_cases! { + Some(SimplifyEnumsKind::SimplifyToEnumsWithNoBody), + Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), + Some(SimplifyEnumsKind::ReplaceWithUInt), + None, + } sep = comma_sep; } write!( @@ -2612,6 +2689,7 @@ impl Default for ExportOptions { Self { simplify_memories: true, simplify_enums: None, + __private: ExportOptionsPrivate(()), } } } @@ -2630,7 +2708,17 @@ pub fn export( #[doc(hidden)] #[track_caller] pub fn assert_export_firrtl_impl(top_module: &Module, expected: TestBackend) { - let result = export(TestBackend::default(), top_module, expected.options).unwrap(); + let result = export( + TestBackend { + files: BTreeMap::default(), + error_after: expected.error_after, + options: expected.options, + __private: expected.__private, + }, + top_module, + expected.options, + ) + .unwrap(); if result != expected { panic!( "assert_export_firrtl failed:\nyou can update the expected output by using:\n-------START-------\n{result:?}\n-------END-------" @@ -2647,21 +2735,69 @@ pub fn make_test_expected_files(v: &[(&str, &str)]) -> BTreeMap macro_rules! assert_export_firrtl { { $m:ident => - $($file_name:literal: $file_contents:literal,)* $($field:ident: $value:expr,)* + @parsed_fields($($field_strings:expr,)*) + $($file_name:literal: $file_contents:literal,)* } => { $crate::firrtl::assert_export_firrtl_impl( &$m, $crate::firrtl::TestBackend { + $($field: $value,)* files: $crate::firrtl::make_test_expected_files(&[ $(($file_name, $file_contents),)* ]), - $($field: $value,)* __private: $crate::firrtl::TestBackendPrivate { module_var_name: stringify!($m), + included_fields: &[$($field_strings,)*], }, ..<$crate::firrtl::TestBackend as $crate::__std::default::Default>::default() }, ); }; + { + $m:ident => + $($parsed_fields:ident: $parsed_field_values:expr,)* + @parsed_fields($($field_strings:expr,)*) + options: ExportOptions { + $($export_option_fields:ident: $parsed_export_option_field_values:expr,)* + ..$export_option_default:expr + }, + $($rest:tt)* + } => { + $crate::assert_export_firrtl!( + $m => + $($parsed_fields: $parsed_field_values,)* + options: ExportOptions { + $($export_option_fields: $parsed_export_option_field_values,)* + ..$export_option_default + }, + @parsed_fields($($field_strings,)* "options", $(concat!("options.", stringify!($export_option_fields)),)*) + $($rest)* + ); + }; + { + $m:ident => + $($parsed_fields:ident: $parsed_field_values:expr,)* + @parsed_fields($($field_strings:expr,)*) + $field:ident: $field_value:expr, + $($rest:tt)* + } => { + $crate::assert_export_firrtl!( + $m => + $($parsed_fields: $parsed_field_values,)* + $field: $field_value, + @parsed_fields($($field_strings,)* stringify!($field),) + $($rest)* + ); + }; + { + $m:ident => + $($rest:tt)* + } => { + $crate::assert_export_firrtl!( + $m => + @parsed_fields() + $($rest)* + ); + }; }