diff --git a/rust/candid/src/lib.rs b/rust/candid/src/lib.rs index d9df4e0d0..c22779d0c 100644 --- a/rust/candid/src/lib.rs +++ b/rust/candid/src/lib.rs @@ -288,7 +288,7 @@ macro_rules! Encode { Encode!(@PutValue builder $($x,)*) }}; ( @PutValue $builder:ident $x:expr, $($tail:expr,)* ) => {{ - $builder.arg($x).and_then(|mut builder| Encode!(@PutValue builder $($tail,)*)) + $builder.arg($x).and_then(|builder| Encode!(@PutValue builder $($tail,)*)) }}; ( @PutValue $builder:ident ) => {{ $builder.serialize_to_vec() diff --git a/rust/candid/src/parser/typing.rs b/rust/candid/src/parser/typing.rs index 1beb0c9d5..82165ba67 100644 --- a/rust/candid/src/parser/typing.rs +++ b/rust/candid/src/parser/typing.rs @@ -44,6 +44,13 @@ impl TypeEnv { t => Ok(t), } } + pub fn trace_type<'a>(&'a self, t: &'a Type) -> Result { + match t { + Type::Var(id) => self.trace_type(self.find_type(id)?), + Type::Knot(ref id) => self.trace_type(&crate::types::internal::find_type(id).unwrap()), + t => Ok(t.clone()), + } + } pub fn as_func<'a>(&'a self, t: &'a Type) -> Result<&'a Function> { match t { Type::Func(f) => Ok(f), diff --git a/rust/candid/src/parser/value.rs b/rust/candid/src/parser/value.rs index 6c6cc5e9f..605a41d9d 100644 --- a/rust/candid/src/parser/value.rs +++ b/rust/candid/src/parser/value.rs @@ -154,7 +154,6 @@ impl IDLValue { let ty = crate::types::internal::find_type(id).unwrap(); self.annotate_type(from_parser, env, &ty)? } - (IDLValue::Null, Type::Opt(_)) if from_parser => IDLValue::None, (IDLValue::Float64(n), Type::Float32) if from_parser => IDLValue::Float32(*n as f32), (IDLValue::Number(str), t) if from_parser => match t { Type::Int => IDLValue::Int(str.parse::()?), @@ -177,8 +176,9 @@ impl IDLValue { (_, Type::Reserved) => IDLValue::Reserved, (IDLValue::Null, Type::Null) => IDLValue::Null, (IDLValue::Bool(b), Type::Bool) => IDLValue::Bool(*b), - (IDLValue::Int(i), Type::Int) => IDLValue::Int(i.clone()), (IDLValue::Nat(n), Type::Nat) => IDLValue::Nat(n.clone()), + (IDLValue::Int(i), Type::Int) => IDLValue::Int(i.clone()), + (IDLValue::Nat(n), Type::Int) => IDLValue::Int(n.clone().into()), (IDLValue::Nat8(n), Type::Nat8) => IDLValue::Nat8(*n), (IDLValue::Nat16(n), Type::Nat16) => IDLValue::Nat16(*n), (IDLValue::Nat32(n), Type::Nat32) => IDLValue::Nat32(*n), @@ -190,11 +190,23 @@ impl IDLValue { (IDLValue::Float64(n), Type::Float64) => IDLValue::Float64(*n), (IDLValue::Float32(n), Type::Float32) => IDLValue::Float32(*n), (IDLValue::Text(s), Type::Text) => IDLValue::Text(s.to_owned()), + // opt parsing. NB: Always succeeds! + (IDLValue::Null, Type::Opt(_)) => IDLValue::None, + (IDLValue::Reserved, Type::Opt(_)) => IDLValue::None, (IDLValue::None, Type::Opt(_)) => IDLValue::None, - (IDLValue::Opt(v), Type::Opt(ty)) => { - let v = v.annotate_type(from_parser, env, ty)?; - IDLValue::Opt(Box::new(v)) + // liberal decoding of optionals + (IDLValue::Opt(v), Type::Opt(ty)) => v + .annotate_type(from_parser, env, ty) + .map(|v| IDLValue::Opt(Box::new(v))) + .unwrap_or(IDLValue::None), + // try consituent type + (v, Type::Opt(ty)) if !matches!(env.trace_type(ty)?, Type::Null|Type::Reserved|Type::Opt(_)) => { + v.annotate_type(from_parser, env, ty) + .map(|v| IDLValue::Opt(Box::new(v))) + .unwrap_or(IDLValue::None) } + // fallback + (_, Type::Opt(_)) => IDLValue::None, (IDLValue::Vec(vec), Type::Vec(ty)) => { let mut res = Vec::new(); for e in vec.iter() { @@ -210,7 +222,13 @@ impl IDLValue { for Field { id, ty } in fs.iter() { let val = fields .get(&id) - .ok_or_else(|| Error::msg(format!("field {} not found", id)))?; + .cloned() + .or_else(|| match env.trace_type(ty).unwrap() { + Type::Opt(_) => Some(&IDLValue::None), + Type::Reserved => Some(&IDLValue::Reserved), + _ => None, + }) + .ok_or_else(|| Error::msg(format!("required field {} not found", id)))?; let val = val.annotate_type(from_parser, env, ty)?; res.push(IDLField { id: id.clone(), diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 00531bab9..ae4445ece 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -351,7 +351,7 @@ thread_local! { pub(crate) fn find_type(id: &TypeId) -> Option { ENV.with(|e| match e.borrow().get(id) { None => None, - Some(t) => Some((*t).clone()), + Some(t) => Some(t.clone()), }) } diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index 605a494c7..7546345a1 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -26,6 +26,14 @@ impl From for Nat { } } +impl From for Int { + #[inline(always)] + fn from(n: Nat) -> Self { + let i: BigInt = n.0.into(); + i.into() + } +} + impl From for BigInt { #[inline(always)] fn from(i: Int) -> Self { diff --git a/test/construct.test.did b/test/construct.test.did index 59f50597f..a517a7ea2 100644 --- a/test/construct.test.did +++ b/test/construct.test.did @@ -6,6 +6,8 @@ Encoding tests for construct types type Opt = opt Opt; type Vec = vec Vec; type EmptyRecord = record { EmptyRecord }; +type OptRec = opt record { OptRec }; + type List = opt record { head : int; tail : List }; type List1 = opt List2; type List2 = record { head : int; tail : List1 }; @@ -47,7 +49,7 @@ assert blob "DIDL\02\6e\01\6e\00\01\00\01\01\00" == "(opt opt null)" : (Opt) "op // vector assert blob "DIDL\01\6d\7c\01\00\00" == "(vec {})" : (vec int) "vec: empty"; assert blob "DIDL\01\6d\7c\01\00\02\01\02" == "(vec { 1; 2 })" : (vec int) "vec"; -assert blob "DIDL\01\6d\7b\01\00\02\01\02" == "(blob \"\01\02\")" : (vec nat8) "vec: blob"; +assert blob "DIDL\01\6d\7b\01\00\02\01\02" == "(blob \"\\01\\02\")" : (vec nat8) "vec: blob"; assert blob "DIDL\01\6d\00\01\00\00" == "(vec {})" : (Vec) "vec: recursive vector"; assert blob "DIDL\01\6d\00\01\00\02\00\00" == "(vec { vec {}; vec {} })" : (Vec) "vec: tree"; assert blob "DIDL\01\6d\00\01\00\02\00\00" == "(vec { vec {}; vec {} })" : (vec vec empty) "vec: non-recursive tree"; @@ -85,14 +87,47 @@ assert blob "DIDL\01\6c\02\00\7c\01\7e\02\7e\01\00\2a\01\00" ! assert blob "DIDL\01\6c\02\00\01\01\7e\01\00\00\00" !: (record {}) "record: type out of range"; assert blob "DIDL\01\6c\02\00\01\01\7e\01\7c\2a" !: (reserved) "record: type out of range"; +// opt +assert blob "DIDL\00\01\7f" == "(null)" : (opt empty) "opt: parsing null : null"; +assert blob "DIDL\01\6e\6f\01\00\00" == "(null)" : (opt empty) "opt: parsing null : opt empty"; +assert blob "DIDL\01\6e\7e\01\00\00" == "(null)" : (opt bool) "opt: parsing null : opt bool "; +assert blob "DIDL\01\6e\7e\01\00\01\00" == "(opt false)" : (opt bool) "opt: parsing opt false : opt bool"; +assert blob "DIDL\01\6e\7e\01\00\01\01" == "(opt true)" : (opt bool) "opt: parsing opt true : opt bool"; +assert blob "DIDL\01\6e\7e\01\00\01\02" !: (opt bool) "opt: parsing invalid bool at opt bool"; + +// nested opt +assert blob "DIDL\02\6e\01\6e\7e\01\00\00" == "(null)" : (opt opt bool) "opt: parsing null : opt opt bool"; +assert blob "DIDL\02\6e\01\6e\7e\01\00\01\00" == "(opt null)" : (opt opt bool) "opt: parsing opt null : opt opt bool"; +assert blob "DIDL\02\6e\01\6e\7e\01\00\01\01\00" == "(opt opt false)" : (opt opt bool) "opt: parsing opt opt false : opt opt bool"; + +// opt subtype to constituent +assert blob "DIDL\00\01\7e\00" == "(opt false)" : (opt bool) "opt: parsing false : opt bool"; +assert blob "DIDL\00\01\7e\01" == "(opt true)" : (opt bool) "opt: parsing true : opt bool"; +assert blob "DIDL\00\01\7e\02" !: (opt bool) "opt: parsing invalid bool at opt bool"; +assert blob "DIDL\01\6e\7f\01\00\00" == "(null)" : (opt opt null) "opt: parsing (null : opt null) at opt opt null gives null, not opt null"; +assert blob "DIDL\00\01\7e\01" == "(null)" : (opt opt bool) "opt: parsing true : opt opt bool gives null"; +assert blob "DIDL\00\01\7e\01" == "(null)" : (Opt) "opt: parsing (true : bool) at \"fix opt\" gives null"; +assert blob "DIDL\01\6c\01\00\00\01\00" !: (OptRec) "opt: parsing \"fix record\" at \"fix record opt\" fails"; + +// special opt subtyping +assert blob "DIDL\00\01\70" == "(null)" : (opt nat) "reserved <: opt nat"; +assert blob "DIDL\01\6e\7e\01\00\00" == "(null)" : (opt nat) "null : opt bool <: opt nat"; +assert blob "DIDL\01\6e\7e\01\00\01\01" == "(null)" : (opt nat) "opt true : opt bool <: opt nat"; +assert blob "DIDL\01\6e\7e\01\00\01\02" !: (opt nat) "opt bool <: opt nat with invalid boolean value"; +assert blob "DIDL\02\6e\01\6e\7e\01\00\01\01\01" == "(null)" : (opt nat) "opt opt true : opt opt bool <: opt nat"; +assert blob "DIDL\02\6e\01\6e\7e\01\00\01\01\01" == "(opt null)" : (opt opt nat) "opt opt true : opt opt bool <: opt opt nat"; +assert blob "DIDL\02\6e\01\6b\01\00\7e\01\00\01\00\01" == "(null)" : (opt variant { 0 : int }) "opt: recovered coercion error under variant"; + +// special record field rules +assert blob "DIDL\01\6c\00\01\00" == "(record { foo = null })" : (record { foo : opt bool }) "missing optional field"; +assert blob "DIDL\01\6c\00\01\00" == "(record { foo = \"☃\" })" : (record { foo : reserved }) "missing reserved field"; + // variant assert "(variant {})" !: (variant {}) "variant: no empty value"; assert blob "DIDL\01\6b\00\01\00" !: (variant {}) "variant: no empty value"; assert blob "DIDL\01\6b\01\00\7f\01\00\00" == "(variant {0})" : (variant {0}) "variant: numbered field"; assert blob "DIDL\01\6b\01\00\7f\01\00\00\2a" !: (variant {0:int}) "variant: type mismatch"; - -// TODO Should this pass? -assert blob "DIDL\01\6b\02\00\7f\01\7c\01\00\01\2a" : (variant {0:int; 1:int}) "??? variant: type mismatch"; +assert blob "DIDL\01\6b\02\00\7f\01\7c\01\00\01\2a" : (variant {0:int; 1:int}) "variant: type mismatch in unused tag"; assert blob "DIDL\01\6b\01\00\7f\01\00\00" == "(variant {0})" : (variant {0;1}) "variant: ignore field"; assert blob "DIDL\01\6b\02\00\7f\01\7f\01\00\00" == "(variant {0})" : (variant {0}) "variant: ignore field"; assert blob "DIDL\01\6b\02\00\7f\01\7f\01\00\01" == "(variant {1})" : (variant {1;2}) "variant: change index"; @@ -101,6 +136,8 @@ assert blob "DIDL\01\6b\01\00\7f\01\00\01" ! assert blob "DIDL\01\6b\02\00\7f\00\7f\01\00\00" !: (variant {0}) "variant: duplicate fields"; assert blob "DIDL\01\6b\03\00\7f\01\7f\01\7f\01\00\00" !: (variant {0}) "variant: duplicate fields"; assert blob "DIDL\01\6b\02\01\7f\00\7f\01\00\00" !: (variant {0;1}) "variant: unsorted"; +assert blob "DIDL\01\6b\03\00\7f\01\7f\ff\ff\ff\ff\0f\7f\01\00\00" : (variant {0;1;4294967295}) "variant: ok with maxInt"; +assert blob "DIDL\01\6b\03\00\7f\ff\ff\ff\ff\0f\7f\01\7f\01\00\00" !: (variant {0;1;4294967295}) "variant: unsorted with maxInt"; assert blob "DIDL\01\6b\02\b3\d3\c9\01\7f\e6\fd\d5\01\7f\01\00\00" == "(variant { Bar })" : (variant { Foo; Bar; }) "variant: enum"; assert blob "DIDL\01\6b\01\cd\84\b0\05\7f\01\00\00" == "(variant { \"☃\" })" : (variant { "☃" }) "variant: unicode field"; assert blob "DIDL\01\6b\02\bc\8a\01\71\c5\fe\d2\01\71\01\00\00\04\67\6f\6f\64" diff --git a/test/prim.test.did b/test/prim.test.did index 90d055d53..2035f9ec9 100644 --- a/test/prim.test.did +++ b/test/prim.test.did @@ -52,6 +52,12 @@ assert blob "DIDL\00\01\7c\80\7f" == "(-128)" : (int) "int: leb not overlong w assert blob "DIDL\00\01\7c\80\80\98\f4\e9\b5\ca\ea\00" == "(60000000000000000)" : (int) "int: big number"; assert blob "DIDL\00\01\7c\80\80\e8\8b\96\ca\b5\95\7f" == "(-60000000000000000)" : (int) "int: negative big number"; +assert blob "DIDL\00\01\7d\00" == "(0)" : (int) "nat <: int: 0"; +assert blob "DIDL\00\01\7d\01" == "(1)" : (int) "nat <: int: 1"; +assert blob "DIDL\00\01\7d\7f" == "(127)" : (int) "nat <: int: leb (two bytes)"; +assert blob "DIDL\00\01\7d\80\01" == "(128)" : (int) "nat <: int: leb (two bytes)"; +assert blob "DIDL\00\01\7d\ff\7f" == "(16383)" : (int) "nat <: int: leb (two bytes, all bits)"; + assert blob "DIDL\00\01\7b\00" == "(0)" : (nat8) "nat8: 0"; assert blob "DIDL\00\01\7b\01" == "(1)" : (nat8) "nat8: 1"; assert blob "DIDL\00\01\7b\ff" == "(255)" : (nat8) "nat8: 255";