diff --git a/vlib/json/json_test.v b/vlib/json/json_test.v index 74424f8f1b..24b2584430 100644 --- a/vlib/json/json_test.v +++ b/vlib/json/json_test.v @@ -2,7 +2,7 @@ import json struct Employee { name string - age int + age int } fn test_simple() { @@ -11,23 +11,24 @@ fn test_simple() { assert s == '{"name":"Peter","age":28}' y := json.decode(Employee, s) or { assert false + Employee{} } assert y.name == 'Peter' assert y.age == 28 } struct User2 { - age int - nums []int + age int + nums []int } struct User { - age int - nums []int - last_name string [json:lastName] - is_registered bool [json:IsRegistered] - typ int [json:'type'] - pets string [raw; json:'pet_animals'] + age int + nums []int + last_name string [json:lastName] + is_registered bool [json:IsRegistered] + typ int [json:'type'] + pets string [raw; json:'pet_animals'] } fn test_parse_user() { @@ -51,8 +52,15 @@ fn test_parse_user() { assert u.pets == '{"name":"Bob","animal":"Dog"}' } -fn test_encode_user(){ - usr := User{ age: 10, nums: [1,2,3], last_name: 'Johnson', is_registered: true, typ: 0, pets: 'foo'} +fn test_encode_user() { + usr := User{ + age: 10 + nums: [1, 2, 3] + last_name: 'Johnson' + is_registered: true + typ: 0 + pets: 'foo' + } expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}' out := json.encode(usr) println(out) @@ -60,17 +68,17 @@ fn test_encode_user(){ } struct Color { - space string - point string [raw] + space string + point string [raw] } fn test_raw_json_field() { - color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or { - println('text') - return - } - assert color.point == '{"Y":123}' - assert color.space == 'YCbCr' + color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or { + println('text') + return + } + assert color.point == '{"Y":123}' + assert color.space == 'YCbCr' } struct City { @@ -79,7 +87,7 @@ struct City { struct Country { cities []City - name string + name string } fn test_struct_in_struct() { @@ -92,6 +100,4 @@ fn test_struct_in_struct() { assert country.cities[0].name == 'London' assert country.cities[1].name == 'Manchester' println(country.cities) - } - diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 93f8886313..8da1abe720 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -43,8 +43,9 @@ pub: pub struct ExprStmt { pub: expr Expr - typ table.Type pos token.Position +pub mut: + typ table.Type } pub struct IntegerLiteral { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index a6e6bf32f5..4839af11fc 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -848,7 +848,9 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { c.error('json.decode: second argument needs to be a string', call_expr.pos) } typ := expr as ast.Type - return typ.typ.set_flag(.optional) + ret_type := typ.typ.set_flag(.optional) + call_expr.return_type = ret_type + return ret_type } // look for function in format `mod.fn` or `fn` (main/builtin) mut f := table.Fn{} @@ -1012,17 +1014,15 @@ fn (mut c Checker) type_implements(typ, inter_typ table.Type, pos token.Position } } -pub fn (mut c Checker) check_expr_opt_call(x ast.Expr, xtype table.Type, is_return_used bool) { - match x { - ast.CallExpr { - if it.return_type.flag_is(.optional) { - c.check_or_block(it, xtype, is_return_used) - } else if it.or_block.is_used && it.name != 'json.decode' { // TODO remove decode hack - c.error('unexpected `or` block, the function `$it.name` does not return an optional', - it.pos) - } +pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, xtype table.Type, is_return_used bool) { + if expr is ast.CallExpr { + call_expr := expr as ast.CallExpr + if call_expr.return_type.flag_is(.optional) { + c.check_or_block(call_expr, xtype, is_return_used) + } else if call_expr.or_block.is_used { + c.error('unexpected `or` block, the function `$call_expr.name` does not return an optional', + call_expr.pos) } - else {} } } @@ -1052,12 +1052,13 @@ pub fn (mut c Checker) check_or_block(mut call_expr ast.CallExpr, ret_type table } match last_stmt { ast.ExprStmt { - type_fits := c.table.check(c.expr(it.expr), ret_type) + it.typ = c.expr(it.expr) + type_fits := c.table.check(it.typ, ret_type) is_panic_or_exit := is_expr_panic_or_exit(it.expr) if type_fits || is_panic_or_exit { return } - type_name := c.table.get_type_symbol(c.expr(it.expr)).name + type_name := c.table.get_type_symbol(it.typ).name expected_type_name := c.table.get_type_symbol(ret_type).name c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`', it.pos) @@ -1509,9 +1510,9 @@ fn (mut c Checker) stmt(node ast.Stmt) { c.enum_decl(it) } ast.ExprStmt { - etype := c.expr(it.expr) + it.typ = c.expr(it.expr) c.expected_type = table.void_type - c.check_expr_opt_call(it.expr, etype, false) + c.check_expr_opt_call(it.expr, it.typ, false) } ast.FnDecl { c.fn_decl(it) @@ -1986,6 +1987,7 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type { match branch.stmts[branch.stmts.len - 1] { ast.ExprStmt { ret_type = c.expr(it.expr) + it.typ = ret_type } else { // TODO: ask alex about this @@ -2106,14 +2108,14 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt { last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt c.expected_type = former_expected_type - expr_type := c.expr(last_expr.expr) - if expr_type != node.typ { - // first branch of if expression + last_expr.typ = c.expr(last_expr.expr) + if last_expr.typ != node.typ { if node.typ == table.void_type { + // first branch of if expression node.is_expr = true - node.typ = expr_type + node.typ = last_expr.typ } else { - c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(expr_type)}`', + c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(last_expr.typ)}`', node.pos) } } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 745b1e5425..db679f2b35 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -903,16 +903,11 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { g.is_assign_rhs = true g.expr(assign_stmt.right[0]) g.is_assign_rhs = false - if is_optional { - val := assign_stmt.right[0] - match val { - ast.CallExpr { - or_stmts = it.or_block.stmts - return_type = it.return_type - g.or_block(mr_var_name, or_stmts, return_type) - } - else {} - } + if is_optional && assign_stmt.right[0] is ast.CallExpr { + val := assign_stmt.right[0] as ast.CallExpr + or_stmts = val.or_block.stmts + return_type = val.return_type + g.or_block(mr_var_name, or_stmts, return_type) } g.writeln(';') for i, ident in assign_stmt.left { @@ -969,7 +964,6 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { } else {} } - gen_or := is_call && return_type.flag_is(.optional) g.is_assign_rhs = true if blank_assign { if is_call { @@ -1060,9 +1054,6 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { g.expr_with_cast(val, assign_stmt.left_types[i], ident_var_info.typ) } } - if gen_or { - g.or_block(ident.name, or_stmts, return_type) - } } g.is_assign_rhs = false if g.inside_ternary == 0 { @@ -2945,6 +2936,7 @@ fn (mut g Gen) insert_before_stmt(s string) { // If the user is not using the optional return value. We need to pass a temp var // to access its fields (`.ok`, `.error` etc) // `os.cp(...)` => `Option bool tmp = os__cp(...); if (!tmp.ok) { ... }` +// Returns the type of the last stmt fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Type) { cvar_name := c_name(var_name) mr_styp := g.base_type(return_type) @@ -2952,50 +2944,37 @@ fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Typ g.writeln('if (!${cvar_name}.ok) {') g.writeln('\tstring err = ${cvar_name}.v_error;') g.writeln('\tint errcode = ${cvar_name}.ecode;') - last_type, type_of_last_expression := g.type_of_last_statement(stmts) - if last_type == 'v.ast.ExprStmt' && type_of_last_expression != 'void' { + if stmts.len > 0 && stmts[stmts.len - 1] is ast.ExprStmt && (stmts[stmts.len - 1] as ast.ExprStmt).typ != + table.void_type { g.indent++ for i, stmt in stmts { if i == stmts.len - 1 { - g.indent-- - g.write('\t*(${mr_styp}*) ${cvar_name}.data = ') + expr_stmt := stmt as ast.ExprStmt + g.stmt_path_pos << g.out.len + g.write('*(${mr_styp}*) ${cvar_name}.data = ') + is_opt_call := expr_stmt.expr is ast.CallExpr && expr_stmt.typ.flag_is(.optional) + if is_opt_call { + g.write('*(${mr_styp}*) ') + } + g.expr(expr_stmt.expr) + if is_opt_call { + g.write('.data') + } + if g.inside_ternary == 0 && !(expr_stmt.expr is ast.IfExpr) { + g.writeln(';') + } + g.stmt_path_pos.delete(g.stmt_path_pos.len - 1) + } else { + g.stmt(stmt) } - g.stmt(stmt) } + g.indent-- } else { g.stmts(stmts) } g.write('}') } -fn (mut g Gen) type_of_last_statement(stmts []ast.Stmt) (string, string) { - mut last_type := '' - mut last_expr_result_type := '' - if stmts.len > 0 { - last_stmt := stmts[stmts.len - 1] - last_type = typeof(last_stmt) - if last_type == 'v.ast.ExprStmt' { - match last_stmt { - ast.ExprStmt { - it_expr_type := typeof(it.expr) - if it_expr_type == 'v.ast.CallExpr' { - g.writeln('\t // typeof it_expr_type: $it_expr_type') - last_expr_result_type = g.type_of_call_expr(it.expr) - } else { - last_expr_result_type = it_expr_type - } - } - else { - last_expr_result_type = last_type - } - } - } - } - g.writeln('\t// last_type: $last_type') - g.writeln('\t// last_expr_result_type: $last_expr_result_type') - return last_type, last_expr_result_type -} - fn (mut g Gen) type_of_call_expr(node ast.Expr) string { match node { ast.CallExpr { return g.typ(it.return_type) } diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 6f22bdee61..77d02b9b0f 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -284,7 +284,14 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { if node.should_be_skipped { return } - gen_or := !g.is_assign_rhs && node.or_block.stmts.len > 0 + gen_or := node.or_block.stmts.len > 0 + cur_line := if gen_or && g.is_assign_rhs { + line := g.go_before_stmt(0) + g.out.write(tabs[g.indent]) + line + } else { + '' + } tmp_opt := if gen_or { g.new_tmp_var() } else { '' } if gen_or { styp := g.typ(node.return_type) @@ -297,6 +304,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { } if gen_or { g.or_block(tmp_opt, node.or_block.stmts, node.return_type) + g.write('\n${cur_line}${tmp_opt}') } } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 18ba3fa713..574446aa90 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -45,6 +45,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp // `foo() or {}`` mut or_stmts := []ast.Stmt{} if p.tok.kind == .key_orelse { + was_inside_or_expr := p.inside_or_expr p.inside_or_expr = true p.next() p.open_scope() @@ -63,7 +64,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp is_or_block_used = true or_stmts = p.parse_block_no_scope() p.close_scope() - p.inside_or_expr = false + p.inside_or_expr = was_inside_or_expr } if p.tok.kind == .question { // `foo()?` diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 209695cad4..e749ccc4b3 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -668,13 +668,12 @@ fn (mut p Parser) parse_multi_expr() ast.Stmt { expr: p.assign_expr(collected[0]) pos: epos } - } else { - return ast.ExprStmt{ - expr: p.assign_expr(ast.ConcatExpr{ - vals: collected - }) - pos: epos - } + } + return ast.ExprStmt{ + expr: p.assign_expr(ast.ConcatExpr{ + vals: collected + }) + pos: epos } } else { if collected.len == 1 { @@ -1000,12 +999,10 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { is_used: is_or_block_used } } - mut node := ast.Expr{} - node = mcall_expr if is_filter { p.close_scope() } - return node + return mcall_expr } sel_expr := ast.SelectorExpr{ expr: left diff --git a/vlib/v/tests/option_default_values_test.v b/vlib/v/tests/option_default_values_test.v index 4d0eea62e0..4ec908419a 100644 --- a/vlib/v/tests/option_default_values_test.v +++ b/vlib/v/tests/option_default_values_test.v @@ -1,4 +1,3 @@ - struct Abc { x int } @@ -24,12 +23,18 @@ fn string_0(x int) ?string { return '$x' } +fn b_0(b bool) ?bool { + if b == false { + return error('my error 4') + } + return b +} + fn return_a_string() string { return 'abcdef' } // - fn test_optional_int() { a := i_0(0) or { 4999 @@ -41,6 +46,18 @@ fn test_optional_int() { assert b == 4123 } +/* +fn test_optional_bool() { + a := true && b_0(false) { + true + } + assert a == true + b := false || b_0(true) { + false + } + assert b == true +} +*/ fn test_optional_struct() { sa := struct_0(0) or { Abc{7999} @@ -58,6 +75,10 @@ fn test_optional_with_statements_before_last_expression() { Abc{12345} } assert s.x == 12345 + b := b_0(true) or { + false + } + assert b == true } fn test_optional_with_fn_call_as_last_expression() { @@ -75,3 +96,61 @@ fn test_optional_with_fn_call_last_expr_and_preceding_statements() { } assert s == 'abcdef' } + +fn test_nested_optional() { + a := i_0(1) or { + b := i_0(0) or { + 3 + } + b + } + assert a == 1 + b := i_0(0) or { + c := i_0(1) or { + 3 + } + c + } + assert b == 1 + c := i_0(0) or { + d := i_0(0) or { + 3 + } + d + } + assert c == 3 +} + +fn test_nested_optional_with_opt_fn_call_as_last_value() { + a := i_0(1) or { + i_0(0) or { + 3 + } + } + assert a == 1 + b := i_0(0) or { + i_0(1) or { + 3 + } + } + assert b == 1 + c := i_0(0) or { + i_0(0) or { + 3 + } + } + assert c == 3 + // TODO Enable once optional in boolean expressions are working + // d := b_0(true) or { + // false && b_0(true) or { + // true + // } + // } + // assert d == false + // e := b_0(true) or { + // true && b_0(true) or { + // false + // } + // } + // assert e == false +} diff --git a/vlib/v/tests/option_test.v b/vlib/v/tests/option_test.v index d1e0b5ffd1..1d0a479de6 100644 --- a/vlib/v/tests/option_test.v +++ b/vlib/v/tests/option_test.v @@ -97,6 +97,41 @@ fn test_q() { // assert foo_ok()? == true } +fn or_return_val() int { + a := ret_none() or { + return 1 + } + return a +} + +fn or_return_error() ?int { + a := ret_none() or { + return error('Nope') + } + return a +} + +fn or_return_none() ?int { + a := ret_none() or { + return none + } + return a +} + +fn test_or_return() { + assert or_return_val() == 1 + if _ := or_return_error() { + assert false + } else { + assert true + } + if _ := or_return_none() { + assert false + } else { + assert true + } +} + fn test_reassignment() { mut x2 := foo_ok() or { assert false @@ -107,7 +142,7 @@ fn test_reassignment() { assert x2 == 100 x2 += 1 assert x2 == 101 - /// + // mut x3 := 0 x3 = foo_ok() or { assert false @@ -170,11 +205,9 @@ fn opt_ptr(a &int) ?&int { fn test_opt_ptr() { if true { - } // - else{ - + else { } a := 3 mut r := opt_ptr(&a) or { @@ -207,7 +240,6 @@ fn test_multi_return_opt() { } } */ - fn foo() ?void { return error('something') } @@ -220,7 +252,6 @@ fn test_optional_void() { } } - fn bar() ? { return error('bar error') } @@ -232,3 +263,13 @@ fn test_optional_void_only_question() { return } } + +fn test_optional_void_with_empty_or() { + foo() or {} + assert true +} + +fn test_optional_val_with_empty_or() { + ret_none() or {} + assert true +}