diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index ed63e8329e..eb4cbc2745 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -314,6 +314,7 @@ pub: is_volatile bool is_deprecated bool pub mut: + is_recursive bool default_expr Expr default_expr_typ Type name string diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 5b80c1b183..43da13e413 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1313,7 +1313,8 @@ pub fn (t &Table) has_deep_child_no_ref(ts &TypeSymbol, name string) bool { if ts.info is Struct { for field in ts.info.fields { sym := t.sym(field.typ) - if !field.typ.is_ptr() && (sym.name == name || t.has_deep_child_no_ref(sym, name)) { + if !field.typ.is_ptr() && ((sym.name == name && !field.typ.has_flag(.option)) + || t.has_deep_child_no_ref(sym, name)) { return true } } diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index aa5fe0b953..1bf8b6ec0b 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -56,6 +56,20 @@ fn (mut c Checker) struct_decl(mut node ast.StructDecl) { util.timing_start('Checker.struct setting default_expr_typ') old_expected_type := c.expected_type for mut field in node.fields { + // when the field has the same type that the struct itself (recursive) + if field.typ.clear_flag(.option).set_nr_muls(0) == struct_typ_idx { + for mut symfield in struct_sym.info.fields { + if symfield.name == field.name { + // only ?&Struct is allowed to be recursive + if field.typ.is_ptr() { + symfield.is_recursive = true + } else { + c.error('recursive struct is only possible with optional pointer (e.g. ?&${c.table.type_to_str(field.typ.clear_flag(.option))})', + node.pos) + } + } + } + } if field.has_default_expr { c.expected_type = field.typ field.default_expr_typ = c.expr(field.default_expr) @@ -522,7 +536,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini } } else { if field_info.typ.is_ptr() && !expr_type.is_real_pointer() - && field.expr.str() != '0' { + && field.expr.str() != '0' && !field_info.typ.has_flag(.option) { c.error('reference field must be initialized with reference', field.pos) } @@ -598,8 +612,9 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini } continue } - if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !node.has_update_expr - && !c.pref.translated && !c.file.is_translated { + if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) + && !field.typ.has_flag(.option) && !node.has_update_expr && !c.pref.translated + && !c.file.is_translated { c.warn('reference field `${type_sym.name}.${field.name}` must be initialized', node.pos) continue @@ -721,7 +736,8 @@ fn (mut c Checker) check_ref_fields_initialized(struct_sym &ast.TypeSymbol, mut // an embedded struct field continue } - if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !field.has_default_expr { + if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !field.typ.has_flag(.option) + && !field.has_default_expr { c.warn('reference field `${linked_name}.${field.name}` must be initialized (part of struct `${struct_sym.name}`)', node.pos) continue diff --git a/vlib/v/checker/tests/invalid_recursive_struct_err.out b/vlib/v/checker/tests/invalid_recursive_struct_err.out new file mode 100644 index 0000000000..a70f3f3ff2 --- /dev/null +++ b/vlib/v/checker/tests/invalid_recursive_struct_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/invalid_recursive_struct_err.vv:1:1: error: recursive struct is only possible with optional pointer (e.g. ?&Node) + 1 | struct Node { + | ~~~~~~~~~~~ + 2 | mut: + 3 | next ?Node diff --git a/vlib/v/checker/tests/invalid_recursive_struct_err.vv b/vlib/v/checker/tests/invalid_recursive_struct_err.vv new file mode 100644 index 0000000000..9dc8408a34 --- /dev/null +++ b/vlib/v/checker/tests/invalid_recursive_struct_err.vv @@ -0,0 +1,4 @@ +struct Node { +mut: + next ?Node +} \ No newline at end of file diff --git a/vlib/v/gen/c/dumpexpr.v b/vlib/v/gen/c/dumpexpr.v index 8a0dd8f376..20239f5437 100644 --- a/vlib/v/gen/c/dumpexpr.v +++ b/vlib/v/gen/c/dumpexpr.v @@ -84,13 +84,14 @@ fn (mut g Gen) dump_expr_definitions() { 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_flags(.shared_f, .result)) - ptr_asterisk := if is_ptr { '*'.repeat(typ.nr_muls()) } else { '' } + mut ptr_asterisk := if is_ptr { '*'.repeat(typ.nr_muls()) } else { '' } mut str_dumparg_type := '' if dump_sym.kind == .none_ { str_dumparg_type = 'IError' + ptr_asterisk } else { if typ.has_flag(.option) { str_dumparg_type += '_option_' + ptr_asterisk = ptr_asterisk.replace('*', '_ptr') } str_dumparg_type += g.cc_type(dump_type, true) + ptr_asterisk } @@ -118,8 +119,13 @@ fn (mut g Gen) dump_expr_definitions() { } else if dump_sym.kind == .none_ { surrounder.add('\tstring value = _SLIT("none");', '\tstring_free(&value);') } else if is_ptr { - surrounder.add('\tstring value = (dump_arg == NULL) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);', - '\tstring_free(&value);') + if typ.has_flag(.option) { + surrounder.add('\tstring value = isnil(&dump_arg.data) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);', + '\tstring_free(&value);') + } else { + surrounder.add('\tstring value = (dump_arg == NULL) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);', + '\tstring_free(&value);') + } } else { surrounder.add('\tstring value = ${to_string_fn_name}(${deref}dump_arg);', '\tstring_free(&value);') diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 7cc09431c1..d38a7e9407 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -324,8 +324,12 @@ fn (mut g Gen) zero_struct_field(field ast.StructField) bool { } g.write('.${field_name} = ') if field.typ.has_flag(.option) { - tmp_var := g.new_tmp_var() - g.expr_with_tmp_var(default_init, field.typ, field.typ, tmp_var) + if field.is_recursive { + g.expr_with_opt(ast.None{}, ast.none_type, field.typ) + } else { + tmp_var := g.new_tmp_var() + g.expr_with_tmp_var(default_init, field.typ, field.typ, tmp_var) + } } else { g.struct_init(default_init) } @@ -421,6 +425,7 @@ fn (mut g Gen) struct_decl(s ast.Struct, name string, is_anon bool) { } else { g.type_definitions.writeln('struct ${name} {') } + if s.fields.len > 0 || s.embeds.len > 0 { for field in s.fields { // Some of these structs may want to contain diff --git a/vlib/v/tests/option_nested_struct_test.v b/vlib/v/tests/option_nested_struct_test.v new file mode 100644 index 0000000000..924594b033 --- /dev/null +++ b/vlib/v/tests/option_nested_struct_test.v @@ -0,0 +1,15 @@ +struct Node { +mut: + next ?&Node +} + +fn test_struct_nested_option() { + mut b := Node{} + mut a := Node{ + next: &b + } + dump(a) + dump(a.next) + dump(a.next?.next) + assert a.next?.next == none +}