checker,cgen: evaluate comptime $if results in checker *only* (fix #25123) (fix #25156) (#25150)

This commit is contained in:
kbkpbot 2025-08-24 04:40:50 +08:00 committed by GitHub
parent 7dd91ecef7
commit a3f86eed1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 605 additions and 461 deletions

View file

@ -1538,7 +1538,6 @@ fn (t Tree) if_branch(node ast.IfBranch) &Node {
obj.add_terse('cond', t.expr(node.cond)) obj.add_terse('cond', t.expr(node.cond))
obj.add('pos', t.pos(node.pos)) obj.add('pos', t.pos(node.pos))
obj.add('body_pos', t.pos(node.body_pos)) obj.add('body_pos', t.pos(node.body_pos))
obj.add_terse('pkg_exist', t.bool_node(node.pkg_exist))
obj.add_terse('stmts', t.array_node_stmt(node.stmts)) obj.add_terse('stmts', t.array_node_stmt(node.stmts))
obj.add('scope', t.number_node(int(node.scope))) obj.add('scope', t.number_node(int(node.scope)))
obj.add('comments', t.array_node_comment(node.comments)) obj.add('comments', t.array_node_comment(node.comments))

View file

@ -1235,7 +1235,6 @@ pub:
comments []Comment comments []Comment
pub mut: pub mut:
cond Expr cond Expr
pkg_exist bool
stmts []Stmt stmts []Stmt
scope &Scope = unsafe { nil } scope &Scope = unsafe { nil }
} }
@ -2131,6 +2130,8 @@ pub fn (cc ComptimeCall) expr_str() string {
if arg.expr.is_pure_literal() { if arg.expr.is_pure_literal() {
str = "\$${cc.method_name}('${cc.args_var}', ${arg})" str = "\$${cc.method_name}('${cc.args_var}', ${arg})"
} }
} else if cc.kind == .pkgconfig {
str = "\$${cc.method_name}('${cc.args_var}')"
} }
return str return str
} }

View file

@ -94,6 +94,13 @@ pub mut:
anon_struct_counter int anon_struct_counter int
anon_union_names map[string]int // anon union name -> union sym idx anon_union_names map[string]int // anon union name -> union sym idx
anon_union_counter int anon_union_counter int
comptime_is_true map[string]ComptTimeCondResult // The evaluate cond results for different generic types combination, such as `comptime_is_true['T=int,X=string|main.v|pos ...'] = {true, '!DEFINED(WINDOWS)'}`
}
pub struct ComptTimeCondResult {
pub mut:
val bool
c_str string
} }
// used by vls to avoid leaks // used by vls to avoid leaks

View file

