v/vlib/v/gen/c/assign.v

1317 lines
40 KiB
V

// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module c
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,
in_heap bool) {
gen_or := expr is ast.Ident && expr.or_expr.kind != .absent
if gen_or {
old_inside_opt_or_res := g.inside_opt_or_res
g.inside_opt_or_res = true
g.expr_with_cast(expr, expr_typ, ret_typ)
if in_heap {
g.write('))')
}
g.writeln(';')
expr_var := if expr is ast.Ident && expr.kind == .constant {
g.get_const_name(expr)
} else if expr is ast.Ident && expr.is_auto_heap() {
'(*${expr.name})'
} else {
'${expr}'
}
g.writeln('if (${c_name(expr_var)}.state != 0) { // assign')
if expr is ast.Ident && expr.or_expr.kind == .propagate_option {
g.writeln('\tpanic_option_not_set(_SLIT("none"));')
} else {
g.inside_or_block = true
defer {
g.inside_or_block = false
}
stmts := (expr as ast.Ident).or_expr.stmts
// handles stmt block which returns something
// e.g. { return none }
if stmts.len > 0 && stmts.last() is ast.ExprStmt && stmts.last().typ != ast.void_type {
g.gen_or_block_stmts(c_name(var_expr.str()), '', stmts, ret_typ, false)
} else {
// handles stmt block which doesn't returns value
// e.g. { return }
g.stmts(stmts)
if stmts.len > 0 && stmts.last() is ast.ExprStmt {
g.writeln(';')
}
}
}
g.writeln('}')
g.inside_opt_or_res = old_inside_opt_or_res
} else {
g.expr_with_opt(expr, expr_typ, ret_typ)
}
}
// expr_opt_with_alias handles conversion from different option alias type name
fn (mut g Gen) expr_opt_with_alias(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string {
styp := g.base_type(ret_typ)
line := g.go_before_last_stmt().trim_space()
g.empty_line = true
ret_var := g.new_tmp_var()
ret_styp := g.styp(ret_typ).replace('*', '_ptr')
g.writeln('${ret_styp} ${ret_var} = {.state=2, .err=_const_none__, .data={EMPTY_STRUCT_INITIALIZATION}};')
if expr !is ast.None {
is_option_expr := expr_typ.has_flag(.option)
if is_option_expr {
g.write('_option_clone((${option_name}*)')
} else {
g.write('_option_ok(&(${styp}[]){ ')
}
has_addr := is_option_expr && expr !in [ast.Ident, ast.SelectorExpr]
if has_addr {
expr_styp := g.styp(expr_typ).replace('*', '_ptr')
g.write('ADDR(${expr_styp}, ')
} else if is_option_expr {
g.write('&')
}
g.expr(expr)
if has_addr {
g.write(')')
}
if !is_option_expr {
g.write(' }')
}
g.writeln(', (${option_name}*)&${ret_var}, sizeof(${styp}));')
}
g.write(line)
if g.inside_return {
g.write(' ')
}
g.write(ret_var)
return ret_var
}
// 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 {
if expr is ast.CallExpr && expr.return_type.has_flag(.option) {
return g.expr_opt_with_alias(expr, expr_typ, ret_typ)
} else {
past := g.past_tmp_var_new()
defer {
g.past_tmp_var_done(past)
}
styp := g.base_type(ret_typ)
decl_styp := g.styp(ret_typ).replace('*', '_ptr')
g.writeln('${decl_styp} ${past.tmp_var};')
is_none := expr is ast.CastExpr && expr.expr is ast.None
if is_none {
g.write('_option_none(&(${styp}[]) {')
} else {
g.write('_option_ok(&(${styp}[]) {')
}
if expr is ast.CastExpr && expr_typ.has_flag(.option) {
ret_sym := g.table.sym(ret_typ)
if ret_sym.kind == .sum_type {
exp_sym := g.table.sym(expr_typ)
fname := g.get_sumtype_casting_fn(expr_typ, ret_typ)
g.call_cfn_for_casting_expr(fname, expr, ret_typ, ret_sym.cname, expr_typ.is_ptr(),
exp_sym.kind == .function, g.styp(expr_typ))
} else {
g.write('*((${g.base_type(expr_typ)}*)')
g.expr(expr)
g.write('.data)')
}
} else {
old_inside_opt_or_res := g.inside_opt_or_res
g.inside_opt_or_res = false
g.expr_with_cast(expr, expr_typ, ret_typ)
g.inside_opt_or_res = old_inside_opt_or_res
}
g.writeln(' }, (${option_name}*)(&${past.tmp_var}), sizeof(${styp}));')
return past.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 {
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
}
if expr_typ.has_flag(.option) && ret_typ.has_flag(.option) && !g.is_arraymap_set
&& expr in [ast.SelectorExpr, ast.DumpExpr, 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)
}
}
g.expr(expr)
if expr is ast.ComptimeSelector {
return g.gen_comptime_selector(expr)
} else {
return expr.str()
}
} else {
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 {
g.write('static ')
}
if node.is_volatile {
g.write('volatile ')
}
mut return_type := ast.void_type
is_decl := node.op == .decl_assign
g.assign_op = node.op
g.inside_assign = true
g.assign_ct_type = 0
g.arraymap_set_pos = 0
g.is_arraymap_set = false
g.is_assign_lhs = false
g.is_shared = false
defer {
g.assign_op = .unknown
g.inside_assign = false
g.assign_ct_type = 0
g.arraymap_set_pos = 0
g.is_arraymap_set = false
g.is_assign_lhs = false
g.is_shared = false
}
op := if is_decl { token.Kind.assign } else { node.op }
right_expr := node.right[0]
match right_expr {
ast.CallExpr { return_type = right_expr.return_type }
ast.LockExpr { return_type = right_expr.typ }
ast.MatchExpr { return_type = right_expr.return_type }
ast.IfExpr { return_type = right_expr.typ }
else {}
}
// Free the old value assigned to this string var (only if it's `str = [new value]`
// or `x.str = [new value]` )
mut af := g.is_autofree && !g.is_builtin_mod && node.op == .assign && node.left_types.len == 1
&& node.left[0] in [ast.Ident, ast.SelectorExpr]
mut sref_name := ''
mut type_to_free := ''
if af {
first_left_type := node.left_types[0]
first_left_sym := g.table.sym(node.left_types[0])
if first_left_type == ast.string_type || first_left_sym.kind == .array {
type_to_free = if first_left_type == ast.string_type { 'string' } else { 'array' }
mut ok := true
left0 := node.left[0]
if left0 is ast.Ident {
if left0.name == '_' {
ok = false
}
}
if ok {
sref_name = '_sref${node.pos.pos}'
g.write('${type_to_free} ${sref_name} = (') // TODO: we are copying the entire string here, optimize
// we can't just do `.str` since we need the extra data from the string struct
// doing `&string` is also not an option since the stack memory with the data will be overwritten
g.expr(left0) // node.left[0])
g.writeln('); // free ${type_to_free} on re-assignment2')
defer {
if af {
g.writeln('${type_to_free}_free(&${sref_name});')
}
}
} else {
af = false
}
} else {
af = false
}
}
// TODO: g.gen_assign_vars_autofree(node)
// json_test failed w/o this check
if return_type != ast.void_type && return_type != 0 {
sym := g.table.sym(return_type)
if sym.kind == .multi_return {
g.gen_multi_return_assign(node, return_type, sym)
return
}
}
// TODO: non idents on left (exprs)
if node.has_cross_var {
g.gen_cross_var_assign(node)
}
// `a := 1` | `a,b := 1,2`
if node.right.len < node.left.len {
g.checker_bug('node.right.len < node.left.len', node.pos)
}
if node.right_types.len < node.left.len {
g.checker_bug('node.right_types.len < node.left.len', node.pos)
}
if node.left_types.len < node.left.len {
g.checker_bug('node.left_types.len < node.left.len', node.pos)
}
last_curr_var_name := g.curr_var_name.clone()
defer {
g.curr_var_name = last_curr_var_name
}
g.curr_var_name = []
for i, mut left in node.left {
mut is_auto_heap := false
mut var_type := node.left_types[i]
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_va_list := false // C varargs
mut ident := ast.Ident{
scope: unsafe { nil }
}
mut cur_indexexpr := -1
left_sym := g.table.sym(g.unwrap_generic(var_type))
is_va_list = left_sym.language == .c && left_sym.name == 'C.va_list'
if mut left is ast.Ident {
ident = left
g.curr_var_name << ident.name
// id_info := ident.var_info()
// var_type = id_info.typ
blank_assign = left.kind == .blank_ident
// TODO: temporary, remove this
left_info := left.info
if left_info is ast.IdentVar {
share := left_info.share
if share == .shared_t {
var_type = var_type.set_flag(.shared_f)
}
if share == .atomic_t {
var_type = var_type.set_flag(.atomic_f)
}
}
if mut left.obj is ast.Var {
if is_decl {
if val is ast.Ident && val.ct_expr {
ctyp := g.unwrap_generic(g.type_resolver.get_type(val))
if ctyp != ast.void_type {
var_type = ctyp
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
g.assign_ct_type = var_type
}
} else if val is ast.ComptimeSelector {
if val.typ_key != '' {
if is_decl {
var_type = g.type_resolver.get_ct_type_or_default(val.typ_key,
var_type)
val_type = var_type
left.obj.typ = var_type
} else {
val_type = g.type_resolver.get_ct_type_or_default(val.typ_key,
var_type)
}
g.assign_ct_type = var_type
}
} else if val is ast.ComptimeCall {
key_str := '${val.method_name}.return_type'
var_type = g.type_resolver.get_ct_type_or_default(key_str, var_type)
left.obj.typ = var_type
g.assign_ct_type = var_type
} else if val is ast.Ident && val.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
}
} else if val is ast.DumpExpr {
if val.expr is ast.ComptimeSelector {
if val.expr.typ_key != '' {
var_type = g.type_resolver.get_ct_type_or_default(val.expr.typ_key,
var_type)
val_type = var_type
left.obj.typ = var_type
}
g.assign_ct_type = var_type
}
} else if val is ast.IndexExpr && (val.left is ast.Ident && val.left.ct_expr) {
ctyp := g.unwrap_generic(g.type_resolver.get_type(val))
if ctyp != ast.void_type {
var_type = ctyp
val_type = var_type
left.obj.typ = var_type
g.assign_ct_type = var_type
}
} else if left.obj.ct_type_var == .generic_var && val is ast.CallExpr {
if val.return_type_generic != 0
&& val.return_type_generic.has_flag(.generic) {
fn_ret_type := g.resolve_return_type(val)
if fn_ret_type != ast.void_type {
var_type = fn_ret_type
val_type = var_type
left.obj.typ = var_type
}
} else if val.is_static_method && val.left_type.has_flag(.generic) {
fn_ret_type := g.resolve_return_type(val)
var_type = fn_ret_type
val_type = var_type
left.obj.typ = var_type
g.assign_ct_type = var_type
} else if val.left_type != 0 && g.table.type_kind(val.left_type) == .array
&& val.name == 'map' && val.args.len > 0
&& val.args[0].expr is ast.AsCast
&& val.args[0].expr.typ.has_flag(.generic) {
var_type = g.table.find_or_register_array(g.unwrap_generic((val.args[0].expr as ast.AsCast).typ))
val_type = var_type
left.obj.typ = var_type
g.assign_ct_type = var_type
}
} else if val is ast.InfixExpr && val.op in [.plus, .minus, .mul, .div, .mod]
&& val.left_ct_expr {
ctyp := g.type_resolver.promote_type(g.unwrap_generic(g.type_resolver.get_type(val.left)),
g.unwrap_generic(g.type_resolver.get_type_or_default(val.right,
val.right_type)))
if ctyp != ast.void_type {
ct_type_var := g.comptime.get_ct_type_var(val.left)
if ct_type_var in [.key_var, .value_var] {
g.type_resolver.update_ct_type(left.name, g.unwrap_generic(ctyp))
}
var_type = ctyp
val_type = var_type
left.obj.typ = var_type
g.assign_ct_type = var_type
}
} else if val is ast.PostfixExpr && val.op == .question
&& (val.expr is ast.Ident && val.expr.ct_expr) {
ctyp := g.unwrap_generic(g.type_resolver.get_type(val))
if ctyp != ast.void_type {
var_type = ctyp
val_type = var_type
left.obj.typ = var_type
g.assign_ct_type = var_type
ct_type_var := g.comptime.get_ct_type_var(val.expr)
if ct_type_var == .field_var {
g.type_resolver.update_ct_type(left.name, ctyp)
}
}
}
}
is_auto_heap = left.obj.is_auto_heap
}
} else if mut left is ast.ComptimeSelector {
if left.typ_key != '' {
var_type = g.type_resolver.get_ct_type_or_default(left.typ_key, var_type)
}
g.assign_ct_type = var_type
if val is ast.ComptimeSelector {
if val.typ_key != '' {
val_type = g.type_resolver.get_ct_type_or_default(val.typ_key, var_type)
}
} else if val is ast.CallExpr {
g.assign_ct_type = g.comptime.comptime_for_field_type
}
} else if mut left is ast.IndexExpr && val is ast.ComptimeSelector {
if val.typ_key != '' {
val_type = g.type_resolver.get_ct_type_or_default(val.typ_key, var_type)
}
g.assign_ct_type = val_type
}
mut styp := g.styp(var_type)
mut is_fixed_array_init := false
mut has_val := false
match val {
ast.ArrayInit {
is_fixed_array_init = val.is_fixed
has_val = val.has_val
}
ast.CallExpr {
is_call = true
if val.comptime_ret_val {
return_type = g.comptime.comptime_for_field_type
styp = g.styp(return_type)
} else {
return_type = val.return_type
}
}
// TODO: no buffer fiddling
ast.AnonFn {
if !var_type.has_option_or_result() {
if blank_assign {
g.write('{')
}
// if it's a decl assign (`:=`) or a blank assignment `_ =`/`_ :=` then generate `void (*ident) (args) =`
if (is_decl || blank_assign) && left is ast.Ident {
sig := g.fn_var_signature(val.decl.return_type, val.decl.params.map(it.typ),
ident.name)
g.write(sig + ' = ')
} else {
g.is_assign_lhs = true
g.assign_op = node.op
g.expr(left)
g.is_assign_lhs = false
g.is_arraymap_set = false
if mut left is ast.IndexExpr {
sym := g.table.final_sym(left.left_type)
if sym.kind in [.map, .array] {
g.expr(val)
g.writeln('});')
continue
}
}
g.write(' = ')
}
g.expr(val)
g.writeln(';')
if blank_assign {
g.write('}')
}
continue
}
}
else {}
}
unwrapped_val_type := g.unwrap_generic(val_type)
right_sym := g.table.sym(unwrapped_val_type)
unaliased_right_sym := g.table.final_sym(unwrapped_val_type)
is_fixed_array_var := !g.pref.translated && unaliased_right_sym.kind == .array_fixed
&& val !is ast.ArrayInit
&& (val in [ast.Ident, ast.IndexExpr, ast.CallExpr, ast.SelectorExpr, ast.DumpExpr, ast.InfixExpr]
|| (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.is_assign_lhs = true
g.assign_op = node.op
g.left_is_opt = var_type.has_option_or_result()
g.right_is_opt = val_type.has_option_or_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
}
g.is_assign_lhs = false
if is_call {
old_is_void_expr_stmt := g.is_void_expr_stmt
g.is_void_expr_stmt = true
g.expr(val)
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* _ = ')
} else {
g.write('{${styp} _ = ')
}
if val in [ast.MatchExpr, ast.IfExpr] && unaliased_right_sym.info is ast.ArrayFixed {
tmp_var := g.expr_with_var(val, var_type, false)
g.fixed_array_var_init(tmp_var, false, unaliased_right_sym.info.elem_type,
unaliased_right_sym.info.size)
} else {
g.expr(val)
}
g.writeln(';}')
}
} else if node.op == .assign && !g.pref.translated && (is_fixed_array_init
|| (unaliased_right_sym.kind == .array_fixed && val in [ast.Ident, ast.CastExpr])) {
// Fixed arrays
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) {
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 {
mut v_var := ''
arr_typ := styp.trim('*')
if is_fixed_array_init {
right := val as ast.ArrayInit
v_var = g.new_tmp_var()
g.write('${arr_typ} ${v_var} = ')
g.expr(right)
g.writeln(';')
} else {
right := val as ast.Ident
v_var = right.name
}
pos := g.out.len
g.expr(left)
if g.is_arraymap_set && g.arraymap_set_pos >= 0 {
if g.arraymap_set_pos > 0 {
g.go_back_to(g.arraymap_set_pos)
}
g.write(', &${v_var})')
g.is_arraymap_set = false
g.arraymap_set_pos = 0
} else {
g.go_back_to(pos)
is_var_mut := !is_decl && left.is_auto_deref_var()
addr_left := if is_var_mut { '' } else { '&' }
g.writeln('')
g.write('memcpy(${addr_left}')
g.expr(left)
addr_val := if is_fixed_array_var { '' } else { '&' }
g.writeln(', ${addr_val}${v_var}, sizeof(${arr_typ}));')
}
g.is_assign_lhs = false
}
} else {
is_inside_ternary := g.inside_ternary != 0
cur_line := if is_inside_ternary && is_decl {
g.register_ternary_name(ident.name)
g.empty_line = false
g.go_before_ternary()
} else {
''
}
mut str_add := false
mut op_overloaded := false
mut op_expected_left := ast.no_type
mut op_expected_right := ast.no_type
is_shared_re_assign := !is_decl && node.left_types[i].has_flag(.shared_f)
&& left is ast.Ident && left_sym.kind in [.array, .map, .struct]
if node.op == .plus_assign && unaliased_right_sym.kind == .string {
if mut left is ast.IndexExpr {
if g.table.sym(left.left_type).kind == .array_fixed {
// strs[0] += str2 => `strs[0] = string__plus(strs[0], str2)`
g.expr(left)
g.write(' = string__plus(')
} else {
// a[0] += str => `array_set(&a, 0, &(string[]) {string__plus(...))})`
g.expr(left)
g.write('string__plus(')
}
} else {
// allow literal values to auto deref var (e.g.`for mut v in values { v += 1.0 }`)
if left.is_auto_deref_var() {
g.write('*')
}
// str += str2 => `str = string__plus(str, str2)`
g.expr(left)
g.write(' = string__plus(')
}
g.is_assign_lhs = false
str_add = true
}
// Assignment Operator Overloading
if ((left_sym.kind == .struct && right_sym.kind == .struct)
|| (left_sym.kind == .alias && right_sym.kind == .alias))
&& node.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] {
extracted_op := match node.op {
.plus_assign { '+' }
.minus_assign { '-' }
.div_assign { '/' }
.mod_assign { '%' }
.mult_assign { '*' }
else { 'unknown op' }
}
pos := g.out.len
g.expr(left)
if left_sym.info is ast.Struct && left_sym.info.generic_types.len > 0 {
concrete_types := left_sym.info.concrete_types
mut method_name := left_sym.cname + '_' + util.replace_op(extracted_op)
method_name = g.generic_fn_name(concrete_types, method_name)
g.write(' = ${method_name}(')
g.expr(left)
g.write(', ')
g.expr(val)
g.writeln(');')
return
} else if left_sym.kind == .alias
&& g.table.final_sym(g.unwrap_generic(var_type)).is_number()
&& !left_sym.has_method(extracted_op) {
g.write(' = ')
g.expr(left)
g.write(' ${extracted_op} ')
g.expr(val)
if !g.inside_for_c_stmt {
g.write(';')
}
return
} else {
if g.table.final_sym(g.unwrap_generic(var_type)).kind == .array_fixed {
g.go_back_to(pos)
g.empty_line = true
g.write('memcpy(')
g.expr(left)
g.write(', ${styp}_${util.replace_op(extracted_op)}(')
} else {
g.write(' = ${styp}_${util.replace_op(extracted_op)}(')
}
method := g.table.find_method(left_sym, extracted_op) or {
// the checker will most likely have found this, already...
g.error('assignment operator `${extracted_op}=` used but no `${extracted_op}` method defined',
node.pos)
ast.Fn{}
}
op_expected_left = method.params[0].typ
op_expected_right = method.params[1].typ
op_overloaded = true
}
}
final_left_sym := g.table.final_sym(g.unwrap_generic(var_type))
final_right_sym := g.table.final_sym(unwrapped_val_type)
if final_left_sym.kind == .bool && final_right_sym.kind == .bool
&& node.op in [.boolean_or_assign, .boolean_and_assign] {
extracted_op := match node.op {
.boolean_or_assign {
'||'
}
.boolean_and_assign {
'&&'
}
else {
'unknown op'
}
}
g.expr(left)
g.write(' = ')
g.expr(left)
g.write(' ${extracted_op} ')
g.expr(val)
g.writeln(';')
return
}
if right_sym.info is ast.FnType && is_decl {
if is_inside_ternary {
g.out.write_string(util.tabs(g.indent - g.inside_ternary))
}
fn_name := c_fn_name(g.get_ternary_name(ident.name))
if val_type.has_flag(.option) {
ret_styp := g.styp(g.unwrap_generic(val_type))
g.write('${ret_styp} ${fn_name}')
} else {
ret_styp := g.styp(right_sym.info.func.return_type)
mut call_conv := ''
mut msvc_call_conv := ''
for attr in right_sym.info.func.attrs {
match attr.name {
'callconv' {
if g.is_cc_msvc {
msvc_call_conv = '__${attr.arg} '
} else {
call_conv = '${attr.arg}'
}
}
else {}
}
}
call_conv_attribute_suffix := if call_conv.len != 0 {
'__attribute__((${call_conv}))'
} else {
''
}
g.write('${ret_styp} (${msvc_call_conv}*${fn_name}) (')
def_pos := g.definitions.len
g.fn_decl_params(right_sym.info.func.params, unsafe { nil }, false,
false)
g.definitions.go_back(g.definitions.len - def_pos)
g.write(')${call_conv_attribute_suffix}')
}
} else {
if is_decl {
if is_inside_ternary {
g.out.write_string(util.tabs(g.indent - g.inside_ternary))
}
mut is_used_var_styp := false
if ident.name !in g.defer_vars {
val_sym := g.table.sym(val_type)
if val_sym.info is ast.Struct && val_sym.info.generic_types.len > 0 {
if val is ast.StructInit {
var_styp := g.styp(val.typ)
if var_type.has_flag(.shared_f) {
g.write('__shared__${var_styp}* ')
} else {
g.write('${var_styp} ')
}
is_used_var_styp = true
} else if val is ast.PrefixExpr {
if val.op == .amp && val.right is ast.StructInit {
var_styp := g.styp(val.right.typ.ref())
if var_type.has_flag(.shared_f) {
g.write('__shared__')
}
g.write('${var_styp} ')
is_used_var_styp = true
}
}
}
if !is_used_var_styp {
if !val_type.has_flag(.option) && left_sym.is_array_fixed() {
if left_sym.info is ast.Alias {
parent_sym := g.table.final_sym(left_sym.info.parent_type)
styp = g.styp(left_sym.info.parent_type)
if !parent_sym.is_array_fixed_ret() {
g.write('${styp} ')
} else {
g.write('${styp[3..]} ')
}
} else {
if !left_sym.is_array_fixed_ret() {
g.write('${styp} ')
} else {
g.write('${styp[3..]} ')
}
}
} else {
g.write('${styp} ')
}
}
if is_auto_heap && !(val_type.is_ptr() && val_type.has_flag(.option)) {
g.write('*')
}
}
}
if left in [ast.Ident, ast.SelectorExpr] {
g.prevent_sum_type_unwrapping_once = true
}
if !is_fixed_array_var || is_decl || is_shared_re_assign {
if op_overloaded {
g.op_arg(left, op_expected_left, var_type)
} else {
if !is_decl && !is_shared_re_assign && left.is_auto_deref_var() {
g.write('*')
}
g.expr(left)
if !is_decl && var_type.has_flag(.shared_f) {
g.write('->val') // don't reset the mutex, just change the value
}
}
}
}
if is_inside_ternary && is_decl {
g.write(';\n${cur_line}')
g.out.write_string(util.tabs(g.indent))
g.expr(left)
}
g.is_assign_lhs = false
if left is ast.IndexExpr && g.cur_indexexpr.len > 0 {
cur_indexexpr = g.cur_indexexpr.index(left.pos().pos)
}
if is_fixed_array_var || is_va_list {
if is_decl {
g.writeln(';')
if is_va_list {
continue
}
}
} else if cur_indexexpr == -1 && !str_add && !op_overloaded {
g.write(' ${op} ')
} else if str_add || op_overloaded {
g.write(', ')
}
mut cloned := false
if g.is_autofree {
if right_sym.kind in [.array, .string] && !unwrapped_val_type.has_flag(.shared_f) {
if g.gen_clone_assignment(var_type, val, unwrapped_val_type, false) {
cloned = true
}
} else if right_sym.info is ast.Interface && var_type != ast.error_type {
g.register_free_method(var_type)
}
}
if !cloned {
if g.comptime.comptime_for_field_var == ''
&& ((var_type.has_flag(.option) && !val_type.has_flag(.option))
|| (var_type.has_flag(.result) && !val_type.has_flag(.result))) {
old_inside_opt_or_res := g.inside_opt_or_res
defer {
g.inside_opt_or_res = old_inside_opt_or_res
}
g.inside_opt_or_res = true
if is_auto_heap && var_type.has_flag(.option) {
g.write('&')
}
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.styp(val_type).trim('*')
final_typ_str := if is_fixed_array_var { '' } else { '(${typ_str}*)' }
final_ref_str := if is_fixed_array_var {
''
} else if val_type.is_ptr() {
'(byte*)'
} else {
'(byte*)&'
}
if val_type.has_flag(.option) {
g.expr(left)
g.write(' = ')
g.expr(val)
} else {
if op_overloaded {
g.expr(left)
g.write(', ')
g.expr(val)
g.write(').ret_arr, sizeof(${typ_str})')
} else {
g.write('memcpy(${final_typ_str}')
g.expr(left)
g.write(', ${final_ref_str}')
g.expr(val)
g.write(', sizeof(${typ_str}))')
}
}
} else if is_decl {
g.is_shared = var_type.has_flag(.shared_f)
if is_fixed_array_init && !has_val {
if val is ast.ArrayInit {
g.array_init(val, c_name(ident.name))
} else {
g.write('{0}')
}
} else {
is_option_unwrapped := val is ast.Ident && val.or_expr.kind != .absent
is_option_auto_heap := is_auto_heap && is_option_unwrapped
if is_auto_heap {
g.write('HEAP(${styp}, (')
}
if val.is_auto_deref_var() && !is_option_unwrapped {
g.write('*')
}
if (var_type.has_flag(.option) && val !in [ast.Ident, ast.SelectorExpr])
|| gen_or {
g.expr_with_opt_or_block(val, val_type, left, var_type, is_option_auto_heap)
} 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 val in [ast.MatchExpr, ast.IfExpr]
&& unaliased_right_sym.info is ast.ArrayFixed {
tmp_var := g.expr_with_var(val, var_type, false)
g.fixed_array_var_init(tmp_var, false, unaliased_right_sym.info.elem_type,
unaliased_right_sym.info.size)
} else {
g.expr(val)
}
if is_auto_heap && !is_option_auto_heap {
g.write('))')
}
}
} else {
// var = &auto_heap_var
old_is_auto_heap := g.is_option_auto_heap
defer {
g.is_option_auto_heap = old_is_auto_heap
}
g.is_option_auto_heap = val_type.has_flag(.option) && val is ast.PrefixExpr
&& val.right is ast.Ident && (val.right as ast.Ident).is_auto_heap()
if var_type.has_flag(.option) || gen_or {
g.expr_with_opt_or_block(val, val_type, left, var_type, false)
} else if node.has_cross_var {
g.gen_cross_tmp_variable(node.left, val)
} else {
if op_overloaded {
g.op_arg(val, op_expected_right, val_type)
} else {
exp_type := if var_type.is_ptr()
&& (left.is_auto_deref_var() || var_type.has_flag(.shared_f)) {
var_type.deref()
} else {
var_type
}.clear_flag(.shared_f) // don't reset the mutex, just change the value
g.expr_with_cast(val, val_type, exp_type)
}
}
}
}
if str_add || op_overloaded {
g.write(')')
}
if cur_indexexpr != -1 {
g.cur_indexexpr.delete(cur_indexexpr)
g.write(' })')
g.is_arraymap_set = g.cur_indexexpr.len > 0
}
g.is_shared = false
}
g.right_is_opt = false
if g.inside_ternary == 0 && (node.left.len > 1 || !node.is_simple) {
g.writeln(';')
}
}
}
fn (mut g Gen) gen_multi_return_assign(node &ast.AssignStmt, return_type ast.Type, return_sym ast.TypeSymbol) {
// multi return
// TODO: Handle in if_expr
mr_var_name := 'mr_${node.pos.pos}'
mut is_option := return_type.has_flag(.option)
mut mr_styp := g.styp(return_type.clear_flag(.result))
if node.right[0] is ast.CallExpr && node.right[0].or_block.kind != .absent {
is_option = false
mr_styp = g.styp(return_type.clear_option_and_result())
}
g.write('${mr_styp} ${mr_var_name} = ')
g.expr(node.right[0])
g.writeln(';')
mr_types := (return_sym.info as ast.MultiReturn).types
for i, lx in node.left {
mut cur_indexexpr := -1
mut is_auto_heap := false
mut ident := ast.Ident{
scope: unsafe { nil }
}
if lx is ast.Ident {
ident = lx
if lx.kind == .blank_ident {
continue
}
if lx.obj is ast.Var {
is_auto_heap = lx.obj.is_auto_heap
}
}
if lx is ast.IndexExpr && g.cur_indexexpr.len > 0 {
cur_indexexpr = g.cur_indexexpr.index(lx.pos.pos)
}
styp := if ident.name in g.defer_vars { '' } else { g.styp(node.left_types[i]) }
if node.op == .decl_assign {
g.write('${styp} ')
}
if lx.is_auto_deref_var() {
g.write('*')
}
noscan := if is_auto_heap { g.check_noscan(return_type) } else { '' }
if node.left_types[i].has_flag(.option) {
base_typ := g.base_type(node.left_types[i])
tmp_var := if is_auto_heap {
'HEAP${noscan}(${styp}, ${mr_var_name}.arg${i})'
} else if is_option {
'(*((${g.base_type(return_type)}*)${mr_var_name}.data)).arg${i}'
} else {
'${mr_var_name}.arg${i}'
}
if mr_types[i].has_flag(.option) {
g.expr(lx)
g.write(' = ${tmp_var};')
} else {
g.write('_option_ok(&(${base_typ}[]) { ${tmp_var} }, (${option_name}*)(&')
tmp_left_is_opt := g.left_is_opt
g.left_is_opt = true
g.expr(lx)
g.left_is_opt = tmp_left_is_opt
g.writeln('), sizeof(${base_typ}));')
}
} else {
g.expr(lx)
sym := g.table.final_sym(node.left_types[i])
if sym.kind == .array_fixed {
g.writeln2(';', 'memcpy(&${g.expr_string(lx)}, &${mr_var_name}.arg${i}, sizeof(${styp}));')
} else {
if cur_indexexpr != -1 {
if is_auto_heap {
g.writeln('HEAP${noscan}(${styp}, ${mr_var_name}.arg${i}) });')
} else if is_option {
g.writeln('(*((${g.base_type(return_type)}*)${mr_var_name}.data)).arg${i} });')
} else {
g.writeln('${mr_var_name}.arg${i} });')
}
g.cur_indexexpr.delete(cur_indexexpr)
} else {
if is_auto_heap {
g.writeln(' = HEAP${noscan}(${styp}, ${mr_var_name}.arg${i});')
} else if is_option {
g.writeln(' = (*((${g.base_type(return_type)}*)${mr_var_name}.data)).arg${i};')
} else {
g.writeln(' = ${mr_var_name}.arg${i};')
}
}
}
}
}
if g.is_arraymap_set {
g.is_arraymap_set = false
}
}
fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) {
for i, left in node.left {
left_is_auto_deref_var := left.is_auto_deref_var()
match left {
ast.Ident {
left_typ := node.left_types[i]
left_sym := g.table.sym(left_typ)
mut anon_ctx := ''
if g.anon_fn {
if obj := left.scope.find_var(left.name) {
if obj.is_inherited {
anon_ctx = '${closure_ctx}->'
}
}
}
if left_sym.info is ast.FnType {
g.write_fn_ptr_decl(&left_sym.info, '_var_${left.pos.pos}')
g.writeln(' = ${anon_ctx}${c_name(left.name)};')
} else if left_is_auto_deref_var {
styp := g.styp(left_typ).trim('*')
if left_sym.kind == .array {
g.writeln('${styp} _var_${left.pos.pos} = array_clone(${anon_ctx}${c_name(left.name)});')
} else {
g.writeln('${styp} _var_${left.pos.pos} = *${anon_ctx}${c_name(left.name)};')
}
} else {
styp := g.styp(left_typ)
if left_sym.kind == .array {
g.writeln('${styp} _var_${left.pos.pos} = array_clone(&${anon_ctx}${c_name(left.name)});')
} else {
g.writeln('${styp} _var_${left.pos.pos} = ${anon_ctx}${c_name(left.name)};')
}
}
}
ast.IndexExpr {
sym := g.table.sym(g.table.unaliased_type(left.left_type))
if sym.kind == .array {
info := sym.info as ast.Array
elem_typ := g.table.sym(info.elem_type)
needs_clone := info.elem_type == ast.string_type && g.is_autofree
if elem_typ.kind == .function {
left_typ := node.left_types[i]
left_sym := g.table.sym(left_typ)
g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_${left.pos.pos}')
g.write(' = *(voidptr*)array_get(')
} else {
styp := g.styp(info.elem_type)
string_clone := if needs_clone { 'string_clone(' } else { '' }
g.write('${styp} _var_${left.pos.pos} = ${string_clone}*(${styp}*)array_get(')
}
if left.left_type.is_ptr() {
g.write('*')
}
g.expr(left.left)
g.write(', ')
g.expr(left.index)
if needs_clone {
g.write(')')
}
g.writeln(');')
} else if sym.kind == .array_fixed {
info := sym.info as ast.ArrayFixed
elem_typ := g.table.sym(info.elem_type)
if elem_typ.kind == .function {
left_typ := node.left_types[i]
left_sym := g.table.sym(left_typ)
g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_${left.pos.pos}')
g.write(' = *(voidptr*)')
} else {
styp := g.styp(info.elem_type)
g.write('${styp} _var_${left.pos.pos} = ')
}
if left.left_type.is_ptr() {
g.write('*')
}
needs_clone := info.elem_type == ast.string_type && g.is_autofree
if needs_clone {
g.write('string_clone(')
}
g.expr(left)
if needs_clone {
g.write(')')
}
g.writeln(';')
} else if sym.kind == .map {
info := sym.info as ast.Map
skeytyp := g.styp(info.key_type)
styp := g.styp(info.value_type)
zero := g.type_default(info.value_type)
val_typ := g.table.sym(info.value_type)
if val_typ.kind == .function {
left_type := node.left_types[i]
left_sym := g.table.sym(left_type)
g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_${left.pos.pos}')
g.write(' = *(voidptr*)map_get(')
} else {
g.write('${styp} _var_${left.pos.pos} = *(${styp}*)map_get(')
}
if !left.left_type.is_ptr() {
g.write('ADDR(map, ')
g.expr(left.left)
g.write(')')
} else {
g.expr(left.left)
}
g.write(', &(${skeytyp}[]){')
g.expr(left.index)
g.write('}')
if val_typ.kind == .function {
g.writeln(', &(voidptr[]){ ${zero} });')
} else {
g.writeln(', &(${styp}[]){ ${zero} });')
}
}
}
ast.SelectorExpr {
styp := g.styp(left.typ)
g.write('${styp} _var_${left.pos.pos} = ')
g.expr(left.expr)
sel := g.dot_or_ptr(left.expr_type)
g.writeln('${sel}${left.field_name};')
}
else {}
}
}
}
fn (mut g Gen) gen_cross_tmp_variable(left []ast.Expr, val ast.Expr) {
val_ := val
match val {
ast.Ident {
mut has_var := false
for lx in left {
if lx is ast.Ident {
if val.name == lx.name {
g.write2('_var_', lx.pos.pos.str())
has_var = true
break
}
}
}
if !has_var {
g.expr(val_)
}
}
ast.IndexExpr {
mut has_var := false
for lx in left {
if val_.str() == lx.str() {
g.write2('_var_', lx.pos().pos.str())
has_var = true
break
}
}
if !has_var {
g.expr(val_)
}
}
ast.InfixExpr {
sym := g.table.sym(val.left_type)
svalop := val.op.str()
if _ := g.table.find_method(sym, svalop) {
left_styp := g.styp(val.left_type.set_nr_muls(0))
g.write2(left_styp, '_')
g.write2(util.replace_op(svalop), '(')
g.gen_cross_tmp_variable(left, val.left)
g.write(', ')
g.gen_cross_tmp_variable(left, val.right)
g.write(')')
} else {
g.gen_cross_tmp_variable(left, val.left)
g.write(svalop)
g.gen_cross_tmp_variable(left, val.right)
}
}
ast.ParExpr {
g.write('(')
g.gen_cross_tmp_variable(left, val.expr)
g.write(')')
}
ast.CallExpr {
if val.is_method {
unwrapped_rec_type, typ_sym := g.unwrap_receiver_type(val)
left_type := g.unwrap_generic(val.left_type)
left_sym := g.table.sym(left_type)
final_left_sym := g.table.final_sym(left_type)
rec_typ_name := g.resolve_receiver_name(val, unwrapped_rec_type, final_left_sym,
left_sym, typ_sym)
fn_name := util.no_dots('${rec_typ_name}_${val.name}')
g.write('${fn_name}(&')
g.gen_cross_tmp_variable(left, val.left)
for i, arg in val.args {
g.gen_cross_tmp_variable(left, arg.expr)
if i != val.args.len - 1 {
g.write(', ')
}
}
g.write(')')
} else {
mut fn_name := val.name.replace('.', '__')
if val.concrete_types.len > 0 {
fn_name = g.generic_fn_name(val.concrete_types, fn_name)
}
g.write('${fn_name}(')
for i, arg in val.args {
g.gen_cross_tmp_variable(left, arg.expr)
if i != val.args.len - 1 {
g.write(', ')
}
}
g.write(')')
}
}
ast.PrefixExpr {
g.write(val.op.str())
g.gen_cross_tmp_variable(left, val.right)
}
ast.PostfixExpr {
g.gen_cross_tmp_variable(left, val.expr)
g.write(val.op.str())
}
ast.SelectorExpr {
mut has_var := false
for lx in left {
if val_.str() == lx.str() {
g.write2('_var_', lx.pos().pos.str())
has_var = true
break
}
}
if !has_var {
g.expr(val_)
}
}
else {
g.expr(val_)
}
}
}