1
0
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:
Felipe Pena 2023-01-02 15:14:12 -03:00 committed by GitHub
parent 9bee702c62
commit b0d39814be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 31 deletions

View File

@ -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

View File

@ -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 }
}
}

View File

@ -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

View File

@ -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
} }
} }
} }

View 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{})
}