Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revise existing tests to match #311 (subtype checking only for refere… #323

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
15 changes: 10 additions & 5 deletions test/construct.test.did
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ assert blob "DIDL\00\00" == "(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\00" !: (vec int8) "vec: non subtype empty";
assert blob "DIDL\01\6d\7c\01\00\00" : (vec int8) "vec: non subtype 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\00\01\00\00" == "(vec {})" : (Vec) "vec: recursive vector";
Expand Down Expand Up @@ -139,10 +139,10 @@ assert "(variant {})" !
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";
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\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;1} !<: variant {0}";
assert blob "DIDL\01\6b\02\00\7f\01\7f\01\00\00" == "(null)" : (opt variant {0}) "variant {0;1} <: opt variant {0}";
assert blob "DIDL\01\6b\02\00\7f\01\7f\01\00\00" : (variant {0}) "variant {0;1} <: variant {0}";
assert blob "DIDL\01\6b\02\00\7f\01\7f\01\00\00" == "(variant {0})" : (opt variant {0}) "variant {0;1} <: opt variant {0}";
nomeata marked this conversation as resolved.
Show resolved Hide resolved
assert blob "DIDL\01\6b\02\00\7f\01\7f\01\00\01" == "(variant {1})" : (variant {0;1;2}) "variant: change index";
assert blob "DIDL\01\6b\01\00\7f\01\00\00" !: (variant {1}) "variant: missing field";
assert blob "DIDL\01\6b\01\00\7f\01\00\01" !: (variant {0}) "variant: index out of range";
Expand Down Expand Up @@ -188,7 +188,7 @@ assert blob "DIDL\02\6b\02\d1\a7\cf\02\7f\f1\f3\92\8e\04\01\6c\02\a0\d2\ac\a8\04
== "(variant { cons = record { head = 1; tail = variant { cons = record { head = 2; tail = variant { nil } } } } })" : (VariantList) "variant: list";

assert blob "DIDL\02\6b\02\d1\a7\cf\02\7f\f1\f3\92\8e\04\01\6c\02\a0\d2\ac\a8\04\7c\90\ed\da\e7\04\00\01\00\00"
== "(variant {nil}, null, null, null, null)" : (VariantList, opt VariantList, null, reserved, opt int) "variant: extra args";
== "(variant {nil}, null, null, null, null)" : (VariantList, opt VariantList, opt empty, reserved, opt int) "variant: extra args";

assert blob "DIDL\02\6b\02\d1\a7\cf\02\7f\f1\f3\92\8e\04\01\6c\02\a0\d2\ac\a8\04\7c\90\ed\da\e7\04\00\01\00\00" !: (VariantList, opt int, vec int) "non-null extra args";

Expand All @@ -203,3 +203,8 @@ assert blob "DIDL\07\6c\07\c3\e3\aa\02\01\d3\e3\aa\02\7e\d5\e3\aa\02\02\db\e3\aa

assert blob "DIDL\07\6c\07\c3\e3\aa\02\01\d3\e3\aa\02\7e\d5\e3\aa\02\02\db\e3\aa\02\01\a2\e5\aa\02\04\bb\f1\aa\02\06\86\8e\b7\02\75\6c\02\d3\e3\aa\02\7e\86\8e\b7\02\75\6b\02\d1\a7\cf\02\7f\f1\f3\92\8e\04\03\6c\02\a0\d2\ac\a8\04\7c\90\ed\da\e7\04\02\6e\05\6c\02\a0\d2\ac\a8\04\7c\90\ed\da\e7\04\04\6b\02\d3\e3\aa\02\7f\86\8e\b7\02\7f\01\00\01\0b\00\00\00\01\00\00\0a\00\00\00\01\14\00\00\2a\00\00\00" !: (record { foo: int; new_field: bool }) "new record field";

// Future types
// This uses 0x67 for the “future type”; bump (well, decrement) once that
// becomes a concrete future type
assert blob "DIDL\01\67\00\02\00\7e\00\00\01" == "(null, true)" : (opt empty,bool) "skipping minimal future type";
assert blob "DIDL\01\67\03ABC\02\00\7e\05\00hello\01" == "(null,true)" : (opt empty,bool) "skipping future type with data";
14 changes: 11 additions & 3 deletions test/prim.test.did
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Encoding tests for primitive types

