1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

all: implement struct field optional and disallow storing result (#16392)

This commit is contained in:
shove 2022-11-17 13:51:50 +08:00 committed by GitHub
parent 41dd8985fc
commit 37700502f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 374 additions and 69 deletions

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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)
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 = [

View File

@ -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 {

View File

@ -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 {

View File

@ -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 | }

View File

@ -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 | }

View File

@ -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?
}

View File

@ -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 | }

View File

@ -1,11 +0,0 @@
struct Foo {
bar !int
baz ?int
}
fn main() {
_ := Foo{
bar: 1
baz: 1
}
}

View File

@ -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) {

View File

@ -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('*')

View File

@ -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())

View File

@ -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<</+=/-= infix expr
inside_map_index bool
inside_opt_or_res bool
inside_opt_data bool
inside_if_optional bool
inside_if_result bool
@ -647,6 +649,7 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen {
options_forward: global_g.options_forward
results_forward: global_g.results_forward
done_optionals: global_g.done_optionals
done_results: global_g.done_results
is_autofree: global_g.pref.autofree
referenced_fns: global_g.referenced_fns
is_cc_msvc: global_g.is_cc_msvc
@ -1130,6 +1133,9 @@ fn (mut g Gen) write_optionals() {
fn (mut g Gen) write_results() {
mut done := []string{}
rlock g.done_results {
done = g.done_results.clone()
}
for base, styp in g.results {
if base in done {
continue
@ -1572,6 +1578,22 @@ pub fn (mut g Gen) write_multi_return_types() {
}
}
}
if mr_typ.has_flag(.result) {
// result in multi_return
// Dont use g.typ() here because it will register
// result and we dont want that
styp, base := g.result_type_name(mr_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)
}
}
}
g.type_definitions.writeln('\t${type_name} arg${i};')
}
g.type_definitions.writeln('};\n')
@ -1776,6 +1798,42 @@ fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) bool {
return last_stmt_was_return
}
// expr_with_tmp_var is used in assign expr to `optinal` or `result` type.
// applicable to situations where the expr_typ does not have `optinal` and `result`,
// e.g. field default: "foo ?int = 1", field assign: "foo = 1", field init: "foo: 1"
fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type, tmp_var string) {
if !ret_typ.has_flag(.optional) && !ret_typ.has_flag(.result) {
panic('cgen: parameter `ret_typ` of function `expr_with_tmp_var()` must have optional or result')
}
stmt_str := g.go_before_stmt(0).trim_space()
styp := g.base_type(ret_typ)
g.empty_line = true
if g.table.sym(expr_typ).kind == .none_ {
g.write('${g.typ(ret_typ)} ${tmp_var} = ')
g.gen_optional_error(ret_typ, expr)
g.writeln(';')
} else {
g.writeln('${g.typ(ret_typ)} ${tmp_var};')
if ret_typ.has_flag(.optional) {
g.write('_option_ok(&(${styp}[]) { ')
} else {
g.write('_result_ok(&(${styp}[]) { ')
}
g.expr_with_cast(expr, expr_typ, ret_typ)
if ret_typ.has_flag(.optional) {
g.writeln(' }, (${c.option_name}*)(&${tmp_var}), sizeof(${styp}));')
} else {
g.writeln(' }, (${c.result_name}*)(&${tmp_var}), sizeof(${styp}));')
}
}
g.write(stmt_str)
g.write(' ')
g.write(tmp_var)
}
[inline]
fn (mut g Gen) write_v_source_line_info(pos token.Pos) {
if g.inside_ternary == 0 && g.pref.is_vlines && g.is_vlines_enabled {
@ -3504,6 +3562,22 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
if node.expr_type == 0 {
g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos)
}
if node.or_block.kind != .absent && !g.is_assign_lhs && g.table.sym(node.typ).kind != .chan {
stmt_str := g.go_before_stmt(0).trim_space()
styp := g.typ(node.typ)
g.empty_line = true
tmp_var := g.new_tmp_var()
g.write('${styp} ${tmp_var} = ${node.expr}.${node.field_name};')
g.or_block(tmp_var, node.or_block, node.typ)
g.write(stmt_str)
g.write(' ')
unwrapped_typ := node.typ.clear_flag(.optional).clear_flag(.result)
unwrapped_styp := g.typ(unwrapped_typ)
g.write('(*(${unwrapped_styp}*)${tmp_var}.data)')
return
}
sym := g.table.sym(g.unwrap_generic(node.expr_type))
// if node expr is a root ident and an optional
mut is_opt_or_res := node.expr is ast.Ident
@ -4122,9 +4196,13 @@ 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_optional && !(g.is_assign_lhs && g.right_is_opt) {
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 {

View File

@ -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_ {

View File

@ -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 {

View File

@ -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,8 +460,15 @@ 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 */&')
}
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
}
}

View File

@ -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()
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}