diff --git a/Changelog.md b/Changelog.md index 08427fc6..17b1e7ee 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,16 +10,19 @@ * `de::ArgumentDecoder`, `ser::ArgumentEncoder` moved to `utils::{ArgumentDecoder, ArgumentEncoder}` * `types::subtype` returns `Result<()>` instead of `bool` for better error message * Disable subtyping conversion for opt rules in `IDLValue.annotate_type` +* Display type annotations for number types ### Non-breaking changes * Better error messages in deserialization * Update test suite to conform with the new spec +* `didc hash` to compute hash of a field name +* `didc decode` can decode blob format ### Pending issues * Update opt rule for subtyping and coercion -* Performance regression in decoding struct type +* Performance regression in decoding struct type, likely due to `Type` clone * Integration test with production canisters ## 2021-04-07 (Rust 0.6.19 -- 0.6.21) diff --git a/rust/candid/src/bindings/javascript.rs b/rust/candid/src/bindings/javascript.rs index 7b0659ca..c08e0150 100644 --- a/rust/candid/src/bindings/javascript.rs +++ b/rust/candid/src/bindings/javascript.rs @@ -248,7 +248,7 @@ pub mod value { enclose_space("{", pp_fields(&fields), "}") } } - Variant(v, _) => enclose_space("{", pp_field(&v), "}"), + Variant(v) => enclose_space("{", pp_field(&v.0), "}"), _ => RcDoc::as_string(v), } } diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 59b021e9..74ceeb86 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -204,8 +204,12 @@ impl<'de> Deserializer<'de> { Ok(()) } fn unroll_type(&mut self) -> Result<()> { - self.expect_type = self.table.trace_type(&self.expect_type)?; - self.wire_type = self.table.trace_type(&self.wire_type)?; + if matches!(self.expect_type, Type::Var(_) | Type::Knot(_)) { + self.expect_type = self.table.trace_type(&self.expect_type)?; + } + if matches!(self.wire_type, Type::Var(_) | Type::Knot(_)) { + self.wire_type = self.table.trace_type(&self.wire_type)?; + } Ok(()) } // Should always call set_field_name to set the field_name. After deserialize_identifier @@ -227,6 +231,7 @@ impl<'de> Deserializer<'de> { V: Visitor<'de>, { use std::convert::TryInto; + self.unroll_type()?; assert!(self.expect_type == Type::Int); let mut bytes = vec![0u8]; let int = match &self.wire_type { @@ -246,6 +251,7 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { + self.unroll_type()?; assert!(self.expect_type == Type::Nat && self.wire_type == Type::Nat); let mut bytes = vec![1u8]; let nat = Nat::decode(&mut self.input).map_err(Error::msg)?; @@ -256,6 +262,7 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { + self.unroll_type()?; assert!(self.expect_type == Type::Principal && self.wire_type == Type::Principal); let mut bytes = vec![2u8]; let id = PrincipalBytes::read(&mut self.input)?.inner; @@ -312,6 +319,7 @@ macro_rules! primitive_impl { paste::item! { fn [](self, visitor: V) -> Result where V: Visitor<'de> { + self.unroll_type()?; assert!(self.expect_type == $type && self.wire_type == $type); let val = self.input.$($value)*().map_err(|_| Error::msg(format!("Cannot read {} value", stringify!($type))))?; //let val: $ty = self.input.read_le()?; @@ -330,8 +338,8 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { if self.field_name.is_some() { return self.deserialize_identifier(visitor); } - let t = self.table.trace_type(&self.expect_type)?; - match t { + self.unroll_type()?; + match &self.expect_type { Type::Int => self.deserialize_int(visitor), Type::Nat => self.deserialize_nat(visitor), Type::Nat8 => self.deserialize_u8(visitor), @@ -389,6 +397,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { V: Visitor<'de>, { use std::convert::TryInto; + self.unroll_type()?; assert!(self.expect_type == Type::Int); let value: i128 = match &self.wire_type { Type::Int => { @@ -408,6 +417,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { V: Visitor<'de>, { use std::convert::TryInto; + self.unroll_type()?; assert!(self.expect_type == Type::Nat && self.wire_type == Type::Nat); let nat = Nat::decode(&mut self.input).map_err(Error::msg)?; let value: u128 = nat.0.try_into().map_err(Error::msg)?; @@ -417,6 +427,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { + self.unroll_type()?; assert!(self.expect_type == Type::Null && self.wire_type == Type::Null); visitor.visit_unit() } @@ -424,6 +435,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { + self.unroll_type()?; assert!(self.expect_type == Type::Bool && self.wire_type == Type::Bool); let res = BoolValue::read(&mut self.input)?; visitor.visit_bool(res.0) @@ -432,6 +444,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { + self.unroll_type()?; assert!(self.expect_type == Type::Text && self.wire_type == Type::Text); let len = Len::read(&mut self.input)?.0; let bytes = self.borrow_bytes(len)?.to_owned(); @@ -442,6 +455,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { + self.unroll_type()?; assert!(self.expect_type == Type::Text && self.wire_type == Type::Text); let len = Len::read(&mut self.input)?.0; let slice = self.borrow_bytes(len)?; @@ -654,21 +668,12 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { ))); } let wire = w[index].clone(); - let e_tuple = e + let expect = e .iter() - .enumerate() - .find(|(_, ref f)| f.id == wire.id) - .ok_or_else(|| Error::msg(format!("Unknown variant field {}", wire.id)))?; - let expect_index = e_tuple.0; - let expect = e_tuple.1.clone(); - visitor.visit_enum(Compound::new( - &mut self, - Style::Enum { - expect, - expect_index, - wire, - }, - )) + .find(|ref f| f.id == wire.id) + .ok_or_else(|| Error::msg(format!("Unknown variant field {}", wire.id)))? + .clone(); + visitor.visit_enum(Compound::new(&mut self, Style::Enum { expect, wire })) } _ => assert!(false), } @@ -702,7 +707,6 @@ enum Style { }, Enum { expect: Field, - expect_index: usize, wire: Field, }, Map { @@ -848,11 +852,7 @@ impl<'de, 'a> de::EnumAccess<'de> for Compound<'a, 'de> { V: de::DeserializeSeed<'de>, { match &self.style { - Style::Enum { - expect, - expect_index, - wire, - } => { + Style::Enum { expect, wire } => { self.de.expect_type = expect.ty.clone(); self.de.wire_type = wire.ty.clone(); let (mut label, label_type) = match &expect.id { @@ -865,7 +865,7 @@ impl<'de, 'a> de::EnumAccess<'de> for Compound<'a, 'de> { Type::Record(_) => "struct", _ => "newtype", }; - label += &format!(",{},{},{}", label_type, accessor, expect_index); + label += &format!(",{},{}", label_type, accessor); } self.de.set_field_name(Label::Named(label)); let field = seed.deserialize(&mut *self.de)?; diff --git a/rust/candid/src/lib.rs b/rust/candid/src/lib.rs index 1c4f76bc..f45620fa 100644 --- a/rust/candid/src/lib.rs +++ b/rust/candid/src/lib.rs @@ -156,7 +156,7 @@ //! We also provide a parser to parse Candid values in text format. //! //! ``` -//! use candid::IDLArgs; +//! use candid::{IDLArgs, TypeEnv}; //! // Candid values represented in text format //! let text_value = r#" //! (42, opt true, vec {1;2;3}, @@ -174,7 +174,8 @@ //! // Convert IDLArgs to text format //! let output: String = decoded.to_string(); //! let parsed_args: IDLArgs = output.parse()?; -//! assert_eq!(args, parsed_args); +//! let annotated_args = args.annotate_types(true, &TypeEnv::new(), &parsed_args.get_types())?; +//! assert_eq!(annotated_args, parsed_args); //! # Ok::<(), candid::Error>(()) //! ``` //! Note that when parsing Candid values, we assume the number literals are always of type `Int`. diff --git a/rust/candid/src/parser/grammar.lalrpop b/rust/candid/src/parser/grammar.lalrpop index e5afa7f4..caafbe50 100644 --- a/rust/candid/src/parser/grammar.lalrpop +++ b/rust/candid/src/parser/grammar.lalrpop @@ -1,4 +1,4 @@ -use super::value::{IDLField, IDLValue, IDLArgs}; +use super::value::{IDLField, IDLValue, IDLArgs, VariantValue}; use super::typing::{check_unique, TypeEnv}; use super::types::{IDLType, PrimType, TypeField, FuncType, FuncMode, Binding, Dec, IDLProg, IDLTypes}; use super::test::{Assert, Input, Test}; @@ -81,7 +81,7 @@ pub Arg: IDLValue = { check_unique(fs.iter().map(|f| &f.id)).map_err(|e| error2(e, span))?; Ok(IDLValue::Record(fs)) }, - "variant" "{" "}" => IDLValue::Variant(Box::new(<>), 0), + "variant" "{" "}" => IDLValue::Variant(VariantValue(Box::new(<>), 0)), "principal" > =>? Ok(IDLValue::Principal(Principal::from_text(&<>.0).map_err(|e| error2(e, <>.1))?)), "service" > =>? Ok(IDLValue::Service(Principal::from_text(&<>.0).map_err(|e| error2(e, <>.1))?)), "func" > "." =>? { diff --git a/rust/candid/src/parser/pretty.rs b/rust/candid/src/parser/pretty.rs index 659df408..dd62db3b 100644 --- a/rust/candid/src/parser/pretty.rs +++ b/rust/candid/src/parser/pretty.rs @@ -38,28 +38,48 @@ impl fmt::Debug for IDLArgs { } } } +fn has_type_annotation(v: &IDLValue) -> bool { + use IDLValue::*; + matches!( + v, + Int(_) + | Nat(_) + | Nat8(_) + | Nat16(_) + | Nat32(_) + | Nat64(_) + | Int8(_) + | Int16(_) + | Int32(_) + | Int64(_) + | Float32(_) + | Float64(_) + | Null + | Reserved + ) +} impl fmt::Debug for IDLValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use IDLValue::*; match self { - Null => write!(f, "null"), + Null => write!(f, "null : null"), Bool(b) => write!(f, "{}", b), Number(n) => write!(f, "{}", n), - Int(i) => write!(f, "{}", i), - Nat(n) => write!(f, "{}", n), - Nat8(n) => write!(f, "{}", n), - Nat16(n) => write!(f, "{}", pp_num_str(&n.to_string())), - Nat32(n) => write!(f, "{}", pp_num_str(&n.to_string())), - Nat64(n) => write!(f, "{}", pp_num_str(&n.to_string())), - Int8(n) => write!(f, "{}", n), - Int16(n) => write!(f, "{}", pp_num_str(&n.to_string())), - Int32(n) => write!(f, "{}", pp_num_str(&n.to_string())), - Int64(n) => write!(f, "{}", pp_num_str(&n.to_string())), - Float32(n) => write!(f, "{}", n), - Float64(n) => write!(f, "{}", n), + Int(i) => write!(f, "{} : int", i), + Nat(n) => write!(f, "{} : nat", n), + Nat8(n) => write!(f, "{} : nat8", n), + Nat16(n) => write!(f, "{} : nat16", pp_num_str(&n.to_string())), + Nat32(n) => write!(f, "{} : nat32", pp_num_str(&n.to_string())), + Nat64(n) => write!(f, "{} : nat64", pp_num_str(&n.to_string())), + Int8(n) => write!(f, "{} : int8", n), + Int16(n) => write!(f, "{} : int16", pp_num_str(&n.to_string())), + Int32(n) => write!(f, "{} : int32", pp_num_str(&n.to_string())), + Int64(n) => write!(f, "{} : int64", pp_num_str(&n.to_string())), + Float32(n) => write!(f, "{} : float32", n), + Float64(n) => write!(f, "{} : float64", n), Text(s) => write!(f, "{:?}", s), None => write!(f, "null"), - Reserved => write!(f, "reserved"), + Reserved => write!(f, "null : reserved"), Principal(id) => write!(f, "principal \"{}\"", id), Service(id) => write!(f, "service \"{}\"", id), Func(id, meth) => write!( @@ -68,6 +88,7 @@ impl fmt::Debug for IDLValue { id, crate::bindings::candid::ident_string(meth) ), + Opt(v) if has_type_annotation(v) => write!(f, "opt ({:?})", v), Opt(v) => write!(f, "opt {:?}", v), Vec(vs) => { if let Some(Nat8(_)) = vs.first() { @@ -98,12 +119,12 @@ impl fmt::Debug for IDLValue { } write!(f, "}}") } - Variant(v, _) => { + Variant(v) => { write!(f, "variant {{ ")?; - if v.val == Null { - write!(f, "{}", v.id)?; + if v.0.val == Null { + write!(f, "{}", v.0.id)?; } else { - write!(f, "{:?}", v)?; + write!(f, "{:?}", v.0)?; } write!(f, " }}") } @@ -146,7 +167,7 @@ fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc { } pub fn pp_char(v: u8) -> String { - if (0x20..=0x7e).contains(&v) && v != 0x22 && v != 0x5c { + if (0x20..=0x7e).contains(&v) && v != 0x22 && v != 0x27 && v != 0x60 && v != 0x5c { std::char::from_u32(v as u32).unwrap().to_string() } else { format!("\\{:02x}", v) @@ -160,6 +181,9 @@ pub fn pp_value(depth: usize, v: &IDLValue) -> RcDoc { } match v { Text(ref s) => RcDoc::as_string(format!("\"{}\"", s)), + Opt(v) if has_type_annotation(v) => { + kwd("opt").append(enclose("(", pp_value(depth - 1, v), ")")) + } Opt(v) => kwd("opt").append(pp_value(depth - 1, v)), Vec(vs) => { if let Some(Nat8(_)) = vs.first() { @@ -179,7 +203,7 @@ pub fn pp_value(depth: usize, v: &IDLValue) -> RcDoc { kwd("record").append(pp_fields(depth, &fields)) } } - Variant(v, _) => kwd("variant").append(enclose_space("{", pp_field(depth, &v, true), "}")), + Variant(v) => kwd("variant").append(enclose_space("{", pp_field(depth, &v.0, true), "}")), _ => RcDoc::as_string(format!("{:?}", v)), } } diff --git a/rust/candid/src/parser/random.rs b/rust/candid/src/parser/random.rs index b84920d9..bf4eed71 100644 --- a/rust/candid/src/parser/random.rs +++ b/rust/candid/src/parser/random.rs @@ -1,6 +1,6 @@ use super::configs::{path_name, Configs}; use super::typing::TypeEnv; -use super::value::{IDLArgs, IDLField, IDLValue}; +use super::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use crate::types::{Field, Type}; use crate::Deserialize; use crate::{Error, Result}; @@ -201,7 +201,7 @@ impl<'a> GenState<'a> { id: id.clone(), val, }; - IDLValue::Variant(Box::new(field), idx as u64) + IDLValue::Variant(VariantValue(Box::new(field), idx as u64)) } _ => unimplemented!(), }); diff --git a/rust/candid/src/parser/value.rs b/rust/candid/src/parser/value.rs index e669b333..f16f0d78 100644 --- a/rust/candid/src/parser/value.rs +++ b/rust/candid/src/parser/value.rs @@ -19,7 +19,7 @@ pub enum IDLValue { Opt(Box), Vec(Vec), Record(Vec), - Variant(Box, u64), // u64 represents the index from the type, defaults to 0 when parsing + Variant(VariantValue), Principal(crate::Principal), Service(crate::Principal), Func(crate::Principal, String), @@ -39,6 +39,14 @@ pub enum IDLValue { Reserved, } +#[derive(Clone)] +pub struct VariantValue(pub Box, pub u64); // u64 represents the index from the type, defaults to 0 when parsing, only used for serialization +impl PartialEq for VariantValue { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + #[derive(PartialEq, Clone)] pub struct IDLField { pub id: Label, @@ -78,6 +86,9 @@ impl IDLArgs { } Ok(IDLArgs { args }) } + pub fn get_types(&self) -> Vec { + self.args.iter().map(|v| v.value_ty()).collect() + } /// Encode IDLArgs with the given types. Note that this is not equivalent to /// `idl_args.annotate_types(true, env, types).to_bytes()` for recursive types. pub fn to_bytes_with_types(&self, env: &TypeEnv, types: &[Type]) -> Result> { @@ -229,18 +240,18 @@ impl IDLValue { } IDLValue::Record(res) } - (IDLValue::Variant(v, _), Type::Variant(fs)) => { + (IDLValue::Variant(v), Type::Variant(fs)) => { for (i, f) in fs.iter().enumerate() { - if v.id == f.id { - let val = v.val.annotate_type(from_parser, env, &f.ty)?; + if v.0.id == f.id { + let val = v.0.val.annotate_type(from_parser, env, &f.ty)?; let field = IDLField { id: f.id.clone(), val, }; - return Ok(IDLValue::Variant(Box::new(field), i as u64)); + return Ok(IDLValue::Variant(VariantValue(Box::new(field), i as u64))); } } - return Err(Error::msg(format!("variant field {} not found", v.id))); + return Err(Error::msg(format!("variant field {} not found", v.0.id))); } (IDLValue::Principal(id), Type::Principal) => IDLValue::Principal(id.clone()), (IDLValue::Service(_), Type::Service(_)) => self.clone(), @@ -314,11 +325,10 @@ impl IDLValue { .collect(); Type::Record(fs) } - IDLValue::Variant(ref v, idx) => { - assert_eq!(idx, 0); + IDLValue::Variant(ref v) => { let f = Field { - id: v.id.clone(), - ty: v.val.value_ty(), + id: v.0.id.clone(), + ty: v.0.val.value_ty(), }; Type::Variant(vec![f]) } @@ -387,9 +397,9 @@ impl crate::CandidType for IDLValue { } Ok(()) } - IDLValue::Variant(ref v, idx) => { - let mut ser = serializer.serialize_variant(idx)?; - ser.serialize_element(&v.val)?; + IDLValue::Variant(ref v) => { + let mut ser = serializer.serialize_variant(v.1)?; + ser.serialize_element(&v.0.val)?; Ok(()) } IDLValue::Principal(ref id) => serializer.serialize_principal(id.as_slice()), @@ -527,17 +537,9 @@ impl<'de> Deserialize<'de> for IDLValue { let (variant, visitor) = data.variant::()?; if let IDLValue::Text(v) = variant { let v: Vec<_> = v.split(',').collect(); - let (id, style, ind) = match v.as_slice() { - [name, "name", style, ind] => ( - Label::Named(name.to_string()), - style, - ind.parse::().unwrap(), - ), - [hash, "id", style, ind] => ( - Label::Id(hash.parse::().unwrap()), - style, - ind.parse::().unwrap(), - ), + let (id, style) = match v.as_slice() { + [name, "name", style] => (Label::Named(name.to_string()), style), + [hash, "id", style] => (Label::Id(hash.parse::().unwrap()), style), _ => unreachable!(), }; let val = match *style { @@ -552,7 +554,7 @@ impl<'de> Deserialize<'de> for IDLValue { let f = IDLField { id, val }; // Deserialized variant always has 0 index to ensure untyped // serialization is correct. - Ok(IDLValue::Variant(Box::new(f), ind)) + Ok(IDLValue::Variant(VariantValue(Box::new(f), 0))) } else { unreachable!() } diff --git a/rust/candid/tests/parse_value.rs b/rust/candid/tests/parse_value.rs index 25f7a6a5..01b6bbe9 100644 --- a/rust/candid/tests/parse_value.rs +++ b/rust/candid/tests/parse_value.rs @@ -1,5 +1,5 @@ use candid::parser::typing::TypeEnv; -use candid::parser::value::{IDLArgs, IDLField, IDLValue}; +use candid::parser::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, Type}; fn parse_args(input: &str) -> IDLArgs { @@ -41,7 +41,7 @@ fn parse_literals() { ); assert_eq!( format!("{}", args), - "(true, null, 42, 42, 42.42, -4200000, 0.0004242)" + "(\n true,\n null : null,\n 42,\n 42 : float64,\n 42.42 : float64,\n -4200000 : float64,\n 0.0004242 : float64,\n)" ); } @@ -103,7 +103,7 @@ fn parse_more_literals() { ); assert_eq!( format!("{}", args), - "(true, null, 42, \"哈哈\", \"string with whitespace\", 42, -42, false)" + "(\n true,\n null : null,\n 42 : nat,\n \"哈哈\",\n \"string with whitespace\",\n 42 : int,\n -42 : int,\n false,\n)" ); } @@ -122,7 +122,10 @@ fn parse_vec() { IDLValue::Nat(4.into()) ])] ); - assert_eq!(format!("{}", args), "(vec { 1; 2; 3; 4 })"); + assert_eq!( + format!("{}", args), + "(vec { 1 : nat; 2 : nat; 3 : nat; 4 : nat })" + ); } #[test] @@ -151,18 +154,18 @@ fn parse_optional_record() { val: IDLValue::Text("test".to_owned()) }, ]), - IDLValue::Variant( + IDLValue::Variant(VariantValue( Box::new(IDLField { id: Label::Id(5), val: IDLValue::Null }), 0 - ) + )) ] ); assert_eq!( format!("{}", args), - "(opt record {}, record { 1 = 42; 2 = false; 44 = \"test\" }, variant { 5 })" + "(opt record {}, record { 1 = 42 : nat; 2 = false; 44 = \"test\" }, variant { 5 })" ); } @@ -203,12 +206,12 @@ fn parse_nested_record() { } ])] ); - assert_eq!(format!("{}", args), "(\n record {\n 43 = record { \"opt\" = \"hello\"; test = \"test\" };\n long_label = opt null;\n label = 42;\n },\n)"); + assert_eq!(format!("{}", args), "(\n record {\n 43 = record { \"opt\" = \"hello\"; test = \"test\" };\n long_label = opt (null : null);\n label = 42 : nat;\n },\n)"); let skip_typ = parse_type("record { label: nat }"); args.args[0] = args.args[0] .annotate_type(true, &TypeEnv::new(), &skip_typ) .unwrap(); - assert_eq!(format!("{}", args), "(record { label = 42 })"); + assert_eq!(format!("{}", args), "(record { label = 42 : nat })"); } #[test] diff --git a/rust/candid/tests/value.rs b/rust/candid/tests/value.rs index 1399fad8..7b78d2c5 100644 --- a/rust/candid/tests/value.rs +++ b/rust/candid/tests/value.rs @@ -1,7 +1,7 @@ use candid::parser::{ types::IDLProg, typing::{check_prog, TypeEnv}, - value::{IDLArgs, IDLField, IDLValue}, + value::{IDLArgs, IDLField, IDLValue, VariantValue}, }; use candid::types::Label; use candid::{decode_args, decode_one, Decode}; @@ -57,11 +57,11 @@ service : { let args = str.parse::().unwrap(); let encoded = args.to_bytes_with_types(&env, &method.rets).unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); - assert_eq!(decoded.to_string(), "(\n opt record {\n 1_158_359_328 = 1_000;\n 1_291_237_008 = opt record { 1_158_359_328 = -2_000; 1_291_237_008 = null };\n },\n variant { 97 = 42 },\n)"); + assert_eq!(decoded.to_string(), "(\n opt record {\n 1_158_359_328 = 1_000 : int16;\n 1_291_237_008 = opt record {\n 1_158_359_328 = -2_000 : int16;\n 1_291_237_008 = null;\n };\n },\n variant { 97 = 42 : nat },\n)"); let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &method.rets).unwrap(); assert_eq!( decoded.to_string(), - "(\n opt record { head = 1_000; tail = opt record { head = -2_000; tail = null } },\n variant { a = 42 },\n)" + "(\n opt record {\n head = 1_000 : int16;\n tail = opt record { head = -2_000 : int16; tail = null };\n },\n variant { a = 42 : nat },\n)" ); let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &[]).unwrap(); assert_eq!(decoded.to_string(), "()"); @@ -109,13 +109,13 @@ fn test_value() { #[test] fn test_variant() { use IDLValue::*; - let value = Variant( + let value = Variant(VariantValue( Box::new(IDLField { id: Label::Id(3_303_859), val: Null, }), 0, - ); + )); let bytes = hex("4449444c016b02b3d3c9017fe6fdd5017f010000"); test_decode(&bytes, &value); let encoded = IDLArgs::new(&[value.clone()]).to_bytes().unwrap(); @@ -128,7 +128,10 @@ fn parse_check(str: &str) { let decoded = IDLArgs::from_bytes(&encoded).unwrap(); let output = decoded.to_string(); let back_args = output.parse::().unwrap(); - assert_eq!(args, back_args); + let annotated_args = args + .annotate_types(true, &TypeEnv::new(), &back_args.get_types()) + .unwrap(); + assert_eq!(annotated_args, back_args); } fn check(v: IDLValue, bytes: &str) { diff --git a/tools/candiff/src/lib.rs b/tools/candiff/src/lib.rs index c18c5c65..34b08985 100644 --- a/tools/candiff/src/lib.rs +++ b/tools/candiff/src/lib.rs @@ -230,9 +230,9 @@ pub fn value_diff_rec(v1: &Value, v2: &Value, _t: &Option) -> ValueEdit { - if f1.id == f2.id { - let edit = value_diff(&f1.val, &f2.val, &Option::None); + (Variant(f1), Variant(f2)) => { + if f1.0.id == f2.0.id { + let edit = value_diff(&f1.0.val, &f2.0.val, &Option::None); if value_edit_is_skip(&edit) { Skip } else { diff --git a/tools/candiff/tests/candiff.rs b/tools/candiff/tests/candiff.rs index c4c1ff17..5a9e690e 100644 --- a/tools/candiff/tests/candiff.rs +++ b/tools/candiff/tests/candiff.rs @@ -38,7 +38,7 @@ mod echo { let mut cmd = candiff(); cmd.arg("echo").arg("1").arg("-t nat").arg("-d"); cmd.assert() - .stdout(predicate::eq(b"1\n" as &[u8])) + .stdout(predicate::eq(b"1 : nat\n" as &[u8])) .success(); } @@ -47,7 +47,7 @@ mod echo { let mut cmd = candiff(); cmd.arg("echo").arg("1").arg("-t int").arg("-d"); cmd.assert() - .stdout(predicate::eq(b"1\n" as &[u8])) + .stdout(predicate::eq(b"1 : int\n" as &[u8])) .success(); } @@ -174,7 +174,7 @@ mod diff { cmd.arg("diff").arg(v1).arg(v2).arg("-t vec vec nat"); cmd.assert() .stdout(predicate::eq( - b"vec { edit { 1 vec { edit { 1 put { 3 } }; } }; }\n" as &[u8], + b"vec { edit { 1 vec { edit { 1 put { 3 : nat } }; } }; }\n" as &[u8], )) .success(); } @@ -187,7 +187,7 @@ mod diff { cmd.arg("diff").arg(v1).arg(v2).arg("-t vec vec nat"); cmd.assert() .stdout(predicate::eq( - b"vec { edit { 2 vec { insert { 2 2 }; } }; }\n" as &[u8], + b"vec { edit { 2 vec { insert { 2 2 : nat }; } }; }\n" as &[u8], )) .success(); } diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 1903c013..cf1c3961 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -37,6 +37,8 @@ enum Command { /// Specifies target language target: String, }, + /// Compute the hash of a field name + Hash { input: String }, /// Encode Candid value Encode { #[structopt(parse(try_from_str = parse_args))] @@ -52,11 +54,11 @@ enum Command { Decode { /// Specifies Candid binary data in hex string blob: String, + #[structopt(short, long, possible_values = &["hex", "blob"], default_value = "hex")] + /// Specifies hex format + format: String, #[structopt(flatten)] annotate: TypeAnnotation, - #[structopt(short, long)] - /// Disable pretty printing - flat: bool, }, /// Generate random Candid values Random { @@ -215,6 +217,9 @@ fn main() -> Result<()> { }; println!("{}", content); } + Command::Hash { input } => { + println!("{}", candid::idl_hash(&input)); + } Command::Encode { args, format, @@ -234,7 +239,7 @@ fn main() -> Result<()> { for ch in bytes.iter() { res.push_str(&candid::parser::pretty::pp_char(*ch)); } - res + format!("blob \"{}\"", res) } _ => unreachable!(), }; @@ -242,21 +247,36 @@ fn main() -> Result<()> { } Command::Decode { blob, + format, annotate, - flat, } => { - let bytes = hex::decode(&blob)?; + let bytes = match format.as_str() { + "hex" => hex::decode(&blob)?, + "blob" => { + use candid::parser::value::IDLValue; + match pretty_parse::("blob", &blob)? { + IDLValue::Vec(vec) => vec + .iter() + .map(|v| { + if let IDLValue::Nat8(u) = v { + *u + } else { + unreachable!() + } + }) + .collect(), + _ => unreachable!(), + } + } + _ => unreachable!(), + }; let value = if annotate.is_empty() { IDLArgs::from_bytes(&bytes)? } else { let (env, types) = annotate.get_types(Mode::Decode)?; IDLArgs::from_bytes_with_types(&bytes, &env, &types)? }; - if !flat { - println!("{}", value); - } else { - println!("{:?}", value); - } + println!("{}", value); } Command::Random { annotate,