diff --git a/Cargo.lock b/Cargo.lock index 2f19ed704b2..d63e1379fa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2763,6 +2763,7 @@ dependencies = [ "rand 0.8.5", "rayon", "serde", + "serde_json", "thiserror", "tracing", "walkdir", @@ -3194,12 +3195,8 @@ name = "noirc_printable_type" version = "1.0.0-beta.0" dependencies = [ "acvm", - "iter-extended", - "jsonrpc", "regex", "serde", - "serde_json", - "thiserror", ] [[package]] diff --git a/compiler/noirc_printable_type/Cargo.toml b/compiler/noirc_printable_type/Cargo.toml index 8bb56703e8a..e011d641f8c 100644 --- a/compiler/noirc_printable_type/Cargo.toml +++ b/compiler/noirc_printable_type/Cargo.toml @@ -13,11 +13,7 @@ workspace = true [dependencies] acvm.workspace = true -iter-extended.workspace = true regex = "1.9.1" serde.workspace = true -serde_json.workspace = true -thiserror.workspace = true -jsonrpc.workspace = true [dev-dependencies] diff --git a/compiler/noirc_printable_type/src/lib.rs b/compiler/noirc_printable_type/src/lib.rs index 838a2472125..bde06a7e901 100644 --- a/compiler/noirc_printable_type/src/lib.rs +++ b/compiler/noirc_printable_type/src/lib.rs @@ -1,10 +1,13 @@ +#![forbid(unsafe_code)] +#![warn(unused_crate_dependencies, unused_extern_crates)] +#![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] + use std::{collections::BTreeMap, str}; -use acvm::{acir::AcirField, brillig_vm::brillig::ForeignCallParam}; -use iter_extended::vecmap; +use acvm::AcirField; use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; -use thiserror::Error; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "lowercase")] @@ -67,95 +70,28 @@ pub enum PrintableValueDisplay { FmtString(String, Vec<(PrintableValue, PrintableType)>), } -#[derive(Debug, Error)] -pub enum ForeignCallError { - #[error("No handler could be found for foreign call `{0}`")] - NoHandler(String), - - #[error("Foreign call inputs needed for execution are missing")] - MissingForeignCallInputs, - - #[error("Could not parse PrintableType argument. {0}")] - ParsingError(#[from] serde_json::Error), - - #[error("Failed calling external resolver. {0}")] - ExternalResolverError(#[from] jsonrpc::Error), - - #[error("Assert message resolved after an unsatisified constrain. {0}")] - ResolvedAssertMessage(String), -} - -impl TryFrom<&[ForeignCallParam]> for PrintableValueDisplay { - type Error = ForeignCallError; +impl std::fmt::Display for PrintableValueDisplay { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Plain(value, typ) => { + let output_string = to_string(value, typ).ok_or(std::fmt::Error)?; + write!(fmt, "{output_string}") + } + Self::FmtString(template, values) => { + let mut display_iter = values.iter(); + let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}").map_err(|_| std::fmt::Error)?; - fn try_from(foreign_call_inputs: &[ForeignCallParam]) -> Result { - let (is_fmt_str, foreign_call_inputs) = - foreign_call_inputs.split_last().ok_or(ForeignCallError::MissingForeignCallInputs)?; + let formatted_str = replace_all(&re, template, |_: &Captures| { + let (value, typ) = display_iter.next().ok_or(std::fmt::Error)?; + to_string(value, typ).ok_or(std::fmt::Error) + })?; - if is_fmt_str.unwrap_field().is_one() { - convert_fmt_string_inputs(foreign_call_inputs) - } else { - convert_string_inputs(foreign_call_inputs) + write!(fmt, "{formatted_str}") + } } } } -fn convert_string_inputs( - foreign_call_inputs: &[ForeignCallParam], -) -> Result, ForeignCallError> { - // Fetch the PrintableType from the foreign call input - // The remaining input values should hold what is to be printed - let (printable_type_as_values, input_values) = - foreign_call_inputs.split_last().ok_or(ForeignCallError::MissingForeignCallInputs)?; - let printable_type = fetch_printable_type(printable_type_as_values)?; - - // We must use a flat map here as each value in a struct will be in a separate input value - let mut input_values_as_fields = input_values.iter().flat_map(|param| param.fields()); - - let value = decode_value(&mut input_values_as_fields, &printable_type); - - Ok(PrintableValueDisplay::Plain(value, printable_type)) -} - -fn convert_fmt_string_inputs( - foreign_call_inputs: &[ForeignCallParam], -) -> Result, ForeignCallError> { - let (message, input_and_printable_types) = - foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?; - - let message_as_fields = message.fields(); - let message_as_string = decode_string_value(&message_as_fields); - - let (num_values, input_and_printable_types) = input_and_printable_types - .split_first() - .ok_or(ForeignCallError::MissingForeignCallInputs)?; - - let mut output = Vec::new(); - let num_values = num_values.unwrap_field().to_u128() as usize; - - let types_start_at = input_and_printable_types.len() - num_values; - let mut input_iter = - input_and_printable_types[0..types_start_at].iter().flat_map(|param| param.fields()); - for printable_type in input_and_printable_types.iter().skip(types_start_at) { - let printable_type = fetch_printable_type(printable_type)?; - let value = decode_value(&mut input_iter, &printable_type); - - output.push((value, printable_type)); - } - - Ok(PrintableValueDisplay::FmtString(message_as_string, output)) -} - -fn fetch_printable_type( - printable_type: &ForeignCallParam, -) -> Result { - let printable_type_as_fields = printable_type.fields(); - let printable_type_as_string = decode_string_value(&printable_type_as_fields); - let printable_type: PrintableType = serde_json::from_str(&printable_type_as_string)?; - - Ok(printable_type) -} - fn to_string(value: &PrintableValue, typ: &PrintableType) -> Option { let mut output = String::new(); match (value, typ) { @@ -193,7 +129,7 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op (PrintableValue::Vec { array_elements, is_slice }, PrintableType::Array { typ, .. }) | (PrintableValue::Vec { array_elements, is_slice }, PrintableType::Slice { typ }) => { if *is_slice { - output.push('&') + output.push('&'); } output.push('['); let mut values = array_elements.iter().peekable(); @@ -253,46 +189,6 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Op Some(output) } -// Taken from Regex docs directly -fn replace_all( - re: &Regex, - haystack: &str, - mut replacement: impl FnMut(&Captures) -> Result, -) -> Result { - let mut new = String::with_capacity(haystack.len()); - let mut last_match = 0; - for caps in re.captures_iter(haystack) { - let m = caps.get(0).unwrap(); - new.push_str(&haystack[last_match..m.start()]); - new.push_str(&replacement(&caps)?); - last_match = m.end(); - } - new.push_str(&haystack[last_match..]); - Ok(new) -} - -impl std::fmt::Display for PrintableValueDisplay { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Plain(value, typ) => { - let output_string = to_string(value, typ).ok_or(std::fmt::Error)?; - write!(fmt, "{output_string}") - } - Self::FmtString(template, values) => { - let mut display_iter = values.iter(); - let re = Regex::new(r"\{([a-zA-Z0-9_]+)\}").map_err(|_| std::fmt::Error)?; - - let formatted_str = replace_all(&re, template, |_: &Captures| { - let (value, typ) = display_iter.next().ok_or(std::fmt::Error)?; - to_string(value, typ).ok_or(std::fmt::Error) - })?; - - write!(fmt, "{formatted_str}") - } - } - } -} - /// This trims any leading zeroes. /// A singular '0' will be prepended as well if the trimmed string has an odd length. /// A hex string's length needs to be even to decode into bytes, as two digits correspond to @@ -308,85 +204,20 @@ fn format_field_string(field: F) -> String { "0x".to_owned() + &trimmed_field } -/// Assumes that `field_iterator` contains enough field elements in order to decode the [PrintableType] -pub fn decode_value( - field_iterator: &mut impl Iterator, - typ: &PrintableType, -) -> PrintableValue { - match typ { - PrintableType::Field - | PrintableType::SignedInteger { .. } - | PrintableType::UnsignedInteger { .. } - | PrintableType::Boolean => { - let field_element = field_iterator.next().unwrap(); - - PrintableValue::Field(field_element) - } - PrintableType::Array { length, typ } => { - let length = *length as usize; - let mut array_elements = Vec::with_capacity(length); - for _ in 0..length { - array_elements.push(decode_value(field_iterator, typ)); - } - - PrintableValue::Vec { array_elements, is_slice: false } - } - PrintableType::Slice { typ } => { - let length = field_iterator - .next() - .expect("not enough data to decode variable array length") - .to_u128() as usize; - let mut array_elements = Vec::with_capacity(length); - for _ in 0..length { - array_elements.push(decode_value(field_iterator, typ)); - } - - PrintableValue::Vec { array_elements, is_slice: true } - } - PrintableType::Tuple { types } => PrintableValue::Vec { - array_elements: vecmap(types, |typ| decode_value(field_iterator, typ)), - is_slice: false, - }, - PrintableType::String { length } => { - let field_elements: Vec = field_iterator.take(*length as usize).collect(); - - PrintableValue::String(decode_string_value(&field_elements)) - } - PrintableType::Struct { fields, .. } => { - let mut struct_map = BTreeMap::new(); - - for (field_key, param_type) in fields { - let field_value = decode_value(field_iterator, param_type); - - struct_map.insert(field_key.to_owned(), field_value); - } - - PrintableValue::Struct(struct_map) - } - PrintableType::Function { env, .. } => { - let field_element = field_iterator.next().unwrap(); - let func_ref = PrintableValue::Field(field_element); - // we want to consume the fields from the environment, but for now they are not actually printed - decode_value(field_iterator, env); - func_ref - } - PrintableType::MutableReference { typ } => { - // we decode the reference, but it's not really used for printing - decode_value(field_iterator, typ) - } - PrintableType::Unit => PrintableValue::Field(F::zero()), +// Taken from Regex docs directly +fn replace_all( + re: &Regex, + haystack: &str, + mut replacement: impl FnMut(&Captures) -> Result, +) -> Result { + let mut new = String::with_capacity(haystack.len()); + let mut last_match = 0; + for caps in re.captures_iter(haystack) { + let m = caps.get(0).unwrap(); + new.push_str(&haystack[last_match..m.start()]); + new.push_str(&replacement(&caps)?); + last_match = m.end(); } -} - -pub fn decode_string_value(field_elements: &[F]) -> String { - // TODO: Replace with `into` when Char is supported - let string_as_slice = vecmap(field_elements, |e| { - let mut field_as_bytes = e.to_be_bytes(); - let char_byte = field_as_bytes.pop().unwrap(); // A character in a string is represented by a u8, thus we just want the last byte of the element - assert!(field_as_bytes.into_iter().all(|b| b == 0)); // Assert that the rest of the field element's bytes are empty - char_byte - }); - - let final_string = str::from_utf8(&string_as_slice).unwrap(); - final_string.to_owned() + new.push_str(&haystack[last_match..]); + Ok(new) } diff --git a/tooling/debugger/src/foreign_calls.rs b/tooling/debugger/src/foreign_calls.rs index ecf27a22f29..886e8508886 100644 --- a/tooling/debugger/src/foreign_calls.rs +++ b/tooling/debugger/src/foreign_calls.rs @@ -3,10 +3,9 @@ use acvm::{ pwg::ForeignCallWaitInfo, AcirField, FieldElement, }; -use nargo::foreign_calls::{DefaultForeignCallExecutor, ForeignCallExecutor}; +use nargo::foreign_calls::{DefaultForeignCallExecutor, ForeignCallError, ForeignCallExecutor}; use noirc_artifacts::debug::{DebugArtifact, DebugVars, StackFrame}; use noirc_errors::debug_info::{DebugFnId, DebugVarId}; -use noirc_printable_type::ForeignCallError; pub(crate) enum DebugForeignCall { VarAssign, diff --git a/tooling/nargo/Cargo.toml b/tooling/nargo/Cargo.toml index 1dbb9978b0b..a8e0d3ceea6 100644 --- a/tooling/nargo/Cargo.toml +++ b/tooling/nargo/Cargo.toml @@ -27,6 +27,7 @@ rayon.workspace = true jsonrpc.workspace = true rand.workspace = true serde.workspace = true +serde_json.workspace = true walkdir = "2.5.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index 5256f28e36c..00c411bf7e4 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -16,9 +16,10 @@ use noirc_errors::{ pub use noirc_errors::Location; use noirc_driver::CrateName; -use noirc_printable_type::ForeignCallError; use thiserror::Error; +use crate::foreign_calls::ForeignCallError; + /// Errors covering situations where a package cannot be compiled. #[derive(Debug, Error)] pub enum CompileError { diff --git a/tooling/nargo/src/foreign_calls/mocker.rs b/tooling/nargo/src/foreign_calls/mocker.rs index c93d16bbaf6..9fbff649d42 100644 --- a/tooling/nargo/src/foreign_calls/mocker.rs +++ b/tooling/nargo/src/foreign_calls/mocker.rs @@ -3,10 +3,9 @@ use acvm::{ pwg::ForeignCallWaitInfo, AcirField, }; -use noirc_printable_type::{decode_string_value, ForeignCallError}; use serde::{Deserialize, Serialize}; -use super::{ForeignCall, ForeignCallExecutor}; +use super::{ForeignCall, ForeignCallError, ForeignCallExecutor}; /// This struct represents an oracle mock. It can be used for testing programs that use oracles. #[derive(Debug, PartialEq, Eq, Clone)] @@ -74,7 +73,7 @@ impl MockForeignCallExecutor { fn parse_string(param: &ForeignCallParam) -> String { let fields: Vec<_> = param.fields().to_vec(); - decode_string_value(&fields) + noirc_abi::decode_string_value(&fields) } } diff --git a/tooling/nargo/src/foreign_calls/mod.rs b/tooling/nargo/src/foreign_calls/mod.rs index 16ed71e11e3..63d57dc3170 100644 --- a/tooling/nargo/src/foreign_calls/mod.rs +++ b/tooling/nargo/src/foreign_calls/mod.rs @@ -2,11 +2,11 @@ use std::path::PathBuf; use acvm::{acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo, AcirField}; use mocker::MockForeignCallExecutor; -use noirc_printable_type::ForeignCallError; use print::PrintForeignCallExecutor; use rand::Rng; use rpc::RPCForeignCallExecutor; use serde::{Deserialize, Serialize}; +use thiserror::Error; pub(crate) mod mocker; pub(crate) mod print; @@ -64,6 +64,24 @@ impl ForeignCall { } } +#[derive(Debug, Error)] +pub enum ForeignCallError { + #[error("No handler could be found for foreign call `{0}`")] + NoHandler(String), + + #[error("Foreign call inputs needed for execution are missing")] + MissingForeignCallInputs, + + #[error("Could not parse PrintableType argument. {0}")] + ParsingError(#[from] serde_json::Error), + + #[error("Failed calling external resolver. {0}")] + ExternalResolverError(#[from] jsonrpc::Error), + + #[error("Assert message resolved after an unsatisified constrain. {0}")] + ResolvedAssertMessage(String), +} + #[derive(Debug, Default)] pub struct DefaultForeignCallExecutor { /// The executor for any [`ForeignCall::Print`] calls. diff --git a/tooling/nargo/src/foreign_calls/print.rs b/tooling/nargo/src/foreign_calls/print.rs index 92fcd65ae28..42ec85d0760 100644 --- a/tooling/nargo/src/foreign_calls/print.rs +++ b/tooling/nargo/src/foreign_calls/print.rs @@ -1,7 +1,13 @@ -use acvm::{acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo, AcirField}; -use noirc_printable_type::{ForeignCallError, PrintableValueDisplay}; +use acvm::{ + acir::brillig::{ForeignCallParam, ForeignCallResult}, + pwg::ForeignCallWaitInfo, + AcirField, +}; +use noirc_printable_type::{PrintableType, PrintableValueDisplay}; -use super::{ForeignCall, ForeignCallExecutor}; +use noirc_abi::{decode_printable_value as decode_value, decode_string_value}; + +use super::{ForeignCall, ForeignCallError, ForeignCallExecutor}; #[derive(Debug, Default)] pub(crate) struct PrintForeignCallExecutor; @@ -15,18 +21,16 @@ impl ForeignCallExecutor for PrintForeignCallExecutor { match ForeignCall::lookup(foreign_call_name) { Some(ForeignCall::Print) => { let skip_newline = foreign_call.inputs[0].unwrap_field().is_zero(); + let is_fmt_str = foreign_call.inputs[1].unwrap_field().is_one(); - let foreign_call_inputs = foreign_call - .inputs - .split_first() - .ok_or(ForeignCallError::MissingForeignCallInputs)? - .1; - - let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; - let display_string = - format!("{display_values}{}", if skip_newline { "" } else { "\n" }); + let foreign_call_inputs = &foreign_call.inputs[2..]; + let display_values = if is_fmt_str { + convert_fmt_string_inputs(foreign_call_inputs)? + } else { + convert_string_inputs(foreign_call_inputs)? + }; - print!("{display_string}"); + print!("{display_values}{}", if skip_newline { "" } else { "\n" }); Ok(ForeignCallResult::default()) } @@ -34,3 +38,59 @@ impl ForeignCallExecutor for PrintForeignCallExecutor { } } } + +fn convert_string_inputs( + foreign_call_inputs: &[ForeignCallParam], +) -> Result, ForeignCallError> { + // Fetch the PrintableType from the foreign call input + // The remaining input values should hold what is to be printed + let (printable_type_as_values, input_values) = + foreign_call_inputs.split_last().ok_or(ForeignCallError::MissingForeignCallInputs)?; + let printable_type = fetch_printable_type(printable_type_as_values)?; + + // We must use a flat map here as each value in a struct will be in a separate input value + let mut input_values_as_fields = input_values.iter().flat_map(|param| param.fields()); + + let value = decode_value(&mut input_values_as_fields, &printable_type); + + Ok(PrintableValueDisplay::Plain(value, printable_type)) +} + +fn convert_fmt_string_inputs( + foreign_call_inputs: &[ForeignCallParam], +) -> Result, ForeignCallError> { + let (message, input_and_printable_types) = + foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?; + + let message_as_fields = message.fields(); + let message_as_string = decode_string_value(&message_as_fields); + + let (num_values, input_and_printable_types) = input_and_printable_types + .split_first() + .ok_or(ForeignCallError::MissingForeignCallInputs)?; + + let mut output = Vec::new(); + let num_values = num_values.unwrap_field().to_u128() as usize; + + let types_start_at = input_and_printable_types.len() - num_values; + let mut input_iter = + input_and_printable_types[0..types_start_at].iter().flat_map(|param| param.fields()); + for printable_type in input_and_printable_types.iter().skip(types_start_at) { + let printable_type = fetch_printable_type(printable_type)?; + let value = decode_value(&mut input_iter, &printable_type); + + output.push((value, printable_type)); + } + + Ok(PrintableValueDisplay::FmtString(message_as_string, output)) +} + +fn fetch_printable_type( + printable_type: &ForeignCallParam, +) -> Result { + let printable_type_as_fields = printable_type.fields(); + let printable_type_as_string = decode_string_value(&printable_type_as_fields); + let printable_type: PrintableType = serde_json::from_str(&printable_type_as_string)?; + + Ok(printable_type) +} diff --git a/tooling/nargo/src/foreign_calls/rpc.rs b/tooling/nargo/src/foreign_calls/rpc.rs index 0653eb1c7e3..4854754837a 100644 --- a/tooling/nargo/src/foreign_calls/rpc.rs +++ b/tooling/nargo/src/foreign_calls/rpc.rs @@ -2,10 +2,9 @@ use std::path::PathBuf; use acvm::{acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo, AcirField}; use jsonrpc::{arg as build_json_rpc_arg, minreq_http::Builder, Client}; -use noirc_printable_type::ForeignCallError; use serde::{Deserialize, Serialize}; -use super::ForeignCallExecutor; +use super::{ForeignCallError, ForeignCallExecutor}; #[derive(Debug)] pub(crate) struct RPCForeignCallExecutor { diff --git a/tooling/nargo/src/ops/test.rs b/tooling/nargo/src/ops/test.rs index e258627b522..773d45e90cb 100644 --- a/tooling/nargo/src/ops/test.rs +++ b/tooling/nargo/src/ops/test.rs @@ -12,7 +12,6 @@ use noirc_abi::Abi; use noirc_driver::{compile_no_check, CompileError, CompileOptions}; use noirc_errors::{debug_info::DebugInfo, FileDiagnostic}; use noirc_frontend::hir::{def_map::TestFunction, Context}; -use noirc_printable_type::ForeignCallError; use rand::Rng; use serde::{Deserialize, Serialize}; @@ -21,6 +20,7 @@ use crate::{ foreign_calls::{ mocker::MockForeignCallExecutor, print::PrintForeignCallExecutor, rpc::RPCForeignCallExecutor, ForeignCall, ForeignCallExecutor, + ForeignCallError, }, NargoError, }; @@ -63,7 +63,7 @@ pub fn run_test>( if test_function_has_no_arguments { // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, // otherwise constraints involving these expressions will not error. - let mut foreign_call_executor = TestForeignCallExecutor::new( + let mut foreign_call_executor = TestForeignCallExecutor::::new( show_output, foreign_call_resolver_url, root_path, diff --git a/tooling/noirc_abi/Cargo.toml b/tooling/noirc_abi/Cargo.toml index 22114408e18..79e14471200 100644 --- a/tooling/noirc_abi/Cargo.toml +++ b/tooling/noirc_abi/Cargo.toml @@ -15,10 +15,10 @@ workspace = true acvm.workspace = true iter-extended.workspace = true noirc_printable_type.workspace = true -toml.workspace = true serde_json = "1.0" serde.workspace = true thiserror.workspace = true +toml.workspace = true num-bigint = "0.4" num-traits = "0.2" diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index bd5674d64f1..be01192e51d 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -13,10 +13,7 @@ use acvm::{ use errors::AbiError; use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; -use noirc_printable_type::{ - decode_value as printable_type_decode_value, PrintableType, PrintableValue, - PrintableValueDisplay, -}; + use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::{collections::BTreeMap, str}; @@ -30,8 +27,12 @@ mod arbitrary; pub mod errors; pub mod input_parser; +mod printable_type; mod serialization; +use noirc_printable_type::{PrintableType, PrintableValue, PrintableValueDisplay}; +pub use printable_type::decode_value as decode_printable_value; + /// A map from the fields in an TOML/JSON file which correspond to some ABI to their values pub type InputMap = BTreeMap; @@ -417,7 +418,7 @@ pub fn decode_value( Ok(value) } -fn decode_string_value(field_elements: &[FieldElement]) -> String { +pub fn decode_string_value(field_elements: &[F]) -> String { let string_as_slice = vecmap(field_elements, |e| { let mut field_as_bytes = e.to_be_bytes(); let char_byte = field_as_bytes.pop().unwrap(); // A character in a string is represented by a u8, thus we just want the last byte of the element @@ -476,21 +477,21 @@ pub fn display_abi_error( AbiErrorType::FmtString { length, item_types } => { let mut fields_iter = fields.iter().copied(); let PrintableValue::String(string) = - printable_type_decode_value(&mut fields_iter, &PrintableType::String { length }) + decode_printable_value(&mut fields_iter, &PrintableType::String { length }) else { unreachable!("Got non-string from string decoding"); }; let _length_of_items = fields_iter.next(); let items = item_types.into_iter().map(|abi_type| { let printable_typ = (&abi_type).into(); - let decoded = printable_type_decode_value(&mut fields_iter, &printable_typ); + let decoded = decode_printable_value(&mut fields_iter, &printable_typ); (decoded, printable_typ) }); PrintableValueDisplay::FmtString(string, items.collect()) } AbiErrorType::Custom(abi_typ) => { let printable_type = (&abi_typ).into(); - let decoded = printable_type_decode_value(&mut fields.iter().copied(), &printable_type); + let decoded = decode_printable_value(&mut fields.iter().copied(), &printable_type); PrintableValueDisplay::Plain(decoded, printable_type) } AbiErrorType::String { string } => { diff --git a/tooling/noirc_abi/src/printable_type.rs b/tooling/noirc_abi/src/printable_type.rs new file mode 100644 index 00000000000..a81eb0ce8f6 --- /dev/null +++ b/tooling/noirc_abi/src/printable_type.rs @@ -0,0 +1,78 @@ +use std::collections::BTreeMap; + +use acvm::acir::AcirField; +use iter_extended::vecmap; + +use noirc_printable_type::{PrintableType, PrintableValue}; + +use crate::decode_string_value; + +/// Assumes that `field_iterator` contains enough field elements in order to decode the [PrintableType] +pub fn decode_value( + field_iterator: &mut impl Iterator, + typ: &PrintableType, +) -> PrintableValue { + match typ { + PrintableType::Field + | PrintableType::SignedInteger { .. } + | PrintableType::UnsignedInteger { .. } + | PrintableType::Boolean => { + let field_element = field_iterator.next().unwrap(); + + PrintableValue::Field(field_element) + } + PrintableType::Array { length, typ } => { + let length = *length as usize; + let mut array_elements = Vec::with_capacity(length); + for _ in 0..length { + array_elements.push(decode_value(field_iterator, typ)); + } + + PrintableValue::Vec { array_elements, is_slice: false } + } + PrintableType::Slice { typ } => { + let length = field_iterator + .next() + .expect("not enough data to decode variable array length") + .to_u128() as usize; + let mut array_elements = Vec::with_capacity(length); + for _ in 0..length { + array_elements.push(decode_value(field_iterator, typ)); + } + + PrintableValue::Vec { array_elements, is_slice: true } + } + PrintableType::Tuple { types } => PrintableValue::Vec { + array_elements: vecmap(types, |typ| decode_value(field_iterator, typ)), + is_slice: false, + }, + PrintableType::String { length } => { + let field_elements: Vec = field_iterator.take(*length as usize).collect(); + + PrintableValue::String(decode_string_value(&field_elements)) + } + PrintableType::Struct { fields, .. } => { + let mut struct_map = BTreeMap::new(); + + for (field_key, param_type) in fields { + let field_value = decode_value(field_iterator, param_type); + + struct_map.insert(field_key.to_owned(), field_value); + } + + PrintableValue::Struct(struct_map) + } + PrintableType::Function { env, .. } => { + let field_element = field_iterator.next().unwrap(); + let func_ref = PrintableValue::Field(field_element); + // we want to consume the fields from the environment, but for now they are not actually printed + decode_value(field_iterator, env); + func_ref + } + PrintableType::MutableReference { typ } => { + // we decode the reference, but it's not really used for printing + decode_value(field_iterator, typ) + } + PrintableType::Unit => PrintableValue::Field(F::zero()), + } +} diff --git a/tooling/noirc_artifacts/src/debug_vars.rs b/tooling/noirc_artifacts/src/debug_vars.rs index aa9328432b8..f8a0e352e5e 100644 --- a/tooling/noirc_artifacts/src/debug_vars.rs +++ b/tooling/noirc_artifacts/src/debug_vars.rs @@ -1,8 +1,10 @@ use acvm::AcirField; +use noirc_abi::decode_printable_value; use noirc_errors::debug_info::{ DebugFnId, DebugFunction, DebugInfo, DebugTypeId, DebugVarId, DebugVariable, }; -use noirc_printable_type::{decode_value, PrintableType, PrintableValue}; +use noirc_printable_type::{PrintableType, PrintableValue}; + use std::collections::HashMap; #[derive(Debug, Default, Clone)] @@ -72,7 +74,7 @@ impl DebugVars { .last_mut() .expect("unexpected empty stack frames") .1 - .insert(var_id, decode_value(&mut values.iter().copied(), ptype)); + .insert(var_id, decode_printable_value(&mut values.iter().copied(), ptype)); } pub fn assign_field(&mut self, var_id: DebugVarId, indexes: Vec, values: &[F]) { @@ -143,7 +145,7 @@ impl DebugVars { } }; } - *cursor = decode_value(&mut values.iter().copied(), cursor_type); + *cursor = decode_printable_value(&mut values.iter().copied(), cursor_type); } pub fn assign_deref(&mut self, _var_id: DebugVarId, _values: &[F]) {