diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index a7a26b1953..a42486e918 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -1459,6 +1459,7 @@ fn (t Tree) ident(node ast.Ident) &Node { obj.add('mut_pos', t.pos(node.mut_pos)) obj.add('obj', t.scope_object(node.obj)) obj.add('scope', t.number_node(int(node.scope))) + obj.add_terse('or_expr', t.or_expr(node.or_expr)) return obj } diff --git a/vlib/builtin/option.v b/vlib/builtin/option.v index 5514392905..ed2e0ce2fe 100644 --- a/vlib/builtin/option.v +++ b/vlib/builtin/option.v @@ -125,6 +125,16 @@ struct _option { // derived _option_xxx types } +fn _option_none(data voidptr, mut option _option, size int) { + unsafe { + *option = _option{ + state: 2 + } + // use err to get the end of OptionBase and then memcpy into it + vmemcpy(&u8(&option.err) + sizeof(IError), data, size) + } +} + fn _option_ok(data voidptr, mut option _option, size int) { unsafe { *option = _option{} diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 45586b783e..a9722453aa 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -822,13 +822,14 @@ pub: mut_pos token.Pos comptime bool pub mut: - scope &Scope = unsafe { nil } - obj ScopeObject - mod string - name string - kind IdentKind - info IdentInfo - is_mut bool // if mut *token* is before name. Use `is_mut()` to lookup mut variable + scope &Scope = unsafe { nil } + obj ScopeObject + mod string + name string + kind IdentKind + info IdentInfo + is_mut bool // if mut *token* is before name. Use `is_mut()` to lookup mut variable + or_expr OrExpr } pub fn (i &Ident) is_mut() bool { diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 778d822bbc..8b559ef76c 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -56,7 +56,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.error('unexpected `mut` on right-hand side of assignment', right.mut_pos) } } - if mut right is ast.None { + if is_decl && mut right is ast.None { c.error('cannot assign a `none` value to a variable', right.pos) } // Handle `left_name := unsafe { none }` @@ -112,6 +112,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } is_blank_ident := left.is_blank_ident() mut left_type := ast.void_type + mut var_option := false if !is_decl && !is_blank_ident { if left in [ast.Ident, ast.SelectorExpr] { c.prevent_sum_type_unwrapping_once = true @@ -123,6 +124,10 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.is_index_assign = false c.expected_type = c.unwrap_generic(left_type) } + if c.inside_comptime_for_field && mut left is ast.ComptimeSelector { + left_type = c.comptime_fields_default_type + c.expected_type = c.unwrap_generic(left_type) + } if node.right_types.len < node.left.len { // first type or multi return types added above old_inside_ref_lit := c.inside_ref_lit if mut left is ast.Ident { @@ -149,6 +154,9 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } } } + if right.or_expr.kind in [.propagate_option, .block] { + right_type = right_type.clear_flag(.option) + } } else if right is ast.ComptimeSelector { right_type = c.comptime_fields_default_type } @@ -193,6 +201,10 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { // Make sure the variable is mutable c.fail_if_immutable(left) // left_type = c.expr(left) + // if right is ast.None && !left_type.has_flag(.option) { + // println(left_type) + // c.error('cannot assign a `none` value to a non-option variable', right.pos()) + // } } if right_type.is_ptr() && left_type.is_ptr() { if mut right is ast.Ident { @@ -271,6 +283,9 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { if ident_var_info.share == .atomic_t { left_type = left_type.set_flag(.atomic_f) } + if ident_var_info.is_option { + var_option = true + } node.left_types[i] = left_type ident_var_info.typ = left_type left.info = ident_var_info @@ -618,7 +633,9 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.error('enums can only be assigned `int` values', right.pos()) } } else { - c.error('cannot assign to `${left}`: ${err.msg()}', right.pos()) + if !var_option || (var_option && right_type_unwrapped != ast.none_type) { + c.error('cannot assign to `${left}`: ${err.msg()}', right.pos()) + } } } } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 86f8e7f2c5..d5ff9b21cb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -990,12 +990,12 @@ fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Typ } if expr_ret_type.has_flag(.option) || expr_ret_type.has_flag(.result) { return_modifier_kind := if expr_ret_type.has_flag(.option) { - 'an option' + 'an Option' } else { 'a result' } return_modifier := if expr_ret_type.has_flag(.option) { '?' } else { '!' } - if expr.or_block.kind == .absent { + if expr_ret_type.has_flag(.result) && expr.or_block.kind == .absent { if c.inside_defer { c.error('${expr.name}() returns ${return_modifier_kind}, so it should have an `or {}` block at the end', expr.pos) @@ -1004,14 +1004,16 @@ fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Typ expr.pos) } } else { - c.check_or_expr(expr.or_block, ret_type, expr_ret_type) + if expr.or_block.kind != .absent { + c.check_or_expr(expr.or_block, ret_type, expr_ret_type, expr) + } } - return ret_type.clear_flag(.option).clear_flag(.result) + return ret_type.clear_flag(.result) } else if expr.or_block.kind == .block { - c.error('unexpected `or` block, the function `${expr.name}` does not return an option or a result', + c.error('unexpected `or` block, the function `${expr.name}` does not return an Option or a result', expr.or_block.pos) } else if expr.or_block.kind == .propagate_option { - c.error('unexpected `?`, the function `${expr.name}` does not return an option', + c.error('unexpected `?`, the function `${expr.name}` does not return an Option', expr.or_block.pos) } else if expr.or_block.kind == .propagate_result { c.error('unexpected `!`, the function `${expr.name}` does not return a result', @@ -1020,12 +1022,12 @@ fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Typ } else if expr is ast.SelectorExpr && c.table.sym(ret_type).kind != .chan { if expr.typ.has_flag(.option) || expr.typ.has_flag(.result) { with_modifier_kind := if expr.typ.has_flag(.option) { - 'an option' + 'an Option' } else { 'a result' } with_modifier := if expr.typ.has_flag(.option) { '?' } else { '!' } - if expr.or_block.kind == .absent { + if expr.typ.has_flag(.result) && expr.or_block.kind == .absent { if c.inside_defer { c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have an `or {}` block at the end', expr.pos) @@ -1034,21 +1036,23 @@ fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Typ expr.pos) } } else { - c.check_or_expr(expr.or_block, ret_type, expr.typ) + if expr.or_block.kind != .absent { + c.check_or_expr(expr.or_block, ret_type, expr.typ, expr) + } } - return ret_type.clear_flag(.option).clear_flag(.result) + return ret_type.clear_flag(.result) } else if expr.or_block.kind == .block { - c.error('unexpected `or` block, the field `${expr.field_name}` is neither an option, nor a result', + c.error('unexpected `or` block, the field `${expr.field_name}` is neither an Option, nor a result', expr.or_block.pos) } else if expr.or_block.kind == .propagate_option { - c.error('unexpected `?`, the field `${expr.field_name}` is not an option', + c.error('unexpected `?`, the field `${expr.field_name}` is not an Option', expr.or_block.pos) } else if expr.or_block.kind == .propagate_result { c.error('unexpected `!`, result fields are not supported', expr.or_block.pos) } } else if expr is ast.IndexExpr { if expr.or_expr.kind != .absent { - c.check_or_expr(expr.or_expr, ret_type, ret_type.set_flag(.result)) + c.check_or_expr(expr.or_expr, ret_type, ret_type.set_flag(.result), expr) } } else if expr is ast.CastExpr { c.check_expr_opt_call(expr.expr, ret_type) @@ -1058,20 +1062,25 @@ fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Typ return ret_type } -fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type) { +fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type, expr ast.Expr) { if node.kind == .propagate_option { if c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.return_type.has_flag(.option) && !c.table.cur_fn.is_main && !c.table.cur_fn.is_test && !c.inside_const { c.add_instruction_for_option_type() - c.error('to propagate the call, `${c.table.cur_fn.name}` must return an option type', - node.pos) + if expr is ast.Ident { + c.error('to propagate the Option, `${c.table.cur_fn.name}` must return an Option type', + expr.pos) + } else { + c.error('to propagate the call, `${c.table.cur_fn.name}` must return an Option type', + node.pos) + } } - if !expr_return_type.has_flag(.option) { + if expr !is ast.Ident && !expr_return_type.has_flag(.option) { if expr_return_type.has_flag(.result) { - c.warn('propagating a result like an option is deprecated, use `foo()!` instead of `foo()?`', + c.warn('propagating a result like an Option is deprecated, use `foo()!` instead of `foo()?`', node.pos) } else { - c.error('to propagate an option, the call must also return an option type', + c.error('to propagate an Option, the call must also return an Option type', node.pos) } } @@ -1109,6 +1118,17 @@ fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_ret c.expected_type = ret_type c.expected_or_type = ret_type.clear_flag(.option).clear_flag(.result) last_stmt_typ := c.expr(stmt.expr) + + if stmt.expr is ast.Ident { + if ret_type.has_flag(.option) && last_stmt_typ.has_flag(.option) { + expected_type_name := c.table.type_to_str(expr_return_type) + got_type_name := c.table.type_to_str(last_stmt_typ) + c.error('`or` block must provide a value of type `${expected_type_name}`, not `${got_type_name}`', + stmt.expr.pos) + return + } + } + c.expected_or_type = ast.void_type type_fits := c.check_types(last_stmt_typ, ret_type) && last_stmt_typ.nr_muls() == ret_type.nr_muls() @@ -1140,6 +1160,9 @@ fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_ret && c.table.sym(last_stmt_typ).kind == .voidptr { return } + if last_stmt_typ == ast.none_type_idx { + return + } type_name := c.table.type_to_str(last_stmt_typ) expected_type_name := c.table.type_to_str(ret_type.clear_flag(.option).clear_flag(.result)) c.error('wrong return type `${type_name}` in the `or {}` block, expected `${expected_type_name}`', @@ -1281,7 +1304,7 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { node.expr_type = typ if !(node.expr is ast.Ident && (node.expr as ast.Ident).kind == .constant) { if node.expr_type.has_flag(.option) { - c.error('cannot access fields of an option, handle the error with `or {...}` or propagate it with `?`', + c.error('cannot access fields of an Option, handle the error with `or {...}` or propagate it with `?`', node.pos) } else if node.expr_type.has_flag(.result) { c.error('cannot access fields of a result, handle the error with `or {...}` or propagate it with `!`', @@ -2372,7 +2395,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } } else if expr_type_sym.kind == .interface_ && type_sym.kind == .interface_ { c.ensure_type_exists(node.typ, node.pos) or {} - } else if node.expr_type != node.typ { + } else if node.expr_type.clear_flag(.option) != node.typ.clear_flag(.option) { mut s := 'cannot cast non-sum type `${expr_type_sym.name}` using `as`' if type_sym.kind == .sum_type { s += ' - use e.g. `${type_sym.name}(some_expr)` instead.' @@ -2405,13 +2428,13 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } if !ret_type.has_flag(.option) && !ret_type.has_flag(.result) { if node.or_block.kind == .block { - c.error('unexpected `or` block, the function `${node.name}` does not return an option or a result', + c.error('unexpected `or` block, the function `${node.name}` does not return an Option or a result', node.or_block.pos) } else if node.or_block.kind == .propagate_option { - c.error('unexpected `?`, the function `${node.name}` does not return an option or a result', + c.error('unexpected `?`, the function `${node.name}` does not return an Option or a result', node.or_block.pos) } else if node.or_block.kind == .propagate_result { - c.error('unexpected `!`, the function `${node.name}` does not return an option or a result', + c.error('unexpected `!`, the function `${node.name}` does not return an Option or a result', node.or_block.pos) } } @@ -2504,7 +2527,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { else {} } if no_opt_or_res { - c.error('expression should either return an option or a result', node.expr.pos()) + c.error('expression should either return an Option or a result', node.expr.pos()) } } return ast.bool_type @@ -2582,13 +2605,13 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { if !ret_type.has_flag(.option) && !ret_type.has_flag(.result) { if node.or_block.kind == .block { - c.error('unexpected `or` block, the field `${node.field_name}` is neither an option, nor a result', + c.error('unexpected `or` block, the field `${node.field_name}` is neither an Option, nor a result', node.or_block.pos) } else if node.or_block.kind == .propagate_option { - c.error('unexpected `?`, the field `${node.field_name}` is neither an option, nor a result', + c.error('unexpected `?`, the field `${node.field_name}` is neither an Option, nor a result', node.or_block.pos) } else if node.or_block.kind == .propagate_result { - c.error('unexpected `!`, the field `${node.field_name}` is neither an option, nor a result', + c.error('unexpected `!`, the field `${node.field_name}` is neither an Option, nor a result', node.or_block.pos) } } @@ -2707,9 +2730,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { mut to_sym := c.table.sym(to_type) // type to be used as cast mut final_to_sym := c.table.final_sym(to_type) - if to_type.has_flag(.option) { - c.error('casting to option type is forbidden', node.pos) - } else if to_type.has_flag(.result) { + if to_type.has_flag(.result) { c.error('casting to result type is forbidden', node.pos) } @@ -2758,8 +2779,10 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { && !(to_sym.info as ast.Struct).is_typedef { // For now we ignore C typedef because of `C.Window(C.None)` in vlib/clipboard if from_sym.kind == .struct_ && !from_type.is_ptr() { - c.warn('casting to struct is deprecated, use e.g. `Struct{...expr}` instead', - node.pos) + if !to_type.has_flag(.option) { + c.warn('casting to struct is deprecated, use e.g. `Struct{...expr}` instead', + node.pos) + } from_type_info := from_sym.info as ast.Struct to_type_info := to_sym.info as ast.Struct if !c.check_struct_signature(from_type_info, to_type_info) { @@ -2816,11 +2839,11 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('cannot cast type `${ft}` to `${tt}`', node.pos) - } else if from_type.has_flag(.option) || from_type.has_flag(.result) - || from_type.has_flag(.variadic) { + } else if (from_type.has_flag(.option) && !to_type.has_flag(.option)) + || from_type.has_flag(.result) || from_type.has_flag(.variadic) { // variadic case can happen when arrays are converted into variadic msg := if from_type.has_flag(.option) { - 'an option' + 'an Option' } else if from_type.has_flag(.result) { 'a result' } else { @@ -2848,10 +2871,17 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { snexpr := node.expr.str() tt := c.table.type_to_str(to_type) c.error('cannot cast string to `${tt}`, use `${snexpr}[index]` instead.', node.pos) - } else if final_from_sym.kind == .array && !from_type.is_ptr() && to_type != ast.string_type { + } else if final_from_sym.kind == .array && !from_type.is_ptr() && to_type != ast.string_type + && !(to_type.has_flag(.option) && from_type.idx() == to_type.idx()) { ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('cannot cast array `${ft}` to `${tt}`', node.pos) + } else if from_type.has_flag(.option) && to_type.has_flag(.option) + && to_sym.kind != final_from_sym.kind { + ft := c.table.type_to_str(from_type) + tt := c.table.type_to_str(to_type) + c.error('cannot cast incompatible option ${final_to_sym.name} `${ft}` to `${tt}`', + node.pos) } if to_sym.kind == .rune && from_sym.is_string() { @@ -3101,6 +3131,16 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { if node.kind in [.constant, .global, .variable] { info := node.info as ast.IdentVar // Got a var with type T, return current generic type + if node.or_expr.kind != .absent { + if !info.typ.has_flag(.option) && node.or_expr.kind == .propagate_option { + c.error('cannot use `?` on non-option variable', node.pos) + } + unwrapped_typ := info.typ.clear_flag(.option).clear_flag(.result) + c.expected_or_type = unwrapped_typ + c.stmts_ending_with_expression(node.or_expr.stmts) + c.check_or_expr(node.or_expr, info.typ, c.expected_or_type, node) + return unwrapped_typ + } return info.typ } else if node.kind == .function { info := node.info as ast.IdentFn @@ -3171,6 +3211,7 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { } } is_option := typ.has_flag(.option) || typ.has_flag(.result) + || node.or_expr.kind != .absent node.kind = .variable node.info = ast.IdentVar{ typ: typ @@ -3182,7 +3223,17 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { node.obj = obj // unwrap option (`println(x)`) if is_option { - return typ.clear_flag(.option).clear_flag(.result) + if node.or_expr.kind == .absent { + return typ.clear_flag(.result) + } + if !typ.has_flag(.option) && node.or_expr.kind == .propagate_option { + c.error('cannot use `?` on non-option variable', node.pos) + } + unwrapped_typ := typ.clear_flag(.option).clear_flag(.result) + c.expected_or_type = unwrapped_typ + c.stmts_ending_with_expression(node.or_expr.stmts) + c.check_or_expr(node.or_expr, typ, c.expected_or_type, node) + return unwrapped_typ } return typ } @@ -3667,7 +3718,7 @@ fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type { } if node.right.typ.has_flag(.option) { - c.error('cannot take the address of an option field', node.pos.extend(node.right.pos)) + c.error('cannot take the address of an Option field', node.pos.extend(node.right.pos)) } } } @@ -3843,8 +3894,13 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { c.error('type `${typ_sym.name}` does not support indexing', node.pos) } if typ.has_flag(.option) { - c.error('type `?${typ_sym.name}` is an option, it does not support indexing', - node.left.pos()) + if node.left is ast.Ident && (node.left as ast.Ident).or_expr.kind == .absent { + c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped first; use `var?[]` to do it', + node.left.pos()) + } else if node.left is ast.CallExpr { + c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped with `func()?`, or use `func() or {default}`', + node.left.pos()) + } } else if typ.has_flag(.result) { c.error('type `!${typ_sym.name}` is a result, it does not support indexing', node.left.pos()) } diff --git a/vlib/v/checker/containers.v b/vlib/v/checker/containers.v index 8d866664fb..e8e3bffde2 100644 --- a/vlib/v/checker/containers.v +++ b/vlib/v/checker/containers.v @@ -12,6 +12,10 @@ fn (mut c Checker) array_init(mut node ast.ArrayInit) ast.Type { if node.typ != ast.void_type { if node.elem_type != 0 { elem_sym := c.table.sym(node.elem_type) + + if node.typ.has_flag(.option) && (node.has_cap || node.has_len) { + c.error('Option array `${elem_sym.name}` cannot have initializers', node.pos) + } if elem_sym.kind == .struct_ { elem_info := elem_sym.info as ast.Struct if elem_info.generic_types.len > 0 && elem_info.concrete_types.len == 0 diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index a05cc46fd1..0fccc21111 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -117,9 +117,6 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { if multi_type == ast.error_type { c.error('type `IError` cannot be used in multi-return, return an option instead', node.return_type_pos) - } else if multi_type.has_flag(.option) { - c.error('option cannot be used in multi-return, return an option instead', - node.return_type_pos) } else if multi_type.has_flag(.result) { c.error('result cannot be used in multi-return, return a result instead', node.return_type_pos) @@ -211,8 +208,8 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.error('invalid use of reserved type `${param.name}` as a parameter name', param.pos) } - if param.typ.has_flag(.option) || param.typ.has_flag(.result) { - c.error('option or result type argument is not supported currently', param.type_pos) + if param.typ.has_flag(.result) { + c.error('result type argument is not supported currently', param.type_pos) } arg_typ_sym := c.table.sym(param.typ) if arg_typ_sym.info is ast.Struct { @@ -503,7 +500,7 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { node.free_receiver = true } } - c.expected_or_type = node.return_type.clear_flag(.option).clear_flag(.result) + c.expected_or_type = node.return_type.clear_flag(.result) c.stmts_ending_with_expression(node.or_block.stmts) c.expected_or_type = ast.void_type @@ -1036,8 +1033,8 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } } arg_typ_sym := c.table.sym(arg_typ) - if arg_typ_sym.kind == .none_ { - c.error('cannot use `none` as function argument', call_arg.pos) + if arg_typ_sym.kind == .none_ && param.typ.has_flag(.generic) { + c.error('cannot use `none` as generic argument', call_arg.pos) } param_typ_sym := c.table.sym(param.typ) if func.is_variadic && arg_typ.has_flag(.variadic) && node.args.len - 1 > i { @@ -1768,8 +1765,8 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { } continue } - if final_arg_sym.kind == .none_ { - c.error('cannot use `none` as method argument', arg.pos) + if final_arg_sym.kind == .none_ && param.typ.has_flag(.generic) { + c.error('cannot use `none` as generic argument', arg.pos) } if param.typ.is_ptr() && !arg.typ.is_real_pointer() && arg.expr.is_literal() && !c.pref.translated { diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index 56d00d7860..8cbb9d25c6 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -27,7 +27,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { return ast.bool_type } node.right_type = right_type - if left_type.is_number() && !left_type.is_ptr() + if !left_type.has_flag(.option) && left_type.is_number() && !left_type.is_ptr() && right_type in [ast.int_literal_type, ast.float_literal_type] { node.right_type = left_type if left_type in [ast.f32_type_idx, ast.f64_type_idx] && right_type == ast.float_literal_type { @@ -438,7 +438,8 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } } } - } else if left_type.has_flag(.option) || right_type.has_flag(.option) { + } else if node.left !is ast.Ident + && (left_type.has_flag(.option) || right_type.has_flag(.option)) { opt_comp_pos := if left_type.has_flag(.option) { left_pos } else { right_pos } c.error('unwrapped option cannot be compared in an infix expression', opt_comp_pos) @@ -668,7 +669,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { // TODO move this to symmetric_check? Right now it would break `return 0` for `fn()?int ` left_is_option := left_type.has_flag(.option) right_is_option := right_type.has_flag(.option) - if left_is_option || right_is_option { + if node.left !is ast.Ident && (left_is_option || right_is_option) { opt_infix_pos := if left_is_option { left_pos } else { right_pos } c.error('unwrapped option cannot be used in an infix expression', opt_infix_pos) } @@ -694,6 +695,9 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { node.promoted_type = return_type return return_type } + if node.right is ast.None && left_is_option { + return ast.bool_type + } c.error('infix expr: cannot use `${right_sym.name}` (right expression) as `${left_sym.name}`', left_right_pos) } else if left_type.is_ptr() { diff --git a/vlib/v/checker/return.v b/vlib/v/checker/return.v index 5f853a18bc..9d17312fb4 100644 --- a/vlib/v/checker/return.v +++ b/vlib/v/checker/return.v @@ -125,9 +125,17 @@ fn (mut c Checker) return_stmt(mut node ast.Return) { return } for i, exp_type in expected_types { + exprv := node.exprs[expr_idxs[i]] + if exprv is ast.Ident && (exprv as ast.Ident).or_expr.kind == .propagate_option { + if exp_type.has_flag(.option) { + c.warn('unwrapping option is redundant as the function returns option', + node.pos) + } else { + c.error('should not unwrap option var on return, it could be none', node.pos) + } + } got_typ := c.unwrap_generic(got_types[i]) - if got_typ.has_flag(.option) && (!exp_type.has_flag(.option) - || c.table.type_to_str(got_typ) != c.table.type_to_str(exp_type)) { + if got_typ.has_flag(.option) && got_typ.clear_flag(.option) != exp_type.clear_flag(.option) { pos := node.exprs[expr_idxs[i]].pos() c.error('cannot use `${c.table.type_to_str(got_typ)}` as type `${c.table.type_to_str(exp_type)}` in return argument', pos) diff --git a/vlib/v/checker/tests/alias_type_cast_option_result_unhandled_err.out b/vlib/v/checker/tests/alias_type_cast_option_result_unhandled_err.out index d0a48118e0..a3ed55b133 100644 --- a/vlib/v/checker/tests/alias_type_cast_option_result_unhandled_err.out +++ b/vlib/v/checker/tests/alias_type_cast_option_result_unhandled_err.out @@ -1,13 +1,7 @@ vlib/v/checker/tests/alias_type_cast_option_result_unhandled_err.vv:15:12: error: ret_abc_result() returns a result, so it should have either an `or {}` block, or `!` at the end 13 | } - 14 | + 14 | 15 | a := Alias(ret_abc_result()) | ~~~~~~~~~~~~~~~~ 16 | b := Alias(ret_abc_option()) 17 | println('${a}${b}') -vlib/v/checker/tests/alias_type_cast_option_result_unhandled_err.vv:16:12: error: ret_abc_option() returns an option, so it should have either an `or {}` block, or `?` at the end - 14 | - 15 | a := Alias(ret_abc_result()) - 16 | b := Alias(ret_abc_option()) - | ~~~~~~~~~~~~~~~~ - 17 | println('${a}${b}') diff --git a/vlib/v/checker/tests/aliased_option_fn_call_err.out b/vlib/v/checker/tests/aliased_option_fn_call_err.out deleted file mode 100644 index 73187c0655..0000000000 --- a/vlib/v/checker/tests/aliased_option_fn_call_err.out +++ /dev/null @@ -1,6 +0,0 @@ -vlib/v/checker/tests/aliased_option_fn_call_err.vv:8:10: error: foo() returns an option, so it should have either an `or {}` block, or `?` at the end - 6 | - 7 | fn main() { - 8 | println(foo()) - | ~~~~~ - 9 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/aliased_option_fn_call_err.vv b/vlib/v/checker/tests/aliased_option_fn_call_err.vv deleted file mode 100644 index c7de9d4dbb..0000000000 --- a/vlib/v/checker/tests/aliased_option_fn_call_err.vv +++ /dev/null @@ -1,9 +0,0 @@ -type OptStr = ?string - -fn foo() OptStr { - return 'abc' -} - -fn main() { - println(foo()) -} diff --git a/vlib/v/checker/tests/as_cast_option_result_unhandled_err.out b/vlib/v/checker/tests/as_cast_option_result_unhandled_err.out index c2d5571db4..1f4f9ef01b 100644 --- a/vlib/v/checker/tests/as_cast_option_result_unhandled_err.out +++ b/vlib/v/checker/tests/as_cast_option_result_unhandled_err.out @@ -1,11 +1,6 @@ -vlib/v/checker/tests/as_cast_option_result_unhandled_err.vv:11:6: error: ret_sum_result() returns a result, so it should have either an `or {}` block, or `!` at the end - 9 | } - 10 | - 11 | _ := ret_sum_result() as int - | ~~~~~~~~~~~~~~~~ - 12 | _ := ret_sum_option() as string -vlib/v/checker/tests/as_cast_option_result_unhandled_err.vv:12:6: error: ret_sum_option() returns an option, so it should have either an `or {}` block, or `?` at the end - 10 | - 11 | _ := ret_sum_result() as int - 12 | _ := ret_sum_option() as string - | ~~~~~~~~~~~~~~~~ +vlib/v/checker/tests/as_cast_option_result_unhandled_err.vv:11:6: error: ret_sum_result() returns a result, so it should have either an `or {}` block, or `!` at the end + 9 | } + 10 | + 11 | _ := ret_sum_result() as int + | ~~~~~~~~~~~~~~~~ + 12 | _ := ret_sum_option() as string diff --git a/vlib/v/checker/tests/defer_option.out b/vlib/v/checker/tests/defer_option.out deleted file mode 100644 index f4cf353061..0000000000 --- a/vlib/v/checker/tests/defer_option.out +++ /dev/null @@ -1,7 +0,0 @@ -vlib/v/checker/tests/defer_option.vv:5:3: error: opt() returns an option, so it should have an `or {}` block at the end - 3 | fn thing() ?string { - 4 | defer { - 5 | opt() - | ~~~~~ - 6 | } - 7 | return 'ok' \ No newline at end of file diff --git a/vlib/v/checker/tests/defer_option.vv b/vlib/v/checker/tests/defer_option.vv deleted file mode 100644 index acb4537853..0000000000 --- a/vlib/v/checker/tests/defer_option.vv +++ /dev/null @@ -1,8 +0,0 @@ -fn opt() ? {} - -fn thing() ?string { - defer { - opt() - } - return 'ok' -} diff --git a/vlib/v/checker/tests/expression_should_return_an_option.out b/vlib/v/checker/tests/expression_should_return_an_option.out index eb615f09aa..4a1ccc5d94 100644 --- a/vlib/v/checker/tests/expression_should_return_an_option.out +++ b/vlib/v/checker/tests/expression_should_return_an_option.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/expression_should_return_an_option.vv:26:10: error: expression should either return an option or a result +vlib/v/checker/tests/expression_should_return_an_option.vv:26:10: error: expression should either return an Option or a result 24 | return_option(true) or { println(err) } 25 | // should be an checker error: 26 | if x := return_string() { | ~~~~~~~~~~~~~~~ 27 | println('x: ${x}') - 28 | } \ No newline at end of file + 28 | } diff --git a/vlib/v/checker/tests/fn_arg_of_option_err.out b/vlib/v/checker/tests/fn_arg_of_option_err.out deleted file mode 100644 index b6e19ee0da..0000000000 --- a/vlib/v/checker/tests/fn_arg_of_option_err.out +++ /dev/null @@ -1,5 +0,0 @@ -vlib/v/checker/tests/fn_arg_of_option_err.vv:1:17: error: option or result type argument is not supported currently - 1 | fn option_arg(x ?int) { - | ~~~~ - 2 | println('int type: ${x}') - 3 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/fn_arg_of_option_err.vv b/vlib/v/checker/tests/fn_arg_of_option_err.vv deleted file mode 100644 index 0a1fe42a0a..0000000000 --- a/vlib/v/checker/tests/fn_arg_of_option_err.vv +++ /dev/null @@ -1,7 +0,0 @@ -fn option_arg(x ?int) { - println('int type: ${x}') -} - -fn main() { - option_arg(1) -} diff --git a/vlib/v/checker/tests/fn_call_using_none_arg_err.out b/vlib/v/checker/tests/fn_call_using_none_arg_err.out index 1c2491b46b..d316c1f4cc 100644 --- a/vlib/v/checker/tests/fn_call_using_none_arg_err.out +++ b/vlib/v/checker/tests/fn_call_using_none_arg_err.out @@ -1,5 +1,5 @@ -vlib/v/checker/tests/fn_call_using_none_arg_err.vv:6:4: error: cannot use `none` as function argument - 4 | +vlib/v/checker/tests/fn_call_using_none_arg_err.vv:6:4: error: cannot use `none` as generic argument + 4 | 5 | fn main() { 6 | f(none) | ~~~~ diff --git a/vlib/v/checker/tests/fn_return_or_err.out b/vlib/v/checker/tests/fn_return_or_err.out index 3aa6f5cb9b..b034a57a15 100644 --- a/vlib/v/checker/tests/fn_return_or_err.out +++ b/vlib/v/checker/tests/fn_return_or_err.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/fn_return_or_err.vv:6:17: error: unexpected `or` block, the function `pop` does not return an option or a result - 4 | +vlib/v/checker/tests/fn_return_or_err.vv:6:17: error: unexpected `or` block, the function `pop` does not return an Option or a result + 4 | 5 | pub fn next(mut v []Typ) Typ { 6 | return v.pop() or { Typ{} } | ~~~~~~~~~~~~ 7 | } - 8 | \ No newline at end of file + 8 | diff --git a/vlib/v/checker/tests/go_wait_or.out b/vlib/v/checker/tests/go_wait_or.out index 11a9b5d181..6a6f555181 100644 --- a/vlib/v/checker/tests/go_wait_or.out +++ b/vlib/v/checker/tests/go_wait_or.out @@ -1,25 +1,25 @@ -vlib/v/checker/tests/go_wait_or.vv:11:16: error: unexpected `?`, the function `wait` does not return an option or a result +vlib/v/checker/tests/go_wait_or.vv:11:16: error: unexpected `?`, the function `wait` does not return an Option or a result 9 | spawn d(1) 10 | ] 11 | r := tg.wait()? | ^ 12 | println(r) 13 | s := tg[0].wait() or { panic('problem') } -vlib/v/checker/tests/go_wait_or.vv:13:20: error: unexpected `or` block, the function `wait` does not return an option or a result +vlib/v/checker/tests/go_wait_or.vv:13:20: error: unexpected `or` block, the function `wait` does not return an Option or a result 11 | r := tg.wait()? 12 | println(r) 13 | s := tg[0].wait() or { panic('problem') } | ~~~~~~~~~~~~~~~~~~~~~~~ 14 | println(s) 15 | tg2 := [ -vlib/v/checker/tests/go_wait_or.vv:19:13: error: unexpected `or` block, the function `wait` does not return an option or a result +vlib/v/checker/tests/go_wait_or.vv:19:13: error: unexpected `or` block, the function `wait` does not return an Option or a result 17 | spawn e(1) 18 | ] 19 | tg2.wait() or { panic('problem') } | ~~~~~~~~~~~~~~~~~~~~~~~ 20 | tg2[0].wait()? 21 | tg3 := [ -vlib/v/checker/tests/go_wait_or.vv:20:15: error: unexpected `?`, the function `wait` does not return an option or a result +vlib/v/checker/tests/go_wait_or.vv:20:15: error: unexpected `?`, the function `wait` does not return an Option or a result 18 | ] 19 | tg2.wait() or { panic('problem') } 20 | tg2[0].wait()? @@ -33,20 +33,6 @@ vlib/v/checker/tests/go_wait_or.vv:25:6: error: `.wait()` cannot be called for a | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | for t in tg3 { 27 | a := t.wait() -vlib/v/checker/tests/go_wait_or.vv:27:10: error: wait() returns an option, so it should have either an `or {}` block, or `?` at the end - 25 | tg3.wait() or { panic('problem') } - 26 | for t in tg3 { - 27 | a := t.wait() - | ~~~~~~ - 28 | println(a) - 29 | } -vlib/v/checker/tests/go_wait_or.vv:31:15: error: wait() returns an option, so it should have either an `or {}` block, or `?` at the end - 29 | } - 30 | for i, _ in tg3 { - 31 | a := tg3[i].wait() - | ~~~~~~ - 32 | println(a) - 33 | } vlib/v/checker/tests/go_wait_or.vv:38:6: error: `.wait()` cannot be called for an array when thread functions return options. Iterate over the arrays elements instead and handle each returned option with `or`. 36 | spawn g(1) 37 | ] @@ -54,16 +40,9 @@ vlib/v/checker/tests/go_wait_or.vv:38:6: error: `.wait()` cannot be called for a | ~~~~~~ 39 | tg4[0].wait() 40 | spawn g(3) or { panic('problem') } -vlib/v/checker/tests/go_wait_or.vv:39:9: error: wait() returns an option, so it should have either an `or {}` block, or `?` at the end - 37 | ] - 38 | tg4.wait() - 39 | tg4[0].wait() - | ~~~~~~ - 40 | spawn g(3) or { panic('problem') } - 41 | } vlib/v/checker/tests/go_wait_or.vv:40:13: error: option handling cannot be done in `spawn` call. Do it when calling `.wait()` 38 | tg4.wait() 39 | tg4[0].wait() 40 | spawn g(3) or { panic('problem') } | ~~~~~~~~~~~~~~~~~~~~~~~ - 41 | } \ No newline at end of file + 41 | } diff --git a/vlib/v/checker/tests/ierror_in_return_tuple.out b/vlib/v/checker/tests/ierror_in_return_tuple.out index 60d648331f..a68f80892d 100644 --- a/vlib/v/checker/tests/ierror_in_return_tuple.out +++ b/vlib/v/checker/tests/ierror_in_return_tuple.out @@ -3,10 +3,3 @@ vlib/v/checker/tests/ierror_in_return_tuple.vv:1:29: error: type `IError` cannot | ~~~~~~~~~~~~~~ 2 | return false, error('') 3 | } -vlib/v/checker/tests/ierror_in_return_tuple.vv:5:29: error: option cannot be used in multi-return, return an option instead - 3 | } - 4 | - 5 | fn return_option_in_tuple() (bool, ?bool) { - | ~~~~~~~~~~~~~ - 6 | return false, error('') - 7 | } diff --git a/vlib/v/checker/tests/index_of_option_err.out b/vlib/v/checker/tests/index_of_option_err.out index f4c0af0851..8e392dd3f9 100644 --- a/vlib/v/checker/tests/index_of_option_err.out +++ b/vlib/v/checker/tests/index_of_option_err.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/index_of_option_err.vv:6:7: error: type `?[]int` is an option, it does not support indexing - 4 | +vlib/v/checker/tests/index_of_option_err.vv:6:7: error: type `?[]int` is an Option, it must be unwrapped with `func()?`, or use `func() or {default}` + 4 | 5 | fn main() { 6 | a := abc()[0] or { 5 } | ~~~~~ 7 | dump(a) - 8 | } \ No newline at end of file + 8 | } diff --git a/vlib/v/checker/tests/option_fields_addr_err.out b/vlib/v/checker/tests/option_fields_addr_err.out index 7c608d7c08..18ceb52b64 100644 --- a/vlib/v/checker/tests/option_fields_addr_err.out +++ b/vlib/v/checker/tests/option_fields_addr_err.out @@ -1,13 +1,13 @@ -vlib/v/checker/tests/option_fields_addr_err.vv:9:10: error: cannot take the address of an option field +vlib/v/checker/tests/option_fields_addr_err.vv:9:10: error: cannot take the address of an Option field 7 | fn main() { 8 | x := Wrapper{} 9 | if _ := &x.value { | ~~~~~~~~ 10 | } 11 | -vlib/v/checker/tests/option_fields_addr_err.vv:12:7: error: cannot take the address of an option field +vlib/v/checker/tests/option_fields_addr_err.vv:12:7: error: cannot take the address of an Option field 10 | } - 11 | + 11 | 12 | _ := &x.value | ~~~~~~~~ - 13 | } \ No newline at end of file + 13 | } diff --git a/vlib/v/checker/tests/option_fn_err.out b/vlib/v/checker/tests/option_fn_err.out index 789972da75..6162ae5349 100644 --- a/vlib/v/checker/tests/option_fn_err.out +++ b/vlib/v/checker/tests/option_fn_err.out @@ -1,101 +1,10 @@ -vlib/v/checker/tests/option_fn_err.vv:12:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 10 | - 11 | const ( - 12 | const_value = bar(0) - | ~~~~~~ - 13 | ) - 14 | -vlib/v/checker/tests/option_fn_err.vv:18:15: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 16 | f fn (int) - 17 | mut: - 18 | value int = bar(0) - | ~~~~~~ - 19 | opt ?int = bar(0) - 20 | } -vlib/v/checker/tests/option_fn_err.vv:32:2: error: foo() returns an option, so it should have either an `or {}` block, or `?` at the end - 30 | fn main() { - 31 | // call fn - 32 | foo() - | ~~~~~ - 33 | _ := bar(0) - 34 | println(twice(bar(0))) -vlib/v/checker/tests/option_fn_err.vv:33:7: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 31 | // call fn - 32 | foo() - 33 | _ := bar(0) - | ~~~~~~ - 34 | println(twice(bar(0))) - 35 | -vlib/v/checker/tests/option_fn_err.vv:34:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 32 | foo() - 33 | _ := bar(0) - 34 | println(twice(bar(0))) - | ~~~~~~ - 35 | - 36 | // anon fn -vlib/v/checker/tests/option_fn_err.vv:37:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 35 | - 36 | // anon fn - 37 | fn (_ int) {}(bar(0)) - | ~~~~~~ - 38 | - 39 | // assert -vlib/v/checker/tests/option_fn_err.vv:40:9: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 38 | +vlib/v/checker/tests/option_fn_err.vv:40:9: error: assert can be used only with `bool` expressions, but found `bool` instead + 38 | 39 | // assert 40 | assert bar(true) | ~~~~~~~~~ - 41 | + 41 | 42 | // struct -vlib/v/checker/tests/option_fn_err.vv:45:10: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 43 | mut v := Data{ - 44 | f: fn (_ int) {} - 45 | value: bar(0) - | ~~~~~~ - 46 | opt: bar(0) - 47 | } -vlib/v/checker/tests/option_fn_err.vv:48:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 46 | opt: bar(0) - 47 | } - 48 | v.add(bar(0)) // call method - | ~~~~~~ - 49 | v.f(bar(0)) // call fn field - 50 | -vlib/v/checker/tests/option_fn_err.vv:49:6: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 47 | } - 48 | v.add(bar(0)) // call method - 49 | v.f(bar(0)) // call fn field - | ~~~~~~ - 50 | - 51 | // array -vlib/v/checker/tests/option_fn_err.vv:53:9: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 51 | // array - 52 | mut arr := [1, 2] - 53 | arr << bar(0) - | ~~~~~~ - 54 | // init - 55 | _ := [bar(0)] -vlib/v/checker/tests/option_fn_err.vv:55:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 53 | arr << bar(0) - 54 | // init - 55 | _ := [bar(0)] - | ~~~~~~ - 56 | _ := []int{len: 1, init: bar(0)} - 57 | _ := [bar(0)]! -vlib/v/checker/tests/option_fn_err.vv:56:27: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 54 | // init - 55 | _ := [bar(0)] - 56 | _ := []int{len: 1, init: bar(0)} - | ~~~~~~ - 57 | _ := [bar(0)]! - 58 | _ := [1]int{init: bar(0)} -vlib/v/checker/tests/option_fn_err.vv:57:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 55 | _ := [bar(0)] - 56 | _ := []int{len: 1, init: bar(0)} - 57 | _ := [bar(0)]! - | ~~~~~~ - 58 | _ := [1]int{init: bar(0)} - 59 | // index vlib/v/checker/tests/option_fn_err.vv:60:13: error: cannot use option or result as index (array type `[]int`) 58 | _ := [1]int{init: bar(0)} 59 | // index @@ -103,66 +12,24 @@ vlib/v/checker/tests/option_fn_err.vv:60:13: error: cannot use option or result | ~~~~~~~~ 61 | // array builtin methods 62 | arr.insert(0, bar(0)) -vlib/v/checker/tests/option_fn_err.vv:62:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 60 | println(arr[bar(0)]) - 61 | // array builtin methods - 62 | arr.insert(0, bar(0)) - | ~~~~~~ - 63 | arr.prepend(bar(0)) - 64 | arr.contains(bar(0)) -vlib/v/checker/tests/option_fn_err.vv:63:14: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 61 | // array builtin methods - 62 | arr.insert(0, bar(0)) - 63 | arr.prepend(bar(0)) - | ~~~~~~ - 64 | arr.contains(bar(0)) - 65 | arr.index(bar(0)) -vlib/v/checker/tests/option_fn_err.vv:64:15: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 62 | arr.insert(0, bar(0)) - 63 | arr.prepend(bar(0)) - 64 | arr.contains(bar(0)) - | ~~~~~~ - 65 | arr.index(bar(0)) - 66 | println(arr.map(bar(0))) -vlib/v/checker/tests/option_fn_err.vv:65:12: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 63 | arr.prepend(bar(0)) - 64 | arr.contains(bar(0)) - 65 | arr.index(bar(0)) - | ~~~~~~ - 66 | println(arr.map(bar(0))) - 67 | println(arr.filter(bar(true))) -vlib/v/checker/tests/option_fn_err.vv:66:18: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 64 | arr.contains(bar(0)) - 65 | arr.index(bar(0)) - 66 | println(arr.map(bar(0))) - | ~~~~~~ - 67 | println(arr.filter(bar(true))) - 68 | println(arr.any(bar(true))) -vlib/v/checker/tests/option_fn_err.vv:67:21: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end +vlib/v/checker/tests/option_fn_err.vv:67:21: error: type mismatch, `bar` must return a bool 65 | arr.index(bar(0)) 66 | println(arr.map(bar(0))) 67 | println(arr.filter(bar(true))) | ~~~~~~~~~ 68 | println(arr.any(bar(true))) 69 | println(arr.all(bar(true))) -vlib/v/checker/tests/option_fn_err.vv:68:18: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end +vlib/v/checker/tests/option_fn_err.vv:68:18: error: type mismatch, `bar` must return a bool 66 | println(arr.map(bar(0))) 67 | println(arr.filter(bar(true))) 68 | println(arr.any(bar(true))) | ~~~~~~~~~ 69 | println(arr.all(bar(true))) 70 | -vlib/v/checker/tests/option_fn_err.vv:69:18: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end +vlib/v/checker/tests/option_fn_err.vv:69:18: error: type mismatch, `bar` must return a bool 67 | println(arr.filter(bar(true))) 68 | println(arr.any(bar(true))) 69 | println(arr.all(bar(true))) | ~~~~~~~~~ - 70 | + 70 | 71 | match bar(0) { -vlib/v/checker/tests/option_fn_err.vv:71:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 69 | println(arr.all(bar(true))) - 70 | - 71 | match bar(0) { - | ~~~~~~ - 72 | 0 {} - 73 | else {} \ No newline at end of file diff --git a/vlib/v/checker/tests/option_in_dump_err.out b/vlib/v/checker/tests/option_in_dump_err.out deleted file mode 100644 index b13d4b128a..0000000000 --- a/vlib/v/checker/tests/option_in_dump_err.out +++ /dev/null @@ -1,6 +0,0 @@ -vlib/v/checker/tests/option_in_dump_err.vv:10:7: error: create() returns an option, so it should have either an `or {}` block, or `?` at the end - 8 | - 9 | fn main() { - 10 | dump(create()) - | ~~~~~~~~ - 11 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/option_in_dump_err.vv b/vlib/v/checker/tests/option_in_dump_err.vv deleted file mode 100644 index 5554ce417a..0000000000 --- a/vlib/v/checker/tests/option_in_dump_err.vv +++ /dev/null @@ -1,11 +0,0 @@ -struct AStruct { - field1 int -} - -fn create() ?AStruct { - return AStruct{123} -} - -fn main() { - dump(create()) -} diff --git a/vlib/v/checker/tests/option_or_block_none_err.out b/vlib/v/checker/tests/option_or_block_none_err.out deleted file mode 100644 index 8fdb5fcbc3..0000000000 --- a/vlib/v/checker/tests/option_or_block_none_err.out +++ /dev/null @@ -1,7 +0,0 @@ -vlib/v/checker/tests/option_or_block_none_err.vv:19:32: error: wrong return type `none` in the `or {}` block, expected `Animal` - 17 | - 18 | fn main() { - 19 | mut dog := new_animal(9) or { none } - | ~~~~ - 20 | - 21 | println(dog) \ No newline at end of file diff --git a/vlib/v/checker/tests/option_or_block_none_err.vv b/vlib/v/checker/tests/option_or_block_none_err.vv deleted file mode 100644 index 45315d381f..0000000000 --- a/vlib/v/checker/tests/option_or_block_none_err.vv +++ /dev/null @@ -1,22 +0,0 @@ -module main - -struct Animal { -mut: - height u8 -} - -fn new_animal(height u8) ?Animal { - if height < 10 { - return error('Too small to be an animal!') - } - - return Animal{ - height: height - } -} - -fn main() { - mut dog := new_animal(9) or { none } - - println(dog) -} diff --git a/vlib/v/checker/tests/option_var_unwrap_err.out b/vlib/v/checker/tests/option_var_unwrap_err.out new file mode 100644 index 0000000000..58d1887898 --- /dev/null +++ b/vlib/v/checker/tests/option_var_unwrap_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/option_var_unwrap_err.vv:5:9: error: to propagate the Option, `abc_2` must return an Option type + 3 | + 4 | fn abc_2(a ?string) string { + 5 | return a? + | ^ + 6 | } + 7 | +Details: vlib/v/checker/tests/option_var_unwrap_err.vv:4:21: details: prepend ? before the declaration of the return type of `abc_2` + 2 | } + 3 | + 4 | fn abc_2(a ?string) string { + | ~~~~~~ + 5 | return a? + 6 | } diff --git a/vlib/v/checker/tests/option_var_unwrap_err.vv b/vlib/v/checker/tests/option_var_unwrap_err.vv new file mode 100644 index 0000000000..6874b9e0d5 --- /dev/null +++ b/vlib/v/checker/tests/option_var_unwrap_err.vv @@ -0,0 +1,10 @@ +fn abc_1() { +} + +fn abc_2(a ?string) string { + return a? +} + +fn test_main() { + abc_2(none) +} diff --git a/vlib/v/checker/tests/option_variable_err.out b/vlib/v/checker/tests/option_variable_err.out deleted file mode 100644 index e45a818e75..0000000000 --- a/vlib/v/checker/tests/option_variable_err.out +++ /dev/null @@ -1,5 +0,0 @@ -vlib/v/checker/tests/option_variable_err.vv:2:7: error: casting to option type is forbidden - 1 | fn main() { - 2 | _ := ?bool(false) - | ~~~~~~~~~~~~ - 3 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/option_variable_err.vv b/vlib/v/checker/tests/option_variable_err.vv deleted file mode 100644 index 1ce984c915..0000000000 --- a/vlib/v/checker/tests/option_variable_err.vv +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - _ := ?bool(false) -} diff --git a/vlib/v/checker/tests/or_expr_types_mismatch.out b/vlib/v/checker/tests/or_expr_types_mismatch.out deleted file mode 100644 index 352b56b85d..0000000000 --- a/vlib/v/checker/tests/or_expr_types_mismatch.out +++ /dev/null @@ -1,14 +0,0 @@ -vlib/v/checker/tests/or_expr_types_mismatch.vv:3:19: error: wrong return type `none` in the `or {}` block, expected `string` - 1 | fn get_map() ?string { - 2 | m := {1: 'a', 2: 'b'} - 3 | return m[1] or { none } - | ~~~~ - 4 | } - 5 | -vlib/v/checker/tests/or_expr_types_mismatch.vv:8:19: error: wrong return type `none` in the `or {}` block, expected `int` - 6 | fn get_array() ?int { - 7 | a := [1, 2, 3] - 8 | return a[4] or { none } - | ~~~~ - 9 | } - 10 | diff --git a/vlib/v/checker/tests/or_expr_types_mismatch.vv b/vlib/v/checker/tests/or_expr_types_mismatch.vv deleted file mode 100644 index ab5f1c8b1f..0000000000 --- a/vlib/v/checker/tests/or_expr_types_mismatch.vv +++ /dev/null @@ -1,17 +0,0 @@ -fn get_map() ?string { - m := {1: 'a', 2: 'b'} - return m[1] or { none } -} - -fn get_array() ?int { - a := [1, 2, 3] - return a[4] or { none } -} - -fn main() { - map_result := get_map() or { return } - println(map_result) - - array_result := get_array() or { return } - println(array_result) -} diff --git a/vlib/v/checker/tests/propagate_option_with_result_err.out b/vlib/v/checker/tests/propagate_option_with_result_err.out index 71ef918307..947b1d78f6 100644 --- a/vlib/v/checker/tests/propagate_option_with_result_err.out +++ b/vlib/v/checker/tests/propagate_option_with_result_err.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/propagate_option_with_result_err.vv:6:7: warning: propagating a result like an option is deprecated, use `foo()!` instead of `foo()?` +vlib/v/checker/tests/propagate_option_with_result_err.vv:6:7: warning: propagating a result like an Option is deprecated, use `foo()!` instead of `foo()?` 4 | 5 | fn bar() ?string { 6 | foo()? diff --git a/vlib/v/checker/tests/selector_expr_option_err.out b/vlib/v/checker/tests/selector_expr_option_err.out index a5f6b49ddb..4d9b5ce1e3 100644 --- a/vlib/v/checker/tests/selector_expr_option_err.out +++ b/vlib/v/checker/tests/selector_expr_option_err.out @@ -1,6 +1,6 @@ -vlib/v/checker/tests/selector_expr_option_err.vv:10:16: error: cannot access fields of an option, handle the error with `or {...}` or propagate it with `?` - 8 | +vlib/v/checker/tests/selector_expr_option_err.vv:10:16: error: cannot access fields of an Option, handle the error with `or {...}` or propagate it with `?` + 8 | 9 | fn main() { 10 | println(abc().status_code) | ~~~~~~~~~~~ - 11 | } \ No newline at end of file + 11 | } diff --git a/vlib/v/checker/tests/struct_field_option_err.out b/vlib/v/checker/tests/struct_field_option_err.out index 30c6f2d484..26292bb458 100644 --- a/vlib/v/checker/tests/struct_field_option_err.out +++ b/vlib/v/checker/tests/struct_field_option_err.out @@ -5,37 +5,30 @@ vlib/v/checker/tests/struct_field_option_err.vv:3:6: error: struct field does no | ^ 4 | bar ?int = 1 5 | baz int = 1 -vlib/v/checker/tests/struct_field_option_err.vv:11:8: error: field `bar` is an option, so it should have either an `or {}` block, or `?` at the end - 9 | mut f := Foo{} - 10 | - 11 | _ = f.bar - | ~~~ - 12 | _ = f.bar + 1 - 13 | vlib/v/checker/tests/struct_field_option_err.vv:12:12: error: `+` cannot be used with `?int` - 10 | + 10 | 11 | _ = f.bar 12 | _ = f.bar + 1 | ^ - 13 | + 13 | 14 | _ = f.bar! vlib/v/checker/tests/struct_field_option_err.vv:14:11: error: to propagate a result, the call must also return a result type 12 | _ = f.bar + 1 - 13 | + 13 | 14 | _ = f.bar! | ^ - 15 | + 15 | 16 | _ = f.bar or { _ = 1 } vlib/v/checker/tests/struct_field_option_err.vv:16:19: error: last statement in the `or {}` block should be an expression of type `int` or exit parent scope 14 | _ = f.bar! - 15 | + 15 | 16 | _ = f.bar or { _ = 1 } | ^ - 17 | + 17 | 18 | _ = f.baz? -vlib/v/checker/tests/struct_field_option_err.vv:18:11: error: unexpected `?`, the field `baz` is neither an option, nor a result +vlib/v/checker/tests/struct_field_option_err.vv:18:11: error: unexpected `?`, the field `baz` is neither an Option, nor a result 16 | _ = f.bar or { _ = 1 } - 17 | + 17 | 18 | _ = f.baz? | ^ - 19 | } \ No newline at end of file + 19 | } diff --git a/vlib/v/checker/tests/type_cast_option_err.out b/vlib/v/checker/tests/type_cast_option_err.out index 9a3276f9e8..95931d13f9 100644 --- a/vlib/v/checker/tests/type_cast_option_err.out +++ b/vlib/v/checker/tests/type_cast_option_err.out @@ -1,5 +1,5 @@ -vlib/v/checker/tests/type_cast_option_err.vv:2:10: error: cannot type cast an option +vlib/v/checker/tests/type_cast_option_err.vv:2:10: error: cannot type cast an Option 1 | fn main() { 2 | println(int('hi'.last_index('i'))) | ~~~~~~~~~~~~~~~~~~~~~~~~~ - 3 | } \ No newline at end of file + 3 | } diff --git a/vlib/v/checker/tests/unexpected_or.out b/vlib/v/checker/tests/unexpected_or.out index 4c5e42664b..10f0159ae6 100644 --- a/vlib/v/checker/tests/unexpected_or.out +++ b/vlib/v/checker/tests/unexpected_or.out @@ -1,6 +1,6 @@ -vlib/v/checker/tests/unexpected_or.vv:6:17: error: unexpected `or` block, the function `ret_zero` does not return an option or a result - 4 | +vlib/v/checker/tests/unexpected_or.vv:6:17: error: unexpected `or` block, the function `ret_zero` does not return an Option or a result + 4 | 5 | fn main() { 6 | _ = ret_zero() or { 1 } | ~~~~~~~~ - 7 | } \ No newline at end of file + 7 | } diff --git a/vlib/v/checker/tests/unexpected_or_propagate.out b/vlib/v/checker/tests/unexpected_or_propagate.out index d4ac1c447f..206e13d1f1 100644 --- a/vlib/v/checker/tests/unexpected_or_propagate.out +++ b/vlib/v/checker/tests/unexpected_or_propagate.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/unexpected_or_propagate.vv:6:17: error: unexpected `?`, the function `ret_zero` does not return an option or a result - 4 | +vlib/v/checker/tests/unexpected_or_propagate.vv:6:17: error: unexpected `?`, the function `ret_zero` does not return an Option or a result + 4 | 5 | fn opt_fn() ?int { 6 | a := ret_zero()? | ^ 7 | return a - 8 | } \ No newline at end of file + 8 | } diff --git a/vlib/v/checker/tests/var_option_index_error.out b/vlib/v/checker/tests/var_option_index_error.out new file mode 100644 index 0000000000..cfb79c4706 --- /dev/null +++ b/vlib/v/checker/tests/var_option_index_error.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/var_option_index_error.vv:4:2: error: type `?map[string]string` is an Option, it must be unwrapped first; use `var?[]` to do it + 2 | mut var4 := ?map[string]string{} + 3 | var4?['a'] = 'b' + 4 | var4['b'] = 'c' + | ~~~~ + 5 | } diff --git a/vlib/v/checker/tests/var_option_index_error.vv b/vlib/v/checker/tests/var_option_index_error.vv new file mode 100644 index 0000000000..5b8030d191 --- /dev/null +++ b/vlib/v/checker/tests/var_option_index_error.vv @@ -0,0 +1,5 @@ +fn main() { + mut var4 := ?map[string]string{} + var4?['a'] = 'b' + var4['b'] = 'c' +} diff --git a/vlib/v/checker/tests/var_option_wrong_default.out b/vlib/v/checker/tests/var_option_wrong_default.out new file mode 100644 index 0000000000..8e1acf5818 --- /dev/null +++ b/vlib/v/checker/tests/var_option_wrong_default.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/var_option_wrong_default.vv:4:19: error: `or` block must provide a value of type `int`, not `?int` + 2 | + 3 | var2 := var1 or { var1? } + 4 | var3 := var1 or { var1 } + | ~~~~ + 5 | + 6 | println(var2) diff --git a/vlib/v/checker/tests/var_option_wrong_default.vv b/vlib/v/checker/tests/var_option_wrong_default.vv new file mode 100644 index 0000000000..d8fd2738f4 --- /dev/null +++ b/vlib/v/checker/tests/var_option_wrong_default.vv @@ -0,0 +1,7 @@ +var1 := ?int(none) + +var2 := var1 or { var1? } +var3 := var1 or { var1 } + +println(var2) +println(var3) diff --git a/vlib/v/checker/tests/var_option_wrong_type.out b/vlib/v/checker/tests/var_option_wrong_type.out new file mode 100644 index 0000000000..ce1a755fbd --- /dev/null +++ b/vlib/v/checker/tests/var_option_wrong_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/var_option_wrong_type.vv:3:23: error: mismatched types `?f64` and `string` + 1 | var_none := ?f64(none) + 2 | + 3 | var3 := var_none or { var_none + 'foo' } + | ~~~~~~~~~~~~~~~~ + 4 | println(var3) diff --git a/vlib/v/checker/tests/var_option_wrong_type.vv b/vlib/v/checker/tests/var_option_wrong_type.vv new file mode 100644 index 0000000000..68bfb9b292 --- /dev/null +++ b/vlib/v/checker/tests/var_option_wrong_type.vv @@ -0,0 +1,4 @@ +var_none := ?f64(none) + +var3 := var_none or { var_none + 'foo' } +println(var3) diff --git a/vlib/v/checker/tests/var_option_wrong_unwrap.out b/vlib/v/checker/tests/var_option_wrong_unwrap.out new file mode 100644 index 0000000000..9da08058f5 --- /dev/null +++ b/vlib/v/checker/tests/var_option_wrong_unwrap.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/var_option_wrong_unwrap.vv:3:9: error: cannot use `?` on non-option variable + 1 | var1 := ?int(none) + 2 | var2 := var1 or { var1? } + 3 | println(var2?) + | ~~~~ diff --git a/vlib/v/checker/tests/var_option_wrong_unwrap.vv b/vlib/v/checker/tests/var_option_wrong_unwrap.vv new file mode 100644 index 0000000000..739eb6eff3 --- /dev/null +++ b/vlib/v/checker/tests/var_option_wrong_unwrap.vv @@ -0,0 +1,3 @@ +var1 := ?int(none) +var2 := var1 or { var1? } +println(var2?) diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 9b7c9b1970..216add9291 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2006,6 +2006,11 @@ pub fn (mut f Fmt) ident(node ast.Ident) { } name := f.short_module(node.name) f.write(name) + if node.or_expr.kind == .propagate_option { + f.write('?') + } else if node.or_expr.kind == .block { + f.or_expr(node.or_expr) + } f.mark_import_as_used(name) } } diff --git a/vlib/v/fmt/struct.v b/vlib/v/fmt/struct.v index 85b5b24164..1e58f0ed42 100644 --- a/vlib/v/fmt/struct.v +++ b/vlib/v/fmt/struct.v @@ -247,6 +247,9 @@ pub fn (mut f Fmt) struct_init(node ast.StructInit) { if name == 'void' { name = '' } + if node.typ.has_flag(.option) { + f.write('?') + } if node.is_anon { f.write('struct ') } diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 84bed5766a..54e0989557 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -7,6 +7,97 @@ import v.ast import v.util import v.token +fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr ast.Expr, ret_typ ast.Type) { + gen_or := expr is ast.Ident && (expr as ast.Ident).or_expr.kind != .absent + if gen_or { + old_inside_opt_data := g.inside_opt_data + g.inside_opt_data = true + g.expr_with_cast(expr, expr_typ, ret_typ) + g.writeln(';') + g.writeln('if (${expr}.state != 0) {') + if expr is ast.Ident && (expr as ast.Ident).or_expr.kind == .propagate_option { + g.write('return ') + g.gen_option_error(g.cur_fn.return_type, expr) + g.writeln(';') + } else { + g.gen_or_block_stmts(var_expr.str(), '', (expr as ast.Ident).or_expr.stmts, + ret_typ, false) + } + g.writeln('}') + g.inside_opt_data = old_inside_opt_data + } else { + g.expr_with_opt(expr, expr_typ, ret_typ) + } +} + +// expr_opt_with_cast is used in cast expr when converting compatible option types +// e.g. ?int(?u8(0)) +fn (mut g Gen) expr_opt_with_cast(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string { + if !expr_typ.has_flag(.option) || !ret_typ.has_flag(.option) { + panic('cgen: expected expr_type and ret_typ to be options') + } + + if expr_typ.idx() == ret_typ.idx() && g.table.sym(expr_typ).kind != .alias { + return g.expr_with_opt(expr, expr_typ, ret_typ) + } else { + stmt_str := g.go_before_stmt(0).trim_space() + styp := g.base_type(ret_typ) + g.empty_line = true + tmp_var := g.new_tmp_var() + g.writeln('${g.typ(ret_typ)} ${tmp_var};') + g.write('_option_ok(&(${styp}[]) {') + + if expr is ast.CastExpr && expr_typ.has_flag(.option) { + g.write('*((${g.base_type(expr_typ)}*)') + g.expr(expr) + g.write('.data)') + } else { + g.inside_opt_or_res = false + g.expr(expr) + } + g.writeln(' }, (${option_name}*)(&${tmp_var}), sizeof(${styp}));') + g.write(stmt_str) + g.write(tmp_var) + return tmp_var + } +} + +// expr_with_opt is used in assigning an expression to an `option` variable +// e.g. x = y (option lhs and rhs), mut x = ?int(123), y = none +fn (mut g Gen) expr_with_opt(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string { + if expr_typ == ast.none_type { + old_inside_opt_data := g.inside_opt_data + g.inside_opt_or_res = true + defer { + g.inside_opt_data = old_inside_opt_data + } + } + if expr_typ.has_flag(.option) && ret_typ.has_flag(.option)&& (expr in [ast.Ident, ast.ComptimeSelector, ast.AsCast, ast.CallExpr, ast.MatchExpr, ast.IfExpr, ast.IndexExpr, ast.UnsafeExpr, ast.CastExpr]) { + if expr in [ast.Ident, ast.CastExpr] { + if expr_typ.idx() != ret_typ.idx() { + return g.expr_opt_with_cast(expr, expr_typ, ret_typ) + } + old_inside_opt_data := g.inside_opt_data + g.inside_opt_or_res = true + defer { + g.inside_opt_data = old_inside_opt_data + } + } + g.expr(expr) + return expr.str() + } else { + old_inside_opt_data := g.inside_opt_data + g.inside_opt_or_res = true + defer { + g.inside_opt_data = old_inside_opt_data + } + tmp_out_var := g.new_tmp_var() + g.expr_with_tmp_var(expr, expr_typ, ret_typ, tmp_out_var) + return tmp_out_var + } + return '' +} + fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { mut node := unsafe { node_ } if node.is_static { @@ -98,8 +189,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { mut val_type := node.right_types[i] val := node.right[i] mut is_call := false + mut gen_or := false mut blank_assign := false - mut is_comptime_var := false mut ident := ast.Ident{ scope: 0 } @@ -125,21 +216,30 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { && (val as ast.Ident).info is ast.IdentVar && (val as ast.Ident).kind == .variable && (val as ast.Ident).obj is ast.Var && ((val as ast.Ident).obj as ast.Var).is_comptime_field { var_type = g.unwrap_generic(g.comptime_for_field_type) val_type = var_type + gen_or = val.or_expr.kind != .absent + if gen_or { + var_type = val_type.clear_flag(.option) + } left.obj.typ = var_type - is_comptime_var = true } else if val is ast.ComptimeSelector { key_str := g.get_comptime_selector_key_type(val) if key_str != '' { var_type = g.comptime_var_type_map[key_str] or { var_type } val_type = var_type left.obj.typ = var_type - is_comptime_var = true + val_type = var_type } } else if val is ast.ComptimeCall { key_str := '${val.method_name}.return_type' var_type = g.comptime_var_type_map[key_str] or { var_type } left.obj.typ = var_type - is_comptime_var = true + } else if is_decl && val is ast.Ident && (val as ast.Ident).info is ast.IdentVar { + val_info := (val as ast.Ident).info + gen_or = val.or_expr.kind != .absent + if val_info.is_option && gen_or { + var_type = val_type.clear_flag(.option) + left.obj.typ = var_type + } } is_auto_heap = left.obj.is_auto_heap } @@ -218,9 +318,14 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { && !g.pref.translated g.is_assign_lhs = true g.assign_op = node.op - if val_type.has_flag(.option) || val_type.has_flag(.result) { - g.right_is_opt = true + + g.left_is_opt = var_type.has_flag(.option) || var_type.has_flag(.result) + g.right_is_opt = val_type.has_flag(.option) || val_type.has_flag(.result) + defer { + g.left_is_opt = false + g.right_is_opt = false } + if blank_assign { if val is ast.IndexExpr { g.assign_op = .decl_assign @@ -233,6 +338,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.is_void_expr_stmt = old_is_void_expr_stmt } else if g.inside_for_c_stmt { g.expr(val) + } else if var_type.has_flag(.option) { + g.expr_with_opt(val, val_type, var_type) } else { if left_sym.kind == .function { g.write('{void* _ = ') @@ -441,28 +548,11 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { cloned = true } } - unwrap_option := !var_type.has_flag(.option) && val_type.has_flag(.option) - if unwrap_option { - // Unwrap the option now that the testing code has been prepended. - // `pos := s.index(... - // `int pos = *(int)_t10.data;` - // if g.is_autofree { - /* - if is_option { - g.write('*($styp*)') - g.write(tmp_opt + '.data/*FFz*/') - g.right_is_opt = false - if g.inside_ternary == 0 && !node.is_simple { - g.writeln(';') - } - return - } - */ - } if !cloned { if !g.inside_comptime_for_field && ((var_type.has_flag(.option) && !val_type.has_flag(.option)) || (var_type.has_flag(.result) && !val_type.has_flag(.result))) { + g.inside_opt_or_res = true tmp_var := g.new_tmp_var() g.expr_with_tmp_var(val, val_type, var_type, tmp_var) } else if is_fixed_array_var { @@ -497,17 +587,12 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { if val.is_auto_deref_var() { g.write('*') } - if val is ast.ArrayInit { + if var_type.has_flag(.option) || gen_or { + g.expr_with_opt_or_block(val, val_type, left, var_type) + } else if val is ast.ArrayInit { g.array_init(val, c_name(ident.name)) } else if val_type.has_flag(.shared_f) { g.expr_with_cast(val, val_type, var_type) - } else if is_comptime_var && g.right_is_opt { - if var_type.has_flag(.option) && val is ast.ComptimeSelector { - g.expr(val) - } else { - tmp_var := g.new_tmp_var() - g.expr_with_tmp_var(val, val_type, var_type, tmp_var) - } } else { g.expr(val) } @@ -516,7 +601,9 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } } else { - if node.has_cross_var { + if var_type.has_flag(.option) || gen_or { + g.expr_with_opt_or_block(val, val_type, left, var_type) + } else if node.has_cross_var { g.gen_cross_tmp_variable(node.left, val) } else { if op_overloaded { diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 3e815fe12e..1158b52eed 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -64,12 +64,6 @@ fn (mut g Gen) get_str_fn(typ ast.Type) string { unwrapped = ast.u64_type } } - if typ.has_flag(.option) { - unwrapped.set_flag(.option) - } - if typ.has_flag(.result) { - unwrapped.set_flag(.result) - } styp := g.typ(unwrapped) mut sym := g.table.sym(unwrapped) mut str_fn_name := styp_to_str_fn_name(styp) @@ -904,9 +898,13 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, typ_str string, sftyp := g.typ(ftyp_noshared) mut field_styp := sftyp.replace('*', '') field_styp_fn_name := if sym_has_str_method { - left_cc_type := g.cc_type(ftyp_noshared, false) - left_fn_name := util.no_dots(left_cc_type) - mut field_fn_name := '${left_fn_name}_str' + mut field_fn_name := if ftyp_noshared.has_flag(.option) { + '${field_styp}_str' + } else { + left_cc_type := g.cc_type(ftyp_noshared, false) + left_fn_name := util.no_dots(left_cc_type) + '${left_fn_name}_str' + } if sym.info is ast.Struct { field_fn_name = g.generic_fn_name(sym.info.concrete_types, field_fn_name) } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4b3ebe2ef7..a75e4cdd7e 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -147,7 +147,8 @@ mut: arraymap_set_pos int // map or array set value position stmt_path_pos []int // positions of each statement start, for inserting C statements before the current statement skip_stmt_pos bool // for handling if expressions + autofree (since both prepend C statements) - right_is_opt bool + left_is_opt bool // left hand side on assignment is an option + right_is_opt bool // right hand side on assignment is an option indent int empty_line bool assign_op token.Kind // *=, =, etc (for array_set) @@ -1842,7 +1843,11 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T } else { g.writeln('${g.typ(ret_typ)} ${tmp_var};') if ret_typ.has_flag(.option) { - g.write('_option_ok(&(${styp}[]) { ') + if expr_typ.has_flag(.option) && expr in [ast.StructInit, ast.ArrayInit, ast.MapInit] { + g.write('_option_none(&(${styp}[]) { ') + } else { + g.write('_option_ok(&(${styp}[]) { ') + } } else { g.write('_result_ok(&(${styp}[]) { ') } @@ -4102,7 +4107,7 @@ fn (mut g Gen) ident(node ast.Ident) { if node.obj is ast.Var { if !g.is_assign_lhs && node.obj.is_comptime_field { if g.comptime_for_field_type.has_flag(.option) { - if g.inside_opt_or_res { + if (g.inside_opt_or_res && node.or_expr.kind == .absent) || g.left_is_opt { g.write('${name}') } else { g.write('/*opt*/') @@ -4112,6 +4117,12 @@ fn (mut g Gen) ident(node ast.Ident) { } else { g.write('${name}') } + if node.or_expr.kind != .absent { + stmt_str := g.go_before_stmt(0).trim_space() + g.empty_line = true + g.or_block(name, node.or_expr, g.comptime_for_field_type) + g.writeln(stmt_str) + } return } } @@ -4120,13 +4131,19 @@ fn (mut g Gen) ident(node ast.Ident) { // `x = new_opt()` => `x = new_opt()` (g.right_is_opt == true) // `println(x)` => `println(*(int*)x.data)` if node.info.is_option && !(g.is_assign_lhs && g.right_is_opt) { - if g.inside_opt_or_res { + if (g.inside_opt_or_res && node.or_expr.kind == .absent) || g.left_is_opt { g.write('${name}') } else { g.write('/*opt*/') styp := g.base_type(node.info.typ) g.write('(*(${styp}*)${name}.data)') } + if node.or_expr.kind != .absent { + stmt_str := g.go_before_stmt(0).trim_space() + g.empty_line = true + g.or_block(name, node.or_expr, node.info.typ) + g.writeln(stmt_str) + } return } if !g.is_assign_lhs && node.info.share == .shared_t { @@ -4214,8 +4231,15 @@ fn (mut g Gen) cast_expr(node ast.CastExpr) { expr_type = g.unwrap_generic(g.comptime_for_field_type) } if sym.kind in [.sum_type, .interface_] { - g.expr_with_cast(node.expr, expr_type, node_typ) - } else if sym.kind == .struct_ && !node.typ.is_ptr() && !(sym.info as ast.Struct).is_typedef { + if node.typ.has_flag(.option) && node.expr is ast.None { + g.gen_option_error(node.typ, node.expr) + } else if node.typ.has_flag(.option) { + g.expr_with_opt(node.expr, expr_type, node.typ) + } else { + g.expr_with_cast(node.expr, expr_type, node_typ) + } + } else if !node.typ.has_flag(.option) && sym.kind == .struct_ && !node.typ.is_ptr() + && !(sym.info as ast.Struct).is_typedef { // deprecated, replaced by Struct{...exr} styp := g.typ(node.typ) g.write('*((${styp} *)(&') @@ -4250,6 +4274,12 @@ fn (mut g Gen) cast_expr(node ast.CastExpr) { } if node.typ.has_flag(.option) && node.expr is ast.None { g.gen_option_error(node.typ, node.expr) + } else if node.typ.has_flag(.option) { + if sym.kind == .alias && node.expr_type.has_flag(.option) { + g.expr_opt_with_cast(node.expr, expr_type, node.typ) + } else { + g.expr_with_opt(node.expr, expr_type, node.typ) + } } else { g.write('(${cast_label}(') if sym.kind == .alias && g.table.final_sym(node.typ).kind == .string { @@ -4521,6 +4551,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { fn_return_is_multi := sym.kind == .multi_return fn_return_is_option := fn_ret_type.has_flag(.option) fn_return_is_result := fn_ret_type.has_flag(.result) + mut has_semicolon := false if node.exprs.len == 0 { g.write_defer_stmts_when_needed() @@ -4671,6 +4702,8 @@ fn (mut g Gen) return_stmt(node ast.Return) { } if g.table.sym(mr_info.types[i]).kind in [.sum_type, .interface_] { g.expr_with_cast(expr, node.types[i], mr_info.types[i]) + } else if mr_info.types[i].has_flag(.option) && !node.types[i].has_flag(.option) { + g.expr_with_opt(expr, node.types[i], mr_info.types[i]) } else { g.expr(expr) } @@ -4811,7 +4844,11 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.expr(expr0) } } else { - g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) + if g.fn_decl.return_type.has_flag(.option) { + g.expr_with_opt(node.exprs[0], node.types[0], g.fn_decl.return_type) + } else { + g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) + } } if use_tmp_var { g.writeln(';') @@ -5727,6 +5764,52 @@ fn (mut g Gen) sort_structs(typesa []&ast.TypeSymbol) []&ast.TypeSymbol { } } +fn (mut g Gen) gen_or_block_stmts(cvar_name string, cast_typ string, stmts []ast.Stmt, return_type ast.Type, is_option bool) { + g.indent++ + for i, stmt in stmts { + if i == stmts.len - 1 { + expr_stmt := stmt as ast.ExprStmt + g.set_current_pos_as_last_stmt_pos() + if g.inside_return && (expr_stmt.typ.idx() == ast.error_type_idx + || expr_stmt.typ in [ast.none_type, ast.error_type]) { + // `return foo() or { error('failed') }` + if g.cur_fn != unsafe { nil } { + if g.cur_fn.return_type.has_flag(.result) { + g.write('return ') + g.gen_result_error(g.cur_fn.return_type, expr_stmt.expr) + g.writeln(';') + } else if g.cur_fn.return_type.has_flag(.option) { + g.write('return ') + g.gen_option_error(g.cur_fn.return_type, expr_stmt.expr) + g.writeln(';') + } + } + } else { + if expr_stmt.typ == ast.none_type_idx { + g.write('${cvar_name} = ') + g.gen_option_error(return_type, expr_stmt.expr) + g.writeln(';') + } else { + if is_option { + g.write('*(${cast_typ}*) ${cvar_name}.data = ') + } else { + g.write('${cvar_name} = ') + } + old_inside_opt_data := g.inside_opt_data + g.inside_opt_data = true + g.expr_with_cast(expr_stmt.expr, expr_stmt.typ, return_type.clear_flag(.option).clear_flag(.result)) + g.inside_opt_data = old_inside_opt_data + g.writeln(';') + g.stmt_path_pos.delete_last() + } + } + } else { + g.stmt(stmt) + } + } + g.indent-- +} + // fn (mut g Gen) start_tmp() { // } // If user is accessing the return value eg. in assigment, pass the variable name. @@ -5768,39 +5851,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty stmts := or_block.stmts if stmts.len > 0 && stmts.last() is ast.ExprStmt && (stmts.last() as ast.ExprStmt).typ != ast.void_type { - g.indent++ - for i, stmt in stmts { - if i == stmts.len - 1 { - expr_stmt := stmt as ast.ExprStmt - g.set_current_pos_as_last_stmt_pos() - if g.inside_return && (expr_stmt.typ.idx() == ast.error_type_idx - || expr_stmt.typ in [ast.none_type, ast.error_type]) { - // `return foo() or { error('failed') }` - if g.cur_fn != unsafe { nil } { - if g.cur_fn.return_type.has_flag(.result) { - g.write('return ') - g.gen_result_error(g.cur_fn.return_type, expr_stmt.expr) - g.writeln(';') - } else if g.cur_fn.return_type.has_flag(.option) { - g.write('return ') - g.gen_option_error(g.cur_fn.return_type, expr_stmt.expr) - g.writeln(';') - } - } - } else { - g.write('*(${mr_styp}*) ${cvar_name}.data = ') - old_inside_opt_data := g.inside_opt_data - g.inside_opt_data = true - g.expr_with_cast(expr_stmt.expr, expr_stmt.typ, return_type.clear_flag(.option).clear_flag(.result)) - g.inside_opt_data = old_inside_opt_data - g.writeln(';') - g.stmt_path_pos.delete_last() - } - } else { - g.stmt(stmt) - } - } - g.indent-- + g.gen_or_block_stmts(cvar_name, mr_styp, stmts, return_type, true) } else { g.stmts(stmts) if stmts.len > 0 && stmts.last() is ast.ExprStmt { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 60776f45fe..1e7301acae 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -883,7 +883,11 @@ fn (mut g Gen) gen_to_str_method_call(node ast.CallExpr) bool { } } else if left_node is ast.Ident { if left_node.obj is ast.Var { - if g.comptime_var_type_map.len > 0 { + if left_node.obj.is_comptime_field { + rec_type = g.comptime_for_field_type + g.gen_expr_to_string(left_node, rec_type) + return true + } else if g.comptime_var_type_map.len > 0 { rec_type = left_node.obj.typ g.gen_expr_to_string(left_node, rec_type) return true @@ -900,6 +904,9 @@ fn (mut g Gen) gen_to_str_method_call(node ast.CallExpr) bool { } else if left_node is ast.None { g.gen_expr_to_string(left_node, ast.none_type) return true + } else if node.left_type.has_flag(.option) { + g.gen_expr_to_string(left_node, g.unwrap_generic(node.left_type)) + return true } g.get_str_fn(rec_type) return false @@ -980,8 +987,9 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } mut typ_sym := g.table.sym(unwrapped_rec_type) - // alias type that undefined this method (not include `str`) need to use parent type - if typ_sym.kind == .alias && node.name != 'str' && !typ_sym.has_method(node.name) { + // non-option alias type that undefined this method (not include `str`) need to use parent type + if !left_type.has_flag(.option) && typ_sym.kind == .alias && node.name != 'str' + && !typ_sym.has_method(node.name) { unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type typ_sym = g.table.sym(unwrapped_rec_type) } else if typ_sym.kind == .array && !typ_sym.has_method(node.name) { @@ -2354,6 +2362,9 @@ fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang as g.expr(arg.expr) g.write('->val') return + } else if expected_type.has_flag(.option) { + g.expr_with_opt(arg.expr, arg_typ, expected_type) + return } else if arg.expr is ast.ArrayInit { if arg.expr.is_fixed { if !arg.expr.has_it { diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 2e5324781e..078aa6fdf1 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -97,7 +97,10 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { g.gen_plain_infix_expr(node) return } - if (left.typ.is_ptr() && right.typ.is_int()) || (right.typ.is_ptr() && left.typ.is_int()) { + is_none_check := node.left_type.has_flag(.option) && node.right is ast.None + if is_none_check { + g.gen_is_none_check(node) + } else if (left.typ.is_ptr() && right.typ.is_int()) || (right.typ.is_ptr() && left.typ.is_int()) { g.gen_plain_infix_expr(node) } else if (left.typ.idx() == ast.string_type_idx || (!has_defined_eq_operator && left.unaliased.idx() == ast.string_type_idx)) && node.right is ast.StringLiteral @@ -951,6 +954,19 @@ fn (mut g Gen) infix_expr_and_or_op(node ast.InfixExpr) { g.gen_plain_infix_expr(node) } +fn (mut g Gen) gen_is_none_check(node ast.InfixExpr) { + stmt_str := g.go_before_stmt(0).trim_space() + g.empty_line = true + left_var := g.expr_with_opt(node.left, node.left_type, node.left_type) + g.writeln(';') + g.write(stmt_str) + g.write(' ') + + g.write('${left_var}.state') + g.write(' ${node.op.str()} ') + g.write('2') // none state +} + // gen_plain_infix_expr generates basic code for infix expressions, // without any overloading of any kind // i.e. v`a + 1` => c`a + 1` diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index f57632fc96..9d9db7f959 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -63,8 +63,8 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { typ = typ.clear_flag(.shared_f).set_nr_muls(0) } mut sym := g.table.sym(typ) - // when type is alias and doesn't has `str()`, print the aliased value - if mut sym.info is ast.Alias && !sym.has_method('str') { + // when type is non-option alias and doesn't has `str()`, print the aliased value + if mut sym.info is ast.Alias && !sym.has_method('str') && !etype.has_flag(.option) { parent_sym := g.table.sym(sym.info.parent_type) if parent_sym.has_method('str') { typ = sym.info.parent_type @@ -103,9 +103,11 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } } else if sym_has_str_method || sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return, .sum_type, .interface_] { - is_ptr := typ.is_ptr() + unwrap_option := expr is ast.Ident && (expr as ast.Ident).or_expr.kind == .propagate_option + exp_typ := if unwrap_option { typ.clear_flag(.option) } else { typ } + is_ptr := exp_typ.is_ptr() is_var_mut := expr.is_auto_deref_var() - str_fn_name := g.get_str_fn(typ) + str_fn_name := g.get_str_fn(exp_typ) if is_ptr && !is_var_mut { ref_str := '&'.repeat(typ.nr_muls()) g.write('str_intp(1, _MOV((StrIntpData[]){{_SLIT("${ref_str}"), ${si_s_code} ,{.d_s = isnil(') @@ -126,7 +128,12 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } } } - g.expr_with_cast(expr, typ, typ) + if unwrap_option { + g.expr(expr) + } else { + g.expr_with_cast(expr, typ, typ) + } + if is_shared { g.write('->val') } diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index a3b0972b9c..490d3f41fe 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -62,6 +62,8 @@ fn (mut g Gen) struct_init(node ast.StructInit) { } else { g.write('&(${basetyp}){') } + } else if node.typ.has_flag(.option) { + g.write('(${g.base_type(node.typ)}){') } else if g.inside_cinit { if is_multiline { g.writeln('{') diff --git a/vlib/v/parser/containers.v b/vlib/v/parser/containers.v index 32bc44e486..777c97f85a 100644 --- a/vlib/v/parser/containers.v +++ b/vlib/v/parser/containers.v @@ -8,6 +8,10 @@ import v.token fn (mut p Parser) array_init() ast.ArrayInit { first_pos := p.tok.pos() + is_option := p.tok.kind == .question + if is_option { + p.next() + } mut last_pos := p.tok.pos() p.check(.lsbr) // p.warn('array_init() exp=$p.expected_type') @@ -40,6 +44,9 @@ fn (mut p Parser) array_init() ast.ArrayInit { } else { array_type = ast.new_type(idx) } + if is_option { + array_type = array_type.set_flag(.option) + } has_type = true } last_pos = p.tok.pos() @@ -107,7 +114,8 @@ fn (mut p Parser) array_init() ast.ArrayInit { last_pos = p.tok.pos() p.check(.rcbr) } else { - p.warn_with_pos('use e.g. `x := [1]Type{}` instead of `x := [1]Type`', + modifier := if is_option { '?' } else { '' } + p.warn_with_pos('use e.g. `x := ${modifier}[1]Type{}` instead of `x := ${modifier}[1]Type`', first_pos.extend(last_pos)) } } else { @@ -126,7 +134,9 @@ fn (mut p Parser) array_init() ast.ArrayInit { } if exprs.len == 0 && p.tok.kind != .lcbr && has_type { if !p.pref.is_fmt { - p.warn_with_pos('use `x := []Type{}` instead of `x := []Type`', first_pos.extend(last_pos)) + modifier := if is_option { '?' } else { '' } + p.warn_with_pos('use `x := ${modifier}[]Type{}` instead of `x := ${modifier}[]Type`', + first_pos.extend(last_pos)) } } mut has_len := false @@ -141,6 +151,9 @@ fn (mut p Parser) array_init() ast.ArrayInit { attr_pos = p.tok.pos() key := p.check_name() p.check(.colon) + if is_option { + p.error('Option array cannot have initializers') + } match key { 'len' { has_len = true diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 5213c5519a..07f3efdb62 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -104,6 +104,10 @@ pub fn (mut p Parser) parse_array_type(expecting token.Kind) ast.Type { } pub fn (mut p Parser) parse_map_type() ast.Type { + is_option := p.tok.kind == .question && p.peek_tok.kind == .name // option map + if is_option { + p.next() + } p.next() if p.tok.kind != .lsbr { return ast.map_type @@ -143,7 +147,11 @@ pub fn (mut p Parser) parse_map_type() ast.Type { if key_type.has_flag(.generic) || value_type.has_flag(.generic) { return ast.new_type(idx).set_flag(.generic) } - return ast.new_type(idx) + if is_option { + return ast.new_type(idx).set_flag(.option) + } else { + return ast.new_type(idx) + } } pub fn (mut p Parser) parse_chan_type() ast.Type { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 06fe6da954..c53717f0a7 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2100,6 +2100,10 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { // p.warn('name ') + is_option := p.tok.kind == .question && p.peek_tok.kind == .lsbr + if is_option { + p.next() + } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic if is_shared { @@ -2129,6 +2133,7 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { scope: p.scope } } + in_select := p.prev_tok.kind == .arrow pos := p.tok.pos() mut name := p.check_name() if name == '_' { @@ -2142,6 +2147,7 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { is_mut: false is_static: false is_volatile: false + is_option: is_option } scope: p.scope } @@ -2152,6 +2158,20 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { if p.expr_mod.len > 0 { name = '${p.expr_mod}.${name}' } + + // parsers ident like var?, except on '<- var' '$if ident ?', '[if define ?]' + allowed_cases := !in_select && !p.inside_comptime_if && !p.inside_ct_if_expr + mut or_kind := ast.OrKind.absent + mut or_stmts := []ast.Stmt{} + mut or_pos := token.Pos{} + + if allowed_cases && p.tok.kind == .question && p.peek_tok.kind != .lpar { // var?, not var?( + or_kind = ast.OrKind.propagate_option + p.check(.question) + } else if allowed_cases && p.tok.kind == .key_orelse { + or_kind = ast.OrKind.block + or_stmts, or_pos = p.or_block(.no_err_var) + } return ast.Ident{ tok_kind: p.tok.kind kind: .unresolved @@ -2166,9 +2186,15 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { is_mut: is_mut is_static: is_static is_volatile: is_volatile + is_option: or_kind != ast.OrKind.absent share: ast.sharetype_from_flags(is_shared, is_atomic) } scope: p.scope + or_expr: ast.OrExpr{ + kind: or_kind + stmts: or_stmts + pos: or_pos + } } } @@ -2338,6 +2364,7 @@ fn (mut p Parser) is_generic_cast() bool { pub fn (mut p Parser) name_expr() ast.Expr { prev_tok_kind := p.prev_tok.kind mut node := ast.empty_expr + if p.expecting_type { if p.tok.kind == .dollar { node = p.parse_comptime_type() @@ -2365,7 +2392,8 @@ pub fn (mut p Parser) name_expr() ast.Expr { // p.warn('resetting') p.expr_mod = '' // `map[string]int` initialization - if p.tok.lit == 'map' && p.peek_tok.kind == .lsbr { + if (p.tok.lit == 'map' && p.peek_tok.kind == .lsbr) + || (p.tok.kind == .question && p.peek_tok.lit == 'map') { mut pos := p.tok.pos() map_type := p.parse_map_type() if p.tok.kind == .lcbr { @@ -2484,12 +2512,21 @@ pub fn (mut p Parser) name_expr() ast.Expr { p.check(.dot) p.expr_mod = mod } + is_option := p.tok.kind == .question lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 { - p.tok.lit[0].is_capital() + if is_option { + if p.peek_tok.kind != .eof && p.peek_tok.lit.len > 0 { + p.peek_tok.lit[0].is_capital() + } else { + false + } + } else { + p.tok.lit[0].is_capital() + } } else { false } - is_option := p.tok.kind == .question + is_generic_call := p.is_generic_call() is_generic_cast := p.is_generic_cast() is_generic_struct_init := p.is_generic_struct_init() @@ -2506,9 +2543,16 @@ pub fn (mut p Parser) name_expr() ast.Expr { } } } else if p.peek_tok.kind == .lpar || is_generic_call || is_generic_cast - || (is_option && p.peek_token(2).kind == .lpar) { + || (is_option && p.peek_token(2).kind == .lpar) || (is_option && p.peek_tok.kind == .lsbr + && p.peek_token(2).kind == .rsbr && p.peek_token(3).kind == .name + && p.peek_token(4).kind == .lpar) { + is_array := p.peek_tok.kind == .lsbr // foo(), foo() or type() cast - mut name := if is_option { p.peek_tok.lit } else { p.tok.lit } + mut name := if is_option { + if is_array { p.peek_token(3).lit } else { p.peek_tok.lit } + } else { + p.tok.lit + } if mod.len > 0 { name = '${mod}.${name}' } @@ -2574,7 +2618,8 @@ pub fn (mut p Parser) name_expr() ast.Expr { } } } - } else if (p.peek_tok.kind == .lcbr || is_generic_struct_init) + } else if (((!is_option && p.peek_tok.kind == .lcbr) + || (is_option && p.peek_token(2).kind == .lcbr)) || is_generic_struct_init) && (!p.inside_match || (p.inside_select && prev_tok_kind == .arrow && lit0_is_capital)) && !p.inside_match_case && (!p.inside_if || p.inside_select) && (!p.inside_for || p.inside_select) && !known_var { @@ -2662,8 +2707,9 @@ pub fn (mut p Parser) name_expr() ast.Expr { } p.expr_mod = '' return node + } else if p.tok.kind == .question && p.peek_tok.kind == .lsbr { + return p.array_init() } - ident := p.parse_ident(language) node = ident if p.inside_defer { diff --git a/vlib/v/tests/interface_embedding_smartcast_test.v b/vlib/v/tests/interface_embedding_smartcast_test.v index 537aff4fc1..14bd4504bc 100644 --- a/vlib/v/tests/interface_embedding_smartcast_test.v +++ b/vlib/v/tests/interface_embedding_smartcast_test.v @@ -27,7 +27,7 @@ fn (e MyError) code() int { } // An example function that returns a custom error. -fn foo() ?string { +fn foo() !string { return MyError{ msg: 'foo' blah: 'world' diff --git a/vlib/v/tests/option_call_propagation_test.v b/vlib/v/tests/option_call_propagation_test.v new file mode 100644 index 0000000000..7f38a89576 --- /dev/null +++ b/vlib/v/tests/option_call_propagation_test.v @@ -0,0 +1,16 @@ +fn a() ?int { + return none +} + +fn abc() ?int { + varz := a() + dump(varz) + assert varz == none + return varz +} + +fn test_option_call_propagation() { + var := abc() or { 1 } + assert var == 1 + println(var) +} diff --git a/vlib/v/tests/option_compvar_val_test.v b/vlib/v/tests/option_compvar_val_test.v index 2d0f06a245..639c7296d8 100644 --- a/vlib/v/tests/option_compvar_val_test.v +++ b/vlib/v/tests/option_compvar_val_test.v @@ -30,7 +30,7 @@ fn encode_struct[T](val T) map[string][]string { // work if comment lines 27 and 28 write1(value) wr.write2(value) - out[field.name] << '${value}' + out[field.name] << '${value:d}' } $else { write1(value) wr.write2(value) @@ -40,7 +40,7 @@ fn encode_struct[T](val T) map[string][]string { $if field.is_option { write1(value) wr.write2(value) - out[field.name] << '${value}' + out[field.name] << '${value:d}' } $else { write1(value) wr.write2(value) diff --git a/vlib/v/tests/option_test.v b/vlib/v/tests/option_test.v index a89c40bb5c..f5689a379b 100644 --- a/vlib/v/tests/option_test.v +++ b/vlib/v/tests/option_test.v @@ -401,7 +401,7 @@ fn test_option_c_struct_gen() { _ := get_opt_to_c_struct() or { C.stat{} } } -// For issue #16062: checker disallow the return of voidptr(nil) in or block +// For issue #16062: checker disallowed the return of voidptr(nil) in or block struct Bar {} fn get_bar(should_return_value bool) ?&Bar { @@ -411,7 +411,7 @@ fn get_bar(should_return_value bool) ?&Bar { return none } -fn test_() { +fn test_allow_returning_an_optional_pointer_to_a_struct() { a := get_bar(true)? assert a == unsafe { nil } // @@ -424,3 +424,19 @@ fn test_() { get_bar(false) or { unsafe { nil } } assert true } + +struct AFoo { +mut: + name string +} + +fn (mut f AFoo) opt_string(arr ?[]int) ?string { + return arr?.len.str() +} + +fn test_creating_an_option_from_a_struct_value() { + mut m := ?AFoo(AFoo{}) + assert m?.opt_string([1, 2, 3])? == '3' + m?.name = 'foo' + assert m?.name == 'foo' +} diff --git a/vlib/v/tests/option_var_cast_test.v b/vlib/v/tests/option_var_cast_test.v new file mode 100644 index 0000000000..cad0ee9dd3 --- /dev/null +++ b/vlib/v/tests/option_var_cast_test.v @@ -0,0 +1,61 @@ +type SumType = f64 | int + +type MyByte = u8 + +struct Struct { + a int +} + +type Alias = int + +fn test_main() { + arr := [1, 2, 3] + mut t := ?[]int([]int{len: 10, init: 2}) + assert t != none + t = ?[]int(arr) + assert t != none + t = ?[]int([]int{len: 1, init: 0}) + assert t != none + + mut t2 := ?int(1) + assert t2 != none + mut t3 := ?f64(1.2) + assert t3 != none + mut t4 := ?string('') + assert t4 != none + mut t5 := ?SumType(1) + assert t5 != none + mut t6 := ?SumType(none) + assert t6 == none + mut t7 := ?Struct(Struct{}) + assert t7 != none +} + +fn test_cast() { + var := ?u8(1) + println(?u8(var)) + println(?u8(?u8(255))) + println(?int(none)) + println(?int(var?)) + + a := ?Struct{} + assert a == none + b := ?Struct(Struct{}) + assert b != none + + mut v1 := ?bool(1) + assert v1?.str() == 'true' + v1 = ?bool(true) + assert v1?.str() == 'true' + v1 = ?bool(0) + assert v1?.str() == 'false' + v1 = ?bool(false) + assert v1?.str() == 'false' +} + +fn test_cast_alias() { + assert ?MyByte(0).str() == 'Option(MyByte(0))' + assert ?MyByte(255).str() == 'Option(MyByte(255))' + assert ?MyByte(?u8(0)).str() == 'Option(MyByte(0))' + assert ?MyByte(?u8(255)).str() == 'Option(MyByte(255))' +} diff --git a/vlib/v/tests/option_var_map_test.v b/vlib/v/tests/option_var_map_test.v new file mode 100644 index 0000000000..b05db953a4 --- /dev/null +++ b/vlib/v/tests/option_var_map_test.v @@ -0,0 +1,23 @@ +fn abc(a ?map[string]string) ?string { + return a?['foo'] +} + +fn abc_2(a ?map[string]string) ?string { + return a?['foo'] +} + +fn test_option_map() { + mut var4 := ?map[string]string{} + assert var4 == none + var4 = { + '': '' + } + var4?['foo'] = 'foo' + assert abc(var4)? == 'foo' + assert abc_2(var4)? == 'foo' + assert var4 != none + assert var4? == { + '': '' + 'foo': 'foo' + } +} diff --git a/vlib/v/tests/option_var_test.v b/vlib/v/tests/option_var_test.v new file mode 100644 index 0000000000..4d19a1f746 --- /dev/null +++ b/vlib/v/tests/option_var_test.v @@ -0,0 +1,210 @@ +import time + +type MyAlias = []int +type MySumType = []f64 | []int + +struct Test { +} + +struct StructType2 { + a ?time.Time +} + +struct StructType { +mut: + a string + b ?int + c ?f64 + d ?[]string + e ?MyAlias + f ?MySumType +} + +struct Decoder {} + +fn (d &Decoder) decode[T](typ T) T { + $for field in T.fields { + $if field.is_option { + dump(typ.$(field.name) ?.str()) + typ.$(field.name) = none + dump(typ.$(field.name) ?.str()) + } + } + return typ +} + +fn test_comptime() { + d := Decoder{} + result := d.decode(StructType{ + a: 'foo' + b: 3 + }) + println(result) +} + +fn test_cast_option() { + mut x := ?int(123) + dump(x) + assert x != none + x = none + assert x == none + dump(x) +} + +fn test_assign_from_option() { + mut x := ?int(123) + mut y := x + println(y) + assert x != none + assert y != none + assert x? == 123 + assert y? == 123 +} + +fn test_blank_assign() { + _ := ?bool(false) +} + +fn test_optional_value_assign() { + x := ?int(0) + assert x != none + assert x? == 0 +} + +fn test_assert_initialized() { + mut x := ?int(1) + mut y := ?int(1) + assert x != none + assert y != none +} + +fn test_comptime_checks() { + val := StructType{ + a: 'foo' + b: 3 + } + $for field in StructType.fields { + value := val.$(field.name) + $if field.is_option { + var := val.$(field.name) + var2 := var + dump(var) + dump(var2) + } + } +} + +fn test_none_initialization() { + mut var := ?int(none) + mut var2 := ?int(none) + mut var3 := ?int(none) + + assert var == none + assert var2 == none + assert var3 == none + + var = 1 + var2 = 2 + var3 = 3 + + assert var? == 1 + assert var2? == 2 + assert var3? == 3 + + assert var != none + assert var2 != none + assert var3 != none +} + +fn test_as_cast() { + var := StructType2{} + b := var.a as ?time.Time +} + +fn test_unwrap() { + var := ?int(1) + println(var) + assert var != none + assert var? == 1 + + var2 := var? + 1 + println(var2) + + assert var2 == 2 +} + +fn test_or_block() { + var1 := ?int(none) + var2 := var1 or { 0 } + var3 := var1 or { 1 } + assert var2 + var3 == 1 + var4 := var1 or { + t := 1 + var3 + t + } + + assert var4 == 2 +} + +fn test_default_values() { + var_none := ?f64(none) + var_none2 := ?string(none) + + var2 := var_none or { 1.0 } + println(var2) + assert var2 == 1.0 + + var3 := var_none2 or { 'foo' } + println(var3) + assert var3 == 'foo' +} + +fn test_assert_option() { + var1 := ?int(none) + varz := ?f64(none) + assert var1 == none + + var2 := var1 or { 1 } + + assert var1 == none + assert var2 == 1 + + println(var2) + + var3 := varz or { 0.0 } + assert var3 == 0 + + var4 := varz + assert var4 == none +} + +fn test_opt_assign() { + mut var1 := ?int(none) + assert var1 == none + var1 = 1 + assert var1 != none + var2 := var1 + assert var2? == var1? +} + +fn test_opt_none() { + mut t1 := ?int(none) + t2 := ?int(none) + + t1 = t2 + assert t1 == none + assert t2 == none + + mut t3 := ?Test{} + t4 := t3 + assert t4 == none + assert t3 == none + + mut t5 := ?map[string]string{} + t7 := { + 'foo': 'bar' + } + t6 := if t5 != none { t5?.clone() } else { t7 } + assert t5 == none + assert t6.len == 1 +} diff --git a/vlib/v/tests/var_option_arr_test.v b/vlib/v/tests/var_option_arr_test.v new file mode 100644 index 0000000000..eb30b9ad77 --- /dev/null +++ b/vlib/v/tests/var_option_arr_test.v @@ -0,0 +1,36 @@ +fn abc() ?[]int { + return [1, 2, 3] +} + +fn arr_opt(arr ?[]string) ? { + assert arr != none + assert arr?.len != 0 + + for k, v in arr { + assert arr?[k] == 'foo' + assert v == 'foo' + } +} + +fn test_main() { + mut var2 := abc()? + assert var2.len == 3 + + mut var := ?[]int{} + assert var == none + if var == none { + var = [1] + } + assert var != none + assert var?[0] == 1 + assert var?.len == 1 + + mut var3 := ?[]string{} + assert var3 == none + var3 = ['foo'] + assert var3 != none + + arr_opt(var3) + arr_opt([]string{len: 4, init: 'foo'}) + arr_opt(['foo', 'foo', 'foo', 'foo']) +} diff --git a/vlib/v/tests/var_option_as_arg_test.v b/vlib/v/tests/var_option_as_arg_test.v new file mode 100644 index 0000000000..c09e45474e --- /dev/null +++ b/vlib/v/tests/var_option_as_arg_test.v @@ -0,0 +1,20 @@ +fn is_none(a ?string) bool { + return a == none +} + +fn is_map_none(a ?map[string]int) bool { + return a == none +} + +fn test_opt_call_arg() { + assert is_none(?string(none)) + var := ?string('foo') + assert is_none(var) == false + + mut var2 := ?map[string]int{} + assert var2 == none + var2 = { + 'a': 1 + } + assert is_map_none(var2) == false +} diff --git a/vlib/v/tests/var_option_comptime_test.v b/vlib/v/tests/var_option_comptime_test.v new file mode 100644 index 0000000000..43cac5e5e0 --- /dev/null +++ b/vlib/v/tests/var_option_comptime_test.v @@ -0,0 +1,25 @@ +struct Test { + a ?string +} + +fn foo(a ?string) ? { + println(a?) + if a == none { + assert false + } +} + +fn test_main() { + v := Test{} + $for f in Test.fields { + mut t := v.$(f.name) + assert t == none + z := t or { '' } + assert z == '' + foo(t) + t = 'foo' + assert t != none + foo(t) + assert t? == 'foo' + } +} diff --git a/vlib/v/tests/var_option_opt_arg_test.v b/vlib/v/tests/var_option_opt_arg_test.v new file mode 100644 index 0000000000..550abb664a --- /dev/null +++ b/vlib/v/tests/var_option_opt_arg_test.v @@ -0,0 +1,27 @@ +fn option_arg(x ?int) ?int { + assert x != none + return x +} + +fn option_arg2(x ?f64, y ?int, z ?string) ?string { + assert x != none + assert y != none + assert z != none + return z +} + +fn option_arg3(x ?f64, y ?int, z ?string) bool { + assert x == none + assert y == none + assert z == none + return true +} + +fn test_main() { + var := ?int(1) + assert option_arg(var)? == 1 + assert option_arg(100)? == 100 + assert option_arg2(1.1, 1, '')? == '' + assert option_arg2(1.2, 2, 'foo')? == 'foo' + assert option_arg3(none, none, none) +} diff --git a/vlib/v/tests/var_option_opt_multi_return_test.v b/vlib/v/tests/var_option_opt_multi_return_test.v new file mode 100644 index 0000000000..e308ca8061 --- /dev/null +++ b/vlib/v/tests/var_option_opt_multi_return_test.v @@ -0,0 +1,17 @@ +fn ret_opt() ?string { + return 'foo' +} + +fn option_arg(x ?int) (?string, ?int) { + assert x == none + return '1', x +} + +fn test_main() { + var := ret_opt()? + assert var == 'foo' + + var1, var2 := option_arg(none) + assert var1 != none + assert var2 == none +} diff --git a/vlib/v/tests/var_option_struct_test.v b/vlib/v/tests/var_option_struct_test.v new file mode 100644 index 0000000000..67477bb57d --- /dev/null +++ b/vlib/v/tests/var_option_struct_test.v @@ -0,0 +1,49 @@ +struct Foo { +mut: + name string +} + +struct Test { +pub mut: + a string + b ?string +} + +fn (mut f Foo) test(arr ?[]int) ?string { + return arr?.len.str() +} + +fn (mut f Foo) test2() ?string { + return none +} + +fn fn_a(s ?Test) ? { + println(s?.a) + assert false +} + +fn fn_b(s ?Test) ? { + println(s?.b?) + assert false +} + +fn test_main() { + mut m := ?Foo{} + assert m == none + m = Foo{ + name: 'foo' + } + assert m != none + v := m?.test([1, 2, 3]) or { '4' } + m?.name = 'foo' + assert m?.name == 'foo' + assert v == '3' + var := m?.test2() or { '' } + assert var == '' +} + +fn test_opt_call() { + mut t := ?Test{} + fn_a(none) // returns none + fn_b(t) // returs none +} diff --git a/vlib/v/tests/var_option_sumtype_test.v b/vlib/v/tests/var_option_sumtype_test.v new file mode 100644 index 0000000000..b96e0c3053 --- /dev/null +++ b/vlib/v/tests/var_option_sumtype_test.v @@ -0,0 +1,12 @@ +type Abc = f64 | int + +fn test_main() { + mut v := ?Abc(none) + assert v == none + println(v) + v = Abc(1) + assert v != none + v = 1.2 + println(v) + assert v != none +} diff --git a/vlib/x/json2/json2.v b/vlib/x/json2/json2.v index 7090e1e7d1..d082c1860d 100644 --- a/vlib/x/json2/json2.v +++ b/vlib/x/json2/json2.v @@ -31,7 +31,13 @@ pub fn decode[T](src string) !T { } } - $if field.typ is u8 { + $if field.is_enum { + typ.$(field.name) = if key := res[field.name] { + key.int() + } else { + res[json_name]!.int() + } + } $else $if field.typ is u8 { typ.$(field.name) = res[json_name]!.u64() } $else $if field.typ is u16 { typ.$(field.name) = res[json_name]!.u64() @@ -62,12 +68,6 @@ pub fn decode[T](src string) !T { } $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) = if key := res[field.name] { - key.int() - } else { - res[json_name]!.int() - } } $else $if field.is_alias { } $else $if field.is_map { } $else {