mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
cgen, checker: fix conditional evaluation and code generation for $if/$else $if/$else for known true conditions (#16823)
This commit is contained in:
parent
9bee702c62
commit
b0d39814be
@ -98,6 +98,7 @@ mut:
|
|||||||
comptime_for_field_var string
|
comptime_for_field_var string
|
||||||
comptime_fields_default_type ast.Type
|
comptime_fields_default_type ast.Type
|
||||||
comptime_fields_type map[string]ast.Type
|
comptime_fields_type map[string]ast.Type
|
||||||
|
comptime_for_field_value ast.StructField // value of the field variable
|
||||||
fn_scope &ast.Scope = unsafe { nil }
|
fn_scope &ast.Scope = unsafe { nil }
|
||||||
main_fn_decl_node ast.FnDecl
|
main_fn_decl_node ast.FnDecl
|
||||||
match_exhaustive_cutoff_limit int = 10
|
match_exhaustive_cutoff_limit int = 10
|
||||||
|
@ -180,6 +180,7 @@ fn (mut c Checker) comptime_for(node ast.ComptimeFor) {
|
|||||||
sym_info := sym.info as ast.Struct
|
sym_info := sym.info as ast.Struct
|
||||||
c.inside_comptime_for_field = true
|
c.inside_comptime_for_field = true
|
||||||
for field in sym_info.fields {
|
for field in sym_info.fields {
|
||||||
|
c.comptime_for_field_value = field
|
||||||
c.comptime_for_field_var = node.val_var
|
c.comptime_for_field_var = node.val_var
|
||||||
c.comptime_fields_type[node.val_var] = node.typ
|
c.comptime_fields_type[node.val_var] = node.typ
|
||||||
c.comptime_fields_default_type = field.typ
|
c.comptime_fields_default_type = field.typ
|
||||||
@ -725,7 +726,8 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBran
|
|||||||
ast.SelectorExpr {
|
ast.SelectorExpr {
|
||||||
if c.check_comptime_is_field_selector(cond) {
|
if c.check_comptime_is_field_selector(cond) {
|
||||||
if c.check_comptime_is_field_selector_bool(cond) {
|
if c.check_comptime_is_field_selector_bool(cond) {
|
||||||
return .eval
|
ret_bool := c.get_comptime_selector_bool_field(cond.field_name)
|
||||||
|
return if ret_bool { .eval } else { .skip }
|
||||||
}
|
}
|
||||||
c.error('unknown field `${cond.field_name}` from ${c.comptime_for_field_var}',
|
c.error('unknown field `${cond.field_name}` from ${c.comptime_for_field_var}',
|
||||||
cond.pos)
|
cond.pos)
|
||||||
@ -755,3 +757,23 @@ fn (mut c Checker) check_comptime_is_field_selector_bool(node ast.SelectorExpr)
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) get_comptime_selector_bool_field(field_name string) bool {
|
||||||
|
field := c.comptime_for_field_value
|
||||||
|
field_typ := c.comptime_fields_default_type
|
||||||
|
field_sym := c.table.sym(c.unwrap_generic(c.comptime_fields_default_type))
|
||||||
|
|
||||||
|
match field_name {
|
||||||
|
'is_pub' { return field.is_pub }
|
||||||
|
'is_mut' { return field.is_mut }
|
||||||
|
'is_shared' { return field_typ.has_flag(.shared_f) }
|
||||||
|
'is_atomic' { return field_typ.has_flag(.atomic_f) }
|
||||||
|
'is_optional' { return field.typ.has_flag(.optional) }
|
||||||
|
'is_array' { return field_sym.kind in [.array, .array_fixed] }
|
||||||
|
'is_map' { return field_sym.kind == .map }
|
||||||
|
'is_chan' { return field_sym.kind == .chan }
|
||||||
|
'is_struct' { return field_sym.kind == .struct_ }
|
||||||
|
'is_alias' { return field_sym.kind == .alias }
|
||||||
|
else { return false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -72,7 +72,13 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
|||||||
if node.is_comptime { // Skip checking if needed
|
if node.is_comptime { // Skip checking if needed
|
||||||
// smartcast field type on comptime if
|
// smartcast field type on comptime if
|
||||||
mut comptime_field_name := ''
|
mut comptime_field_name := ''
|
||||||
if mut branch.cond is ast.InfixExpr {
|
if branch.cond is ast.SelectorExpr && skip_state != .unknown {
|
||||||
|
is_comptime_type_is_expr = true
|
||||||
|
} else if mut branch.cond is ast.PrefixExpr {
|
||||||
|
if branch.cond.right is ast.SelectorExpr && skip_state != .unknown {
|
||||||
|
is_comptime_type_is_expr = true
|
||||||
|
}
|
||||||
|
} else if mut branch.cond is ast.InfixExpr {
|
||||||
if branch.cond.op == .key_is {
|
if branch.cond.op == .key_is {
|
||||||
left := branch.cond.left
|
left := branch.cond.left
|
||||||
right := branch.cond.right
|
right := branch.cond.right
|
||||||
|
@ -285,19 +285,28 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
|
|||||||
mut comptime_if_stmts_skip := false // don't write any statements if the condition is false
|
mut comptime_if_stmts_skip := false // don't write any statements if the condition is false
|
||||||
// (so that for example windows calls don't get generated inside `$if macos` which
|
// (so that for example windows calls don't get generated inside `$if macos` which
|
||||||
// will lead to compilation errors)
|
// will lead to compilation errors)
|
||||||
|
mut comptime_may_skip_else := false
|
||||||
|
|
||||||
for i, branch in node.branches {
|
for i, branch in node.branches {
|
||||||
start_pos := g.out.len
|
start_pos := g.out.len
|
||||||
|
if comptime_may_skip_else {
|
||||||
|
continue // if we already have a known true, ignore other branches
|
||||||
|
}
|
||||||
if i == node.branches.len - 1 && node.has_else {
|
if i == node.branches.len - 1 && node.has_else {
|
||||||
g.writeln('#else')
|
g.writeln('#else')
|
||||||
comptime_if_stmts_skip = false
|
comptime_if_stmts_skip = comptime_may_skip_else
|
||||||
} else {
|
} else {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
g.write('#if ')
|
g.write('#if ')
|
||||||
} else {
|
} else {
|
||||||
g.write('#elif ')
|
g.write('#elif ')
|
||||||
}
|
}
|
||||||
comptime_if_stmts_skip = !g.comptime_if_cond(branch.cond, branch.pkg_exist)
|
comptime_if_stmts_skip, comptime_may_skip_else = g.comptime_if_cond(branch.cond,
|
||||||
|
branch.pkg_exist)
|
||||||
|
if !comptime_if_stmts_skip && comptime_may_skip_else {
|
||||||
|
comptime_may_skip_else = false // if the cond is false, not skip else branch
|
||||||
|
}
|
||||||
|
comptime_if_stmts_skip = !comptime_if_stmts_skip
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
}
|
}
|
||||||
expr_str := g.out.last_n(g.out.len - start_pos).trim_space()
|
expr_str := g.out.last_n(g.out.len - start_pos).trim_space()
|
||||||
@ -351,39 +360,45 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the value of the bool comptime expression
|
// returns the value of the bool comptime expression and if next branches may be discarded
|
||||||
// returning `false` means the statements inside the $if can be skipped
|
// returning `false` means the statements inside the $if can be skipped
|
||||||
fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) bool {
|
fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
|
||||||
match cond {
|
match cond {
|
||||||
ast.BoolLiteral {
|
ast.BoolLiteral {
|
||||||
g.expr(cond)
|
g.expr(cond)
|
||||||
return true
|
return cond.val, true
|
||||||
}
|
}
|
||||||
ast.ParExpr {
|
ast.ParExpr {
|
||||||
g.write('(')
|
g.write('(')
|
||||||
is_cond_true := g.comptime_if_cond(cond.expr, pkg_exist)
|
is_cond_true, may_discard := g.comptime_if_cond(cond.expr, pkg_exist)
|
||||||
g.write(')')
|
g.write(')')
|
||||||
return is_cond_true
|
return is_cond_true, may_discard
|
||||||
}
|
}
|
||||||
ast.PrefixExpr {
|
ast.PrefixExpr {
|
||||||
g.write(cond.op.str())
|
g.write(cond.op.str())
|
||||||
return g.comptime_if_cond(cond.right, pkg_exist)
|
is_cond_true, _ := g.comptime_if_cond(cond.right, pkg_exist)
|
||||||
|
if cond.op == .not {
|
||||||
|
if cond.right in [ast.BoolLiteral, ast.SelectorExpr] {
|
||||||
|
return !is_cond_true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return is_cond_true, false
|
||||||
}
|
}
|
||||||
ast.PostfixExpr {
|
ast.PostfixExpr {
|
||||||
ifdef := g.comptime_if_to_ifdef((cond.expr as ast.Ident).name, true) or {
|
ifdef := g.comptime_if_to_ifdef((cond.expr as ast.Ident).name, true) or {
|
||||||
verror(err.msg())
|
verror(err.msg())
|
||||||
return false
|
return false, true
|
||||||
}
|
}
|
||||||
g.write('defined(${ifdef})')
|
g.write('defined(${ifdef})')
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
ast.InfixExpr {
|
ast.InfixExpr {
|
||||||
match cond.op {
|
match cond.op {
|
||||||
.and, .logical_or {
|
.and, .logical_or {
|
||||||
l := g.comptime_if_cond(cond.left, pkg_exist)
|
l, d1 := g.comptime_if_cond(cond.left, pkg_exist)
|
||||||
g.write(' ${cond.op} ')
|
g.write(' ${cond.op} ')
|
||||||
r := g.comptime_if_cond(cond.right, pkg_exist)
|
r, d2 := g.comptime_if_cond(cond.right, pkg_exist)
|
||||||
return if cond.op == .and { l && r } else { l || r }
|
return if cond.op == .and { l && r } else { l || r }, d1 && d1 == d2
|
||||||
}
|
}
|
||||||
.key_is, .not_is {
|
.key_is, .not_is {
|
||||||
left := cond.left
|
left := cond.left
|
||||||
@ -397,14 +412,14 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) bool {
|
|||||||
} else {
|
} else {
|
||||||
g.write('0')
|
g.write('0')
|
||||||
}
|
}
|
||||||
return is_true
|
return is_true, true
|
||||||
} else {
|
} else {
|
||||||
if is_true {
|
if is_true {
|
||||||
g.write('0')
|
g.write('0')
|
||||||
} else {
|
} else {
|
||||||
g.write('1')
|
g.write('1')
|
||||||
}
|
}
|
||||||
return !is_true
|
return !is_true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mut exp_type := ast.Type(0)
|
mut exp_type := ast.Type(0)
|
||||||
@ -429,14 +444,14 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) bool {
|
|||||||
} else {
|
} else {
|
||||||
g.write('0')
|
g.write('0')
|
||||||
}
|
}
|
||||||
return is_true
|
return is_true, true
|
||||||
} else if cond.op == .not_is {
|
} else if cond.op == .not_is {
|
||||||
if is_true {
|
if is_true {
|
||||||
g.write('0')
|
g.write('0')
|
||||||
} else {
|
} else {
|
||||||
g.write('1')
|
g.write('1')
|
||||||
}
|
}
|
||||||
return !is_true
|
return !is_true, true
|
||||||
}
|
}
|
||||||
// matches_interface = '/*iface:$got_type $exp_type*/ true'
|
// matches_interface = '/*iface:$got_type $exp_type*/ true'
|
||||||
//}
|
//}
|
||||||
@ -455,53 +470,53 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) bool {
|
|||||||
|
|
||||||
if cond.op == .key_is {
|
if cond.op == .key_is {
|
||||||
g.write('${exp_type.idx()} == ${got_type.idx()} && ${exp_type.has_flag(.optional)} == ${got_type.has_flag(.optional)}')
|
g.write('${exp_type.idx()} == ${got_type.idx()} && ${exp_type.has_flag(.optional)} == ${got_type.has_flag(.optional)}')
|
||||||
return exp_type == got_type
|
return exp_type == got_type, true
|
||||||
} else {
|
} else {
|
||||||
g.write('${exp_type.idx()} != ${got_type.idx()}')
|
g.write('${exp_type.idx()} != ${got_type.idx()}')
|
||||||
return exp_type != got_type
|
return exp_type != got_type, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.eq, .ne {
|
.eq, .ne {
|
||||||
// TODO Implement `$if method.args.len == 1`
|
// TODO Implement `$if method.args.len == 1`
|
||||||
if cond.left is ast.SelectorExpr || cond.right is ast.SelectorExpr {
|
if cond.left is ast.SelectorExpr || cond.right is ast.SelectorExpr {
|
||||||
l := g.comptime_if_cond(cond.left, pkg_exist)
|
l, d1 := g.comptime_if_cond(cond.left, pkg_exist)
|
||||||
g.write(' ${cond.op} ')
|
g.write(' ${cond.op} ')
|
||||||
r := g.comptime_if_cond(cond.right, pkg_exist)
|
r, d2 := g.comptime_if_cond(cond.right, pkg_exist)
|
||||||
return if cond.op == .eq { l == r } else { l != r }
|
return if cond.op == .eq { l == r } else { l != r }, d1 && d1 == d2
|
||||||
} else {
|
} else {
|
||||||
g.write('1')
|
g.write('1')
|
||||||
return true
|
return true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.Ident {
|
ast.Ident {
|
||||||
ifdef := g.comptime_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker
|
ifdef := g.comptime_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker
|
||||||
g.write('defined(${ifdef})')
|
g.write('defined(${ifdef})')
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
ast.ComptimeCall {
|
ast.ComptimeCall {
|
||||||
g.write('${pkg_exist}')
|
g.write('${pkg_exist}')
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
ast.SelectorExpr {
|
ast.SelectorExpr {
|
||||||
if g.inside_comptime_for_field && cond.expr is ast.Ident
|
if g.inside_comptime_for_field && cond.expr is ast.Ident
|
||||||
&& (cond.expr as ast.Ident).name == g.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_optional', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias'] {
|
&& (cond.expr as ast.Ident).name == g.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_optional', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias'] {
|
||||||
ret_bool := g.get_comptime_selector_bool_field(cond.field_name)
|
ret_bool := g.get_comptime_selector_bool_field(cond.field_name)
|
||||||
g.write(ret_bool.str())
|
g.write(ret_bool.str())
|
||||||
return ret_bool
|
return ret_bool, true
|
||||||
} else {
|
} else {
|
||||||
g.write('1')
|
g.write('1')
|
||||||
return true
|
return true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// should be unreachable, but just in case
|
// should be unreachable, but just in case
|
||||||
g.write('1')
|
g.write('1')
|
||||||
return true
|
return true, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
vlib/v/tests/ifcomp_expr_evaluate_test.v
Normal file
65
vlib/v/tests/ifcomp_expr_evaluate_test.v
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
struct EmptyStruct {
|
||||||
|
b []string
|
||||||
|
pub:
|
||||||
|
a int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_struct[T](val T) {
|
||||||
|
mut result := map[string][]string{}
|
||||||
|
$for field in T.fields {
|
||||||
|
$if field.is_array == false {
|
||||||
|
result[field.name] << '> is not array'
|
||||||
|
} $else {
|
||||||
|
result[field.name] << '> is array'
|
||||||
|
}
|
||||||
|
$if !field.is_array {
|
||||||
|
result[field.name] << '>> is not array'
|
||||||
|
} $else $if field.is_pub {
|
||||||
|
result[field.name] << '>> is public'
|
||||||
|
} $else {
|
||||||
|
result[field.name] << '>> is array'
|
||||||
|
}
|
||||||
|
$if field.is_array {
|
||||||
|
result[field.name] << '>>> is array'
|
||||||
|
} $else {
|
||||||
|
result[field.name] << '>>> is not array'
|
||||||
|
}
|
||||||
|
$if field.is_shared {
|
||||||
|
result[field.name] << '>>>> is shared'
|
||||||
|
} $else $if field.is_pub {
|
||||||
|
result[field.name] << '>>>> is pub'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$if !false {
|
||||||
|
result['bool'] << '1'
|
||||||
|
}
|
||||||
|
$if !true {
|
||||||
|
result['bool'] << '2'
|
||||||
|
}
|
||||||
|
$if true {
|
||||||
|
result['bool'] << '3'
|
||||||
|
}
|
||||||
|
$if false {
|
||||||
|
result['bool'] << '4'
|
||||||
|
}
|
||||||
|
|
||||||
|
assert result['a'].len == 4
|
||||||
|
assert result['b'].len == 3
|
||||||
|
assert result['bool'].len == 2
|
||||||
|
|
||||||
|
assert result['a'][0] == '> is not array'
|
||||||
|
assert result['a'][1] == '>> is not array'
|
||||||
|
assert result['a'][2] == '>>> is not array'
|
||||||
|
assert result['a'][3] == '>>>> is pub'
|
||||||
|
|
||||||
|
assert result['b'][0] == '> is array'
|
||||||
|
assert result['b'][1] == '>> is array'
|
||||||
|
assert result['b'][2] == '>>> is array'
|
||||||
|
|
||||||
|
assert result['bool'][0] == '1'
|
||||||
|
assert result['bool'][1] == '3'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_main() {
|
||||||
|
encode_struct(EmptyStruct{})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user