Skip to content

Commit bf2c556

Browse files
committed
cgen: fixes
1 parent 046ae4f commit bf2c556

5 files changed

Lines changed: 81 additions & 12 deletions

File tree

vlib/v/gen/c/assign.v

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,6 @@ fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr
7575
if gen_or {
7676
old_inside_opt_or_res := g.inside_opt_or_res
7777
g.inside_opt_or_res = true
78-
g.expr_with_cast(expr, expr_typ, ret_typ)
79-
if in_heap {
80-
g.write('))')
81-
}
82-
g.writeln(';')
8378
expr_var := if expr is ast.Ident && expr.kind == .constant {
8479
g.c_const_name(expr.name)
8580
} else if expr is ast.Ident && expr.is_auto_heap() {
@@ -88,6 +83,20 @@ fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr
8883
'${expr}'
8984
}
9085
dot_or_ptr := if !expr_typ.has_flag(.option_mut_param_t) { '.' } else { '-> ' }
86+
mut heap_line := ''
87+
if in_heap {
88+
// When the variable needs heap allocation, the caller has already
89+
// written the partial line `TYPE *var = HEAP(TYPE, (`.
90+
// We must NOT access the option's .data inside the HEAP macro
91+
// before checking the state, because the option may be `none`.
92+
// Pull back the partial line, emit the state check first, then
93+
// write the assignment with data access after the check.
94+
heap_line = g.go_before_last_stmt()
95+
g.empty_line = true
96+
} else {
97+
g.expr_with_cast(expr, expr_typ, ret_typ)
98+
g.writeln(';')
99+
}
91100
g.writeln('if (${c_name(expr_var)}${dot_or_ptr}state != 0) { // assign')
92101
if expr is ast.Ident && expr.or_expr.kind == .propagate_option {
93102
g.writeln('\tbuiltin__panic_option_not_set(_S("none"));')
@@ -123,6 +132,13 @@ fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr
123132
}
124133
}
125134
g.writeln('}')
135+
if in_heap {
136+
// Now that the state has been checked (and we haven't returned/panicked),
137+
// it is safe to access .data and do the heap allocation.
138+
g.write(heap_line)
139+
g.expr_with_cast(expr, expr_typ, ret_typ)
140+
g.writeln('));')
141+
}
126142
g.inside_opt_or_res = old_inside_opt_or_res
127143
} else {
128144
g.expr_with_opt(expr, expr_typ, ret_typ)

vlib/v/gen/c/auto_str_methods.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,7 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin
11351135
} else if ftyp_noshared.is_ptr() {
11361136
// reference types can be "nil"
11371137
if ftyp_noshared.has_flag(.option) {
1138-
funcprefix += 'builtin__isnil(&${it_field_name}) || builtin__isnil(&${it_field_name}.data)'
1138+
funcprefix += '${it_field_name}.state != 0'
11391139
} else {
11401140
funcprefix += 'builtin__isnil(${it_field_name})'
11411141
}

vlib/v/gen/c/if.v

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,21 @@ fn (mut g Gen) need_tmp_var_in_expr(expr ast.Expr) bool {
117117
if g.need_tmp_var_in_expr(expr.right) {
118118
return true
119119
}
120+
// struct pointer equality comparisons may hoist temp vars
121+
// (via gen_struct_pointer_eq_op) which breaks short-circuit
122+
// evaluation when used on the right side of `&&` after an
123+
// `is` check. Detect this so that infix_expr_and_or_op uses
124+
// its safe short-circuit pattern instead.
125+
if expr.op in [.eq, .ne] {
126+
if (expr.left_type.is_ptr() && !expr.left.is_lvalue())
127+
|| (expr.right_type.is_ptr() && !expr.right.is_lvalue()) {
128+
left_sym := g.table.sym(expr.left_type)
129+
right_sym := g.table.sym(expr.right_type)
130+
if left_sym.kind == .struct || right_sym.kind == .struct {
131+
return true
132+
}
133+
}
134+
}
120135
}
121136
ast.MapInit {
122137
for key in expr.keys {

vlib/v/gen/c/infix.v

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,11 +490,16 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) {
490490
}
491491

492492
fn (mut g Gen) gen_struct_pointer_eq_op(node ast.InfixExpr, left_type ast.Type, right_type ast.Type, ptr_typ string) {
493+
// When inside a short-circuit `&&` condition (infix_left_var_name is set),
494+
// do not hoist temp vars before the containing statement, as that would
495+
// evaluate expressions (e.g. as-casts from smartcasts) before the
496+
// short-circuit check has run. Instead, use inline expressions.
497+
inside_and_rhs := g.infix_left_var_name.len > 0
493498
mut stmt_str := ''
494499
mut restore_stmt := false
495500
mut left_expr := ''
496501
mut right_expr := ''
497-
if left_type.is_ptr() && !node.left.is_lvalue() {
502+
if left_type.is_ptr() && !node.left.is_lvalue() && !inside_and_rhs {
498503
if !restore_stmt {
499504
stmt_str = g.go_before_last_stmt().trim_space()
500505
g.empty_line = true
@@ -506,7 +511,7 @@ fn (mut g Gen) gen_struct_pointer_eq_op(node ast.InfixExpr, left_type ast.Type,
506511
} else {
507512
left_expr = g.expr_string(node.left)
508513
}
509-
if right_type.is_ptr() && !node.right.is_lvalue() {
514+
if right_type.is_ptr() && !node.right.is_lvalue() && !inside_and_rhs {
510515
if !restore_stmt {
511516
stmt_str = g.go_before_last_stmt().trim_space()
512517
g.empty_line = true

vlib/v/gen/c/spawn_and_go.v

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,34 @@ fn (mut g Gen) spawn_and_go_expr(node ast.SpawnExpr, mode SpawnGoMode) {
127127
g.expr(arg.expr)
128128
g.writeln(';')
129129
}
130-
call_ret_type := g.unwrap_generic(node.call_expr.return_type)
130+
call_ret_type := if expr.is_fn_var && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0
131+
&& g.cur_fn.generic_names.len > 0 {
132+
// In generic contexts, node.call_expr.return_type may be stale from
133+
// a previous instantiation. Look up the original fn param from the table
134+
// and resolve through convert_generic_type.
135+
mut resolved_ret := g.unwrap_generic(node.call_expr.return_type)
136+
cur_fn_name := g.cur_fn.fkey()
137+
orig_fn := g.table.find_fn(cur_fn_name) or { ast.Fn{} }
138+
for param in orig_fn.params {
139+
if param.name == expr.name {
140+
if param.typ.has_flag(.generic) || g.type_has_unresolved_generic_parts(param.typ) {
141+
mut muttable := unsafe { &ast.Table(g.table) }
142+
if resolved_type := muttable.convert_generic_type(param.typ, orig_fn.generic_names,
143+
g.cur_concrete_types)
144+
{
145+
fn_sym := g.table.sym(resolved_type)
146+
if fn_sym.info is ast.FnType {
147+
resolved_ret = fn_sym.info.func.return_type
148+
}
149+
}
150+
}
151+
break
152+
}
153+
}
154+
resolved_ret
155+
} else {
156+
g.unwrap_generic(node.call_expr.return_type)
157+
}
131158
s_ret_typ := g.styp(g.unwrap_generic(call_ret_type))
132159
if g.pref.os == .windows && call_ret_type != ast.void_type {
133160
g.writeln('${arg_tmp_var}->ret_ptr = (void *) builtin___v_malloc(sizeof(${s_ret_typ}));')
@@ -191,6 +218,7 @@ fn (mut g Gen) spawn_and_go_expr(node ast.SpawnExpr, mode SpawnGoMode) {
191218
g.type_definitions.writeln('\ntypedef struct ${wrapper_struct_name} {')
192219
mut fn_var := ''
193220
mut wrapper_return_type := call_ret_type
221+
mut resolved_fn_params := []ast.Param{}
194222
if node.call_expr.is_fn_var {
195223
mut fn_var_type := node.call_expr.fn_var_type
196224
// In generic contexts, fn_var_type may be stale from the last checker pass.
@@ -217,6 +245,7 @@ fn (mut g Gen) spawn_and_go_expr(node ast.SpawnExpr, mode SpawnGoMode) {
217245
}
218246
fn_sym := g.table.sym(fn_var_type)
219247
info := fn_sym.info as ast.FnType
248+
resolved_fn_params = info.func.params.clone()
220249
wrapper_return_type = info.func.return_type
221250
fn_var = g.fn_var_signature(ast.void_type, wrapper_return_type, info.func.params.map(it.typ),
222251
'fn')
@@ -278,16 +307,20 @@ fn (mut g Gen) spawn_and_go_expr(node ast.SpawnExpr, mode SpawnGoMode) {
278307
need_return_ptr := g.pref.os == .windows && wrapper_return_type != ast.void_type
279308
for i, arg in expr.args {
280309
mut arg_typ := arg.typ
281-
// For AnonFn calls in generic contexts, use the declared parameter
282-
// types instead of argument expression types, since the AST arg types
283-
// may be stale from a previous generic instantiation.
310+
// For fn var and AnonFn calls in generic contexts, use the declared
311+
// parameter types instead of argument expression types, since the
312+
// AST arg types may be stale from a previous generic instantiation.
284313
if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
285314
if expr.left is ast.AnonFn {
286315
anon := expr.left as ast.AnonFn
287316
f := anon.decl
288317
if i < f.params.len {
289318
arg_typ = g.unwrap_generic(f.params[i].typ)
290319
}
320+
} else if expr.is_fn_var && resolved_fn_params.len > 0 {
321+
if i < resolved_fn_params.len {
322+
arg_typ = resolved_fn_params[i].typ
323+
}
291324
} else {
292325
resolved_arg_typ := g.unwrap_generic(arg_typ)
293326
if resolved_arg_typ != 0 {

0 commit comments

Comments
 (0)