From 37700502f5047bd4aac65817d44edd55d572e4cd Mon Sep 17 00:00:00 2001 From: shove Date: Thu, 17 Nov 2022 13:51:50 +0800 Subject: [PATCH] all: implement struct field optional and disallow storing result (#16392) --- cmd/tools/vast/vast.v | 2 + vlib/net/socket_options.c.v | 2 +- vlib/v/ast/ast.v | 2 + vlib/v/checker/assign.v | 7 +- vlib/v/checker/checker.v | 63 +++++++++++++- vlib/v/checker/struct.v | 11 +-- vlib/v/checker/tests/optional_fn_err.out | 25 ++---- .../tests/struct_field_optional_err.out | 41 +++++++++ .../tests/struct_field_optional_err.vv | 19 +++++ .../tests/struct_field_optional_init_err.out | 14 ---- .../tests/struct_field_optional_init_err.vv | 11 --- vlib/v/fmt/fmt.v | 1 + vlib/v/gen/c/assign.v | 8 +- vlib/v/gen/c/auto_str_methods.v | 7 +- vlib/v/gen/c/cgen.v | 84 ++++++++++++++++++- vlib/v/gen/c/dumpexpr.v | 9 +- vlib/v/gen/c/str.v | 5 ++ vlib/v/gen/c/struct.v | 40 +++++++-- vlib/v/parser/parser.v | 22 +++++ vlib/v/parser/struct.v | 6 ++ .../parser/tests/type_alias_same_type_err.out | 2 +- vlib/v/tests/inout/struct_field_optional.out | 22 +++++ vlib/v/tests/inout/struct_field_optional.vv | 40 +++++++++ 23 files changed, 374 insertions(+), 69 deletions(-) create mode 100644 vlib/v/checker/tests/struct_field_optional_err.out create mode 100644 vlib/v/checker/tests/struct_field_optional_err.vv delete mode 100644 vlib/v/checker/tests/struct_field_optional_init_err.out delete mode 100644 vlib/v/checker/tests/struct_field_optional_init_err.vv create mode 100644 vlib/v/tests/inout/struct_field_optional.out create mode 100644 vlib/v/tests/inout/struct_field_optional.vv diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index 49375294ee..52843a4e63 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -612,6 +612,7 @@ fn (t Tree) struct_field(node ast.StructField) &Node { obj.add_terse('name', t.string_node(node.name)) obj.add_terse('typ', t.type_node(node.typ)) obj.add('type_pos', t.pos(node.type_pos)) + obj.add('optional_pos', t.pos(node.optional_pos)) obj.add_terse('has_default_expr', t.bool_node(node.has_default_expr)) obj.add_terse('default_expr_typ', t.type_node(node.default_expr_typ)) obj.add_terse('default_expr', t.expr(node.default_expr)) @@ -1373,6 +1374,7 @@ fn (t Tree) selector_expr(node ast.SelectorExpr) &Node { obj.add_terse('field_name', t.string_node(node.field_name)) obj.add_terse('typ', t.type_node(node.typ)) obj.add_terse('name_type', t.type_node(node.name_type)) + obj.add_terse('or_block', t.or_expr(node.or_block)) obj.add_terse('gkind_field', t.enum_node(node.gkind_field)) obj.add_terse('from_embed_types', t.array_node_type(node.from_embed_types)) obj.add_terse('next_token', t.token_node(node.next_token)) diff --git a/vlib/net/socket_options.c.v b/vlib/net/socket_options.c.v index 4e3240ffcf..14de0fb1f7 100644 --- a/vlib/net/socket_options.c.v +++ b/vlib/net/socket_options.c.v @@ -1,7 +1,7 @@ module net pub enum SocketOption { - // TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it + // TODO: SO_ACCEPT_CONN is not here because windows doesnt support it // and there is no easy way to define it broadcast = C.SO_BROADCAST debug = C.SO_DEBUG diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index e9e922c81a..2bff21b85d 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -260,6 +260,7 @@ pub mut: expr_type Type // type of `Foo` in `Foo.bar` typ Type // type of the entire thing (`Foo.bar`) name_type Type // T in `T.name` or typeof in `typeof(expr).name` + or_block OrExpr gkind_field GenericKindField // `T.name` => ast.GenericKindField.name, `T.typ` => ast.GenericKindField.typ, or .unknown scope &Scope = unsafe { nil } from_embed_types []Type // holds the type of the embed that the method is called from @@ -295,6 +296,7 @@ pub struct StructField { pub: pos token.Pos type_pos token.Pos + optional_pos token.Pos comments []Comment i int has_default_expr bool diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 7da4f9d0d7..937198c8c3 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -17,13 +17,16 @@ pub fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { mut right_len := node.right.len mut right_type0 := ast.void_type for i, mut right in node.right { - if right in [ast.CallExpr, ast.IfExpr, ast.LockExpr, ast.MatchExpr, ast.DumpExpr] { + if right in [ast.CallExpr, ast.IfExpr, ast.LockExpr, ast.MatchExpr, ast.DumpExpr, + ast.SelectorExpr] { if right in [ast.IfExpr, ast.MatchExpr] && node.left.len == node.right.len && !is_decl && node.left[i] in [ast.Ident, ast.SelectorExpr] && !node.left[i].is_blank_ident() { c.expected_type = c.expr(node.left[i]) } right_type := c.expr(right) - c.fail_if_unreadable(right, right_type, 'right-hand side of assignment') + if right in [ast.CallExpr, ast.IfExpr, ast.LockExpr, ast.MatchExpr, ast.DumpExpr] { + c.fail_if_unreadable(right, right_type, 'right-hand side of assignment') + } if i == 0 { right_type0 = right_type node.right_types = [ diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index aec7e8e830..8ce6ccbb84 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -950,6 +950,35 @@ pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast c.error('unexpected `!`, the function `${expr.name}` does not return a result', expr.or_block.pos) } + } else if expr is ast.SelectorExpr && c.table.sym(ret_type).kind != .chan { + if expr.typ.has_flag(.optional) || expr.typ.has_flag(.result) { + with_modifier_kind := if expr.typ.has_flag(.optional) { + 'an option' + } else { + 'a result' + } + with_modifier := if expr.typ.has_flag(.optional) { '?' } else { '!' } + if 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) + } else { + c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have either an `or {}` block, or `${with_modifier}` at the end', + expr.pos) + } + } else { + c.check_or_expr(expr.or_block, ret_type, expr.typ) + } + return ret_type.clear_flag(.optional).clear_flag(.result) + } else if expr.or_block.kind == .block { + c.error('unexpected `or` block, the field `${expr.field_name}` is neither an optional, 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 optional', + 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)) @@ -1281,6 +1310,11 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { } } node.typ = field.typ + if node.or_block.kind == .block { + c.expected_or_type = node.typ.clear_flag(.optional).clear_flag(.result) + c.stmts_ending_with_expression(node.or_block.stmts) + c.expected_or_type = ast.void_type + } return field.typ } if mut method := c.table.find_method(sym, field_name) { @@ -2321,7 +2355,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { unwrapped_expr_type := c.unwrap_generic(node.expr_type) tsym := c.table.sym(unwrapped_expr_type) - c.table.dumps[int(unwrapped_expr_type)] = tsym.cname + c.table.dumps[int(unwrapped_expr_type.clear_flag(.optional).clear_flag(.result))] = tsym.cname node.cname = tsym.cname return node.expr_type } @@ -2422,7 +2456,32 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { return c.select_expr(mut node) } ast.SelectorExpr { - return c.selector_expr(mut node) + mut ret_type := c.selector_expr(mut node) + if c.table.sym(ret_type).kind == .chan { + return ret_type + } + + if !ret_type.has_flag(.optional) && !ret_type.has_flag(.result) { + if node.or_block.kind == .block { + c.error('unexpected `or` block, the field `${node.field_name}` is neither an optional, 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 optional, 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 optional, nor a result', + node.or_block.pos) + } + } + if node.or_block.kind != .absent { + if ret_type.has_flag(.optional) { + ret_type = ret_type.clear_flag(.optional) + } + if ret_type.has_flag(.result) { + ret_type = ret_type.clear_flag(.result) + } + } + return ret_type } ast.SizeOf { if !node.is_type { diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index 9077d1815f..988376ff5a 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -48,6 +48,9 @@ pub fn (mut c Checker) struct_decl(mut node ast.StructDecl) { } } for i, field in node.fields { + if field.typ.has_flag(.result) { + c.error('struct field does not support storing result', field.optional_pos) + } c.ensure_type_exists(field.typ, field.type_pos) or { return } if field.typ.has_flag(.generic) { has_generic_types = true @@ -457,14 +460,6 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type { node.fields[i].typ = expr_type node.fields[i].expected_type = field_info.typ - if field_info.typ.has_flag(.optional) { - c.error('field `${field_info.name}` is optional, but initialization of optional fields currently unsupported', - field.pos) - } - if field_info.typ.has_flag(.result) { - c.error('field `${field_info.name}` is result, but initialization of result fields currently unsupported', - field.pos) - } if expr_type.is_ptr() && expected_type.is_ptr() { if mut field.expr is ast.Ident { if mut field.expr.obj is ast.Var { diff --git a/vlib/v/checker/tests/optional_fn_err.out b/vlib/v/checker/tests/optional_fn_err.out index ddb49a8b38..865753c2b3 100644 --- a/vlib/v/checker/tests/optional_fn_err.out +++ b/vlib/v/checker/tests/optional_fn_err.out @@ -1,5 +1,5 @@ vlib/v/checker/tests/optional_fn_err.vv:13:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 11 | + 11 | 12 | const ( 13 | const_value = bar(0) | ~~~~~~ @@ -31,21 +31,21 @@ vlib/v/checker/tests/optional_fn_err.vv:35:16: error: bar() returns an option, s 34 | _ := bar(0) 35 | println(twice(bar(0))) | ~~~~~~ - 36 | + 36 | 37 | // anon fn vlib/v/checker/tests/optional_fn_err.vv:38:16: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 36 | + 36 | 37 | // anon fn 38 | fn (_ int) {}(bar(0)) | ~~~~~~ - 39 | + 39 | 40 | // assert vlib/v/checker/tests/optional_fn_err.vv:41:9: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end - 39 | + 39 | 40 | // assert 41 | assert bar(true) | ~~~~~~~~~ - 42 | + 42 | 43 | // struct vlib/v/checker/tests/optional_fn_err.vv:46:10: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end 44 | mut v := Data{ @@ -54,13 +54,6 @@ vlib/v/checker/tests/optional_fn_err.vv:46:10: error: bar() returns an option, s | ~~~~~~ 47 | opt: bar(0), 48 | } -vlib/v/checker/tests/optional_fn_err.vv:47:3: error: field `opt` is optional, but initialization of optional fields currently unsupported - 45 | f: fn (_ int) {}, - 46 | value: bar(0), - 47 | opt: bar(0), - | ~~~~~~~~~~~ - 48 | } - 49 | v.add(bar(0)) // call method vlib/v/checker/tests/optional_fn_err.vv:49:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end 47 | opt: bar(0), 48 | } @@ -73,7 +66,7 @@ vlib/v/checker/tests/optional_fn_err.vv:50:6: error: bar() returns an option, so 49 | v.add(bar(0)) // call method 50 | v.f(bar(0)) // call fn field | ~~~~~~ - 51 | + 51 | 52 | // array vlib/v/checker/tests/optional_fn_err.vv:54:9: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end 52 | // array @@ -164,11 +157,11 @@ vlib/v/checker/tests/optional_fn_err.vv:70:18: error: bar() returns an option, s 69 | println(arr.any(bar(true))) 70 | println(arr.all(bar(true))) | ~~~~~~~~~ - 71 | + 71 | 72 | match bar(0) { vlib/v/checker/tests/optional_fn_err.vv:72:8: error: bar() returns an option, so it should have either an `or {}` block, or `?` at the end 70 | println(arr.all(bar(true))) - 71 | + 71 | 72 | match bar(0) { | ~~~~~~ 73 | 0 { } diff --git a/vlib/v/checker/tests/struct_field_optional_err.out b/vlib/v/checker/tests/struct_field_optional_err.out new file mode 100644 index 0000000000..9c3cbd4457 --- /dev/null +++ b/vlib/v/checker/tests/struct_field_optional_err.out @@ -0,0 +1,41 @@ +vlib/v/checker/tests/struct_field_optional_err.vv:3:6: error: struct field does not support storing result + 1 | struct Foo { + 2 | mut: + 3 | foo !int + | ^ + 4 | bar ?int = 1 + 5 | baz int = 1 +vlib/v/checker/tests/struct_field_optional_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_optional_err.vv:12:12: error: `+` cannot be used with `?int` + 10 | + 11 | _ = f.bar + 12 | _ = f.bar + 1 + | ^ + 13 | + 14 | _ = f.bar! +vlib/v/checker/tests/struct_field_optional_err.vv:14:11: error: to propagate a result, the call must also return a result type + 12 | _ = f.bar + 1 + 13 | + 14 | _ = f.bar! + | ^ + 15 | + 16 | _ = f.bar or { _ = 1 } +vlib/v/checker/tests/struct_field_optional_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 | + 16 | _ = f.bar or { _ = 1 } + | ^ + 17 | + 18 | _ = f.baz? +vlib/v/checker/tests/struct_field_optional_err.vv:18:11: error: unexpected `?`, the field `baz` is neither an optional, nor a result + 16 | _ = f.bar or { _ = 1 } + 17 | + 18 | _ = f.baz? + | ^ + 19 | } diff --git a/vlib/v/checker/tests/struct_field_optional_err.vv b/vlib/v/checker/tests/struct_field_optional_err.vv new file mode 100644 index 0000000000..5d7b2456e5 --- /dev/null +++ b/vlib/v/checker/tests/struct_field_optional_err.vv @@ -0,0 +1,19 @@ +struct Foo { +mut: + foo !int + bar ?int = 1 + baz int = 1 +} + +fn main() { + mut f := Foo{} + + _ = f.bar + _ = f.bar + 1 + + _ = f.bar! + + _ = f.bar or { _ = 1 } + + _ = f.baz? +} diff --git a/vlib/v/checker/tests/struct_field_optional_init_err.out b/vlib/v/checker/tests/struct_field_optional_init_err.out deleted file mode 100644 index 88a9c125d2..0000000000 --- a/vlib/v/checker/tests/struct_field_optional_init_err.out +++ /dev/null @@ -1,14 +0,0 @@ -vlib/v/checker/tests/struct_field_optional_init_err.vv:8:3: error: field `bar` is result, but initialization of result fields currently unsupported - 6 | fn main() { - 7 | _ := Foo{ - 8 | bar: 1 - | ~~~~~~ - 9 | baz: 1 - 10 | } -vlib/v/checker/tests/struct_field_optional_init_err.vv:9:3: error: field `baz` is optional, but initialization of optional fields currently unsupported - 7 | _ := Foo{ - 8 | bar: 1 - 9 | baz: 1 - | ~~~~~~ - 10 | } - 11 | } diff --git a/vlib/v/checker/tests/struct_field_optional_init_err.vv b/vlib/v/checker/tests/struct_field_optional_init_err.vv deleted file mode 100644 index b04071e8e0..0000000000 --- a/vlib/v/checker/tests/struct_field_optional_init_err.vv +++ /dev/null @@ -1,11 +0,0 @@ -struct Foo { - bar !int - baz ?int -} - -fn main() { - _ := Foo{ - bar: 1 - baz: 1 - } -} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index bb1339c62e..df026ce85c 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2568,6 +2568,7 @@ pub fn (mut f Fmt) selector_expr(node ast.SelectorExpr) { f.expr(node.expr) f.write('.') f.write(node.field_name) + f.or_expr(node.or_block) } pub fn (mut f Fmt) size_of(node ast.SizeOf) { diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 7db743ff72..579a44922a 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -192,7 +192,7 @@ 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(.optional) { + if val_type.has_flag(.optional) || val_type.has_flag(.result) { g.right_is_opt = true } if blank_assign { @@ -434,7 +434,11 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { */ } if !cloned { - if is_fixed_array_var { + if (var_type.has_flag(.optional) && !val_type.has_flag(.optional)) + || (var_type.has_flag(.result) && !val_type.has_flag(.result)) { + tmp_var := g.new_tmp_var() + g.expr_with_tmp_var(val, val_type, var_type, tmp_var) + } else if is_fixed_array_var { // TODO Instead of the translated check, check if it's a pointer already // and don't generate memcpy & typ_str := g.typ(val_type).trim('*') diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 71298687f5..32b28ffb1a 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -997,7 +997,12 @@ fn struct_auto_str_func(sym &ast.TypeSymbol, _field_type ast.Type, fn_name strin } else if sym.kind == .chan { return '${fn_name}(${deref}it.${c_name(field_name)}${sufix})', true } else { - mut method_str := 'it.${c_name(field_name)}' + mut method_str := '' + if !field_type.is_ptr() && (field_type.has_flag(.optional) || field_type.has_flag(.result)) { + method_str = '(*(${sym.name}*)it.${c_name(field_name)}.data)' + } else { + method_str = 'it.${c_name(field_name)}' + } if sym.kind == .bool { return '${method_str} ? _SLIT("true") : _SLIT("false")', false } else if (field_type.is_int_valptr() || field_type.is_float_valptr()) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 893548b0b8..1a28b24e8b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -110,6 +110,7 @@ mut: results_forward []string // to forward results map[string]string // to avoid duplicates done_optionals shared []string // to avoid duplicates + done_results shared []string // to avoid duplicates chan_pop_optionals map[string]string // types for `x := <-ch or {...}` chan_push_optionals map[string]string // types for `ch <- x or {...}` mtxs string // array of mutexes if the `lock` has multiple variables @@ -120,6 +121,7 @@ mut: inside_map_postfix bool // inside map++/-- postfix expr inside_map_infix bool // inside map< `x = new_opt()` (g.right_is_opt == true) // `println(x)` => `println(*(int*)x.data)` if node.info.is_optional && !(g.is_assign_lhs && g.right_is_opt) { - g.write('/*opt*/') - styp := g.base_type(node.info.typ) - g.write('(*(${styp}*)${name}.data)') + if g.inside_opt_or_res { + g.write('${name}') + } else { + g.write('/*opt*/') + styp := g.base_type(node.info.typ) + g.write('(*(${styp}*)${name}.data)') + } return } if !g.is_assign_lhs && node.info.share == .shared_t { diff --git a/vlib/v/gen/c/dumpexpr.v b/vlib/v/gen/c/dumpexpr.v index 9e8a1df1d0..d7030c228f 100644 --- a/vlib/v/gen/c/dumpexpr.v +++ b/vlib/v/gen/c/dumpexpr.v @@ -23,6 +23,13 @@ fn (mut g Gen) dump_expr(node ast.DumpExpr) { g.write('&') g.expr(node.expr) g.write('->val') + } else if node.expr_type.has_flag(.optional) || node.expr_type.has_flag(.result) { + old_inside_opt_or_res := g.inside_opt_or_res + g.inside_opt_or_res = true + g.write('(*(${name}*)') + g.expr(node.expr) + g.write('.data)') + g.inside_opt_or_res = old_inside_opt_or_res } else { g.expr(node.expr) } @@ -43,7 +50,7 @@ fn (mut g Gen) dump_expr_definitions() { typ := ast.Type(dump_type) is_ptr := typ.is_ptr() deref, _ := deref_kind(str_method_expects_ptr, is_ptr, dump_type) - to_string_fn_name := g.get_str_fn(typ.clear_flag(.shared_f)) + to_string_fn_name := g.get_str_fn(typ.clear_flag(.shared_f).clear_flag(.optional).clear_flag(.result)) ptr_asterisk := if is_ptr { '*'.repeat(typ.nr_muls()) } else { '' } mut str_dumparg_type := '' if dump_sym.kind == .none_ { diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index bc646e1917..e5f05dcb9c 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -52,6 +52,11 @@ fn (mut g Gen) string_inter_literal_sb_optimized(call_expr ast.CallExpr) { } fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { + old_inside_opt_or_res := g.inside_opt_or_res + g.inside_opt_or_res = true + defer { + g.inside_opt_or_res = old_inside_opt_or_res + } is_shared := etype.has_flag(.shared_f) mut typ := etype if is_shared { diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 31065b24b5..7e33c659cd 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -181,11 +181,6 @@ fn (mut g Gen) struct_init(node ast.StructInit) { continue } field_name := c_name(field.name) - if field.typ.has_flag(.optional) || field.typ.has_flag(.result) { - g.write('.${field_name} = {EMPTY_STRUCT_INITIALIZATION},') - initialized = true - continue - } if field.typ in info.embeds { continue } @@ -271,6 +266,15 @@ fn (mut g Gen) zero_struct_field(field ast.StructField) bool { g.expr_with_cast(field.default_expr, field.default_expr_typ, field.typ) return true } + + if (field.typ.has_flag(.optional) && !field.default_expr_typ.has_flag(.optional)) + || (field.typ.has_flag(.result) && !field.default_expr_typ.has_flag(.result)) { + tmp_var := g.new_tmp_var() + g.expr_with_tmp_var(field.default_expr, field.default_expr_typ, field.typ, + tmp_var) + return true + } + g.expr(field.default_expr) } else { g.write(g.type_default(field.typ)) @@ -340,7 +344,7 @@ fn (mut g Gen) struct_decl(s ast.Struct, name string, is_anon bool) { // write the optional in and then continue // FIXME: for parallel cgen (two different files using the same optional in struct fields) if field.typ.has_flag(.optional) { - // Dont use g.typ() here becuase it will register + // Dont use g.typ() here because it will register // optional and we dont want that styp, base := g.optional_type_name(field.typ) lock g.done_optionals { @@ -354,6 +358,21 @@ fn (mut g Gen) struct_decl(s ast.Struct, name string, is_anon bool) { } } } + if field.typ.has_flag(.result) { + // Dont use g.typ() here because it will register + // result and we dont want that + styp, base := g.result_type_name(field.typ) + lock g.done_results { + if base !in g.done_results { + g.done_results << base + last_text := g.type_definitions.after(start_pos).clone() + g.type_definitions.go_back_to(start_pos) + g.typedefs.writeln('typedef struct ${styp} ${styp};') + g.type_definitions.writeln('${g.result_type_text(styp, base)};') + g.type_definitions.write_string(last_text) + } + } + } type_name := g.typ(field.typ) field_name := c_name(field.name) volatile_prefix := if field.is_volatile { 'volatile ' } else { '' } @@ -441,7 +460,14 @@ fn (mut g Gen) struct_init_field(sfield ast.StructInitField, language ast.Langua && !(sfield.typ.is_ptr() || sfield.typ.is_pointer()) && !sfield.typ.is_number() { g.write('/* autoref */&') } - g.expr_with_cast(sfield.expr, sfield.typ, sfield.expected_type) + + if (sfield.expected_type.has_flag(.optional) && !sfield.typ.has_flag(.optional)) + || (sfield.expected_type.has_flag(.result) && !sfield.typ.has_flag(.result)) { + tmp_var := g.new_tmp_var() + g.expr_with_tmp_var(sfield.expr, sfield.typ, sfield.expected_type, tmp_var) + } else { + g.expr_with_cast(sfield.expr, sfield.typ, sfield.expected_type) + } } g.inside_cast_in_heap = inside_cast_in_heap // restore value for further struct inits } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index c8811e865e..27a2b72586 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2935,15 +2935,37 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { } } pos := if p.name_error { left.pos().extend(name_pos) } else { name_pos } + + mut or_kind := ast.OrKind.absent + mut or_stmts := []ast.Stmt{} + mut or_pos := token.Pos{} + if p.tok.kind == .key_orelse { + or_kind = .block + or_stmts, or_pos = p.or_block(.with_err_var) + } else if p.tok.kind == .not { + or_kind = .propagate_result + or_pos = p.tok.pos() + p.next() + } else if p.tok.kind == .question { + or_kind = .propagate_option + or_pos = p.tok.pos() + p.next() + } sel_expr := ast.SelectorExpr{ expr: left field_name: field_name pos: pos is_mut: is_mut mut_pos: mut_pos + or_block: ast.OrExpr{ + kind: or_kind + stmts: or_stmts + pos: or_pos + } scope: p.scope next_token: p.tok.kind } + if is_filter { p.close_scope() } diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index a6a5235830..ef5dedc5e7 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -201,6 +201,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { mut typ := ast.Type(0) mut type_pos := token.Pos{} mut field_pos := token.Pos{} + mut optional_pos := token.Pos{} mut anon_struct_decl := ast.StructDecl{} if is_embed { // struct embedding @@ -260,6 +261,9 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { } type_pos = p.prev_tok.pos() field_pos = field_start_pos.extend(type_pos) + if typ.has_flag(.optional) || typ.has_flag(.result) { + optional_pos = p.peek_token(-2).pos() + } } // Comments after type (same line) comments << p.eat_comments() @@ -292,6 +296,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { typ: typ pos: field_pos type_pos: type_pos + optional_pos: optional_pos comments: comments i: i default_expr: default_expr @@ -311,6 +316,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { typ: typ pos: field_pos type_pos: type_pos + optional_pos: optional_pos comments: comments i: i default_expr: default_expr diff --git a/vlib/v/parser/tests/type_alias_same_type_err.out b/vlib/v/parser/tests/type_alias_same_type_err.out index c5b3797b05..18ec86346c 100644 --- a/vlib/v/parser/tests/type_alias_same_type_err.out +++ b/vlib/v/parser/tests/type_alias_same_type_err.out @@ -1,5 +1,5 @@ vlib/v/parser/tests/type_alias_same_type_err.vv:1:1: error: a type alias can not refer to itself: Foo 1 | type Foo = Foo | ~~~~~~~~~~~~~~ - 2 | + 2 | 3 | fn main() { diff --git a/vlib/v/tests/inout/struct_field_optional.out b/vlib/v/tests/inout/struct_field_optional.out new file mode 100644 index 0000000000..a12fa44263 --- /dev/null +++ b/vlib/v/tests/inout/struct_field_optional.out @@ -0,0 +1,22 @@ +1 +[vlib/v/tests/inout/struct_field_optional.vv:11] f.bar: 1 +2 +[vlib/v/tests/inout/struct_field_optional.vv:18] f.bar: 2 +3 +[vlib/v/tests/inout/struct_field_optional.vv:22] f.bar: 3 +3 +[vlib/v/tests/inout/struct_field_optional.vv:26] a: 3 +9999 +[vlib/v/tests/inout/struct_field_optional.vv:29] b: 9999 +4 +[vlib/v/tests/inout/struct_field_optional.vv:33] sum: 4 +4 +[vlib/v/tests/inout/struct_field_optional.vv:36] sum: 4 +Foo{ + bar: 3 + baz: 0 +} +[vlib/v/tests/inout/struct_field_optional.vv:39] f: Foo{ + bar: 3 + baz: 0 +} diff --git a/vlib/v/tests/inout/struct_field_optional.vv b/vlib/v/tests/inout/struct_field_optional.vv new file mode 100644 index 0000000000..0206bc8504 --- /dev/null +++ b/vlib/v/tests/inout/struct_field_optional.vv @@ -0,0 +1,40 @@ +struct Foo { +mut: + bar ?int = 1 + baz ?int = none +} + +fn main() { + // `default value` test + mut f := Foo{} + println(f.bar?) + dump(f.bar?) + // `init` test + f = Foo{ + bar: 2 + baz: none + } + println(f.bar?) + dump(f.bar?) + // `assign` test + f.bar = 3 + println(f.bar?) + dump(f.bar?) + // `or block` test + a := f.bar or { 123 } + println(a) + dump(a) + b := f.baz or { 9999 } + println(b) + dump(b) + // `infix expr` test + mut sum := f.bar? + 1 + println(sum) + dump(sum) + sum = f.bar or { 123 } + 1 + println(sum) + dump(sum) + // others test + println(f) + dump(f) +}