Corresponding to spec version version 0.1.3
Corresponding to spec version version 0.1.4
*/

// fundamentally wrong
Expand All @@ -19,11 +19,19 @@ assert blob "DIDL\00\01\7f" : () "Additional parameters are ignored";
assert blob "DIDL\00\01\6e" !: () "Not a primitive type";
assert blob "DIDL\00\01\5e" !: () "Out of range type";

// Missing arguments
assert blob "DIDL\00\00" !: (nat) "missing argument: nat fails";
assert blob "DIDL\00\00" !: (empty) "missing argument: empty fails";
assert blob "DIDL\00\00" !: (null) "missing argument: null fails";
assert blob "DIDL\00\00" == "(null)" : (opt empty) "missing argument: opt empty";
assert blob "DIDL\00\00" == "(null)" : (opt null) "missing argument: opt null";
assert blob "DIDL\00\00" == "(null)" : (opt nat) "missing argument: opt nat";
assert blob "DIDL\00\00" == blob "DIDL\00\01\70" : (reserved) "missing argument: reserved";

// primitive types
assert blob "DIDL\00\01\7f" : (null);
assert blob "DIDL\00\01\7e" !: (null) "wrong type";
assert blob "DIDL\00\01\7f\00" !: (null) "null: too long";
assert blob "DIDL\00\00" : (null) "null: extra null values";

assert blob "DIDL\00\01\7e\00" == "(false)" : (bool) "bool: false";
assert blob "DIDL\00\01\7e\01" == "(true)" : (bool) "bool: true";
Expand Down Expand Up @@ -180,12 +188,12 @@ assert blob "DIDL\00\01\70" == blob "DIDL\00\01\7f" : (reserved) "reser
assert blob "DIDL\00\01\70" == blob "DIDL\00\01\7e\01" : (reserved) "reserved from bool";
assert blob "DIDL\00\01\70" == blob "DIDL\00\01\7d\80\01" : (reserved) "reserved from nat";
assert blob "DIDL\00\01\70" == blob "DIDL\00\01\71\06Motoko" : (reserved) "reserved from text";
assert blob "DIDL\00\00" : (reserved) "reserved extra value";
assert blob "DIDL\00\01\71\05Motoko" !: (reserved) "reserved from too short text";
assert blob "DIDL\00\01\71\03\e2\28\a1" !: (reserved) "reserved from invalid utf8 text";

assert blob "DIDL\00\01\6f" !: (empty) "cannot decode empty type";
assert blob "DIDL\01\6e\6f\01\00\00" == "(null)" : (opt empty) "okay to decode non-empty value";


assert blob "DIDL\00\0a\7f\7e\7d\7c\7f\70\7f\7b\7a\79\01\2a\2a\2a\2a\00\2a\00\00\00"
== "(null, true, 42, 42, null, null, null, 42, 42, 42)" : (null, bool, nat, int, null, reserved, null, nat8, nat16, nat32) "multiple arguments";
164 changes: 164 additions & 0 deletions test/subtypes.test.did
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
Encoding tests for subtype tests in decoders

Corresponding to spec version version 0.1.4

This test file contains tests for the subtype check that decoders are expected
to perform upon references.

The shortest way to trigger a test for `t1 <: t2` is to pass `(func () -> (t1))`
and decode at type `(opt func () -> (t2))`, and check if the result is a
`reference` or `null`.

The patterns idioms here are thus

assert blob "DIDL\01\6a\00\01XX00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (t2)) "t1 <: t2";
assert blob "DIDL\01\6a\00\01XX\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (t2)) "t1 </: t2";

where XX is the a primitive type index of t1, or

assert blob "DIDL\02\6a\00\01\01\00XX\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (t2)) "t1 <: t2";
assert blob "DIDL\02\6a\00\01\01\00XX\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (t2)) "t1 </: t2";

where XX is a type entry table for a non-primitive type t1
*/

type Vec = vec Vec;
type EmptyRecord = record { 0 : EmptyRecord };
type EmptyVariant = variant { 0: EmptyVariant };

