From a3f86eed1bcda08c2885f9970ef280abea8830db Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Sun, 24 Aug 2025 04:40:50 +0800 Subject: [PATCH] checker,cgen: evaluate comptime `$if` results in checker *only* (fix #25123) (fix #25156) (#25150) --- cmd/tools/vast/vast.v | 1 - vlib/v/ast/ast.v | 9 +- vlib/v/ast/table.v | 7 + vlib/v/checker/comptime.v | 296 +++++++++-- vlib/v/checker/if.v | 127 ++++- vlib/v/gen/c/cgen.v | 28 +- vlib/v/gen/c/comptime.v | 491 ++++-------------- vlib/v/gen/js/comptime.v | 44 +- vlib/v/gen/wasm/comptime.v | 22 +- vlib/v/tests/comptime/comptime_if_expr_test.v | 5 + .../comptime/comptime_selector_member_test.v | 2 +- .../tests/comptime/comptime_test_ident_test.v | 34 ++ 12 files changed, 605 insertions(+), 461 deletions(-) create mode 100644 vlib/v/tests/comptime/comptime_test_ident_test.v diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index ec1c08de53..5e2ba843b2 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -1538,7 +1538,6 @@ fn (t Tree) if_branch(node ast.IfBranch) &Node { obj.add_terse('cond', t.expr(node.cond)) obj.add('pos', t.pos(node.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('scope', t.number_node(int(node.scope))) obj.add('comments', t.array_node_comment(node.comments)) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index a043690777..179a5a29ee 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1234,10 +1234,9 @@ pub: body_pos token.Pos comments []Comment pub mut: - cond Expr - pkg_exist bool - stmts []Stmt - scope &Scope = unsafe { nil } + cond Expr + stmts []Stmt + scope &Scope = unsafe { nil } } pub struct UnsafeExpr { @@ -2131,6 +2130,8 @@ pub fn (cc ComptimeCall) expr_str() string { if arg.expr.is_pure_literal() { str = "\$${cc.method_name}('${cc.args_var}', ${arg})" } + } else if cc.kind == .pkgconfig { + str = "\$${cc.method_name}('${cc.args_var}')" } return str } diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index aeca41c120..e6858794d9 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -94,6 +94,13 @@ pub mut: anon_struct_counter int anon_union_names map[string]int // anon union name -> union sym idx 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 diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index baac210e86..d744701fc3 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -256,7 +256,11 @@ fn (mut c Checker) comptime_for(mut node ast.ComptimeFor) { node.typ = c.expr(mut node.expr) 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) { 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) @@ -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 { mut branch := expr.branches[i] 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 { last_stmt := branch.stmts.last() 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 - 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 c.inside_ct_attr = false node.ct_evaled = true 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 { match cond { 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`) // `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 {` -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 is_user_ident := false mut ident_name := '' @@ -873,17 +1064,25 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { match mut cond { ast.BoolLiteral { c.expr(mut cond) - return cond.val, false + is_true = cond.val + sb.write_string('${is_true}') + return is_true, false } 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 { if cond.op != .not { c.error('invalid \$if prefix operator, only allow `!`.', cond.pos) 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 } ast.PostfixExpr { @@ -900,16 +1099,20 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { should_record_ident = true is_user_ident = true ident_name = cname - if cname in c.pref.compile_defines { - return true, false - } - return false, false + // ifdef := c.comptime_if_to_ifdef(cname, true) or { + // c.error(err.msg(), cond.pos) + // return false, false + //} + sb.write_string('defined(CUSTOM_DEFINE_${cname})') + is_true = cname in c.pref.compile_defines + return is_true, false } ast.InfixExpr { match cond.op { .and, .logical_or { - l, d1 := c.comptime_if_cond(mut cond.left) - r, d2 := c.comptime_if_cond(mut cond.right) + l, d1 := c.comptime_if_cond(mut cond.left, mut sb) + 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 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, true - } else { - !is_true, true - } + is_true = if cond.op in [.key_in, .key_is] { is_true } else { !is_true } + sb.write_string('${is_true}') + return is_true, true } 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 } } + sb.write_string('${is_true}') return is_true, false } ast.SelectorExpr { @@ -1106,9 +1308,11 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { } match cond.op { .eq { + sb.write_string('${is_true}') return is_true, true } .ne { + sb.write_string('${!is_true}') return !is_true, true } else { @@ -1153,6 +1357,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { return false, false } } + sb.write_string('${is_true}') return is_true, true } else if cond.left.field_name == '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 } } + sb.write_string('${is_true}') return is_true, false } else { 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 { // 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 + sb.write_string('${r}') is_true = if cond.op == .eq { l == r } else { l != r } return is_true, true } @@ -1239,6 +1447,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { return false, false } } + sb.write_string('${is_true}') return is_true, true } else { @@ -1269,17 +1478,13 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { is_user_ident = false ident_name = cname 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 == c.pref.os { - is_true = true - } + if cname_enum_val := pref.os_from_string(cname) { + if cname_enum_val == c.pref.os { + is_true = true } } - return is_true, false } else if cname in ast.valid_comptime_if_compilers { is_true = pref.cc_from_string(cname) == c.pref.ccompiler_type - return is_true, false } else if cname in ast.valid_comptime_if_platforms { if cname == 'aarch64' { 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 is_true, false } else if cname in ast.valid_comptime_if_cpu_features { match cname { 'x64' { @@ -1342,7 +1546,6 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { return false, false } } - return is_true, false } else if cname in ast.valid_comptime_if_other { match cname { 'apk' { @@ -1414,7 +1617,6 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { return false, false } } - return is_true, false } else if cname !in c.pref.compile_defines_all { if cname == 'linux_or_macos' { 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 } is_true = (expr as ast.BoolLiteral).val - return is_true, false } 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) + return false, false } - c.error('invalid \$if condition: unknown indent `${cname}`', cond.pos) - 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 { if cond.kind == .pkgconfig { - mut m := pkgconfig.main([cond.args_var]) or { + if mut m := pkgconfig.main([cond.args_var]) { + if _ := m.run() { + is_true = true + } else { + // pkgconfig not found, do not issue error, just set false + is_true = false + } + } else { c.error(err.msg(), cond.pos) - return false, true + is_true = false } - m.run() or { return false, true } - return true, true + sb.write_string('${is_true}') + return is_true, true } if cond.kind == .d { t := c.expr(mut cond) @@ -1464,7 +1679,9 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr) (bool, bool) { cond.pos) 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) 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 (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) + sb.write_string('${is_true}') return is_true, true } 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 (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 + sb.write_string('${is_true}') return is_true, true } 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 } else { false } } + sb.write_string('${is_true}') return is_true, true } c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_method_var}', diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 00687775e2..806243832a 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -4,6 +4,61 @@ module checker import v.ast 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 { 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 } - for i in 0 .. node.branches.len { - mut branch := node.branches[i] + comptime_branch_context_str := if node.is_comptime { c.gen_branch_context_string() } else { '' } + + 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 { c.warn('unnecessary `()` in `${if_kind}` condition, use `${if_kind} expr {` instead of `${if_kind} (expr) {`.', 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 branch 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 - comptime_if_result, comptime_if_multi_pass_branch = c.comptime_if_cond(mut branch.cond) - node.branches[i].pkg_exist = comptime_if_result + mut sb := strings.new_builder(256) + comptime_if_result, comptime_if_multi_pass_branch = c.comptime_if_cond(mut branch.cond, mut + sb) if comptime_if_multi_pass_branch { 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: - // remove following branchs' stmts by overwrite `comptime_if_result` + + if comptime_if_found_branch { 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 { // check condition type is boolean 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 // 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 + 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 { @@ -119,9 +212,17 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { if c.fn_level == 0 && c.pref.output_cross_c { // do not skip any of the branches for top level `$if OS {` // statements, in `-cross` mode - comptime_if_multi_pass_branch = true + comptime_remove_curr_branch_stmts = false c.skip_flags = false - c.ct_cond_stack << branch.cond + // 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 + } } if !c.skip_flags { if node_is_expr { @@ -147,9 +248,6 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { c.stmts(mut 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 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++ } } + + 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 == node.branches.len { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a498b51c3c..341841bb9b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -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] { 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)') } - if g.pref.is_prod || 'prod' in g.pref.compile_defines { + if g.pref.is_prod { 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)') } - if g.pref.is_prof || 'profile' in g.pref.compile_defines { + if g.pref.is_prof { g.comptime_definitions.writeln('#define _VPROFILE (1)') } 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) { line_nr := node.pos.line_nr + 1 mut ct_condition := '' + 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 { - 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 { - g.write(' && ') + sb.write_string(' && ') } } - ct_condition = g.out.cut_to(ct_condition_start).trim_space() + ct_condition = sb.str() } // #include etc if node.kind == 'include' { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index e62b61abcc..475f9c9441 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -5,6 +5,7 @@ module c import os import v.ast +import v.token import v.util import v.pref 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) { if !node.is_expr && !node.has_else && node.branches.len == 1 { if node.branches[0].stmts.len == 0 { @@ -346,32 +400,42 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { } 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 { 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 { - g.writeln('#else') - comptime_if_stmts_skip = comptime_may_skip_else + // `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 `checker`. + 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] { + // `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 { g.write('#if ') } else { g.write('#elif ') } - 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 + // directly use `checker` evaluate results + // for `cgen`, we can use `is_true.c_str` or `is_true.value` here + g.writeln('${is_true.c_str}') + $if debug_comptime_branch_context ? { + g.writeln('/* ${node.branches[i].cond} | generic=[${comptime_branch_context_str}] */') } - comptime_if_stmts_skip = !comptime_if_stmts_skip - g.writeln('') + } else { + g.writeln('#else') } expr_str := g.out.last_n(g.out.len - start_pos).trim_space() if expr_str != '' { @@ -430,7 +494,7 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { if should_create_scope { g.writeln('{') } - if !comptime_if_stmts_skip { + if is_true.val { g.stmts(branch.stmts) } 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 fn (mut g Gen) push_new_comptime_info() { 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_value: g.comptime.comptime_for_field_value 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: g.comptime.comptime_for_method 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_value = old.comptime_for_field_value 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 = old.comptime_for_method 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') for method in methods { g.push_new_comptime_info() + g.comptime.inside_comptime_for = true // filter vweb route methods (non-generic method) if method.receiver_type != 0 && method.return_type == typ_vweb_result { 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_var = node.val_var - g.writeln('/* method ${i} */ {') + g.writeln('/* method ${i} : ${method.name} */ {') g.writeln('\t${node.val_var}.name = _S("${method.name}");') if method.attrs.len == 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 { g.writeln('\tFieldData ${node.val_var} = {0};') } - g.push_new_comptime_info() for field in fields { + g.push_new_comptime_info() g.comptime.inside_comptime_for = true g.comptime.comptime_for_field_var = node.val_var g.comptime.comptime_for_field_value = field 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}");') if field.attrs.len == 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 unaliased_styp := g.table.unaliased_type(styp) - g.writeln('\t${node.val_var}.typ = ${int(styp.idx())};') - g.writeln('\t${node.val_var}.unaliased_typ = ${int(unaliased_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())};\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_mut = ${field.is_mut};') @@ -1019,8 +745,8 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.stmts(node.stmts) i++ g.writeln('}') + g.pop_comptime_info() } - g.pop_comptime_info() } } else if node.kind == .values { if sym.kind == .enum { @@ -1028,8 +754,9 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { if sym.info.vals.len > 0 { g.writeln('\tEnumData ${node.val_var} = {0};') } - g.push_new_comptime_info() 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.type_resolver.update_ct_type('${node.val_var}.typ', node.typ) @@ -1053,8 +780,8 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.stmts(node.stmts) g.writeln('}') i++ + g.pop_comptime_info() } - g.pop_comptime_info() } } } else if node.kind == .attributes { @@ -1063,7 +790,11 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.writeln('\tVAttribute ${node.val_var} = {0};') 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}.has_arg = ${attr.has_arg};') 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.writeln('}') i++ + g.pop_comptime_info() } } } else if node.kind == .variants { @@ -1079,18 +811,19 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.writeln('\tVariantData ${node.val_var} = {0};') } g.comptime.inside_comptime_for = true - g.push_new_comptime_info() 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.type_resolver.update_ct_type('${node.val_var}.typ', variant) - g.writeln('/* variant ${i} */ {') - g.writeln('\t${node.val_var}.typ = ${int(variant)};') + g.writeln('/* variant ${i} : ${g.table.type_to_str(variant)} */ {') + g.writeln('\t${node.val_var}.typ = ${int(variant)};\t// ') g.stmts(node.stmts) g.writeln('}') i++ + g.pop_comptime_info() } - g.pop_comptime_info() } } else if node.kind == .params { method := g.comptime.comptime_for_method @@ -1098,20 +831,20 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { if method.params.len > 0 { g.writeln('\tMethodParam ${node.val_var} = {0};') } - g.push_new_comptime_info() - g.comptime.inside_comptime_for = true - g.comptime.comptime_for_method_param_var = node.val_var for param in method.params[1..] { + g.push_new_comptime_info() + g.comptime.inside_comptime_for = true + g.comptime.comptime_for_method_param_var = node.val_var g.type_resolver.update_ct_type('${node.val_var}.typ', param.typ) - g.writeln('/* method param ${i} */ {') - g.writeln('\t${node.val_var}.typ = ${int(param.typ)};') + g.writeln('/* method param ${i} : ${param.name} */ {') + 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.stmts(node.stmts) g.writeln('}') i++ + g.pop_comptime_info() } - g.pop_comptime_info() } g.indent-- g.writeln('}// \$for') diff --git a/vlib/v/gen/js/comptime.v b/vlib/v/gen/js/comptime.v index 292febee00..c706756b20 100644 --- a/vlib/v/gen/js/comptime.v +++ b/vlib/v/gen/js/comptime.v @@ -1,7 +1,24 @@ module js +import v.token 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) { if !node.is_expr && !node.has_else && node.branches.len == 1 { 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 { + 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 { g.writeln('else') } else { + result := if is_true.val { '1' } else { '0' } if i == 0 { - g.write('if (') + g.writeln('if (${result})') } 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 { @@ -45,7 +77,9 @@ fn (mut g JsGen) comptime_if(node ast.IfExpr) { } } else { g.writeln('{') - g.stmts(branch.stmts) + if is_true.val { + g.stmts(branch.stmts) + } g.writeln('}') } } diff --git a/vlib/v/gen/wasm/comptime.v b/vlib/v/gen/wasm/comptime.v index 5266c1ed46..b94f944ae2 100644 --- a/vlib/v/gen/wasm/comptime.v +++ b/vlib/v/gen/wasm/comptime.v @@ -5,36 +5,32 @@ module wasm 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 { ast.BoolLiteral { return cond.val } ast.ParExpr { - g.comptime_cond(cond.expr, pkg_exists) + g.comptime_cond(cond.expr) } ast.PrefixExpr { if cond.op == .not { - return !g.comptime_cond(cond.right, pkg_exists) + return !g.comptime_cond(cond.right) } } ast.InfixExpr { match cond.op { .and { - return g.comptime_cond(cond.left, pkg_exists) - && g.comptime_cond(cond.right, pkg_exists) + return g.comptime_cond(cond.left) && g.comptime_cond(cond.right) } .logical_or { - return g.comptime_cond(cond.left, pkg_exists) - || g.comptime_cond(cond.right, pkg_exists) + return g.comptime_cond(cond.left) || g.comptime_cond(cond.right) } .eq { - return g.comptime_cond(cond.left, pkg_exists) == g.comptime_cond(cond.right, - pkg_exists) + return g.comptime_cond(cond.left) == g.comptime_cond(cond.right) } .ne { - return g.comptime_cond(cond.left, pkg_exists) != g.comptime_cond(cond.right, - pkg_exists) + return g.comptime_cond(cond.left) != g.comptime_cond(cond.right) } // wasm doesn't support generics // .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) } ast.ComptimeCall { - return pkg_exists // more documentation needed here... + return false // pkg_exists, more documentation needed here... } ast.PostfixExpr { 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 { 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 } // !node.is_expr || cond diff --git a/vlib/v/tests/comptime/comptime_if_expr_test.v b/vlib/v/tests/comptime/comptime_if_expr_test.v index efee06c438..c7f6db1ff5 100644 --- a/vlib/v/tests/comptime/comptime_if_expr_test.v +++ b/vlib/v/tests/comptime/comptime_if_expr_test.v @@ -4,9 +4,11 @@ const disable_opt_features = true // 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. fn test_ct_expressions() { + mut result := '' foo := version bar := foo $if bar == 123 { + result += 'a' assert true } $else { unknown_fn() @@ -15,6 +17,7 @@ fn test_ct_expressions() { $if bar != 123 { unknown_fn() } $else $if bar != 124 { + result += 'b' assert true } $else { unknown_fn() @@ -23,8 +26,10 @@ fn test_ct_expressions() { $if !disable_opt_features { unknown_fn() } $else { + result += 'c' assert true } + assert result == 'abc' } fn generic_t_is[O]() O { diff --git a/vlib/v/tests/comptime/comptime_selector_member_test.v b/vlib/v/tests/comptime/comptime_selector_member_test.v index b50959fdda..fb609d702a 100644 --- a/vlib/v/tests/comptime/comptime_selector_member_test.v +++ b/vlib/v/tests/comptime/comptime_selector_member_test.v @@ -30,7 +30,7 @@ fn test[T](val T) string { fn test_main() { 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(U32Struct{7}) == 'struct field u32' } diff --git a/vlib/v/tests/comptime/comptime_test_ident_test.v b/vlib/v/tests/comptime/comptime_test_ident_test.v new file mode 100644 index 0000000000..87b1f8c1da --- /dev/null +++ b/vlib/v/tests/comptime/comptime_test_ident_test.v @@ -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' +}