diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index a043690777..0ddea4adca 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 { diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index aeca41c120..bcc3b044cc 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -94,6 +94,7 @@ 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]bool // The evaluate cond results for different generic types combination, such as `comptime_is_true['T=int,X=string|main.v|pos ...'] = true` } // used by vls to avoid leaks diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index baac210e86..af7305a66b 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) diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 00687775e2..1008acad83 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -4,6 +4,60 @@ module checker import v.ast import v.token +import v.util + +// 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 +94,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 +105,37 @@ 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.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 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` - comptime_if_result = false + comptime_if_result = if comptime_if_found_branch { + false + } else { + comptime_if_result } + 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 != comptime_if_result { + c.error('checker error : branch eval wrong', branch.pos) + } + } + + // set `comptime_is_true` which can be used by `cgen` + c.table.comptime_is_true[idx_str] = comptime_if_result } else { // check condition type is boolean c.expected_type = ast.bool_type @@ -78,6 +154,13 @@ 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 + } + idx_str := comptime_branch_context_str + '|${c.file.path}|${branch.pos}|' + c.table.comptime_is_true[idx_str] = comptime_if_result } } if mut branch.cond is ast.IfGuardExpr { @@ -119,7 +202,7 @@ 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 } @@ -147,9 +230,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 +402,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/comptime.v b/vlib/v/gen/c/comptime.v index e62b61abcc..9d27ba16ee 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -313,6 +313,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 +399,46 @@ 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 := false 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 := comptime_branch_context_str + '|${g.file.path}|${branch.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 + if g.pref.output_cross_c { + // generate full `if defined` in cross mode + pos := g.out.len + g.comptime_if_cond(branch.cond, true) + cond_str := g.out.cut_to(pos).trim_space() + g.writeln(cond_str) + } else { + // directly use `checker` evaluate results + if is_true { + g.writeln('1\t/* ${node.branches[i].cond} | generic=[${comptime_branch_context_str}] */') + } else { + g.writeln('0\t/* ${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 +497,7 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { if should_create_scope { g.writeln('{') } - if !comptime_if_stmts_skip { + if is_true { g.stmts(branch.stmts) } if should_create_scope { @@ -847,6 +914,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 +933,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 +958,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 +974,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 +1048,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);') @@ -1019,8 +1091,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 +1100,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 +1126,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 +1136,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 +1148,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 +1157,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('/* variant ${i} : ${g.table.type_to_str(variant)} */ {') g.writeln('\t${node.val_var}.typ = ${int(variant)};') 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 +1177,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('/* method param ${i} : ${param.name} */ {') g.writeln('\t${node.val_var}.typ = ${int(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..efb315827e 100644 --- a/vlib/v/gen/js/comptime.v +++ b/vlib/v/gen/js/comptime.v @@ -2,6 +2,22 @@ module js import v.ast +fn (mut g JsGen) gen_cond_generic_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,7 +26,16 @@ fn (mut g JsGen) comptime_if(node ast.IfExpr) { } } + mut comptime_generic_str := g.gen_cond_generic_string() + mut is_true := false for i, branch in node.branches { + idx_str := comptime_generic_str + '|${g.file.path}|${branch.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 { @@ -19,8 +44,11 @@ fn (mut g JsGen) comptime_if(node ast.IfExpr) { } else { g.write('else if (') } - g.comptime_if_cond(branch.cond, branch.pkg_exist) - g.writeln(')') + if is_true { + g.writeln('1)\t// ${node.branches[i].cond} generic=[${comptime_generic_str}]') + } else { + g.writeln('0)\t// ${node.branches[i].cond} generic=[${comptime_generic_str}]') + } } if node.is_expr { @@ -45,7 +73,9 @@ fn (mut g JsGen) comptime_if(node ast.IfExpr) { } } else { g.writeln('{') - g.stmts(branch.stmts) + if is_true { + g.stmts(branch.stmts) + } g.writeln('}') } } 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' }