checker,cgen: propagate comptime computation results from the checker phase to the code generation (cgen) phase

This commit is contained in:
kbkpbot 2025-08-21 21:53:43 +08:00
parent 2e23f2a18b
commit 93777396eb
7 changed files with 250 additions and 52 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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('}')
}
}

View file

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