@ -256,7 +256,11 @@ fn (mut c Checker) comptime_for(mut node ast.ComptimeFor) {
node.typ = c.expr(mut node.expr) node.typ = c.expr(mut node.expr)
c.unwrap_generic(node.typ) c.unwrap_generic(node.typ)
} }
sym := c.table.final_sym(typ) sym := if node.typ != c.field_data_type {
c.table.final_sym(typ)
} else {
c.table.final_sym(c.comptime.comptime_for_field_type)
}
if sym.kind == .placeholder || typ.has_flag(.generic) { if sym.kind == .placeholder || typ.has_flag(.generic) {
c.error('\$for expects a type name or variable name to be used here, but ${sym.name} is not a type or variable name', c.error('\$for expects a type name or variable name to be used here, but ${sym.name} is not a type or variable name',
node.typ_pos) node.typ_pos)
@ -655,7 +659,8 @@ fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.Comp
for i in 0 .. expr.branches.len { for i in 0 .. expr.branches.len {
mut branch := expr.branches[i] mut branch := expr.branches[i]
if !expr.has_else || i < expr.branches.len - 1 { if !expr.has_else || i < expr.branches.len - 1 {
is_true, _ := c.comptime_if_cond(mut branch.cond) mut sb := strings.new_builder(256)
is_true, _ := c.comptime_if_cond(mut branch.cond, mut sb)
if is_true { if is_true {
last_stmt := branch.stmts.last() last_stmt := branch.stmts.last()
if last_stmt is ast.ExprStmt { if last_stmt is ast.ExprStmt {
@ -771,13 +776,199 @@ fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool {
} }
} }
c.inside_ct_attr = true c.inside_ct_attr = true
is_true, _ := c.comptime_if_cond(mut node.ct_expr) mut sb := strings.new_builder(256)
is_true, _ := c.comptime_if_cond(mut node.ct_expr, mut sb)
node.ct_skip = !is_true node.ct_skip = !is_true
c.inside_ct_attr = false c.inside_ct_attr = false
node.ct_evaled = true node.ct_evaled = true
return node.ct_skip return node.ct_skip
} }
fn (mut c Checker) comptime_if_to_ifdef(name string) !string {
match name {
// platforms/os-es:
'windows' {
return '_WIN32'
}
'ios' {
return '__TARGET_IOS__'
}
'macos' {
return '__APPLE__'
}
'mach' {
return '__MACH__'
}
'darwin' {
return '__DARWIN__'
}
'hpux' {
return '__HPUX__'
}
'gnu' {
return '__GNU__'
}
'qnx' {
return '__QNX__'
}
'linux' {
return '__linux__'
}
'serenity' {
return '__serenity__'
}
'plan9' {
return '__plan9__'
}
'vinix' {
return '__vinix__'
}
'freebsd' {
return '__FreeBSD__'
}
'openbsd' {
return '__OpenBSD__'
}
'netbsd' {
return '__NetBSD__'
}
'bsd' {
return '__BSD__'
}
'dragonfly' {
return '__DragonFly__'
}
'android' {
return '__ANDROID__'
}
'termux' {
// Note: termux is running on Android natively so __ANDROID__ will also be defined
return '__TERMUX__'
}
'solaris' {
return '__sun'
}
'haiku' {
return '__HAIKU__'
}
//
'js' {
return '_VJS'
}
'wasm32_emscripten' {
return '__EMSCRIPTEN__'
}
'native' {
return '_VNATIVE' // when using the native backend, cgen is inactive
}
// compilers:
'gcc' {
return '__V_GCC__'
}
'tinyc' {
return '__TINYC__'
}
'clang' {
return '__clang__'
}
'mingw' {
return '__MINGW32__'
}
'msvc' {
return '_MSC_VER'
}
'cplusplus' {
return '__cplusplus'
}
// other:
'threads' {
return '__VTHREADS__'
}
'gcboehm' {
return '_VGCBOEHM'
}
'debug' {
return '_VDEBUG'
}
'prod' {
return '_VPROD'
}
'profile' {
return '_VPROFILE'
}
'test' {
return '_VTEST'
}
'glibc' {
return '__GLIBC__'
}
'prealloc' {
return '_VPREALLOC'
}
'no_bounds_checking' {
return 'CUSTOM_DEFINE_no_bounds_checking'
}
'freestanding' {
return '_VFREESTANDING'
}
'autofree' {
return '_VAUTOFREE'
}
// architectures:
'amd64' {
return '__V_amd64'
}
'aarch64', 'arm64' {
return '__V_arm64'
}
'arm32' {
return '__V_arm32'
}
'i386' {
return '__V_x86'
}
'rv64', 'riscv64' {
return '__V_rv64'
}
'rv32', 'riscv32' {
return '__V_rv32'
}
's390x' {
return '__V_s390x'
}
'ppc64le' {
return '__V_ppc64le'
}
'loongarch64' {
return '__V_loongarch64'
}
// bitness:
'x64' {
return 'TARGET_IS_64BIT'
}
'x32' {
return 'TARGET_IS_32BIT'
}
// endianness:
'little_endian' {
return 'TARGET_ORDER_IS_LITTLE'
}
'big_endian' {
return 'TARGET_ORDER_IS_BIG'
}
'fast_math' {
if c.pref.ccompiler_type == .msvc {
// turned on by: `-cflags /fp:fast`
return '_M_FP_FAST'
}
// turned on by: `-cflags -ffast-math`
return '__FAST_MATH__'
}
else {}
}
return error('bad os ifdef name "${name}"')
}
fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type {
match cond { match cond {
ast.Ident { ast.Ident {
@ -854,7 +1045,7 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type {
// comptime_if_cond evaluate the `cond` and return (`is_true`, `keep_stmts`) // comptime_if_cond evaluate the `cond` and return (`is_true`, `keep_stmts`)
// `is_true` is the evaluate result of `cond`; // `is_true` is the evaluate result of `cond`;
// `keep_stmts` meaning the branch is a `multi pass branch`, we should keep the branch stmts even `is_true` is false, such as `$if T is int {` // `keep_stmts` meaning the branch is a `multi pass branch`, we should keep the branch stmts even `is_true` is false, such as `$if T is int {`
fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) (bool, bool) {
mut should_record_ident := false mut should_record_ident := false
mut is_user_ident := false mut is_user_ident := false
mut ident_name := '' mut ident_name := ''
@ -873,17 +1064,25 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
match mut cond { match mut cond {
ast.BoolLiteral { ast.BoolLiteral {
c.expr(mut cond) c.expr(mut cond)
return cond.val, false is_true = cond.val
sb.write_string('${is_true}')
return is_true, false
} }
ast.ParExpr { ast.ParExpr {
return c.comptime_if_cond(mut cond.expr) sb.write_string('(')
is_true_result, multi_pass_stmts := c.comptime_if_cond(mut cond.expr, mut
sb)
sb.write_string(')')
return is_true_result, multi_pass_stmts
} }
ast.PrefixExpr { ast.PrefixExpr {
if cond.op != .not { if cond.op != .not {
c.error('invalid \$if prefix operator, only allow `!`.', cond.pos) c.error('invalid \$if prefix operator, only allow `!`.', cond.pos)
return false, false return false, false
} }
is_true_result, multi_pass_stmts := c.comptime_if_cond(mut cond.right) sb.write_string(cond.op.str())
is_true_result, multi_pass_stmts := c.comptime_if_cond(mut cond.right, mut
sb)
return !is_true_result, multi_pass_stmts return !is_true_result, multi_pass_stmts
} }
ast.PostfixExpr { ast.PostfixExpr {
@ -900,16 +1099,20 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
should_record_ident = true should_record_ident = true
is_user_ident = true is_user_ident = true
ident_name = cname ident_name = cname
if cname in c.pref.compile_defines { // ifdef := c.comptime_if_to_ifdef(cname, true) or {
return true, false // c.error(err.msg(), cond.pos)
} // return false, false
return false, false //}
sb.write_string('defined(CUSTOM_DEFINE_${cname})')
is_true = cname in c.pref.compile_defines
return is_true, false
} }
ast.InfixExpr { ast.InfixExpr {
match cond.op { match cond.op {
.and, .logical_or { .and, .logical_or {
l, d1 := c.comptime_if_cond(mut cond.left) l, d1 := c.comptime_if_cond(mut cond.left, mut sb)
r, d2 := c.comptime_if_cond(mut cond.right) sb.write_string(' ${cond.op} ')
r, d2 := c.comptime_if_cond(mut cond.right, mut sb)
// if at least one of the cond has `keep_stmts`, we should keep stmts // if at least one of the cond has `keep_stmts`, we should keep stmts
return if cond.op == .and { l && r } else { l || r }, d1 || d2 return if cond.op == .and { l && r } else { l || r }, d1 || d2
} }
@ -979,11 +1182,9 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
} }
} }
} }
return if cond.op in [.key_in, .key_is] { is_true = if cond.op in [.key_in, .key_is] { is_true } else { !is_true }
is_true, true sb.write_string('${is_true}')
} else { return is_true, true
!is_true, true
}
} }
if cond.left !in [ast.TypeNode, ast.Ident, ast.SelectorExpr] { if cond.left !in [ast.TypeNode, ast.Ident, ast.SelectorExpr] {
@ -1078,6 +1279,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
sb.write_string('${is_true}')
return is_true, false return is_true, false
} }
ast.SelectorExpr { ast.SelectorExpr {
@ -1106,9 +1308,11 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
} }
match cond.op { match cond.op {
.eq { .eq {
sb.write_string('${is_true}')
return is_true, true return is_true, true
} }
.ne { .ne {
sb.write_string('${!is_true}')
return !is_true, true return !is_true, true
} }
else { else {
@ -1153,6 +1357,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
sb.write_string('${is_true}')
return is_true, true return is_true, true
} else if cond.left.field_name == 'return_type' { } else if cond.left.field_name == 'return_type' {
// method.return_type // method.return_type
@ -1182,6 +1387,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
sb.write_string('${is_true}')
return is_true, false return is_true, false
} else { } else {
c.error('only support .return_type compare for \$for method', c.error('only support .return_type compare for \$for method',
@ -1196,8 +1402,10 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
} }
ast.BoolLiteral { ast.BoolLiteral {
// field.is_pub == true // field.is_pub == true
l, _ := c.comptime_if_cond(mut cond.left) l, _ := c.comptime_if_cond(mut cond.left, mut sb)
sb.write_string(' ${cond.op} ')
r := (cond.right as ast.BoolLiteral).val r := (cond.right as ast.BoolLiteral).val
sb.write_string('${r}')
is_true = if cond.op == .eq { l == r } else { l != r } is_true = if cond.op == .eq { l == r } else { l != r }
return is_true, true return is_true, true
} }
@ -1239,6 +1447,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
sb.write_string('${is_true}')
return is_true, true return is_true, true
} }
else { else {
@ -1269,17 +1478,13 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
is_user_ident = false is_user_ident = false
ident_name = cname ident_name = cname
if cname in ast.valid_comptime_if_os { if cname in ast.valid_comptime_if_os {
if !c.pref.output_cross_c {
if cname_enum_val := pref.os_from_string(cname) { if cname_enum_val := pref.os_from_string(cname) {
if cname_enum_val == c.pref.os { if cname_enum_val == c.pref.os {
is_true = true is_true = true
} }
} }
}
return is_true, false
} else if cname in ast.valid_comptime_if_compilers { } else if cname in ast.valid_comptime_if_compilers {
is_true = pref.cc_from_string(cname) == c.pref.ccompiler_type is_true = pref.cc_from_string(cname) == c.pref.ccompiler_type
return is_true, false
} else if cname in ast.valid_comptime_if_platforms { } else if cname in ast.valid_comptime_if_platforms {
if cname == 'aarch64' { if cname == 'aarch64' {
c.note('use `arm64` instead of `aarch64`', cond.pos) c.note('use `arm64` instead of `aarch64`', cond.pos)
@ -1321,7 +1526,6 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
return is_true, false
} else if cname in ast.valid_comptime_if_cpu_features { } else if cname in ast.valid_comptime_if_cpu_features {
match cname { match cname {
'x64' { 'x64' {
@ -1342,7 +1546,6 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
return is_true, false
} else if cname in ast.valid_comptime_if_other { } else if cname in ast.valid_comptime_if_other {
match cname { match cname {
'apk' { 'apk' {
@ -1414,7 +1617,6 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
} }
return is_true, false
} else if cname !in c.pref.compile_defines_all { } else if cname !in c.pref.compile_defines_all {
if cname == 'linux_or_macos' { if cname == 'linux_or_macos' {
c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead', c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead',
@ -1441,21 +1643,34 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
return false, false return false, false
} }
is_true = (expr as ast.BoolLiteral).val is_true = (expr as ast.BoolLiteral).val
return is_true, false
} else if cname in c.pref.compile_defines { } else if cname in c.pref.compile_defines {
return true, false is_true = true
} } else {
c.error('invalid \$if condition: unknown indent `${cname}`', cond.pos) c.error('invalid \$if condition: unknown indent `${cname}`', cond.pos)
return false, false return false, false
} }
if ifdef := c.comptime_if_to_ifdef(cname) {
sb.write_string('defined(${ifdef})')
} else {
sb.write_string('${is_true}')
}
return is_true, false
}
ast.ComptimeCall { ast.ComptimeCall {
if cond.kind == .pkgconfig { if cond.kind == .pkgconfig {
mut m := pkgconfig.main([cond.args_var]) or { if mut m := pkgconfig.main([cond.args_var]) {
c.error(err.msg(), cond.pos) if _ := m.run() {
return false, true is_true = true
} else {
// pkgconfig not found, do not issue error, just set false
is_true = false
} }
m.run() or { return false, true } } else {
return true, true c.error(err.msg(), cond.pos)
is_true = false
}
sb.write_string('${is_true}')
return is_true, true
} }
if cond.kind == .d { if cond.kind == .d {
t := c.expr(mut cond) t := c.expr(mut cond)
@ -1464,7 +1679,9 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
cond.pos) cond.pos)
return false, false return false, false
} }
return cond.compile_value.bool(), false is_true = cond.compile_value.bool()
sb.write_string('${is_true}')
return is_true, false
} }
c.error('invalid \$if condition: unknown ComptimeCall', cond.pos) c.error('invalid \$if condition: unknown ComptimeCall', cond.pos)
return false, false return false, false
@ -1473,6 +1690,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
if c.comptime.comptime_for_field_var != '' && cond.expr is ast.Ident { if c.comptime.comptime_for_field_var != '' && cond.expr is ast.Ident {
if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] { if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] {
is_true = c.type_resolver.get_comptime_selector_bool_field(cond.field_name) is_true = c.type_resolver.get_comptime_selector_bool_field(cond.field_name)
sb.write_string('${is_true}')
return is_true, true return is_true, true
} }
c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_field_var}', c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_field_var}',
@ -1481,6 +1699,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
if c.comptime.comptime_for_attr_var != '' && cond.expr is ast.Ident { if c.comptime.comptime_for_attr_var != '' && cond.expr is ast.Ident {
if (cond.expr as ast.Ident).name == c.comptime.comptime_for_attr_var && cond.field_name == 'has_arg' { if (cond.expr as ast.Ident).name == c.comptime.comptime_for_attr_var && cond.field_name == 'has_arg' {
is_true = c.comptime.comptime_for_attr_value.has_arg is_true = c.comptime.comptime_for_attr_value.has_arg
sb.write_string('${is_true}')
return is_true, true return is_true, true
} }
c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_attr_var}', c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_attr_var}',
@ -1510,6 +1729,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) {
'is_expand_simple_interpolation' { method.is_expand_simple_interpolation } 'is_expand_simple_interpolation' { method.is_expand_simple_interpolation }
else { false } else { false }
} }
sb.write_string('${is_true}')
return is_true, true return is_true, true
} }
c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_method_var}', c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_method_var}',

View file

@ -4,6 +4,61 @@ module checker
import v.ast import v.ast
import v.token import v.token
import v.util
import strings
// gen_branch_context_string generate current branches context string.
// context include generic types, `$for`.
fn (mut c Checker) gen_branch_context_string() string {
mut arr := []string{}
// gen `T=int,X=string`
if c.table.cur_fn.generic_names.len > 0
&& c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len {
for i in 0 .. c.table.cur_fn.generic_names.len {
arr << c.table.cur_fn.generic_names[i] + '=' +
util.strip_main_name(c.table.type_to_str(c.table.cur_concrete_types[i]))
}
}
// gen comptime `$for`
if c.comptime.inside_comptime_for {
// variants
if c.comptime.comptime_for_variant_var.len > 0 {
variant := c.table.type_to_str(c.type_resolver.get_ct_type_or_default('${c.comptime.comptime_for_variant_var}.typ',
ast.no_type))
arr << c.comptime.comptime_for_variant_var + '.typ=' + variant
}
// fields
if c.comptime.comptime_for_field_var.len > 0 {
arr << c.comptime.comptime_for_field_var + '.name=' +
c.comptime.comptime_for_field_value.name
}
// values
if c.comptime.comptime_for_enum_var.len > 0 {
enum_var := c.table.type_to_str(c.type_resolver.get_ct_type_or_default('${c.comptime.comptime_for_enum_var}.typ',
ast.void_type))
arr << c.comptime.comptime_for_enum_var + '.typ=' + enum_var
}
// attributes
if c.comptime.comptime_for_attr_var.len > 0 {
arr << c.comptime.comptime_for_attr_var + '.name=' +
c.comptime.comptime_for_attr_value.name
}
// methods
if c.comptime.comptime_for_method_var.len > 0 {
arr << c.comptime.comptime_for_method_var + '.name=' +
c.comptime.comptime_for_method.name
}
// args
if c.comptime.comptime_for_method_param_var.len > 0 {
arg_var := c.table.type_to_str(c.type_resolver.get_ct_type_or_default('${c.comptime.comptime_for_method_param_var}.typ',
ast.void_type))
arr << c.comptime.comptime_for_method_param_var + '.typ=' + arg_var
}
}
return arr.join(',')
}
fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
if_kind := if node.is_comptime { '\$if' } else { 'if' } if_kind := if node.is_comptime { '\$if' } else { 'if' }
@ -40,8 +95,10 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
c.comptime.inside_comptime_if = last_in_comptime_if c.comptime.inside_comptime_if = last_in_comptime_if
} }
for i in 0 .. node.branches.len { comptime_branch_context_str := if node.is_comptime { c.gen_branch_context_string() } else { '' }
mut branch := node.branches[i]
for i, mut branch in node.branches {
mut comptime_remove_curr_branch_stmts := false
if branch.cond is ast.ParExpr && !c.pref.translated && !c.file.is_translated { if branch.cond is ast.ParExpr && !c.pref.translated && !c.file.is_translated {
c.warn('unnecessary `()` in `${if_kind}` condition, use `${if_kind} expr {` instead of `${if_kind} (expr) {`.', c.warn('unnecessary `()` in `${if_kind}` condition, use `${if_kind} expr {` instead of `${if_kind} (expr) {`.',
branch.pos) branch.pos)
@ -49,17 +106,42 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
if !node.has_else || i < node.branches.len - 1 { if !node.has_else || i < node.branches.len - 1 {
// if branch // if branch
if node.is_comptime { if node.is_comptime {
// `idx_str` is composed of two parts:
// The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json`
// The second part indicates the branch's location in the source file.
// This format must match what is in `cgen`.
idx_str := comptime_branch_context_str + '|${c.file.path}|${branch.cond.pos()}|'
c.comptime.inside_comptime_if = true c.comptime.inside_comptime_if = true
comptime_if_result, comptime_if_multi_pass_branch = c.comptime_if_cond(mut branch.cond) mut sb := strings.new_builder(256)
node.branches[i].pkg_exist = comptime_if_result comptime_if_result, comptime_if_multi_pass_branch = c.comptime_if_cond(mut branch.cond, mut
sb)
if comptime_if_multi_pass_branch { if comptime_if_multi_pass_branch {
comptime_if_has_multi_pass_branch = true comptime_if_has_multi_pass_branch = true
} }
if !comptime_if_has_multi_pass_branch && comptime_if_found_branch {
// when all prev branchs are single pass branchs, and already has a true branch: if comptime_if_found_branch {
// remove following branchs' stmts by overwrite `comptime_if_result`
comptime_if_result = false comptime_if_result = false
} }
if !comptime_if_has_multi_pass_branch
&& (comptime_if_found_branch || !comptime_if_result) {
// when all prev branchs are single pass branchs,
// 1. already has a true branch or
// 2. `comptime_if_result is` false
// remove current branchs' stmts
comptime_remove_curr_branch_stmts = true
}
if old_val := c.table.comptime_is_true[idx_str] {
if old_val.val != comptime_if_result {
c.error('checker erro1r : branch eval wrong', branch.cond.pos())
}
}
// set `comptime_is_true` which can be used by `cgen`
c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{
val: comptime_if_result
c_str: sb.str()
}
} else { } else {
// check condition type is boolean // check condition type is boolean
c.expected_type = ast.bool_type c.expected_type = ast.bool_type
@ -78,6 +160,17 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
comptime_if_result = !comptime_if_found_branch comptime_if_result = !comptime_if_found_branch
// if all other branchs has at least one multi pass branch, we should keep this else branch // if all other branchs has at least one multi pass branch, we should keep this else branch
comptime_if_multi_pass_branch = comptime_if_has_multi_pass_branch comptime_if_multi_pass_branch = comptime_if_has_multi_pass_branch
if !comptime_if_has_multi_pass_branch && comptime_if_found_branch {
// when all prev branchs are single pass branchs, already has a true branch
// remove current branchs' stmts
comptime_remove_curr_branch_stmts = true
}
// hack: as a `else` has no `cond`, so we use `branch.pos` here
idx_str := comptime_branch_context_str + '|${c.file.path}|${branch.pos}|'
c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{
val: comptime_if_result
c_str: ''
}
} }
} }
if mut branch.cond is ast.IfGuardExpr { if mut branch.cond is ast.IfGuardExpr {
@ -119,10 +212,18 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
if c.fn_level == 0 && c.pref.output_cross_c { if c.fn_level == 0 && c.pref.output_cross_c {
// do not skip any of the branches for top level `$if OS {` // do not skip any of the branches for top level `$if OS {`
// statements, in `-cross` mode // statements, in `-cross` mode
comptime_if_multi_pass_branch = true comptime_remove_curr_branch_stmts = false
c.skip_flags = false c.skip_flags = false
// hack: because `else` branch has no `cond`, so create an Ident, set the `pos`, for `hash_stmt()` work.
if branch.cond is ast.NodeError {
c.ct_cond_stack << ast.Ident{
name: '__else_branch__'
pos: branch.pos
}
} else {
c.ct_cond_stack << branch.cond c.ct_cond_stack << branch.cond
} }
}
if !c.skip_flags { if !c.skip_flags {
if node_is_expr { if node_is_expr {
c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type) c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type)
@ -147,9 +248,6 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
c.stmts(mut branch.stmts) c.stmts(mut branch.stmts)
c.check_non_expr_branch_last_stmt(branch.stmts) c.check_non_expr_branch_last_stmt(branch.stmts)
} }
} else if !comptime_if_multi_pass_branch && !comptime_if_result {
// this branch is not a multi pass branch, and current cond result is false, remove branch stmts
node.branches[i].stmts = []
} }
c.skip_flags = cur_skip_flags c.skip_flags = cur_skip_flags
if c.fn_level == 0 && c.pref.output_cross_c && c.ct_cond_stack.len > 0 { if c.fn_level == 0 && c.pref.output_cross_c && c.ct_cond_stack.len > 0 {
@ -322,6 +420,11 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
nbranches_without_return++ nbranches_without_return++
} }
} }
if comptime_remove_curr_branch_stmts {
// remove the branch statements since they may contain OS-specific code.
branch.stmts = []
}
} }
if nbranches_with_return > 0 { if nbranches_with_return > 0 {
if nbranches_with_return == node.branches.len { if nbranches_with_return == node.branches.len {

View file

@ -1030,16 +1030,16 @@ pub fn (mut g Gen) init() {
if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
g.comptime_definitions.writeln('#define _VGCBOEHM (1)') g.comptime_definitions.writeln('#define _VGCBOEHM (1)')
} }
if g.pref.is_debug || 'debug' in g.pref.compile_defines { if g.pref.is_debug {
g.comptime_definitions.writeln('#define _VDEBUG (1)') g.comptime_definitions.writeln('#define _VDEBUG (1)')
} }
if g.pref.is_prod || 'prod' in g.pref.compile_defines { if g.pref.is_prod {
g.comptime_definitions.writeln('#define _VPROD (1)') g.comptime_definitions.writeln('#define _VPROD (1)')
} }
if g.pref.is_test || 'test' in g.pref.compile_defines { if g.pref.is_test {
g.comptime_definitions.writeln('#define _VTEST (1)') g.comptime_definitions.writeln('#define _VTEST (1)')
} }
if g.pref.is_prof || 'profile' in g.pref.compile_defines { if g.pref.is_prof {
g.comptime_definitions.writeln('#define _VPROFILE (1)') g.comptime_definitions.writeln('#define _VPROFILE (1)')
} }
if g.pref.autofree { if g.pref.autofree {
@ -5728,15 +5728,27 @@ fn (mut g Gen) hash_stmt_guarded_include(node ast.HashStmt) string {
fn (mut g Gen) hash_stmt(node ast.HashStmt) { fn (mut g Gen) hash_stmt(node ast.HashStmt) {
line_nr := node.pos.line_nr + 1 line_nr := node.pos.line_nr + 1
mut ct_condition := '' mut ct_condition := ''
if node.ct_conds.len > 0 { if node.ct_conds.len > 0 {
ct_condition_start := g.out.len mut comptime_branch_context_str := g.gen_branch_context_string()
mut is_true := ast.ComptTimeCondResult{}
mut sb := strings.new_builder(256)
for idx, ct_expr in node.ct_conds { for idx, ct_expr in node.ct_conds {
g.comptime_if_cond(ct_expr, false) idx_str := comptime_branch_context_str + '|${g.file.path}|${ct_expr.pos()}|'
if comptime_is_true := g.table.comptime_is_true[idx_str] {
// `g.table.comptime_is_true` are the branch condition results set by `checker`
is_true = comptime_is_true
} else {
g.error('checker error: condition result idx string not found => [${idx_str}]',
ct_expr.pos())
return
}
sb.write_string(is_true.c_str)
if idx < node.ct_conds.len - 1 { if idx < node.ct_conds.len - 1 {
g.write(' && ') sb.write_string(' && ')
} }
} }
ct_condition = g.out.cut_to(ct_condition_start).trim_space() ct_condition = sb.str()
} }
// #include etc // #include etc
if node.kind == 'include' { if node.kind == 'include' {

View file

@ -5,6 +5,7 @@ module c
import os import os
import v.ast import v.ast
import v.token
import v.util import v.util
import v.pref import v.pref
import v.type_resolver import v.type_resolver
@ -313,6 +314,59 @@ fn (mut g Gen) comptime_at(node ast.AtExpr) {
} }
} }
// gen_branch_context_string generate current branches context string.
// context include generic types, `$for`.
fn (mut g Gen) gen_branch_context_string() string {
mut arr := []string{}
// gen `T=int,X=string`
if g.cur_fn != unsafe { nil } && g.cur_fn.generic_names.len > 0
&& g.cur_fn.generic_names.len == g.cur_concrete_types.len {
for i in 0 .. g.cur_fn.generic_names.len {
arr << g.cur_fn.generic_names[i] + '=' +
util.strip_main_name(g.table.type_to_str(g.cur_concrete_types[i]))
}
}
// gen comptime `$for`
if g.comptime.inside_comptime_for {
// variants
if g.comptime.comptime_for_variant_var.len > 0 {
variant := g.table.type_to_str(g.type_resolver.get_ct_type_or_default('${g.comptime.comptime_for_variant_var}.typ',
ast.no_type))
arr << g.comptime.comptime_for_variant_var + '.typ=' + variant
}
// fields
if g.comptime.comptime_for_field_var.len > 0 {
arr << g.comptime.comptime_for_field_var + '.name=' +
g.comptime.comptime_for_field_value.name
}
// values
if g.comptime.comptime_for_enum_var.len > 0 {
enum_var := g.table.type_to_str(g.type_resolver.get_ct_type_or_default('${g.comptime.comptime_for_enum_var}.typ',
ast.void_type))
arr << g.comptime.comptime_for_enum_var + '.typ=' + enum_var
}
// attributes
if g.comptime.comptime_for_attr_var.len > 0 {
arr << g.comptime.comptime_for_attr_var + '.name=' +
g.comptime.comptime_for_attr_value.name
}
// methods
if g.comptime.comptime_for_method_var.len > 0 {
arr << g.comptime.comptime_for_method_var + '.name=' +
g.comptime.comptime_for_method.name
}
// args
if g.comptime.comptime_for_method_param_var.len > 0 {
arg_var := g.table.type_to_str(g.type_resolver.get_ct_type_or_default('${g.comptime.comptime_for_method_param_var}.typ',
ast.void_type))
arr << g.comptime.comptime_for_method_param_var + '.typ=' + arg_var
}
}
return arr.join(',')
}
fn (mut g Gen) comptime_if(node ast.IfExpr) { fn (mut g Gen) comptime_if(node ast.IfExpr) {
if !node.is_expr && !node.has_else && node.branches.len == 1 { if !node.is_expr && !node.has_else && node.branches.len == 1 {
if node.branches[0].stmts.len == 0 { if node.branches[0].stmts.len == 0 {
@ -346,32 +400,42 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
} else { } else {
'' ''
} }
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
// will lead to compilation errors)
mut comptime_may_skip_else := false
mut comptime_branch_context_str := g.gen_branch_context_string()
mut is_true := ast.ComptTimeCondResult{}
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 { // `idx_str` is composed of two parts:
continue // if we already have a known true, ignore other branches // The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json`
} // The second part indicates the branch's location in the source file.
if i == node.branches.len - 1 && node.has_else { // This format must match what is in `checker`.
g.writeln('#else') idx_str := if branch.cond.pos() == token.Pos{} {
comptime_if_stmts_skip = comptime_may_skip_else comptime_branch_context_str + '|${g.file.path}|${branch.pos}|'
} else { } else {
comptime_branch_context_str + '|${g.file.path}|${branch.cond.pos()}|'
}
if comptime_is_true := g.table.comptime_is_true[idx_str] {
// `g.table.comptime_is_true` are the branch condition results set by `checker`
is_true = comptime_is_true
} else {
g.error('checker error: condition result idx string not found => [${idx_str}]',
node.branches[i].cond.pos())
return
}
if !node.has_else || i < node.branches.len - 1 {
if i == 0 { if i == 0 {
g.write('#if ') g.write('#if ')
} else { } else {
g.write('#elif ') g.write('#elif ')
} }
comptime_if_stmts_skip, comptime_may_skip_else = g.comptime_if_cond(branch.cond, // directly use `checker` evaluate results
branch.pkg_exist) // for `cgen`, we can use `is_true.c_str` or `is_true.value` here
if !comptime_if_stmts_skip && comptime_may_skip_else { g.writeln('${is_true.c_str}')
comptime_may_skip_else = false // if the cond is false, not skip else branch $if debug_comptime_branch_context ? {
g.writeln('/* ${node.branches[i].cond} | generic=[${comptime_branch_context_str}] */')
} }
comptime_if_stmts_skip = !comptime_if_stmts_skip } else {
g.writeln('') g.writeln('#else')
} }
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()
if expr_str != '' { if expr_str != '' {
@ -430,7 +494,7 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
if should_create_scope { if should_create_scope {
g.writeln('{') g.writeln('{')
} }
if !comptime_if_stmts_skip { if is_true.val {
g.stmts(branch.stmts) g.stmts(branch.stmts)
} }
if should_create_scope { if should_create_scope {
@ -494,349 +558,6 @@ fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type {
} }
} }
// 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
fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) {
match cond {
ast.BoolLiteral {
g.expr(cond)
return cond.val, true
}
ast.ParExpr {
g.write('(')
is_cond_true, may_discard := g.comptime_if_cond(cond.expr, pkg_exist)
g.write(')')
return is_cond_true, may_discard
}
ast.PrefixExpr {
g.write(cond.op.str())
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 {
dname := (cond.expr as ast.Ident).name
ifdef := g.comptime_if_to_ifdef(dname, true) or {
verror(err.str())
return false, true
}
g.write('defined(${ifdef})')
if dname in g.pref.compile_defines_all && dname !in g.pref.compile_defines {
return false, true
} else {
return true, false
}
}
ast.InfixExpr {
match cond.op {
.and, .logical_or {
l, d1 := g.comptime_if_cond(cond.left, pkg_exist)
g.write(' ${cond.op} ')
r, d2 := g.comptime_if_cond(cond.right, pkg_exist)
return if cond.op == .and { l && r } else { l || r }, d1 && d1 == d2
}
.key_is, .not_is {
if cond.left in [ast.TypeNode, ast.Ident, ast.SelectorExpr]
&& cond.right in [ast.ComptimeType, ast.TypeNode] {
exp_type := g.get_expr_type(cond.left)
if cond.right is ast.ComptimeType {
is_true := g.type_resolver.is_comptime_type(exp_type, cond.right)
if cond.op == .key_is {
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
} else {
if is_true {
g.write('0')
} else {
g.write('1')
}
return !is_true, true
}
} else {
got_type := g.unwrap_generic((cond.right as ast.TypeNode).typ)
got_sym := g.table.sym(got_type)
if got_sym.kind == .interface && got_sym.info is ast.Interface {
is_true := exp_type.has_flag(.option) == got_type.has_flag(.option)
&& g.table.does_type_implement_interface(exp_type, got_type)
if cond.op == .key_is {
if is_true {
g.write('1 && ${exp_type.has_flag(.option)} == ${got_type.has_flag(.option)}')
} else {
g.write('0')
}
return is_true, true
} else if cond.op == .not_is {
if is_true {
g.write('0')
} else {
g.write('1')
}
return !is_true, true
}
}
if got_sym.info is ast.FnType && cond.left is ast.Ident
&& g.comptime.comptime_for_method_var == cond.left.name {
is_compatible := g.table.fn_signature(got_sym.info.func,
skip_receiver: true
type_only: true
) == g.table.fn_signature(g.comptime.comptime_for_method,
skip_receiver: true
type_only: true
)
if cond.op == .key_is {
g.write(int(is_compatible).str())
return is_compatible, true
} else {
g.write(int(!is_compatible).str())
return !is_compatible, true
}
} else if cond.op == .key_is {
g.write('${int(exp_type.idx())} == ${int(got_type.idx())} && ${exp_type.has_flag(.option)} == ${got_type.has_flag(.option)}')
return exp_type == got_type, true
} else {
g.write('${int(exp_type.idx())} != ${int(got_type.idx())}')
return exp_type != got_type, true
}
}
}
}
.eq, .ne {
// TODO: Implement `$if method.args.len == 1`
if cond.left is ast.SelectorExpr && (g.comptime.comptime_for_field_var.len > 0
|| g.comptime.comptime_for_method != unsafe { nil }
|| cond.left.name_type != 0) {
if cond.right is ast.StringLiteral {
if cond.left.expr is ast.Ident && cond.left.field_name == 'name' {
if g.comptime.comptime_for_method_var.len > 0
&& cond.left.expr.name == g.comptime.comptime_for_method_var {
is_true := if cond.op == .eq {
g.comptime.comptime_for_method.name == cond.right.val
} else {
g.comptime.comptime_for_method.name != cond.right.val
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
} else if g.comptime.comptime_for_field_var.len > 0
&& cond.left.expr.name == g.comptime.comptime_for_field_var {
is_true := if cond.op == .eq {
g.comptime.comptime_for_field_value.name == cond.right.val
} else {
g.comptime.comptime_for_field_value.name != cond.right.val
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
}
}
} else if cond.right is ast.IntegerLiteral {
if g.comptime.is_comptime_selector_field_name(cond.left, 'indirections') {
left_muls := if cond.left.name_type != 0 {
g.unwrap_generic(cond.left.name_type).nr_muls()
} else {
g.comptime.comptime_for_field_type.nr_muls()
}
is_true := match cond.op {
.eq { left_muls == cond.right.val.i64() }
.ne { left_muls != cond.right.val.i64() }
else { false }
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
} else if g.comptime.comptime_for_method_var != ''
&& cond.left.expr is ast.Ident
&& cond.left.expr.name == g.comptime.comptime_for_method_var
&& cond.left.field_name == 'return_type' {
is_true := match cond.op {
.eq { g.comptime.comptime_for_method_ret_type.idx() == cond.right.val.i64() }
.ne { g.comptime.comptime_for_method_ret_type.idx() != cond.right.val.i64() }
else { false }
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
}
}
}
if cond.left is ast.SelectorExpr || cond.right is ast.SelectorExpr {
l, d1 := g.comptime_if_cond(cond.left, pkg_exist)
g.write(' ${cond.op} ')
r, d2 := g.comptime_if_cond(cond.right, pkg_exist)
return if cond.op == .eq { l == r } else { l != r }, d1 && d1 == d2
}
if cond.left is ast.SizeOf && cond.left.typ != 0
&& cond.right is ast.IntegerLiteral {
// TODO: support struct.fieldname
s, _ := g.table.type_size(g.unwrap_generic(cond.left.typ))
right := cond.right as ast.IntegerLiteral
is_true := match cond.op {
.eq { s == right.val.i64() }
.ne { s != right.val.i64() }
else { false }
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
} else {
g.write('1')
return true, true
}
}
.key_in, .not_in {
if cond.left in [ast.TypeNode, ast.SelectorExpr, ast.Ident]
&& cond.right is ast.ArrayInit {
checked_type := g.get_expr_type(cond.left)
for expr in cond.right.exprs {
if expr is ast.ComptimeType {
if g.type_resolver.is_comptime_type(checked_type, expr as ast.ComptimeType) {
if cond.op == .key_in {
g.write('1')
} else {
g.write('0')
}
return cond.op == .key_in, true
}
} else if expr is ast.TypeNode {
got_type := g.unwrap_generic(expr.typ)
if checked_type.idx() == got_type.idx()
&& checked_type.has_flag(.option) == got_type.has_flag(.option) {
if cond.op == .key_in {
g.write('1')
} else {
g.write('0')
}
return cond.op == .key_in, true
}
}
}
if cond.op == .not_in {
g.write('1')
} else {
g.write('0')
}
return cond.op == .not_in, true
}
}
.gt, .lt, .ge, .le {
if cond.left is ast.SelectorExpr && cond.right is ast.IntegerLiteral
&& g.comptime.is_comptime_selector_field_name(cond.left, 'indirections') {
left := cond.left as ast.SelectorExpr
left_muls := if left.name_type != 0 {
g.unwrap_generic(left.name_type).nr_muls()
} else {
g.comptime.comptime_for_field_type.nr_muls()
}
is_true := match cond.op {
.gt { left_muls > cond.right.val.i64() }
.lt { left_muls < cond.right.val.i64() }
.ge { left_muls >= cond.right.val.i64() }
.le { left_muls <= cond.right.val.i64() }
else { false }
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
}
if cond.left is ast.SizeOf && cond.left.typ != 0
&& cond.right is ast.IntegerLiteral {
// TODO: support struct.fieldname
s, _ := g.table.type_size(g.unwrap_generic(cond.left.typ))
right := cond.right as ast.IntegerLiteral
is_true := match cond.op {
.gt { s > right.val.i64() }
.lt { s < right.val.i64() }
.ge { s >= right.val.i64() }
.le { s <= right.val.i64() }
else { false }
}
if is_true {
g.write('1')
} else {
g.write('0')
}
return is_true, true
} else {
return true, false
}
}
else {
return true, false
}
}
}
ast.Ident {
ifdef := g.comptime_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker
g.write('defined(${ifdef})')
return true, false
}
ast.ComptimeCall {
if cond.kind == .pkgconfig {
g.write('${pkg_exist}')
return true, false
}
if cond.kind == .d {
if cond.result_type == ast.bool_type {
if cond.compile_value == 'true' {
g.write('1')
} else {
g.write('0')
}
} else {
g.write('defined(CUSTOM_DEFINE_${cond.args_var})')
}
return true, false
}
return true, false
}
ast.SelectorExpr {
if g.comptime.comptime_for_field_var != '' && cond.expr is ast.Ident
&& cond.expr.name == g.comptime.comptime_for_field_var
&& cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] {
ret_bool := g.type_resolver.get_comptime_selector_bool_field(cond.field_name)
g.write(ret_bool.str())
return ret_bool, true
} else {
g.write('1')
return true, true
}
}
else {
// should be unreachable, but just in case
g.write('1')
return true, true
}
}
}
// push_new_comptime_info saves the current comptime information // push_new_comptime_info saves the current comptime information
fn (mut g Gen) push_new_comptime_info() { fn (mut g Gen) push_new_comptime_info() {
g.type_resolver.info_stack << type_resolver.ResolverInfo{ g.type_resolver.info_stack << type_resolver.ResolverInfo{
@ -847,6 +568,8 @@ fn (mut g Gen) push_new_comptime_info() {
comptime_for_field_type: g.comptime.comptime_for_field_type comptime_for_field_type: g.comptime.comptime_for_field_type
comptime_for_field_value: g.comptime.comptime_for_field_value comptime_for_field_value: g.comptime.comptime_for_field_value
comptime_for_enum_var: g.comptime.comptime_for_enum_var comptime_for_enum_var: g.comptime.comptime_for_enum_var
comptime_for_attr_var: g.comptime.comptime_for_attr_var
comptime_for_attr_value: g.comptime.comptime_for_attr_value
comptime_for_method_var: g.comptime.comptime_for_method_var comptime_for_method_var: g.comptime.comptime_for_method_var
comptime_for_method: g.comptime.comptime_for_method comptime_for_method: g.comptime.comptime_for_method
comptime_for_method_ret_type: g.comptime.comptime_for_method_ret_type comptime_for_method_ret_type: g.comptime.comptime_for_method_ret_type
@ -864,6 +587,8 @@ fn (mut g Gen) pop_comptime_info() {
g.comptime.comptime_for_field_type = old.comptime_for_field_type g.comptime.comptime_for_field_type = old.comptime_for_field_type
g.comptime.comptime_for_field_value = old.comptime_for_field_value g.comptime.comptime_for_field_value = old.comptime_for_field_value
g.comptime.comptime_for_enum_var = old.comptime_for_enum_var g.comptime.comptime_for_enum_var = old.comptime_for_enum_var
g.comptime.comptime_for_attr_var = old.comptime_for_attr_var
g.comptime.comptime_for_attr_value = old.comptime_for_attr_value
g.comptime.comptime_for_method_var = old.comptime_for_method_var g.comptime.comptime_for_method_var = old.comptime_for_method_var
g.comptime.comptime_for_method = old.comptime_for_method g.comptime.comptime_for_method = old.comptime_for_method
g.comptime.comptime_for_method_ret_type = old.comptime_for_method_ret_type g.comptime.comptime_for_method_ret_type = old.comptime_for_method_ret_type
@ -887,6 +612,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
typ_vweb_result := g.table.find_type('vweb.Result') typ_vweb_result := g.table.find_type('vweb.Result')
for method in methods { for method in methods {
g.push_new_comptime_info() g.push_new_comptime_info()
g.comptime.inside_comptime_for = true
// filter vweb route methods (non-generic method) // filter vweb route methods (non-generic method)
if method.receiver_type != 0 && method.return_type == typ_vweb_result { if method.receiver_type != 0 && method.return_type == typ_vweb_result {
rec_sym := g.table.sym(method.receiver_type) rec_sym := g.table.sym(method.receiver_type)
@ -902,7 +628,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
} }
g.comptime.comptime_for_method = unsafe { &method } g.comptime.comptime_for_method = unsafe { &method }
g.comptime.comptime_for_method_var = node.val_var g.comptime.comptime_for_method_var = node.val_var
g.writeln('/* method ${i} */ {') g.writeln('/* method ${i} : ${method.name} */ {')
g.writeln('\t${node.val_var}.name = _S("${method.name}");') g.writeln('\t${node.val_var}.name = _S("${method.name}");')
if method.attrs.len == 0 { if method.attrs.len == 0 {
g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);')
@ -976,13 +702,13 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
if fields.len > 0 { if fields.len > 0 {
g.writeln('\tFieldData ${node.val_var} = {0};') g.writeln('\tFieldData ${node.val_var} = {0};')
} }
g.push_new_comptime_info()
for field in fields { for field in fields {
g.push_new_comptime_info()
g.comptime.inside_comptime_for = true g.comptime.inside_comptime_for = true
g.comptime.comptime_for_field_var = node.val_var g.comptime.comptime_for_field_var = node.val_var
g.comptime.comptime_for_field_value = field g.comptime.comptime_for_field_value = field
g.comptime.comptime_for_field_type = field.typ g.comptime.comptime_for_field_type = field.typ
g.writeln('/* field ${i} */ {') g.writeln('/* field ${i} : ${field.name} */ {')
g.writeln('\t${node.val_var}.name = _S("${field.name}");') g.writeln('\t${node.val_var}.name = _S("${field.name}");')
if field.attrs.len == 0 { if field.attrs.len == 0 {
g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);')
@ -996,8 +722,8 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
styp := field.typ styp := field.typ
unaliased_styp := g.table.unaliased_type(styp) unaliased_styp := g.table.unaliased_type(styp)
g.writeln('\t${node.val_var}.typ = ${int(styp.idx())};') g.writeln('\t${node.val_var}.typ = ${int(styp.idx())};\t// ${g.table.type_to_str(styp)}')
g.writeln('\t${node.val_var}.unaliased_typ = ${int(unaliased_styp.idx())};') g.writeln('\t${node.val_var}.unaliased_typ = ${int(unaliased_styp.idx())};\t// ${g.table.type_to_str(unaliased_styp)}')
g.writeln('\t${node.val_var}.is_pub = ${field.is_pub};') g.writeln('\t${node.val_var}.is_pub = ${field.is_pub};')
g.writeln('\t${node.val_var}.is_mut = ${field.is_mut};') g.writeln('\t${node.val_var}.is_mut = ${field.is_mut};')
@ -1019,17 +745,18 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
g.stmts(node.stmts) g.stmts(node.stmts)
i++ i++
g.writeln('}') g.writeln('}')
}
g.pop_comptime_info() g.pop_comptime_info()
} }
}
} else if node.kind == .values { } else if node.kind == .values {
if sym.kind == .enum { if sym.kind == .enum {
if sym.info is ast.Enum { if sym.info is ast.Enum {
if sym.info.vals.len > 0 { if sym.info.vals.len > 0 {
g.writeln('\tEnumData ${node.val_var} = {0};') g.writeln('\tEnumData ${node.val_var} = {0};')
} }
g.push_new_comptime_info()
for val in sym.info.vals { for val in sym.info.vals {
g.push_new_comptime_info()
g.comptime.inside_comptime_for = true
g.comptime.comptime_for_enum_var = node.val_var g.comptime.comptime_for_enum_var = node.val_var
g.type_resolver.update_ct_type('${node.val_var}.typ', node.typ) g.type_resolver.update_ct_type('${node.val_var}.typ', node.typ)
@ -1053,17 +780,21 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
g.stmts(node.stmts) g.stmts(node.stmts)
g.writeln('}') g.writeln('}')
i++ i++
}
g.pop_comptime_info() g.pop_comptime_info()
} }
} }
}
} else if node.kind == .attributes { } else if node.kind == .attributes {
attrs := g.table.get_attrs(sym) attrs := g.table.get_attrs(sym)
if attrs.len > 0 { if attrs.len > 0 {
g.writeln('\tVAttribute ${node.val_var} = {0};') g.writeln('\tVAttribute ${node.val_var} = {0};')
for attr in attrs { for attr in attrs {
g.writeln('/* attribute ${i} */ {') g.push_new_comptime_info()
g.comptime.inside_comptime_for = true
g.comptime.comptime_for_attr_var = node.val_var
g.comptime.comptime_for_attr_value = attr
g.writeln('/* attribute ${i} : ${attr.name} */ {')
g.writeln('\t${node.val_var}.name = _S("${attr.name}");') g.writeln('\t${node.val_var}.name = _S("${attr.name}");')
g.writeln('\t${node.val_var}.has_arg = ${attr.has_arg};') g.writeln('\t${node.val_var}.has_arg = ${attr.has_arg};')
g.writeln('\t${node.val_var}.arg = _S("${util.smart_quote(attr.arg, false)}");') g.writeln('\t${node.val_var}.arg = _S("${util.smart_quote(attr.arg, false)}");')
@ -1071,6 +802,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
g.stmts(node.stmts) g.stmts(node.stmts)
g.writeln('}') g.writeln('}')
i++ i++
g.pop_comptime_info()
} }
} }
} else if node.kind == .variants { } else if node.kind == .variants {
@ -1079,40 +811,41 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
g.writeln('\tVariantData ${node.val_var} = {0};') g.writeln('\tVariantData ${node.val_var} = {0};')
} }
g.comptime.inside_comptime_for = true g.comptime.inside_comptime_for = true
g.push_new_comptime_info()
for variant in sym.info.variants { for variant in sym.info.variants {
g.push_new_comptime_info()
g.comptime.inside_comptime_for = true
g.comptime.comptime_for_variant_var = node.val_var g.comptime.comptime_for_variant_var = node.val_var
g.type_resolver.update_ct_type('${node.val_var}.typ', variant) g.type_resolver.update_ct_type('${node.val_var}.typ', variant)
g.writeln('/* variant ${i} */ {') g.writeln('/* variant ${i} : ${g.table.type_to_str(variant)} */ {')
g.writeln('\t${node.val_var}.typ = ${int(variant)};') g.writeln('\t${node.val_var}.typ = ${int(variant)};\t// ')
g.stmts(node.stmts) g.stmts(node.stmts)
g.writeln('}') g.writeln('}')
i++ i++
}
g.pop_comptime_info() g.pop_comptime_info()
} }
}
} else if node.kind == .params { } else if node.kind == .params {
method := g.comptime.comptime_for_method method := g.comptime.comptime_for_method
if method.params.len > 0 { if method.params.len > 0 {
g.writeln('\tMethodParam ${node.val_var} = {0};') g.writeln('\tMethodParam ${node.val_var} = {0};')
} }
for param in method.params[1..] {
g.push_new_comptime_info() g.push_new_comptime_info()
g.comptime.inside_comptime_for = true g.comptime.inside_comptime_for = true
g.comptime.comptime_for_method_param_var = node.val_var g.comptime.comptime_for_method_param_var = node.val_var
for param in method.params[1..] {
g.type_resolver.update_ct_type('${node.val_var}.typ', param.typ) g.type_resolver.update_ct_type('${node.val_var}.typ', param.typ)
g.writeln('/* method param ${i} */ {') g.writeln('/* method param ${i} : ${param.name} */ {')
g.writeln('\t${node.val_var}.typ = ${int(param.typ)};') g.writeln('\t${node.val_var}.typ = ${int(param.typ)};\t// ${g.table.type_to_str(param.typ)}')
g.writeln('\t${node.val_var}.name = _S("${param.name}");') g.writeln('\t${node.val_var}.name = _S("${param.name}");')
g.stmts(node.stmts) g.stmts(node.stmts)
g.writeln('}') g.writeln('}')
i++ i++
}
g.pop_comptime_info() g.pop_comptime_info()
} }
}
g.indent-- g.indent--
g.writeln('}// \$for') g.writeln('}// \$for')
} }

View file

@ -1,7 +1,24 @@
module js module js
import v.token
import v.ast import v.ast
fn (mut g JsGen) gen_branch_context_string() string {
mut arr := []string{}
// gen `T=int,X=string`
if g.fn_decl != unsafe { nil } && g.fn_decl.generic_names.len > 0
&& g.fn_decl.generic_names.len == g.cur_concrete_types.len {
for i in 0 .. g.fn_decl.generic_names.len {
arr << g.fn_decl.generic_names[i] + '=' +
g.table.type_to_str(g.cur_concrete_types[i]).replace('main.', '')
}
}
// TODO: support comptime `$for`
return arr.join(',')
}
fn (mut g JsGen) comptime_if(node ast.IfExpr) { fn (mut g JsGen) comptime_if(node ast.IfExpr) {
if !node.is_expr && !node.has_else && node.branches.len == 1 { if !node.is_expr && !node.has_else && node.branches.len == 1 {
if node.branches[0].stmts.len == 0 { if node.branches[0].stmts.len == 0 {
@ -10,17 +27,32 @@ fn (mut g JsGen) comptime_if(node ast.IfExpr) {
} }
} }
mut comptime_branch_context_str := g.gen_branch_context_string()
mut is_true := ast.ComptTimeCondResult{}
for i, branch in node.branches { for i, branch in node.branches {
idx_str := if branch.cond.pos() == token.Pos{} {
comptime_branch_context_str + '|${g.file.path}|${branch.pos}|'
} else {
comptime_branch_context_str + '|${g.file.path}|${branch.cond.pos()}|'
}
if comptime_is_true := g.table.comptime_is_true[idx_str] {
is_true = comptime_is_true
} else {
panic('checker error: cond result idx string not found => [${idx_str}]')
return
}
if i == node.branches.len - 1 && node.has_else { if i == node.branches.len - 1 && node.has_else {
g.writeln('else') g.writeln('else')
} else { } else {
result := if is_true.val { '1' } else { '0' }
if i == 0 { if i == 0 {
g.write('if (') g.writeln('if (${result})')
} else { } else {
g.write('else if (') g.writeln('else if (${result})')
}
$if debug_comptime_branch_context ? {
g.writeln('// ${node.branches[i].cond} generic=[${comptime_branch_context_str}]')
} }
g.comptime_if_cond(branch.cond, branch.pkg_exist)
g.writeln(')')
} }
if node.is_expr { if node.is_expr {
@ -45,7 +77,9 @@ fn (mut g JsGen) comptime_if(node ast.IfExpr) {
} }
} else { } else {
g.writeln('{') g.writeln('{')
if is_true.val {
g.stmts(branch.stmts) g.stmts(branch.stmts)
}
g.writeln('}') g.writeln('}')
} }
} }

View file

@ -5,36 +5,32 @@ module wasm
import v.ast import v.ast
pub fn (mut g Gen) comptime_cond(cond ast.Expr, pkg_exists bool) bool { pub fn (mut g Gen) comptime_cond(cond ast.Expr) bool {
match cond { match cond {
ast.BoolLiteral { ast.BoolLiteral {
return cond.val return cond.val
} }
ast.ParExpr { ast.ParExpr {
g.comptime_cond(cond.expr, pkg_exists) g.comptime_cond(cond.expr)
} }
ast.PrefixExpr { ast.PrefixExpr {
if cond.op == .not { if cond.op == .not {
return !g.comptime_cond(cond.right, pkg_exists) return !g.comptime_cond(cond.right)
} }
} }
ast.InfixExpr { ast.InfixExpr {
match cond.op { match cond.op {
.and { .and {
return g.comptime_cond(cond.left, pkg_exists) return g.comptime_cond(cond.left) && g.comptime_cond(cond.right)
&& g.comptime_cond(cond.right, pkg_exists)
} }
.logical_or { .logical_or {
return g.comptime_cond(cond.left, pkg_exists) return g.comptime_cond(cond.left) || g.comptime_cond(cond.right)
|| g.comptime_cond(cond.right, pkg_exists)
} }
.eq { .eq {
return g.comptime_cond(cond.left, pkg_exists) == g.comptime_cond(cond.right, return g.comptime_cond(cond.left) == g.comptime_cond(cond.right)
pkg_exists)
} }
.ne { .ne {
return g.comptime_cond(cond.left, pkg_exists) != g.comptime_cond(cond.right, return g.comptime_cond(cond.left) != g.comptime_cond(cond.right)
pkg_exists)
} }
// wasm doesn't support generics // wasm doesn't support generics
// .key_is, .not_is // .key_is, .not_is
@ -45,7 +41,7 @@ pub fn (mut g Gen) comptime_cond(cond ast.Expr, pkg_exists bool) bool {
return g.comptime_if_to_ifdef(cond.name, false) return g.comptime_if_to_ifdef(cond.name, false)
} }
ast.ComptimeCall { ast.ComptimeCall {
return pkg_exists // more documentation needed here... return false // pkg_exists, more documentation needed here...
} }
ast.PostfixExpr { ast.PostfixExpr {
return g.comptime_if_to_ifdef((cond.expr as ast.Ident).name, true) return g.comptime_if_to_ifdef((cond.expr as ast.Ident).name, true)
@ -66,7 +62,7 @@ pub fn (mut g Gen) comptime_if_expr(node ast.IfExpr, expected ast.Type, existing
for i, branch in node.branches { for i, branch in node.branches {
has_expr := !(node.has_else && i + 1 >= node.branches.len) has_expr := !(node.has_else && i + 1 >= node.branches.len)
if has_expr && !g.comptime_cond(branch.cond, branch.pkg_exist) { if has_expr && !g.comptime_cond(branch.cond) {
continue continue
} }
// !node.is_expr || cond // !node.is_expr || cond

View file

@ -4,9 +4,11 @@ const disable_opt_features = true
// Note: the `unknown_fn()` calls are here on purpose, to make sure that anything // Note: the `unknown_fn()` calls are here on purpose, to make sure that anything
// that doesn't match a compile-time condition is not even parsed. // that doesn't match a compile-time condition is not even parsed.
fn test_ct_expressions() { fn test_ct_expressions() {
mut result := ''
foo := version foo := version
bar := foo bar := foo
$if bar == 123 { $if bar == 123 {
result += 'a'
assert true assert true
} $else { } $else {
unknown_fn() unknown_fn()
@ -15,6 +17,7 @@ fn test_ct_expressions() {
$if bar != 123 { $if bar != 123 {
unknown_fn() unknown_fn()
} $else $if bar != 124 { } $else $if bar != 124 {
result += 'b'
assert true assert true
} $else { } $else {
unknown_fn() unknown_fn()
@ -23,8 +26,10 @@ fn test_ct_expressions() {
$if !disable_opt_features { $if !disable_opt_features {
unknown_fn() unknown_fn()
} $else { } $else {
result += 'c'
assert true assert true
} }
assert result == 'abc'
} }
fn generic_t_is[O]() O { fn generic_t_is[O]() O {

View file

@ -30,7 +30,7 @@ fn test[T](val T) string {
fn test_main() { fn test_main() {
assert test(u32(7)) == 'else block' assert test(u32(7)) == 'else block'
assert test(OtherStruct{'7'}) == 'struct field string' assert test(OtherStruct{'7'}) == 'not u32 or i32 struct field, got type: string'
assert test(I32Struct{-7}) == 'struct field i32' assert test(I32Struct{-7}) == 'struct field i32'
assert test(U32Struct{7}) == 'struct field u32' assert test(U32Struct{7}) == 'struct field u32'
} }

View file

@ -0,0 +1,34 @@
fn test_test_ident() {
mut result := ''
$if test ? {
result += '1'
} $else {
result += '2'
}
$if test {
result += '3'
} $else {
result += '4'
}
$if !test {
result += '5'
} $else {
result += '6'
}
$if $d('test', false) {
result += '7'
} $else {
result += '8'
}
$if $d('test', true) {
result += '9'
} $else {
result += '0'
}
assert result == '23689'
}