// some reflexive cases
assert blob "DIDL\01\6a\00\01\7f\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (null)) "null <: null";
assert blob "DIDL\01\6a\00\01\7e\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (bool)) "bool <: bool";
assert blob "DIDL\01\6a\00\01\7d\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (nat)) "nat <: nat";
assert blob "DIDL\01\6a\00\01\7c\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (int)) "int <: int";

// more basic cases
assert blob "DIDL\01\6a\00\01\7d\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (int)) "nat <: int";
assert blob "DIDL\01\6a\00\01\7f\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (nat)) "int </: nat";
assert blob "DIDL\01\6a\00\01\7d\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (nat8)) "nat </: nat8";
assert blob "DIDL\01\6a\00\01\7b\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (nat)) "nat8 </: nat";

// options. They are supertypes of anything really
assert blob "DIDL\01\6a\00\01\7d\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (opt bool)) "nat <: opt bool";
assert blob "DIDL\02\6a\00\01\01\00\6e\7e\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (opt bool)) "opt bool <: opt bool";
assert blob "DIDL\01\6a\00\01\7e\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (opt bool)) "bool <: opt bool";
assert blob "DIDL\02\6a\00\01\01\00\6e\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (opt opt nat)) "µ opt <: opt opt nat";

// optional record fields
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (record {})) "record {} <: record {}";
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (record { a : opt empty })) "record {} <: record { a : opt empty }";
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (record { a : opt null })) "record {} <: record { a : opt null }";
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (record { a : reserved })) "record {} <: record { a : reserved }";
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (record { a : empty })) "record {} </: record { a : empty }";
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (record { a : nat })) "record {} </: record { a : nat }";
assert blob "DIDL\02\6a\00\01\01\00\6c\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (record { a : null })) "record {} </: record { a : null }";

// optional func results
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> ())) "func () -> () <: func () -> ()";
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> (opt empty))) "func () -> () <: func () -> (opt empty)";
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> (opt null))) "func () -> () <: func () -> (opt null)";
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> (reserved))) "func () -> () <: func () -> (reserved)";
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (func () -> (empty))) "func () -> () </: func () -> (empty)";
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (func () -> (nat))) "func () -> () </: func () -> (nat)";
assert blob "DIDL\02\6a\00\01\01\00\6a\00\00\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (func () -> (null))) "func () -> () </: func () -> (null)";

// optional func arguments
assert blob "DIDL\03\6a\00\01\01\00\6a\01\02\00\00\6e\6f\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> ())) "func (opt empty) -> () <: func () -> ()";
assert blob "DIDL\03\6a\00\01\01\00\6a\01\02\00\00\6e\7f\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> ())) "func (opt null) -> () <: func () -> ()";
assert blob "DIDL\02\6a\00\01\01\00\6a\01\70\00\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (func () -> ())) "func (reserved) -> () <: func () -> ()";
assert blob "DIDL\02\6a\00\01\01\00\6a\01\6f\00\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (func () -> ())) "func (empty) -> () </: func () -> ()";
assert blob "DIDL\02\6a\00\01\01\00\6a\01\7d\00\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (func () -> ())) "func (nat) -> () </: func () -> ()";
assert blob "DIDL\02\6a\00\01\01\00\6a\01\7f\00\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (func () -> ())) "func (null) -> () </: func () -> ()";

// variants
assert blob "DIDL\02\6a\00\01\01\00\6b\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (variant {})) "variant {} <: variant {}";
assert blob "DIDL\02\6a\00\01\01\00\6b\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (variant {0 : nat})) "variant {} <: variant {0 : nat}";
assert blob "DIDL\02\6a\00\01\01\00\6b\01\00\7d\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (variant {0 : nat})) "variant {0 : nat} <: variant {0 : nat}";
assert blob "DIDL\02\6a\00\01\01\00\6b\01\00\7e\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (variant {0 : nat})) "variant {0 : bool} </: variant {0 : nat}";
assert blob "DIDL\02\6a\00\01\01\00\6b\01\00\7e\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (variant {1 : bool})) "variant {0 : bool} </: variant {1 : bool}";

// infinite types

