diff --git a/compiler/noirc_printable_type/src/lib.rs b/compiler/noirc_printable_type/src/lib.rs index fff0dbb017d..1fa1aef38fa 100644 --- a/compiler/noirc_printable_type/src/lib.rs +++ b/compiler/noirc_printable_type/src/lib.rs @@ -112,6 +112,7 @@ pub enum PrintableValue { FmtString(String, Vec>), Vec { array_elements: Vec>, is_slice: bool }, Struct(BTreeMap>), + Enum { tag: usize, elements: Vec> }, Other, } @@ -138,13 +139,22 @@ impl std::fmt::Display for PrintableValueDisplay { } } +/// Format a given [PrintableValue] according to an expected [PrintableType]. +/// +/// Returns `None` if the value is not what we expect based on the type. fn to_string(value: &PrintableValue, typ: &PrintableType) -> Option { let mut output = String::new(); - match (value, typ) { - (PrintableValue::Field(f), PrintableType::Field) => { + match typ { + PrintableType::Field => { + let PrintableValue::Field(f) = value else { + return None; + }; output.push_str(&f.to_short_hex()); } - (PrintableValue::Field(f), PrintableType::UnsignedInteger { width }) => { + PrintableType::UnsignedInteger { width } => { + let PrintableValue::Field(f) = value else { + return None; + }; // Retain the lower 'width' bits debug_assert!( *width <= 128, @@ -157,7 +167,10 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op output.push_str(&uint_cast.to_string()); } - (PrintableValue::Field(f), PrintableType::SignedInteger { width }) => { + PrintableType::SignedInteger { width } => { + let PrintableValue::Field(f) = value else { + return None; + }; let mut uint = f.to_u128(); // Interpret as uint // Extract sign relative to width of input @@ -168,24 +181,33 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op output.push_str(&uint.to_string()); } - (PrintableValue::Field(f), PrintableType::Boolean) => { + PrintableType::Boolean => { + let PrintableValue::Field(f) = value else { + return None; + }; if f.is_one() { output.push_str("true"); } else { output.push_str("false"); } } - (PrintableValue::Field(_), PrintableType::Function { .. }) => { + PrintableType::Function { .. } => { + let PrintableValue::Field(_) = value else { + return None; + }; output.push_str(&format!("<<{typ}>>")); } - (_, PrintableType::Reference { mutable: false, .. }) => { - output.push_str("<>"); - } - (_, PrintableType::Reference { mutable: true, .. }) => { - output.push_str("<>"); + PrintableType::Reference { mutable, .. } => { + if *mutable { + output.push_str("<>"); + } else { + output.push_str("<>"); + } } - (PrintableValue::Vec { array_elements, is_slice }, PrintableType::Array { typ, .. }) - | (PrintableValue::Vec { array_elements, is_slice }, PrintableType::Slice { typ }) => { + PrintableType::Array { typ, .. } | PrintableType::Slice { typ } => { + let PrintableValue::Vec { array_elements, is_slice } = value else { + return None; + }; if *is_slice { output.push('&'); } @@ -202,12 +224,16 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op } output.push(']'); } - - (PrintableValue::String(s), PrintableType::String { .. }) => { + PrintableType::String { .. } => { + let PrintableValue::String(s) = value else { + return None; + }; output.push_str(s); } - - (PrintableValue::FmtString(template, values), PrintableType::FmtString { typ, .. }) => { + PrintableType::FmtString { typ, .. } => { + let PrintableValue::FmtString(template, values) = value else { + return None; + }; let PrintableType::Tuple { types } = typ.as_ref() else { panic!("Expected type to be a Tuple for FmtString"); }; @@ -215,8 +241,10 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op let args = values.iter().cloned().zip(types.iter().cloned()).collect::>(); output.push_str(&PrintableValueDisplay::FmtString(template, args).to_string()); } - - (PrintableValue::Struct(map), PrintableType::Struct { name, fields, .. }) => { + PrintableType::Struct { name, fields, .. } => { + let PrintableValue::Struct(map) = value else { + return None; + }; output.push_str(&format!("{name} {{ ")); let mut fields = fields.iter().peekable(); @@ -233,8 +261,10 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op output.push_str(" }"); } - - (PrintableValue::Vec { array_elements, .. }, PrintableType::Tuple { types }) => { + PrintableType::Tuple { types } => { + let PrintableValue::Vec { array_elements, .. } = value else { + return None; + }; output.push('('); let mut elements = array_elements.iter().zip(types).peekable(); while let Some((value, typ)) = elements.next() { @@ -250,11 +280,33 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op } output.push(')'); } - - (_, PrintableType::Unit) => output.push_str("()"), - - _ => return None, - }; + PrintableType::Unit => { + output.push_str("()"); + } + PrintableType::Enum { name, variants } => { + let PrintableValue::Enum { tag, elements } = value else { + return None; + }; + let (variant_name, types) = &variants[*tag]; + let has_fields = !elements.is_empty(); + output.push_str(&format!("{name}::{variant_name}")); + if has_fields { + output.push('('); + } + let mut elements = elements.iter().zip(types).peekable(); + while let Some((value, typ)) = elements.next() { + output.push_str( + &PrintableValueDisplay::Plain(value.clone(), typ.clone()).to_string(), + ); + if elements.peek().is_some() { + output.push_str(", "); + } + } + if has_fields { + output.push(')'); + } + } + } Some(output) } @@ -418,15 +470,24 @@ pub fn decode_printable_value( PrintableType::Unit => PrintableValue::Field(F::zero()), PrintableType::Enum { name: _, variants } => { let tag = field_iterator.next().expect("not enough data: expected enum tag"); - let tag_value = tag.to_u128() as usize; - - let (_name, variant_types) = &variants[tag_value]; - PrintableValue::Vec { - array_elements: vecmap(variant_types, |typ| { - decode_printable_value(field_iterator, typ) - }), - is_slice: false, + let tag = tag.to_u128() as usize; + // A serialized enum looks as follows: + // [tag, variant0.field0, ..., variant0.fieldN, variant1.field0, ..., variant1.fieldM, ...] + // So the number of fields are always the same, and we have to consume all of them + // to make sure the next item will resume parsing from the right index; + // the tag tells us which ones are non-default values. + + // Striving to keep only the non-default values in memory. + let mut elements = Vec::with_capacity(variants[tag].1.len()); + for (i, (_, types)) in variants.iter().enumerate() { + for typ in types { + let value = decode_printable_value(field_iterator, typ); + if i == tag { + elements.push(value); + } + } } + PrintableValue::Enum { tag, elements } } } } diff --git a/compiler/wasm/test/compiler/node/compile.test.ts b/compiler/wasm/test/compiler/node/compile.test.ts index 3a764d13386..9c6d394a3ce 100644 --- a/compiler/wasm/test/compiler/node/compile.test.ts +++ b/compiler/wasm/test/compiler/node/compile.test.ts @@ -99,6 +99,7 @@ describe('noir-compiler/node', () => { 'regression_7323', 'workspace', 'workspace_default_member', + 'regression_9294', // TODO: Requires the 'enums' unstable feature. ]; getSubdirs(join(testProgramsDir, 'execution_success')) .filter((name) => !filteredExecutionSuccessTests.includes(name)) diff --git a/test_programs/execution_success/regression_9294/Nargo.toml b/test_programs/execution_success/regression_9294/Nargo.toml new file mode 100644 index 00000000000..fc240f3988f --- /dev/null +++ b/test_programs/execution_success/regression_9294/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_9294" +type = "bin" +authors = [""] +compiler_unstable_features = ["enums"] + +[dependencies] diff --git a/test_programs/execution_success/regression_9294/src/main.nr b/test_programs/execution_success/regression_9294/src/main.nr new file mode 100644 index 00000000000..fd337fbc8bb --- /dev/null +++ b/test_programs/execution_success/regression_9294/src/main.nr @@ -0,0 +1,11 @@ +enum Foo { + Zero, + One(Field), + Two(u32, u64), +} + +fn main() { + let enums: [Foo; 3] = [Foo::Zero, Foo::One(10), Foo::Two(20, 30)]; + println((enums, 40)); + println(f"Hello {enums}!") +} diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/regression_9294/execute__tests__expanded.snap b/tooling/nargo_cli/tests/snapshots/execution_success/regression_9294/execute__tests__expanded.snap new file mode 100644 index 00000000000..35b827b8aff --- /dev/null +++ b/tooling/nargo_cli/tests/snapshots/execution_success/regression_9294/execute__tests__expanded.snap @@ -0,0 +1,15 @@ +--- +source: tooling/nargo_cli/tests/execute.rs +expression: expanded_code +--- +enum Foo { + Zero, + One(Field), + Two(u32, u64), +} + +fn main() { + let enums: [Foo; 3] = [Foo::Zero, Foo::One(10_Field), Foo::Two(20_u32, 30_u64)]; + println((enums, 40_Field)); + println(f"Hello {enums}!") +} diff --git a/tooling/nargo_cli/tests/snapshots/execution_success/regression_9294/execute__tests__stdout.snap b/tooling/nargo_cli/tests/snapshots/execution_success/regression_9294/execute__tests__stdout.snap new file mode 100644 index 00000000000..0c857f8da6f --- /dev/null +++ b/tooling/nargo_cli/tests/snapshots/execution_success/regression_9294/execute__tests__stdout.snap @@ -0,0 +1,6 @@ +--- +source: tooling/nargo_cli/tests/execute.rs +expression: stdout +--- +([Foo::Zero, Foo::One(0x0a), Foo::Two(20, 30)], 0x28) +Hello [Foo::Zero, Foo::One(0x0a), Foo::Two(20, 30)]!