From 9c0a7e7955e90ccf5b6579e68c8984fd9c4367cc Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 26 Sep 2024 19:39:34 +0300 Subject: [PATCH] json: support null sum types in decode() --- vlib/v/gen/c/json.v | 21 ++++++++++++++++++++- vlib/v/parser/assign.v | 4 +++- vlib/v/parser/parser.v | 5 ++++- vlib/v/pref/pref.v | 9 +++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index af842ecb9c..20e6b1bebc 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -373,8 +373,26 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st // DECODING (inline) $if !json_no_inline_sumtypes ? { + // Handle "key": null + // In this case the first variant must be used (something like InvalidExpr for example) + // An empty instance of the first variant is generated. + // This way the user can easily check if the sum type was not provided in json ("key":null): + // `if node.expr is InvalidExpr { ... }` + // (Do this only for structs) type_tmp := g.new_tmp_var() - dec.writeln('\tif (cJSON_IsObject(root) || (cJSON_IsArray(root) && cJSON_IsObject(root->child))) {') + first_variant := info.variants[0] + variant_typ := g.typ(first_variant) + fv_sym := g.table.sym(first_variant) + first_variant_name := fv_sym.cname + // println('KIND=${fv_sym.kind}') + if fv_sym.kind == .struct_ && !is_option && field_op != '->' { + dec.writeln('/*sum type ${fv_sym.name} ret_styp=${ret_styp}*/\tif (root->type == cJSON_NULL) { ') + dec.writeln('\t\tstruct ${first_variant_name} empty = {0};') + dec.writeln('res = ${variant_typ}_to_sumtype_${ret_styp}(&empty); } \n else ') + // dec.writeln('res = ${variant_typ}_to_sumtype_${sym.cname}(&empty); } \n else ') + } + // + dec.writeln('if (cJSON_IsObject(root) || (cJSON_IsArray(root) && cJSON_IsObject(root->child))) {') dec.writeln('\t\tcJSON* ${type_tmp} = cJSON_IsObject(root) ? js_get(root, "_type") : js_get(root->child, "_type");') dec.writeln('\t\tif (${type_tmp} != 0) {') dec.writeln('\t\t\tchar* ${type_var} = cJSON_GetStringValue(${type_tmp});') @@ -924,6 +942,7 @@ fn gen_js_get_opt(dec_name string, field_type string, styp string, tmp string, n value_field_type := field_type.replace('*', '_ptr') dec.writeln('\t${result_name}_${value_field_type.replace('*', '_ptr')} ${tmp} = {0};') dec.writeln('\tif (jsonroot_${tmp}) {') + // dec.writeln('\t\tif (jsonroot_${tmp}->type == cJSON_NULL) { puts("${name} IS JSON_NULL"); }') dec.writeln('\t\t${tmp} = ${dec_name}(jsonroot_${tmp});') dec.writeln('\t\tif (${tmp}.is_error) {') dec.writeln('\t\t\treturn (${result_name}_${styp}){ /*A*/ .is_error = true, .err = ${tmp}.err, .data = {0} };') diff --git a/vlib/v/parser/assign.v b/vlib/v/parser/assign.v index 62019b2a63..0655c9a6b5 100644 --- a/vlib/v/parser/assign.v +++ b/vlib/v/parser/assign.v @@ -197,7 +197,9 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr) ast.Stmt { ast.Ident { if op == .decl_assign { if p.scope.known_var(lx.name) { - return p.error_with_pos('redefinition of `${lx.name}`', lx.pos) + if !(p.pref.translated_go && lx.name in ['err', 'ok']) { + return p.error_with_pos('redefinition of `${lx.name}`', lx.pos) + } } mut share := unsafe { ast.ShareType(0) } if mut lx.info is ast.IdentVar { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 2ac6333623..062f4667b1 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2900,7 +2900,10 @@ fn (mut p Parser) name_expr() ast.Expr { } } else if p.peek_tok.kind == .lcbr && ((p.inside_if && lit0_is_capital && p.tok.lit.len > 1 && !known_var && language == .v) - || (p.inside_match_case && p.tok.kind == .name && p.peek_tok.is_next_to(p.tok))) { + || (p.inside_match_case && lit0_is_capital && p.tok.kind == .name + && p.peek_tok.is_next_to(p.tok))) { + // XTODO check iscap + //|| (p.inside_match_case && p.tok.kind == .name && p.peek_tok.is_next_to(p.tok))) { // `if a == Foo{} {...}` or `match foo { Foo{} {...} }` return p.struct_init(p.mod + '.' + p.tok.lit, .normal, is_option) } else if p.peek_tok.kind == .dot && lit0_is_capital && !known_var && language == .v { diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 2363bafa71..6c0b0b85c8 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -125,8 +125,9 @@ pub mut: profile_no_inline bool // when true, @[inline] functions would not be profiled profile_fns []string // when set, profiling will be off by default, but inside these functions (and what they call) it will be on. translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc - obfuscate bool // `v -obf program.v`, renames functions to "f_XXX" - hide_auto_str bool // `v -hide-auto-str program.v`, doesn't generate str() with struct data + translated_go bool = true // Are we running V code translated from Go? Allow err shadowing + obfuscate bool // `v -obf program.v`, renames functions to "f_XXX" + hide_auto_str bool // `v -hide-auto-str program.v`, doesn't generate str() with struct data // Note: passing -cg instead of -g will set is_vlines to false and is_debug to true, thus making v generate cleaner C files, // which are sometimes easier to debug / inspect manually than the .tmp.c files by plain -g (when/if v line number generation breaks). sanitize bool // use Clang's new "-fsanitize" option @@ -659,6 +660,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.translated = true res.gc_mode = .no_gc // no gc in c2v'ed code, at least for now } + '-translated-go' { + println('got -translated-go') + res.translated_go = true + } '-m32', '-m64' { res.m64 = arg[2] == `6` res.cflags += ' ${arg}'