assert blob "DIDL\02\6a\00\01\01\00\6c\01\00\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (EmptyRecord)) "(µ record) <: (µ record)";
assert blob "DIDL\02\6a\00\01\01\00\6c\01\00\01\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (empty)) "(µ record) </: empty";
Comment on lines +127 to +128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert blob "DIDL\02\6a\00\01\01\00\6c\01\00\01\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (empty)) "(µ record) </: empty";
// assert blob "DIDL\02\6a\00\01\01\00\6c\01\00\01\01\00\01\01\00\01m"
// == "(null)" : (opt func () -> (empty)) "(µ record) </: empty";

I will comment out this test for now. It's a bit implementation dependent. In Rust, we replace µ record with empty after parsing the type table, so we actually get opt func instead of null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why? I don't quite follow.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Haskell code I think I too replace mu record with empty, but only after subtype checking?

I believe Motoko gets this right as spec'ed.

If we want to allow implementations to differ here we should say so explicitly in the spec. But I don't know if that's a good idea.

Could you introduce in the rust code a MuRecord type that you can replace it with, which does the job of preventing your code to run into these vicious cycles, while still having the right subtypes relations?

Hmm, but mu record <: mu (record opt), so you probably really can't throw away the structure before subtype checking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved offline. \mu record is shorthand for \mu t. record { 0 = t } which I agree is isomorphic to empty with least fixed points. Do you agree @nomeata or is it actually not safe to identify them in the type table, even if they are isomorphic.
My worry is that Motoko will still distinguish the types that Rust is conflating, which might lead to (obscure) trouble elsewhere.

Copy link
Collaborator

@nomeata nomeata Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s safe, I think, but it is not according to specification. If we go that route, it would be consistent to equate all other empty types as well (empty, {empty, empty} etc.), and it should be part of the spec. And I would guess that @rossberg doesn’t like it :-)

assert blob "DIDL\01\6a\00\01\6f\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (EmptyRecord)) "empty <: (µ record)";
assert blob "DIDL\02\6a\00\01\01\00\6c\01\00\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (record {EmptyRecord})) "(µ record) <: record {µ record}";
assert blob "DIDL\03\6a\00\01\01\00\6c\01\00\02\6c\01\00\02\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (EmptyRecord)) "record {µ record} <: (µ record)";

assert blob "DIDL\02\6a\00\01\01\00\6b\01\00\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (EmptyVariant)) "(µ variant) <: (µ variant)";
assert blob "DIDL\02\6a\00\01\01\00\6b\01\00\01\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (empty)) "(µ variant) </: empty";
assert blob "DIDL\01\6a\00\01\6f\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (EmptyVariant)) "empty <: (µ variant)";
assert blob "DIDL\02\6a\00\01\01\00\6b\01\00\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (variant {0 : EmptyVariant})) "(µ variant) <: variant {µ variant}";
assert blob "DIDL\03\6a\00\01\01\00\6b\01\00\02\6b\01\00\02\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (EmptyVariant)) "variant {µ variant} <: (µ variant)";

assert blob "DIDL\02\6a\00\01\01\00\6d\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (Vec)) "(µ vec) <: (µ vec)";
assert blob "DIDL\02\6a\00\01\01\00\6d\01\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (empty)) "(µ vec) </: empty";
assert blob "DIDL\01\6a\00\01\6f\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (Vec)) "empty <: (µ vec)";
assert blob "DIDL\02\6a\00\01\01\00\6d\01\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (vec Vec)) "(µ vec) <: vec {µ vec}";
assert blob "DIDL\03\6a\00\01\01\00\6d\02\6d\02\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (Vec)) "vec {µ vec} <: (µ vec)";

// future types
// This uses 0x67 for the “future type”; bump (well, decrement) once that
// becomes a concrete future type

assert blob "DIDL\02\6a\00\01\01\00\67\00\01\00\01\01\00\01m"
== "(opt func \"aaaaa-aa\".m)" : (opt func () -> (opt empty)) "(future type) <: (opt empty)";
assert blob "DIDL\02\6a\00\01\01\00\67\00\01\00\01\01\00\01m"
== "(null)" : (opt func () -> (nat)) "(future type) <: (nat)";
nomeata marked this conversation as resolved.
Show resolved Hide resolved