Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions vlib/v/ast/types.v
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,9 @@ pub fn (t &TypeSymbol) is_empty_struct_array() bool {
if elem_sym.info is Struct {
return elem_sym.info.is_empty_struct()
}
if elem_sym.info is ArrayFixed {
return elem_sym.is_empty_struct_array()
}
}
return false
}
Expand Down
12 changes: 11 additions & 1 deletion vlib/v/gen/c/array.v
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,17 @@ fn (mut g Gen) fixed_array_init(node ast.ArrayInit, array_type Type, var_name st
g.add_commas_and_prevent_long_lines(i, info.size)
}
} else if is_amp {
g.write('0')
is_empty := if elem_sym.info is ast.Struct {
elem_sym.info.is_empty_struct()
} else {
// covers nested fixed arrays of empty structs
elem_sym.is_empty_struct_array()
}
if is_empty {
g.write('E_STRUCT')
} else {
g.write('0')
}
} else {
if elem_sym.kind == .map {
// fixed array for map -- [N]map[key_type]value_type
Expand Down
47 changes: 32 additions & 15 deletions vlib/v/gen/c/assign.v
Original file line number Diff line number Diff line change
Expand Up @@ -1444,9 +1444,9 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
&& (val in [ast.Ident, ast.IndexExpr, ast.CallExpr, ast.SelectorExpr, ast.ComptimeSelector, ast.DumpExpr, ast.InfixExpr, ast.IfExpr, ast.MatchExpr]
|| (val is ast.CastExpr && val.expr !is ast.ArrayInit)
|| (val is ast.PrefixExpr && val.op == .arrow)
|| (val is ast.UnsafeExpr && val.expr in [ast.SelectorExpr, ast.Ident, ast.CallExpr]))
&& !((g.pref.translated || g.file.is_translated)
&& unaliased_left_sym.kind != .array_fixed)
|| (val is ast.UnsafeExpr && val.expr in [ast.SelectorExpr, ast.Ident, ast.CallExpr])
|| (val is ast.StructInit && !is_decl && !blank_assign)) && !((g.pref.translated
Comment thread
rilaaax marked this conversation as resolved.
|| g.file.is_translated) && unaliased_left_sym.kind != .array_fixed)
g.is_assign_lhs = true
g.assign_op = node.op

Expand Down Expand Up @@ -1551,26 +1551,33 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
g.expr(left)
g.write(' = ')
g.expr(val)
} else if is_fixed_array_init && var_type.has_flag(.option) {
g.expr(left)
g.write(' = ')
g.expr_with_opt(val, val_type, var_type)
} else if unaliased_right_sym.kind == .array_fixed && val is ast.CastExpr {
if var_type.has_flag(.option) {
} else if var_type.has_flag(.option) {
if is_fixed_array_init || val is ast.StructInit || val_type.has_flag(.option) {
// `val` is either an inline literal that needs
// constructing in place (`Arr{}`, `[N]T{...}!`), or it
// already produces a full, matching option struct
// (`?Arr{}`, `?Arr(none)`, or a plain ident/call/cast of
// option type).
g.expr(left)
g.write(' = ')
g.expr_with_opt(val, val_type, var_type)
} else {
// `val` produces a plain (non-option) fixed array
// value: Ident, CallExpr, SelectorExpr, CastExpr, etc.
g.expr(left)
g.writeln('.state = 0;')
g.write('memcpy(')
g.expr(left)
g.write('.data, ')
g.expr(val)
g.writeln(', sizeof(${g.styp(var_type.clear_flag(.option))}));')
} else {
g.write('memcpy(')
g.expr(left)
g.write(', ')
g.expr(val)
g.writeln(', sizeof(${g.styp(var_type)}));')
}
} else if unaliased_right_sym.kind == .array_fixed && val is ast.CastExpr {
g.write('memcpy(')
g.expr(left)
g.write(', ')
g.expr(val)
g.writeln(', sizeof(${g.styp(var_type)}));')
} else {
arr_typ := styp.trim('*')
old_is_assign_lhs := g.is_assign_lhs
Expand Down Expand Up @@ -1598,6 +1605,16 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
g.expr(right)
g.writeln(';')
fixed_right_expr = right_var
} else if val is ast.StructInit {
// e.g. `a = Arr{}`, where `type Arr = [N]Box`
// struct_init() emits a bare brace-init list with no cast prefix
// for fixed arrays, which is only valid as a declaration initializer,
// not as a memcpy() argument expression.
right_var := g.new_tmp_var()
g.write('${arr_typ} ${right_var} = ')
g.expr(val)
g.writeln(';')
fixed_right_expr = right_var
} else {
fixed_right_expr = g.expr_string(val)
}
Expand Down
14 changes: 7 additions & 7 deletions vlib/v/gen/c/cgen.v
Original file line number Diff line number Diff line change
Expand Up @@ -4572,11 +4572,11 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T
no_cast = true
}
elem_sym := g.table.sym(info.elem_type)
if elem_sym.kind == .struct {
if no_cast {
g.write('builtin___option_ok(')
} else if elem_sym.kind == .struct {
expr_is_fixed_array_var = false
g.write('builtin___option_ok(&(${styp}[]) { ')
} else if no_cast {
g.write('builtin___option_ok(')
} else {
g.write('builtin___option_ok((${g.styp(final_expr_sym.idx)})')
}
Expand Down Expand Up @@ -4628,11 +4628,11 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T
no_cast = true
}
elem_sym := g.table.sym(info.elem_type)
if elem_sym.kind == .struct {
if no_cast {
g.write('builtin___result_ok(')
} else if elem_sym.kind == .struct {
expr_is_fixed_array_var = false
g.write('builtin___result_ok(&(${styp}[]) { ')
} else if no_cast {
g.write('builtin___result_ok(')
} else {
g.write('builtin___result_ok((${g.styp(final_expr_sym.idx)})')
}
Expand Down Expand Up @@ -7791,7 +7791,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
return
} else if node.field_name in ['idx', 'typ', 'unaliased_typ'] {
// `T.idx`, `T.typ`, `T.unaliased_typ`, `typeof(expr).idx`, `typeof(expr).typ`,
// `typeof(expr).unalised_typ`
// `typeof(expr).unaliased_typ`
mut name_type := node.name_type
if node.expr is ast.TypeOf {
if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
Expand Down
10 changes: 7 additions & 3 deletions vlib/v/gen/c/struct.v
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ fn (mut g Gen) struct_init(node ast.StructInit) {

g.write(s)
}
unalised_typ := g.table.unaliased_type(base_node_typ)
styp := if g.table.sym(unalised_typ).language == .v {
g.styp(unalised_typ).replace('*', '')
unaliased_typ := g.table.unaliased_type(base_node_typ)
styp := if base_node_typ.has_option_or_result() {
// unaliased_type() drops option/result. For `?Arr{}` (Arr = [N]T) that would
// make the option tmp use the wrong C type (plain fixed array).
g.styp(base_node_typ)
} else if g.table.sym(unaliased_typ).language == .v {
g.styp(unaliased_typ).replace('*', '')
} else {
g.styp(base_node_typ)
}
Expand Down
53 changes: 53 additions & 0 deletions vlib/v/tests/aliases/alias_fixed_array_of_struct_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type NestedBazFixed = [2]BazFixed
struct Empty {}

type EmptyFixed = [2]Empty
type NestedEmptyFixed = [4][2]Empty

type Dot = [3]f64
type Box = [2]Dot
Expand Down Expand Up @@ -43,6 +44,18 @@ fn test_fixed_array_alias_of_empty_struct() {
fn test_direct_fixed_array_alias_of_empty_struct_init() {
fixed := EmptyFixed{}
assert fixed.len == 2

ref_fixed := &EmptyFixed{}
assert ref_fixed.len == 2
}

fn test_nested_fixed_array_alias_of_empty_struct() {
nested := NestedEmptyFixed{}
assert nested.len == 4
assert nested[0].len == 2

ref_nested := &NestedEmptyFixed{}
assert ref_nested.len == 4
}

fn test_nested_fixed_array_alias_in_struct_init() {
Expand All @@ -56,3 +69,43 @@ fn test_nested_fixed_array_alias_in_struct_init() {
assert v_tst.box[0][0] == 0.0
assert v_tst.box[1][2] == 0.0
}

fn test_blank_plain_assign_nested_fixed_array_of_empty_structs() {
_ = NestedEmptyFixed{}
}

fn test_reassign_fixed_array_alias() {
// Not empty struct
mut fixed := BazFixed{}
fixed = BazFixed{}
assert fixed.len == 2

mut ref_fixed := &BazFixed{}
ref_fixed = &BazFixed{}
assert ref_fixed.len == 2

mut nested_fixed := NestedBazFixed{}
nested_fixed = NestedBazFixed{}
assert nested_fixed.len == 2

mut ref_nested_fixed := &NestedBazFixed{}
ref_nested_fixed = &NestedBazFixed{}
assert ref_nested_fixed.len == 2

// Empty struct
mut empty_fixed := EmptyFixed{}
empty_fixed = EmptyFixed{}
assert empty_fixed.len == 2

mut ref_empty_fixed := &EmptyFixed{}
ref_empty_fixed = &EmptyFixed{}
assert ref_empty_fixed.len == 2

mut nested_empty_fixed := NestedEmptyFixed{}
nested_empty_fixed = NestedEmptyFixed{}
assert nested_empty_fixed.len == 4

mut ref_nested_empty_fixed := &NestedEmptyFixed{}
ref_nested_empty_fixed = &NestedEmptyFixed{}
assert ref_nested_empty_fixed.len == 4
}
23 changes: 23 additions & 0 deletions vlib/v/tests/aliases/return_fixed_array_of_struct_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
struct Box {}

type Arr = [2]Box

fn empty_ident_to_result(a Arr) !Arr {
return a
}

fn empty_ident_to_option(a Arr) ?Arr {
return a
}

fn test_return_ident_fixed_array_of_empty_struct_as_result() {
a := Arr{}
b := empty_ident_to_result(a) or { panic(err) }
assert b.len == 2
}

fn test_return_ident_fixed_array_of_empty_struct_as_option() {
a := Arr{}
b := empty_ident_to_option(a) or { panic('unexpected none') }
assert b.len == 2
}
25 changes: 25 additions & 0 deletions vlib/v/tests/options/option_fixed_array_alias_assign_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
struct Box {}

type Arr = [2]Box

fn test_option_fixed_array_alias_assign_struct_init_literal() {
mut a := ?Arr(none)

a = Arr{}
assert a != none
}

fn test_option_fixed_array_alias_assign_option_cast() {
mut a := ?Arr(Arr{})

a = ?Arr(none)
assert a == none
}

fn test_option_fixed_array_alias_assign_plain_ident() {
mut a := ?Arr(none)
mut plain := Arr{}

a = plain
assert a != none
}
11 changes: 11 additions & 0 deletions vlib/v/tests/options/option_struct_init_fixed_array_alias_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
struct Box {}

type Arr = [2]Box

fn test_option_struct_init_fixed_array_alias() {
a := ?Arr{}
assert a == none

b := ?Arr(none)
assert b == none
}