From d1306ffcf528b5d901603d777dfa15d108ef29ea Mon Sep 17 00:00:00 2001 From: Hitalo Souza <63821277+enghitalo@users.noreply.github.com> Date: Wed, 11 Jan 2023 05:18:45 -0300 Subject: [PATCH] checker,json2: relax checking of x.enum = integer at comptime; refactor json2 to clean it up (#16926) --- vlib/v/checker/assign.v | 11 +++++++- .../checker/tests/assign_enum_at_comptime.out | 7 +++++ .../checker/tests/assign_enum_at_comptime.vv | 15 +++++++++++ vlib/v/tests/comptime_field_selector_test.v | 17 ++++++++++++ vlib/x/json2/encode_struct_test.v | 6 +++++ vlib/x/json2/encoder.v | 5 ++++ vlib/x/json2/json2.v | 7 +++++ .../json_test.v | 26 +++++++++---------- .../json_todo_test.vv | 19 +++++++------- 9 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 vlib/v/checker/tests/assign_enum_at_comptime.out create mode 100644 vlib/v/checker/tests/assign_enum_at_comptime.vv diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 28a459ce53..215e0b7b40 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -593,7 +593,16 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { node.pos) } } else { - c.error('cannot assign to `${left}`: ${err.msg()}', right.pos()) + // allow `t.$(field.name) = 0` where `t.$(field.name)` is a enum + if c.inside_comptime_for_field && left is ast.ComptimeSelector { + field_sym := c.table.sym(c.unwrap_generic(c.comptime_fields_default_type)) + + if field_sym.kind == .enum_ && !right_type.is_int() { + c.error('enums can only be assigned `int` values', right.pos()) + } + } else { + c.error('cannot assign to `${left}`: ${err.msg()}', right.pos()) + } } } } diff --git a/vlib/v/checker/tests/assign_enum_at_comptime.out b/vlib/v/checker/tests/assign_enum_at_comptime.out new file mode 100644 index 0000000000..7508ef78ec --- /dev/null +++ b/vlib/v/checker/tests/assign_enum_at_comptime.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/assign_enum_at_comptime.vv:13:21: error: enums can only be assigned `int` values + 11 | + 12 | $for field in TestStruct.fields { + 13 | t.$(field.name) = '1' + | ~~~ + 14 | } + 15 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/assign_enum_at_comptime.vv b/vlib/v/checker/tests/assign_enum_at_comptime.vv new file mode 100644 index 0000000000..15c90f31fc --- /dev/null +++ b/vlib/v/checker/tests/assign_enum_at_comptime.vv @@ -0,0 +1,15 @@ +enum TestEnum { + one = 1 +} + +struct TestStruct { + test TestEnum +} + +fn main() { + mut t := TestStruct{} + + $for field in TestStruct.fields { + t.$(field.name) = '1' + } +} diff --git a/vlib/v/tests/comptime_field_selector_test.v b/vlib/v/tests/comptime_field_selector_test.v index 39695bb622..fb7061bf8c 100644 --- a/vlib/v/tests/comptime_field_selector_test.v +++ b/vlib/v/tests/comptime_field_selector_test.v @@ -5,6 +5,14 @@ mut: name string } +enum TestEnum { + one = 1 +} + +struct TestStruct { + test TestEnum +} + fn comptime_field_selector_read[T]() []string { mut t := T{} t.name = '2' @@ -42,6 +50,15 @@ fn test_comptime_field_selector_write() { assert res.name == '1' } +fn test_comptime_field_selector_write_enum() { + mut t := TestStruct{} + + $for field in TestStruct.fields { + t.$(field.name) = 1 + } + assert t.test == .one +} + struct Foo2 { f Foo } diff --git a/vlib/x/json2/encode_struct_test.v b/vlib/x/json2/encode_struct_test.v index fcb51eadaa..7c9b3c3118 100644 --- a/vlib/x/json2/encode_struct_test.v +++ b/vlib/x/json2/encode_struct_test.v @@ -63,6 +63,12 @@ fn test_types() { val: 1 } }) == '{"val":{"val":1}}' + + assert json.encode(StructType[Enumerates]{}) == '{"val":0}' + assert json.encode(StructType[Enumerates]{ val: Enumerates.a }) == '{"val":0}' + assert json.encode(StructType[Enumerates]{ val: Enumerates.d }) == '{"val":3}' + assert json.encode(StructType[Enumerates]{ val: Enumerates.e }) == '{"val":99}' + assert json.encode(StructType[Enumerates]{ val: Enumerates.f }) == '{"val":100}' } fn test_option_types() { diff --git a/vlib/x/json2/encoder.v b/vlib/x/json2/encoder.v index dec60b0cb9..c5ba866d60 100644 --- a/vlib/x/json2/encoder.v +++ b/vlib/x/json2/encoder.v @@ -207,6 +207,9 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut wr io.Writer) ! { e.encode_string(parsed_time.format_rfc3339(), mut wr)! } $else $if field.is_array { e.encode_array(value, level + 1, mut wr)! + } $else $if field.is_enum { + option_value := val.$(field.name) as ?int + wr.write(Any(option_value).int().str().bytes())! } $else $if field.is_alias { match field.unaliased_typ { typeof[string]().idx { @@ -253,6 +256,8 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut wr io.Writer) ! { e.encode_array(value, level + 1, mut wr)! } $else $if field.is_struct { e.encode_struct(value, level + 1, mut wr)! + } $else $if field.is_enum { + wr.write(int(val.$(field.name)).str().bytes())! } $else $if field.is_alias { match field.unaliased_typ { typeof[string]().idx { diff --git a/vlib/x/json2/json2.v b/vlib/x/json2/json2.v index 6a87cea87c..f43deea6a1 100644 --- a/vlib/x/json2/json2.v +++ b/vlib/x/json2/json2.v @@ -51,6 +51,13 @@ pub fn decode[T](src string) !T { typ.$(field.name) = res[field.name]!.str() } $else $if field.typ is time.Time { typ.$(field.name) = res[field.name]!.to_time()! + } $else $if field.is_array { + // typ.$(field.name) = res[field.name]!.arr() + } $else $if field.is_struct { + } $else $if field.is_enum { + typ.$(field.name) = res[field.name]!.int() + } $else $if field.is_alias { + } $else $if field.is_map { } $else { return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose") } diff --git a/vlib/x/json2/json_module_compatibility_test/json_test.v b/vlib/x/json2/json_module_compatibility_test/json_test.v index 0da07fe3db..2f9b8b6519 100644 --- a/vlib/x/json2/json_module_compatibility_test/json_test.v +++ b/vlib/x/json2/json_module_compatibility_test/json_test.v @@ -9,28 +9,28 @@ enum JobTitle { struct Employee { pub mut: - name string - age int - salary f32 - // title JobTitle //! FIXME - decode - // sub_employee SubEmployee //! FIXME - decode + name string + age int + salary f32 + title JobTitle + sub_employee SubEmployee //! FIXME - decode } -struct SubEmployee { +pub struct SubEmployee { pub mut: name string age int salary f32 - // title JobTitle //! FIXME - decode + title JobTitle } fn test_simple() { sub_employee := SubEmployee{ name: 'João' } - x := Employee{'Peter', 28, 95000.5} + x := Employee{'Peter', 28, 95000.5, .worker, sub_employee} s := json.encode[Employee](x) - assert s == '{"name":"Peter","age":28,"salary":95000.5}' + assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2,"sub_employee":{"name":"João","age":0,"salary":0.0,"title":0}}' y := json.decode[Employee](s) or { println(err) @@ -40,10 +40,10 @@ fn test_simple() { assert y.name == 'Peter' assert y.age == 28 assert y.salary == 95000.5 - // assert y.title == .worker //! FIXME - // assert y.sub_employee.name == 'Peter' - // assert y.sub_employee.age == 0 - // assert y.sub_employee.salary == 0.0 + assert y.title == .worker + // assert y.sub_employee.name == 'João' + assert y.sub_employee.age == 0 + assert y.sub_employee.salary == 0.0 // assert y.sub_employee.title == .worker //! FIXME } diff --git a/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv b/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv index abbb211d26..8fe7bc9ba1 100644 --- a/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv +++ b/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv @@ -25,7 +25,7 @@ pub struct Price { //! BUGFIX fn test_field_with_default_expr() { data := '[{"net":1},{"net":2,"currencyId":"cjson"}]' - prices := json.decode<[]Price>(data)! + prices := json.decode[[]Price](data)! assert prices == [Price{ net: 1 currency_id: 'cconst' @@ -38,7 +38,7 @@ fn test_field_with_default_expr() { //! BUGFIX - .from_json(res) fn test_decode_top_level_array() { s := '[{"name":"Peter", "age": 29}, {"name":"Bob", "age":31}]' - x := json.decode<[]Employee>(s) or { panic(err) } + x := json.decode[[]Employee](s) or { panic(err) } assert x.len == 2 assert x[0].name == 'Peter' assert x[0].age == 29 @@ -86,7 +86,7 @@ fn test_encode_decode_sumtype() { assert enc == '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"Time","value":${t.unix_time()}}]}' - dec := json.decode(enc)! + dec := json.decode[SomeGame](enc)! assert game.title == dec.title assert game.player == dec.player @@ -114,8 +114,8 @@ pub mut: fn test_parse_user() { s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}' - u2 := json.decode(s)! - u := json.decode(s)! + u2 := json.decode[User2](s)! + u := json.decode[User](s)! assert u.age == 10 assert u.last_name == 'Johnson' assert u.is_registered == true @@ -135,7 +135,7 @@ fn test_encode_decode_time() { } s := json.encode(user) assert s.contains('"reg_date":1608621780') - user2 := json.decode(s)! + user2 := json.decode[User2](s)! assert user2.reg_date.str() == '2020-12-22 07:23:00' } @@ -150,7 +150,7 @@ struct Country { //! BUGFIX - .from_json(res) fn test_struct_in_struct() { - country := json.decode('{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}')! + country := json.decode[Country]('{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}')! assert country.name == 'UK' assert country.cities.len == 2 assert country.cities[0].name == 'London' @@ -165,7 +165,7 @@ fn test_parse_map() { 'three': 3 'four': 4 } - out := json.decode('{"one":1,"two":2,"three":3,"four":4}')! + out := json.decode[map[string]int]('{"one":1,"two":2,"three":3,"four":4}')! assert out == expected } @@ -224,7 +224,7 @@ fn test_nested_type() { } out := json.encode(data) assert out == data_expected - data2 := json.decode(data_expected)! + data2 := json.decode[Data](data_expected)! assert data2.countries.len == data.countries.len for i in 0 .. 1 { assert data2.countries[i].name == data.countries[i].name @@ -333,7 +333,6 @@ fn test_decode_missing_maps_field() { assert '${info.maps}' == '{}' } - struct Foo3 { name string age int [omitempty]