// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module c import os import v.ast import v.util import v.type_resolver fn (g &Gen) veb_context_html_arg() string { if g.fn_decl.params.len < 2 { return 'ctx' } ctx_param := g.fn_decl.params[1] ctx_sym := g.table.final_sym(ctx_param.typ) if ctx_sym.name == 'veb.Context' { return ctx_param.name } if ctx_sym.info is ast.Struct { for embed in ctx_sym.info.embeds { embed_sym := g.table.sym(embed) if embed_sym.name == 'veb.Context' { dot := if ctx_param.typ.is_ptr() { '->' } else { '.' } return '&${ctx_param.name}${dot}${embed_sym.embed_name()}' } } } return ctx_param.name } fn (mut g Gen) is_string_array_type(typ ast.Type) bool { final_typ := g.table.unaliased_type(g.unwrap_generic(typ)) sym := g.table.final_sym(final_typ) if sym.info is ast.Array { return g.table.unaliased_type(sym.info.elem_type) == ast.string_type } return false } fn (mut g Gen) comptime_call_expands_string_args(m &ast.Fn, node ast.ComptimeCall) bool { if node.args.len == 0 || node.args.last().expr !is ast.ArrayDecompose { return false } array_decompose := node.args.last().expr as ast.ArrayDecompose mut array_type := g.resolved_expr_type(array_decompose.expr, array_decompose.expr_type) if array_type == ast.void_type { array_type = array_decompose.expr_type } if !g.is_string_array_type(array_type) || m.params.len - 1 < node.args.len { return false } return !g.is_string_array_type(m.params[node.args.len].typ) } fn (mut g Gen) comptime_zero_value(typ ast.Type) string { resolved_type := g.unwrap_generic(g.recheck_concrete_type(typ)) styp := g.styp(resolved_type) mut default_value := g.type_default(resolved_type) if default_value.len > 0 && default_value[0] == `{` { default_value = '(${styp})${default_value}' } return default_value } fn (mut g Gen) comptime_type_expr_type(expr ast.Expr, fallback_type ast.Type) ast.Type { match expr { ast.ParExpr { return g.comptime_type_expr_type(expr.expr, fallback_type) } ast.TypeNode { return g.unwrap_generic(g.recheck_concrete_type(expr.typ)) } ast.TypeOf { if expr.is_type { return g.unwrap_generic(g.recheck_concrete_type(expr.typ)) } mut default_type := g.get_type(expr.typ) default_type = g.resolve_typeof_expr_type(expr.expr, default_type) if expr.expr is ast.Ident && expr.expr.obj is ast.Var { resolved := g.comptime_typeof_generic_ident_type(expr.expr) if resolved != 0 { return resolved } } if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { resolved := g.resolve_typeof_in_generic(expr) if resolved != 0 { default_type = resolved } } return g.resolved_typeof_name_type(expr, default_type) } ast.ArrayInit { if expr.elem_type_expr !is ast.EmptyExpr { elem_type := g.comptime_type_expr_type(expr.elem_type_expr, expr.elem_type) if elem_type != 0 && elem_type != ast.void_type && elem_type != ast.no_type { if expr.is_fixed { sym := g.table.final_sym(expr.typ) if sym.info is ast.ArrayFixed { return ast.new_type(g.table.find_or_register_array_fixed(elem_type, sym.info.size, sym.info.size_expr, sym.info.is_fn_ret)) } } return ast.new_type(g.table.find_or_register_array(elem_type)) } } return g.unwrap_generic(g.recheck_concrete_type(expr.typ)) } ast.SelectorExpr { return g.comptime_selector_type_expr_type(expr, fallback_type) } ast.Ident { if g.comptime.inside_comptime_for && expr.obj is ast.Var && expr.obj.ct_type_var != .no_comptime { typ := g.type_resolver.get_type_from_comptime_var(expr) if typ != 0 && typ != ast.void_type { return g.unwrap_generic(g.recheck_concrete_type(typ)) } } resolved_generic_type := g.comptime_generic_type_expr_ident_type(expr.name) if resolved_generic_type != 0 { return resolved_generic_type } if expr.name in g.type_resolver.type_map { return g.unwrap_generic(g.recheck_concrete_type(g.type_resolver.get_ct_type_or_default(expr.name, fallback_type))) } if util.is_generic_type_name(expr.name) && g.cur_fn != unsafe { nil } { return g.unwrap_generic(g.recheck_concrete_type(g.table.find_type(expr.name).set_flag(.generic))) } return g.resolved_expr_type(expr, fallback_type) } else { return g.resolved_expr_type(expr, fallback_type) } } } fn (mut g Gen) comptime_generic_type_expr_ident_type(name string) ast.Type { generic_names, concrete_types := g.comptime_current_generic_context() if generic_names.len > 0 && generic_names.len == concrete_types.len { idx := generic_names.index(name) if idx >= 0 && idx < concrete_types.len { return g.unwrap_generic(g.recheck_concrete_type(concrete_types[idx])) } } if g.has_active_call_generic_context() { idx := g.active_call_generic_names.index(name) if idx >= 0 && idx < g.active_call_concrete_types.len { return g.unwrap_generic(g.recheck_concrete_type(g.active_call_concrete_types[idx])) } } return 0 } fn (mut g Gen) comptime_current_generic_context() ([]string, []ast.Type) { if g.cur_fn == unsafe { nil } || g.cur_concrete_types.len == 0 { return []string{}, []ast.Type{} } mut generic_names := g.current_fn_generic_names() mut concrete_types := g.cur_concrete_types.clone() if generic_names.len == 0 || generic_names.len != concrete_types.len { recovered_generic_names, recovered_concrete_types := g.recover_specialized_generic_context_for(g.cur_fn.name) if recovered_generic_names.len > 0 && recovered_generic_names.len == recovered_concrete_types.len { generic_names = recovered_generic_names.clone() concrete_types = recovered_concrete_types.clone() } } if generic_names.len == 0 || generic_names.len != concrete_types.len { return []string{}, []ast.Type{} } return generic_names, concrete_types } fn (mut g Gen) comptime_typeof_generic_ident_type(ident ast.Ident) ast.Type { if ident.obj !is ast.Var { return 0 } var := ident.obj as ast.Var if var.generic_typ == 0 { return 0 } generic_names, concrete_types := g.comptime_current_generic_context() if generic_names.len == 0 || generic_names.len != concrete_types.len { return 0 } mut muttable := unsafe { &ast.Table(g.table) } if resolved := muttable.convert_generic_type(var.generic_typ, generic_names, concrete_types) { return g.unwrap_generic(g.recheck_concrete_type(resolved)) } unwrapped := muttable.unwrap_generic_type_ex(var.generic_typ, generic_names, concrete_types, true) if unwrapped != var.generic_typ { return g.unwrap_generic(g.recheck_concrete_type(unwrapped)) } return 0 } fn (mut g Gen) comptime_selector_type_expr_type(expr ast.SelectorExpr, fallback_type ast.Type) ast.Type { if expr.expr is ast.Ident && g.comptime.inside_comptime_for && expr.field_name in ['typ', 'unaliased_typ', 'indirections', 'pointee_type', 'payload_type', 'variant_types'] { ident := expr.expr as ast.Ident if ident.name == g.comptime.comptime_for_field_var || ident.name == g.comptime.comptime_for_variant_var || ident.name == g.comptime.comptime_for_method_param_var { typ := g.type_resolver.get_type_from_comptime_var(ident) if expr.field_name == 'unaliased_typ' { return g.table.unaliased_type(typ) } if expr.field_name in ['pointee_type', 'payload_type', 'variant_types'] { resolved := g.type_resolver.typeof_field_type(typ, expr.field_name) if resolved != ast.no_type { return g.unwrap_generic(g.recheck_concrete_type(resolved)) } } return g.unwrap_generic(g.recheck_concrete_type(typ)) } } if expr.field_name in ['typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type', 'variant_types'] { mut base_type := g.comptime_type_expr_type(expr.expr, expr.name_type) if (base_type == 0 || base_type == ast.void_type || base_type == ast.no_type) && expr.name_type != 0 { base_type = expr.name_type } if expr.field_name == 'unaliased_typ' { return g.table.unaliased_type(g.unwrap_generic(base_type)) } resolved := g.type_resolver.typeof_field_type(base_type, expr.field_name) if resolved != ast.no_type { return g.unwrap_generic(g.recheck_concrete_type(resolved)) } } return g.resolved_expr_type(expr, fallback_type) } fn (mut g Gen) comptime_zero_new_result_type(node ast.ComptimeCall, fallback_type ast.Type) ast.Type { if node.kind !in [.zero, .new] || node.args.len == 0 { return fallback_type } arg_fallback_type := if node.kind == .new && fallback_type.is_ptr() { fallback_type.deref() } else { fallback_type } mut resolved_type := g.comptime_type_expr_type(node.args[0].expr, arg_fallback_type) if resolved_type == 0 || resolved_type == ast.void_type || resolved_type == ast.no_type { resolved_type = arg_fallback_type } resolved_type = g.unwrap_generic(g.recheck_concrete_type(resolved_type)) return if node.kind == .new { resolved_type.ref() } else { resolved_type } } fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) { left_type := g.resolved_expr_type(node.left, node.left_type) if node.is_method && g.comptime.comptime_for_method != unsafe { nil } { g.selector_expr(ast.SelectorExpr{ pos: node.pos expr: node.left expr_type: left_type typ: g.type_resolver.get_type(node) field_name: g.comptime.comptime_for_method.name has_hidden_receiver: true }) return } is_interface_field := g.table.sym(left_type).kind == .interface if is_interface_field { g.write('*(') } g.expr(node.left) is_auto_heap_ident := node.left is ast.Ident && g.resolved_ident_is_auto_heap(node.left) // When `g.expr` writes an auto-heap ident, it emits `(*(name))` by default, // but skips the deref when it's an LHS / inside a selector LHS / assign-fn-var. // In the deref-skipped case the C result is a pointer, so we need `->`. auto_heap_no_deref := is_auto_heap_ident && (g.is_assign_lhs || g.inside_selector_lhs || g.inside_assign_fn_var) if g.unwrap_generic(left_type).is_ptr() || auto_heap_no_deref { g.write('->') } else { g.write('.') } // check for field.name if node.is_name && node.field_expr is ast.SelectorExpr { if node.field_expr.expr is ast.Ident { if node.field_expr.expr.name == g.comptime.comptime_for_field_var { _, field_name := g.resolve_comptime_selector_field(node, left_type) g.write(c_name(field_name)) if is_interface_field { g.write(')') } return } } } g.expr(node.field_expr) if is_interface_field { g.write(')') } } fn (mut g Gen) gen_comptime_selector(expr ast.ComptimeSelector) string { left_type := g.resolved_expr_type(expr.left, expr.left_type) is_auto_heap_ident := expr.left is ast.Ident && g.resolved_ident_is_auto_heap(expr.left) auto_heap_no_deref := is_auto_heap_ident && (g.is_assign_lhs || g.inside_selector_lhs || g.inside_assign_fn_var) arrow_or_dot := if left_type.is_ptr() || auto_heap_no_deref { '->' } else { '.' } mut field_name := if expr.typ_key.contains('|') { expr.typ_key.all_after('|') } else { g.comptime.comptime_for_field_value.name } if expr.field_expr is ast.SelectorExpr { if expr.field_expr.expr is ast.Ident && expr.field_expr.expr.name == g.comptime.comptime_for_field_var { field_name = g.comptime.comptime_for_field_value.name } } return '${expr.left.str()}${arrow_or_dot}${field_name}' } fn (mut g Gen) resolve_comptime_selector_field(node ast.ComptimeSelector, left_type ast.Type) (ast.StructField, string) { mut field_name := if node.typ_key.contains('|') { node.typ_key.all_after('|') } else { g.comptime.comptime_for_field_value.name } if node.field_expr is ast.SelectorExpr && g.comptime.comptime_for_field_var != '' && g.comptime.comptime_for_method_var == '' && node.field_expr.field_name == 'name' { if node.field_expr.expr is ast.Ident && node.field_expr.expr.name == g.comptime.comptime_for_field_var { field_name = g.comptime.comptime_for_field_value.name } } resolved_left_type := g.unwrap_generic(g.recheck_concrete_type(left_type)) left_sym := g.table.sym(resolved_left_type) field := g.table.find_field_with_embeds(left_sym, field_name) or { g.error('`${node.left}` has no field named `${field_name}`', node.left.pos()) } return field, field_name } fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { if node.kind == .compile_error || node.kind == .compile_warn { // handled by checker, this branch was not taken return } if node.kind == .embed_file { // $embed_file('/path/to/file') g.gen_embed_file_init(mut node) return } if node.kind == .env { // $env('ENV_VAR_NAME') // TODO: deprecate after support for $d() is stable val := util.cescaped_path(os.getenv(node.args_var)) g.write('_S("${val}")') return } if node.kind == .d { // $d('some_string',), affected by `-d some_string=actual_value` val := util.cescaped_path(node.compile_value) if node.result_type == ast.string_type { g.write('_S("${val}")') } else if node.result_type == ast.char_type { g.write("'${val}'") } else { g.write('${val}') } return } if node.kind in [.zero, .new] { result_type := g.comptime_zero_new_result_type(node, node.result_type) resolved_type := if node.kind == .new { result_type.deref() } else { result_type } default_value := g.comptime_zero_value(resolved_type) if node.kind == .new { g.write('HEAP(${g.styp(resolved_type)}, (${default_value}))') } else { g.write(default_value) } return } if node.kind == .res { if node.args_var != '' { g.write('${g.defer_return_tmp_var}.arg${node.args_var}') return } g.write('${g.defer_return_tmp_var}') return } if node.is_template { is_html := node.kind == .html mut cur_line := '' if !is_html { cur_line = g.go_before_last_stmt() } fn_name := g.fn_decl.name.replace('.', '__').to_lower() + node.pos.pos.str() for stmt in node.veb_tmpl.stmts { if stmt is ast.FnDecl { if stmt.name.starts_with('main.veb_tmpl') { prev_inside_veb_tmpl := g.inside_veb_tmpl prev_veb_filter_fn_name := g.veb_filter_fn_name if is_html { g.inside_veb_tmpl = true g.veb_filter_fn_name = 'veb__filter' } // insert stmts from veb_tmpl fn g.stmts(stmt.stmts.filter(it !is ast.Return)) // if is_html { g.inside_veb_tmpl = prev_inside_veb_tmpl g.veb_filter_fn_name = prev_veb_filter_fn_name } break } } } if is_html { g.writeln('veb__Context_html(${g.veb_context_html_arg()}, _tmpl_res_${fn_name});') g.writeln('strings__Builder_free(&sb_${fn_name});') g.writeln('builtin__string_free(&_tmpl_res_${fn_name});') } else { // return $tmpl string if g.inside_return_tmpl { g.writeln('return _tmpl_res_${fn_name};') } else { g.write(cur_line) g.write('_tmpl_res_${fn_name}') } } return } mut left_type := g.resolved_expr_type(node.left, node.left_type) if left_type == 0 { left_type = node.left_type } left_type = g.unwrap_generic(g.recheck_concrete_type(left_type)) sym := g.table.sym(left_type) g.trace_autofree('// \$method call. sym="${sym.name}"') if node.method_name == 'method' { // `app.$method()` m := g.table.find_method(sym, g.comptime.comptime_for_method.name) or { return } /* vals := m.attrs[0].split('/') args := vals.filter(it.starts_with(':')).map(it[1..]) println(vals) for val in vals { } */ if g.inside_call && m.return_type == ast.void_type { g.error('method `${m.name}()` (no value) used as value', node.pos) } expand_strs := g.comptime_call_expands_string_args(m, node) mut has_decompose := !m.is_variadic && node.args.any(it.expr is ast.ArrayDecompose) && !expand_strs // check argument length and types if m.params.len - 1 != node.args.len && !expand_strs { if g.inside_call { g.error('expected ${m.params.len - 1} arguments to method ${sym.name}.${m.name}, but got ${node.args.len}', node.pos) } else { if !has_decompose { // do not generate anything if the argument lengths don't match g.writeln('/* skipping ${sym.name}.${m.name} due to mismatched arguments list: node.args=${node.args.len} m.params=${m.params.len} */') // Adding a println(_S(...)) like this breaks options return } } } mut has_unwrap := false if !g.inside_call && node.or_block.kind != .block && m.return_type.has_option_or_result() { if !(g.assign_ct_type[node.pos.pos] != 0 && g.assign_ct_type[node.pos.pos].has_option_or_result()) { g.write('(*(${g.base_type(m.return_type)}*)') has_unwrap = true } } // TODO: check argument types // Use the declaring receiver type for the symbol prefix, so alias // comptime calls can dispatch to inherited methods. method_receiver_type := g.unwrap_generic(m.params[0].typ) g.write('${g.cc_type(method_receiver_type, false)}_${g.comptime.comptime_for_method.name}(') // try to see if we need to pass a pointer if mut node.left is ast.Ident { if mut node.left.obj is ast.Var { if m.params[0].typ.is_ptr() && !node.left.obj.typ.is_ptr() && !node.left.obj.is_auto_deref { g.write('&') } else if !m.params[0].typ.is_ptr() && node.left.obj.typ.is_ptr() { g.write('*'.repeat(node.left.obj.typ.nr_muls())) } else if !m.params[0].typ.is_ptr() && node.left.obj.is_auto_deref { g.write('*') } } } g.expr(node.left) if m.params.len > 1 { g.write(', ') } for i in 1 .. m.params.len { if mut node.left is ast.Ident { if m.params[i].name == node.left.name { continue } } if i - 1 <= node.args.len - 1 && has_decompose && node.args[i - 1].expr is ast.ArrayDecompose { mut d_count := 0 for d_i in i .. m.params.len { g.write('*(${g.styp(m.params[d_i].typ)}*)builtin__array_get(') g.expr(ast.Expr(node.args[i - 1].expr)) g.write(', ${d_count})') if d_i < m.params.len - 1 { g.write(', ') } d_count++ } break } else if i - 1 < node.args.len - 1 { g.expr(node.args[i - 1].expr) if i < m.params.len - 1 { g.write(', ') } } else if !expand_strs && i == node.args.len { g.expr(node.args[i - 1].expr) break } else { // last argument; try to expand if it's []string idx := i - node.args.len last_arg := g.expr_string(node.args.last().expr) // t := g.table.sym(m.params[i].typ) // g.write('/*nr_args=${node.args.len} m.params.len=${m.params.len} i=${i} t=${t.name} ${m.params}*/') if m.params[i].typ.is_int() || m.params[i].typ.is_float() || m.params[i].typ.idx() == ast.bool_type_idx { // Gets the type name and cast the string to the type with the string_ function type_name := g.table.type_symbols[int(m.params[i].typ)].str() g.write('builtin__string_${type_name}(((string*)${last_arg}.data) [${idx}])') } else { g.write('((string*)${last_arg}.data) [${idx}] ') } if i < m.params.len - 1 { g.write(', ') } } } g.write(')') if has_unwrap { g.write('.data)') } if node.or_block.kind != .absent && m.return_type.has_option_or_result() { if !g.inside_assign { cur_line := g.go_before_last_stmt() tmp_var := g.new_tmp_var() g.write2('${g.styp(m.return_type)} ${tmp_var} = ', cur_line) g.or_block(tmp_var, node.or_block, m.return_type) } } return } mut j := 0 for method in sym.methods { // if method.return_type != ast.void_type { if method.return_type != node.result_type { continue } if method.params.len != 1 { continue } // receiver := method.args[0] // if !p.expr_var.ptr { // p.error('`${p.expr_var.name}` needs to be a reference') // } amp := '' // if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' } if node.is_template { if j > 0 { g.write(' else ') } g.write('if (builtin__string__eq(${node.method_name}, _S("${method.name}"))) ') } g.write('${g.cc_type(left_type, false)}_${method.name}(${amp} ') g.expr(node.left) g.writeln(');') j++ } } fn cgen_attrs(attrs []ast.Attr) []string { mut res := []string{cap: attrs.len} for attr in attrs { mut s := attr.name if attr.has_arg { mut arg := attr.arg if attr.kind == .string { quote := if attr.quote == `"` { '"' } else { "'" } arg = '${quote}${arg}${quote}' } s += ': ${arg}' } res << '_S("${cescape_nonascii(util.smart_quote(s, false))}")' } return res } fn cgen_vattrs(attrs []ast.Attr) []string { mut res := []string{cap: attrs.len} for attr in attrs { name := cescape_nonascii(util.smart_quote(attr.name, false)) arg := cescape_nonascii(util.smart_quote(attr.arg, false)) res << '((VAttribute){.name=_S("${name}"),.has_arg=${attr.has_arg},.arg=_S("${arg}"),.kind=AttributeKind__${attr.kind}})' } return res } fn (mut g Gen) comptime_at(node ast.AtExpr) { if node.kind == .vmod_file { val := cescape_nonascii(util.smart_quote(node.val, false)) g.write('_S("${val}")') } else { val := node.val.replace('\\', '\\\\') g.write('_S("${val}")') } } // gen_branch_context_string generate current branches context string. // context include generic types, `$for`. fn (mut g Gen) recover_specialized_generic_context_for(fn_name string) ([]string, []ast.Type) { if fn_name == '' { return []string{}, []ast.Type{} } if t_idx := fn_name.index('_T_') { if t_idx <= 0 { return []string{}, []ast.Type{} } } else { return []string{}, []ast.Type{} } for generic_fn in g.file.generic_fns { if generic_fn.generic_names.len == 0 { // Methods on generic structs may have no explicit generic_names // but still have receiver generics. if !(generic_fn.is_method && generic_fn.receiver.typ.has_flag(.generic)) { continue } } for base_name in [generic_fn.name, generic_fn.fkey()] { if !fn_name.starts_with(base_name + '_T_') { continue } mut generic_names := generic_fn.generic_names.clone() fkey := generic_fn.fkey() for concrete_types in g.table.fn_generic_types[fkey] { if concrete_types.any(it.has_flag(.generic)) { continue } if g.generic_fn_name(concrete_types, base_name) != fn_name { if !generic_fn.is_method { continue } receiver_generic_names := g.table.generic_type_names(generic_fn.receiver.typ) if receiver_generic_names.len == 0 || concrete_types.len <= receiver_generic_names.len { continue } method_only_concrete_types := concrete_types[receiver_generic_names.len..] if g.generic_fn_name(method_only_concrete_types, base_name) != fn_name { continue } } if generic_fn.is_method && concrete_types.len > generic_names.len { receiver_generic_names := g.table.generic_type_names(generic_fn.receiver.typ) if receiver_generic_names.len > 0 { mut effective_generic_names := []string{cap: receiver_generic_names.len + generic_names.len} for name in receiver_generic_names { if name !in effective_generic_names { effective_generic_names << name } } for name in generic_names { if name !in effective_generic_names { effective_generic_names << name } } if effective_generic_names.len == concrete_types.len { generic_names = effective_generic_names.clone() } } } if generic_names.len == concrete_types.len { return generic_names, concrete_types } return []string{}, concrete_types } } } return []string{}, []ast.Type{} } fn (mut g Gen) gen_branch_context_string() string { mut arr := []string{} // gen `T=int,X=string` mut generic_names := if g.cur_fn != unsafe { nil } { g.cur_fn.generic_names.clone() } else { []string{} } // For methods on generic structs with no explicit generic params, // extract generic names from the receiver type (mirrors checker's effective_fn_generic_names). if generic_names.len == 0 && g.cur_fn != unsafe { nil } && g.cur_fn.is_method && g.cur_fn.receiver.typ.has_flag(.generic) { generic_names = g.table.generic_type_names(g.cur_fn.receiver.typ) } mut concrete_types := g.cur_concrete_types.clone() if generic_names.len == 0 || generic_names.len != concrete_types.len { recovered_generic_names, recovered_concrete_types := g.recover_specialized_generic_context_for(if g.cur_fn != unsafe { nil } { g.cur_fn.name } else { '' }) if recovered_generic_names.len > 0 && recovered_generic_names.len == recovered_concrete_types.len { generic_names = recovered_generic_names.clone() concrete_types = recovered_concrete_types.clone() } } if generic_names.len > 0 && generic_names.len == concrete_types.len { for i in 0 .. generic_names.len { arr << generic_names[i] + '=' + util.strip_main_name(g.table.type_to_str(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 (g &Gen) can_preserve_comptime_if_condition_in_c(cond ast.Expr) bool { match cond { ast.BoolLiteral { return true } ast.Ident { // CPU architecture and compiler conditions are safe to preserve as C // preprocessor checks because they are mutually exclusive categories // derived from real compiler intrinsics (see cheaders.v). // This is important for Android builds where one C file is compiled // by the NDK for multiple architectures (arm64, armv7a, x86, x86_64). return cond.name in ast.valid_comptime_if_platforms || cond.name in ast.valid_comptime_if_compilers } ast.ParExpr { return g.can_preserve_comptime_if_condition_in_c(cond.expr) } ast.PrefixExpr { return cond.op == .not && g.can_preserve_comptime_if_condition_in_c(cond.right) } ast.InfixExpr { return cond.op in [.and, .logical_or] && g.can_preserve_comptime_if_condition_in_c(cond.left) && g.can_preserve_comptime_if_condition_in_c(cond.right) } ast.PostfixExpr { return cond.op == .question && cond.expr is ast.Ident } else { return false } } } fn (g &Gen) comptime_if_condition_for_c(cond ast.Expr, result ast.ComptTimeCondResult) string { if g.pref.output_cross_c || g.can_preserve_comptime_if_condition_in_c(cond) { return result.c_str } // For normal C generation, honor the branch result that V already resolved. // Re-evaluating built-in comptime conditions in the C preprocessor can pick // a different branch than the V checker, e.g. `termux` vs `linux`. return if result.val { '1' } else { '0' } } fn (mut g Gen) comptime_if(node ast.IfExpr) { tmp_var := g.new_tmp_var() mut inferred_typ := node.typ if node.is_expr && node.typ == ast.void_type && node.branches.len > 0 { for branch in node.branches { if branch.stmts.len > 0 { last_stmt := branch.stmts.last() if last_stmt is ast.ExprStmt { expr_typ := g.type_resolver.get_type_or_default(last_stmt.expr, last_stmt.typ) if expr_typ != ast.void_type && !expr_typ.has_flag(.generic) { inferred_typ = expr_typ break } } } } } is_opt_or_result := inferred_typ.has_option_or_result() is_array_fixed := g.table.final_sym(inferred_typ).kind == .array_fixed line := if node.is_expr && inferred_typ != ast.void_type { stmt_str := g.go_before_last_stmt() g.write(util.tabs(g.indent)) styp := g.styp(inferred_typ) g.writeln('${styp} ${tmp_var};') stmt_str } else { '' } // save node for processing hash stmts // we only save the first node, when there is embedded $if old_curr_comptime_node := g.curr_comptime_node if !g.comptime.inside_comptime_if { g.curr_comptime_node = ast.Expr(node) } defer { g.curr_comptime_node = old_curr_comptime_node } mut comptime_branch_context_str := g.gen_branch_context_string() mut is_true := ast.ComptTimeCondResult{} for i, branch in node.branches { g.push_new_comptime_info() g.comptime.inside_comptime_if = true start_pos := g.out.len // `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 is the branch's id. // This format must match what is in `checker`. mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' if g.comptime.inside_comptime_for && g.comptime.comptime_for_field_var != '' { idx_str += '|field_type=${g.comptime.comptime_for_field_type}|' } 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 { // No checker data found for this key. This can happen when: // 1. The markused walker spuriously registers generic instantiations // that the checker never evaluated (e.g. from dead comptime branches). // 2. Type alias resolution causes key mismatches. // Default all branches to false (#if 0) since the function body // is either dead code or will be generated correctly by another // instantiation with matching types. is_true = ast.ComptTimeCondResult{} } if !node.has_else || i < node.branches.len - 1 { if i == 0 { g.write('#if ') } else { g.write('#elif ') } g.writeln(g.comptime_if_condition_for_c(branch.cond, is_true)) $if debug_comptime_branch_context ? { g.writeln('/* ${node.branches[i].cond} | generic=[${comptime_branch_context_str}] */') } } else { g.writeln('#else') } expr_str := g.out.last_n(g.out.len - start_pos).trim_space() if expr_str != '' { if g.defer_ifdef != '' { g.defer_ifdef += '\n' + '\t'.repeat(g.indent + 1) } g.defer_ifdef += expr_str } if node.is_expr { if is_true.val { g.bind_comptime_if_generic_types(branch.cond) len := branch.stmts.len if len > 0 { last := branch.stmts.last() as ast.ExprStmt if len > 1 { g.indent++ g.writeln('{') g.stmts(branch.stmts[..len - 1]) g.set_current_pos_as_last_stmt_pos() prev_skip_stmt_pos := g.skip_stmt_pos g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') g.stmt(last) g.writeln('builtin___result_ok(&(${g.base_type(inferred_typ)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(inferred_typ)}));') g.writeln('}') } else { g.write('\t${tmp_var} = ') g.stmt(last) } g.skip_stmt_pos = prev_skip_stmt_pos g.writeln2(';', '}') g.indent-- g.write_defer_stmts(branch.scope, false, branch.pos) } else { g.indent++ g.set_current_pos_as_last_stmt_pos() prev_skip_stmt_pos := g.skip_stmt_pos g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() base_styp := g.base_type(inferred_typ) g.write('{ ${base_styp} ${tmp_var2} = ') g.stmt(last) g.writeln('builtin___result_ok(&(${base_styp}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${base_styp}));') g.writeln('}') } else if is_array_fixed { tmp_var2 := g.new_tmp_var() base_styp := g.base_type(inferred_typ) g.write('{ ${base_styp} ${tmp_var2} = ') g.stmt(last) if g.out.last_n(2).contains(';') { g.go_back(2) } g.writeln(';') g.writeln2('memcpy(&${tmp_var}, &${tmp_var2}, sizeof(${base_styp}));', '}') } else { g.write('${tmp_var} = ') g.stmt(last) } g.skip_stmt_pos = prev_skip_stmt_pos g.writeln(';') g.indent-- g.write_defer_stmts(branch.scope, false, branch.pos) } } } } else { // Only wrap the contents in {} if we're inside a function, not on the top level scope should_create_scope := unsafe { g.fn_decl != 0 } if should_create_scope { g.writeln('{') } if is_true.val || g.pref.output_cross_c { g.bind_comptime_if_generic_types(branch.cond) g.stmts(branch.stmts) } if should_create_scope { g.write_defer_stmts(branch.scope, false, branch.pos) g.writeln('}') } } g.pop_comptime_info() } g.defer_ifdef = '' g.writeln('#endif') if node.is_expr { g.write('${line}${tmp_var}') } } fn (mut g Gen) bind_comptime_if_generic_types(cond ast.Expr) { match cond { ast.ParExpr { g.bind_comptime_if_generic_types(cond.expr) } ast.PrefixExpr { g.bind_comptime_if_generic_types(cond.right) } ast.Likely { g.bind_comptime_if_generic_types(cond.expr) } ast.InfixExpr { match cond.op { .and, .logical_or { g.bind_comptime_if_generic_types(cond.left) g.bind_comptime_if_generic_types(cond.right) } .key_is { if cond.right is ast.TypeNode { g.type_resolver.bind_matching_generic_type(g.get_expr_type(cond.left), cond.right.typ) } } .key_in { if cond.right is ast.ArrayInit { left_type := g.get_expr_type(cond.left) for expr in cond.right.exprs { if expr is ast.TypeNode && g.type_resolver.bind_matching_generic_type(left_type, expr.typ) { break } } } } else {} } } else {} } } fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type { match cond { ast.Ident { if cond.name in g.type_resolver.type_map { return g.type_resolver.get_ct_type_or_default(cond.name, ast.void_type) } return g.unwrap_generic(g.type_resolver.get_type_or_default(cond, cond.obj.typ)) } ast.TypeNode { return g.unwrap_generic(cond.typ) } ast.SelectorExpr { if cond.name_type != 0 && cond.field_name in ['key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type', 'variant_types'] { return g.type_resolver.typeof_field_type(cond.name_type, cond.field_name) } if cond.gkind_field == .typ { return g.unwrap_generic(cond.name_type) } else if cond.gkind_field == .unaliased_typ { return g.table.unaliased_type(g.unwrap_generic(cond.name_type)) } else if cond.gkind_field == .indirections { return ast.int_type } else { if cond.expr is ast.TypeOf { return g.type_resolver.typeof_field_type(g.type_resolver.typeof_type(cond.expr.expr, cond.name_type), cond.field_name) } name := '${cond.expr}.${cond.field_name}' if name in g.type_resolver.type_map { return g.type_resolver.get_ct_type_or_default(name, ast.void_type) } else { return g.unwrap_generic(cond.typ) } } } ast.IntegerLiteral { return ast.int_type } ast.BoolLiteral { return ast.bool_type } ast.StringLiteral { return ast.string_type } ast.CharLiteral { return ast.char_type } ast.FloatLiteral { return ast.f64_type } else { return ast.void_type } } } // push_new_comptime_info saves the current comptime information fn (mut g Gen) push_new_comptime_info() { g.clear_type_resolution_caches() g.type_resolver.info_stack << type_resolver.ResolverInfo{ saved_type_map: g.type_resolver.type_map.clone() inside_comptime_for: g.comptime.inside_comptime_for inside_comptime_if: g.comptime.inside_comptime_if comptime_for_variant_var: g.comptime.comptime_for_variant_var comptime_for_field_var: g.comptime.comptime_for_field_var 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 comptime_loop_id: g.comptime.comptime_loop_id++ } } // pop_comptime_info pops the current comptime information frame fn (mut g Gen) pop_comptime_info() { old := g.type_resolver.info_stack.pop() g.type_resolver.type_map = old.saved_type_map.clone() g.comptime.inside_comptime_for = old.inside_comptime_for g.comptime.inside_comptime_if = old.inside_comptime_if g.comptime.comptime_for_variant_var = old.comptime_for_variant_var g.comptime.comptime_for_field_var = old.comptime_for_field_var 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 g.clear_type_resolution_caches() } fn (mut g Gen) eval_comptime_for_if_cond(cond ast.Expr) ?bool { match cond { ast.ParExpr { return g.eval_comptime_for_if_cond(cond.expr) } ast.PrefixExpr { if cond.op == .not { return !(g.eval_comptime_for_if_cond(cond.right)?) } } ast.InfixExpr { match cond.op { .and, .logical_or { left := g.eval_comptime_for_if_cond(cond.left)? right := g.eval_comptime_for_if_cond(cond.right)? return if cond.op == .and { left && right } else { left || right } } .key_is, .not_is { if cond.left is ast.SelectorExpr && cond.right is ast.TypeNode { if cond.left.field_name == 'return_type' && cond.left.expr is ast.Ident && cond.left.expr.name == g.comptime.comptime_for_method_var { left_type := g.table.unaliased_type(g.unwrap_generic(g.comptime.comptime_for_method_ret_type)) right_type := g.table.unaliased_type(g.unwrap_generic(cond.right.typ)) is_true := left_type == right_type return if cond.op == .key_is { is_true } else { !is_true } } } } .eq, .ne { if cond.left is ast.SelectorExpr && cond.right is ast.StringLiteral { if cond.left.field_name == 'name' && cond.left.expr is ast.Ident && cond.left.expr.name == g.comptime.comptime_for_method_var { is_true := g.comptime.comptime_for_method.name == cond.right.val return if cond.op == .eq { is_true } else { !is_true } } } } else {} } } else {} } return none } fn (mut g Gen) comptime_if_has_live_branch(node ast.IfExpr) bool { if g.pref.output_cross_c || node.has_else { return true } comptime_branch_context_str := g.gen_branch_context_string() for branch in node.branches { if evaluated := g.eval_comptime_for_if_cond(branch.cond) { if evaluated { return true } continue } mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' if g.comptime.inside_comptime_for && g.comptime.comptime_for_field_var != '' { idx_str += '|field_type=${g.comptime.comptime_for_field_type}|' } if is_true := g.table.comptime_is_true[idx_str] { if is_true.val { return true } } else { return true } } return false } fn (mut g Gen) comptime_for_iteration_has_live_stmts(stmts []ast.Stmt) bool { for stmt in stmts { match stmt { ast.EmptyStmt, ast.SemicolonStmt {} ast.ExprStmt { if stmt.expr is ast.IfExpr && stmt.expr.is_comptime { if g.comptime_if_has_live_branch(stmt.expr) { return true } } else { return true } } else { return true } } } return false } fn (mut g Gen) comptime_for(node ast.ComptimeFor) { resolved_typ := if node.expr !is ast.EmptyExpr { mut expr_typ := g.unwrap_generic(g.recheck_concrete_type(g.resolved_expr_type(node.expr, node.typ))) resolved_ct_typ := g.type_resolver.get_type(node.expr) if resolved_ct_typ != ast.void_type { expr_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_ct_typ)) } expr_typ } else if node.typ != g.field_data_type { g.unwrap_generic(node.typ) } else { g.comptime.comptime_for_field_type } // When the resolved type is FieldData, the expression refers to a comptime // field variable (e.g. `$for f3 in f.fields` where `f` comes from an outer // `$for f in T.fields`). In that case use the actual field type from the // outer comptime loop instead of the FieldData descriptor type. for_typ := if resolved_typ == g.field_data_type { g.comptime.comptime_for_field_type } else { resolved_typ } sym := g.table.final_sym(for_typ) iter_sym_name := if node.kind == .methods { g.table.sym(for_typ).name } else { sym.name } g.writeln('/* \$for ${node.val_var} in ${iter_sym_name}.${node.kind.str()} */ {') g.indent++ mut i := 0 old_defer_stmts := g.defer_stmts if node.kind == .methods { methods := g.table.get_type_methods(for_typ) if methods.len > 0 { g.writeln('FunctionData ${node.val_var} = {0};') } typ_veb_result := g.table.find_type('veb.Result') for method in methods { g.defer_stmts = old_defer_stmts g.push_new_comptime_info() g.comptime.inside_comptime_for = true // filter veb route methods (non-generic method) if method.receiver_type != 0 && method.return_type == typ_veb_result { rec_sym := g.table.sym(method.receiver_type) if rec_sym.kind == .struct { if _ := g.table.find_field_with_embeds(rec_sym, 'Context') { if method.generic_names.len > 0 || (method.params.len > 1 && method.attrs.len == 0) { g.pop_comptime_info() continue } } } } g.comptime.comptime_for_method = unsafe { &method } g.comptime.comptime_for_method_var = node.val_var g.comptime.comptime_for_method_ret_type = method.return_type if !g.comptime_for_iteration_has_live_stmts(node.stmts) { g.pop_comptime_info() continue } g.writeln('/* method ${i} : ${method.name} */ {') g.writeln('\t${node.val_var}.name = _S("${method.name}");') mlocation := util.cescaped_path(util.path_styled_for_error_messages(method.file)) g.writeln('\t${node.val_var}.location = _S("${mlocation}:${method.name_pos.line_nr + 1}:${method.name_pos.col}");') if method.attrs.len == 0 { g.writeln('\t${node.val_var}.attrs = builtin____new_array_with_default(0, 0, sizeof(string), 0);') g.writeln('\t${node.val_var}.attributes = builtin____new_array_with_default(0, 0, sizeof(VAttribute), 0);') } else { attrs := cgen_attrs(method.attrs) vattrs := cgen_vattrs(method.attrs) g.writeln( '\t${node.val_var}.attrs = builtin__new_array_from_c_array(${attrs.len}, ${attrs.len}, sizeof(string), _MOV((string[${attrs.len}]){' + attrs.join(', ') + '}));\n') g.writeln( '\t${node.val_var}.attributes = builtin__new_array_from_c_array(${vattrs.len}, ${vattrs.len}, sizeof(VAttribute), _MOV((VAttribute[${vattrs.len}]){' + vattrs.join(', ') + '}));\n') } if method.params.len < 2 { // 0 or 1 (the receiver) args g.writeln('\t${node.val_var}.args = builtin____new_array_with_default(0, 0, sizeof(FunctionParam), 0);') } else { len := method.params.len - 1 g.write('\t${node.val_var}.args = builtin__new_array_from_c_array(${len}, ${len}, sizeof(FunctionParam), _MOV((FunctionParam[${len}]){') // Skip receiver arg for j, arg in method.params[1..] { typ := arg.typ.idx() g.write('{${typ.str()}, _S("${arg.name}")}') if j < len - 1 { g.write(', ') } g.type_resolver.update_ct_type('${node.val_var}.args[${j}].typ', typ) } g.writeln('}));\n') } mut sig := 'fn (' // skip the first (receiver) arg for j, arg in method.params[1..] { // TODO: ignore mut/pts in sig for now typ := arg.typ.set_nr_muls(0) sig += g.table.sym(typ).name if j < method.params.len - 2 { sig += ', ' } } sig += ')' ret_type := g.table.sym(method.return_type).name if ret_type != 'void' { sig += ' ${ret_type}' } typ := g.table.find_type(sig) // TODO: type aliases ret_typ := method.return_type g.writeln('\t${node.val_var}.typ = ${int(typ)};') g.writeln('\t${node.val_var}.return_type = ${int(ret_typ.idx())};') g.type_resolver.update_ct_type('${node.val_var}.return_type', ret_typ) g.type_resolver.update_ct_type('${node.val_var}.typ', typ) g.stmts(node.stmts) g.write_defer_stmts(node.scope, false, node.pos) i++ g.writeln('}') g.pop_comptime_info() } } else if node.kind == .fields { if sym.kind in [.struct, .interface] { fields := match sym.info { ast.Struct { sym.info.fields } ast.Interface { sym.info.fields } else { g.error('comptime field lookup is supported only for structs and interfaces, and ${sym.name} is neither', node.pos) []ast.StructField{} } } if fields.len > 0 { g.writeln('\tFieldData ${node.val_var} = {0};') } for field in fields { g.defer_stmts = old_defer_stmts 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 resolved_field_typ := g.unwrap_generic(field.typ) g.comptime.comptime_for_field_type = resolved_field_typ 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 = builtin____new_array_with_default(0, 0, sizeof(string), 0);') } else { attrs := cgen_attrs(field.attrs) g.writeln( '\t${node.val_var}.attrs = builtin__new_array_from_c_array(${attrs.len}, ${attrs.len}, sizeof(string), _MOV((string[${attrs.len}]){' + attrs.join(', ') + '}));\n') } field_sym := g.table.sym(resolved_field_typ) styp := resolved_field_typ unaliased_styp := g.table.unaliased_type(styp) 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};') g.writeln('\t${node.val_var}.is_embed = ${field.is_embed};') g.writeln('\t${node.val_var}.is_shared = ${resolved_field_typ.has_flag(.shared_f)};') g.writeln('\t${node.val_var}.is_atomic = ${resolved_field_typ.has_flag(.atomic_f)};') g.writeln('\t${node.val_var}.is_option = ${resolved_field_typ.has_flag(.option)};') g.writeln('\t${node.val_var}.is_array = ${field_sym.kind in [.array, .array_fixed]};') g.writeln('\t${node.val_var}.is_map = ${field_sym.kind == .map};') g.writeln('\t${node.val_var}.is_chan = ${field_sym.kind == .chan};') g.writeln('\t${node.val_var}.is_struct = ${field_sym.kind == .struct};') g.writeln('\t${node.val_var}.is_alias = ${field_sym.kind == .alias};') g.writeln('\t${node.val_var}.is_enum = ${field_sym.kind == .enum};') g.writeln('\t${node.val_var}.indirections = ${resolved_field_typ.nr_muls()};') g.type_resolver.update_ct_type('${node.val_var}.typ', resolved_field_typ) g.type_resolver.update_ct_type('${node.val_var}.unaliased_typ', unaliased_styp) g.stmts(node.stmts) g.write_defer_stmts(node.scope, false, node.pos) i++ g.writeln('}') g.pop_comptime_info() } } } else if node.kind == .values { if sym.kind == .enum { if sym.info is ast.Enum { if sym.info.vals.len > 0 { g.writeln('\tEnumData ${node.val_var} = {0};') } for val in sym.info.vals { g.defer_stmts = old_defer_stmts 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) g.writeln('/* enum vals ${i} */ {') g.writeln('\t${node.val_var}.name = _S("${val}");') g.write('\t${node.val_var}.value = ') if g.pref.translated && node.typ.is_number() { g.writeln('_const_main__${val};') } else { node_sym := g.table.sym(g.unwrap_generic(node.typ)) if node_sym.info is ast.Alias { g.writeln('${g.styp(node_sym.info.parent_type)}__${val};') } else { g.writeln('${g.styp(node.typ)}__${val};') } } enum_attrs := sym.info.attrs[val] if enum_attrs.len == 0 { g.writeln('\t${node.val_var}.attrs = builtin____new_array_with_default(0, 0, sizeof(string), 0);') } else { attrs := cgen_attrs(enum_attrs) g.writeln( '\t${node.val_var}.attrs = builtin__new_array_from_c_array(${attrs.len}, ${attrs.len}, sizeof(string), _MOV((string[${attrs.len}]){' + attrs.join(', ') + '}));\n') } g.stmts(node.stmts) g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') i++ g.pop_comptime_info() } } } } else if node.kind == .attributes { attrs := g.table.get_attrs(sym) if attrs.len > 0 { g.writeln('\tVAttribute ${node.val_var} = {0};') for attr in attrs { g.defer_stmts = old_defer_stmts 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)}");') g.writeln('\t${node.val_var}.kind = AttributeKind__${attr.kind};') g.stmts(node.stmts) g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') i++ g.pop_comptime_info() } } } else if node.kind == .variants { if sym.info is ast.SumType { if sym.info.variants.len > 0 { g.writeln('\tVariantData ${node.val_var} = {0};') } g.comptime.inside_comptime_for = true for variant in sym.info.variants { g.defer_stmts = old_defer_stmts 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.table.type_to_str(variant)} */ {') g.writeln('\t${node.val_var}.typ = ${int(variant)};\t// ') g.stmts(node.stmts) g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') i++ g.pop_comptime_info() } } } else if node.kind == .params { func := if sym.info is ast.FnType { &sym.info.func } else { g.comptime.comptime_for_method } if func.params.len > 0 { g.writeln('\tFunctionParam ${node.val_var} = {0};') } params := if func.is_method { func.params[1..] } else { func.params } for param in params { g.defer_stmts = old_defer_stmts 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} : ${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.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') i++ g.pop_comptime_info() } } g.defer_stmts = old_defer_stmts g.indent-- g.writeln('}// \$for') } // comptime_selector_type computes the selector type from an comptime var fn (mut g Gen) comptime_selector_type(node ast.SelectorExpr) ast.Type { if !(node.expr is ast.Ident && node.expr.ct_expr) { return node.expr_type } prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once g.prevent_sum_type_unwrapping_once = false mut typ := g.type_resolver.get_type(node.expr) if node.expr is ast.Ident && node.expr.obj is ast.Var { ct_var := node.expr.obj.ct_type_var if ct_var == .generic_param || ct_var == .generic_var { if scope_var := node.expr.scope.find_var(node.expr.name) { if scope_var.ct_type_var == ct_var && g.cur_fn != unsafe { nil } && g.cur_fn.generic_names.len > 0 { // For generic_param/generic_var, node.obj.typ is a stale // copy from checker time. Use the refreshed scope var. typ = scope_var.typ } } } } if node.expr.is_auto_deref_var() { if node.expr is ast.Ident { if node.expr.obj is ast.Var { typ = node.expr.obj.typ } } } if g.comptime.inside_comptime_for && typ == g.enum_data_type && node.field_name == 'value' { // for comp-time enum.values return g.type_resolver.get_ct_type_or_default('${g.comptime.comptime_for_enum_var}.typ', ast.void_type) } field_name := node.field_name sym := g.table.sym(typ) final_sym := g.table.final_sym(typ) if (typ.has_flag(.variadic) || final_sym.kind == .array_fixed) && field_name == 'len' { return ast.int_type } if sym.kind == .chan { if field_name == 'closed' { return ast.bool_type } else if field_name in ['len', 'cap'] { return ast.u32_type } } mut has_field := false mut field := ast.StructField{} if field_name.len > 0 && field_name[0].is_capital() && sym.language == .v { if sym.info is ast.Struct { // x.Foo.y => access the embedded struct for embed in sym.info.embeds { embed_sym := g.table.sym(embed) if embed_sym.embed_name() == field_name { return embed } } } } else { if f := g.table.find_field(sym, field_name) { has_field = true field = f } else { // look for embedded field has_field = true g.table.find_field_from_embeds(sym, field_name) or { has_field = false } } if typ.has_flag(.generic) && !has_field { gs := g.table.sym(g.unwrap_generic(typ)) if f := g.table.find_field(gs, field_name) { has_field = true field = f } else { // look for embedded field has_field = true g.table.find_field_from_embeds(gs, field_name) or { has_field = false } } } } if has_field { field_sym := g.table.sym(field.typ) if field_sym.kind in [.sum_type, .interface] { if !prevent_sum_type_unwrapping_once { scope_field := node.scope.find_struct_field(node.expr.str(), typ, field_name) if scope_field != unsafe { nil } { return scope_field.smartcasts.last() } } } return field.typ } if mut method := g.table.sym(g.unwrap_generic(typ)).find_method_with_generic_parent(field_name) { method.params = method.params[1..] method.name = '' fn_type := ast.new_type(g.table.find_or_register_fn_type(method, false, true)) return fn_type } if sym.kind !in [.struct, .aggregate, .interface, .sum_type] { if sym.kind != .placeholder { unwrapped_sym := g.table.sym(g.unwrap_generic(typ)) if unwrapped_sym.kind == .array_fixed && node.field_name == 'len' { return ast.int_type } } } return node.expr_type } fn (mut g Gen) comptime_match(node ast.MatchExpr) { tmp_var := g.new_tmp_var() mut inferred_typ := node.return_type if node.is_expr && (node.return_type == ast.void_type || node.return_type.idx() == 0) && node.branches.len > 0 { for branch in node.branches { if branch.stmts.len > 0 { last_stmt := branch.stmts.last() if last_stmt is ast.ExprStmt { expr_typ := g.type_resolver.get_type_or_default(last_stmt.expr, last_stmt.typ) if expr_typ != ast.void_type && expr_typ.idx() != 0 && !expr_typ.has_flag(.generic) { inferred_typ = expr_typ break } } } } } is_opt_or_result := inferred_typ.has_option_or_result() line := if node.is_expr && inferred_typ != ast.void_type && inferred_typ.idx() != 0 { stmt_str := g.go_before_last_stmt() g.write(util.tabs(g.indent)) styp := g.styp(inferred_typ) g.writeln('${styp} ${tmp_var};') stmt_str } else { '' } mut comptime_branch_context_str := g.gen_branch_context_string() mut is_true := ast.ComptTimeCondResult{} for i, branch in node.branches { // `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 is the branch's id. // This format must match what is in `checker`. mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' if g.comptime.inside_comptime_for && g.comptime.comptime_for_field_var != '' { idx_str += '|field_type=${g.comptime.comptime_for_field_type}|' } 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 { // No checker data - spurious instantiation or alias mismatch. // Default all branches to false (#if 0). is_true = ast.ComptTimeCondResult{} } if !branch.is_else { if i == 0 { g.write('#if ') } else { g.write('#elif ') } // directly use `checker` evaluate results g.writeln('${is_true.val}') $if debug_comptime_branch_context ? { g.writeln('/* | generic=[${comptime_branch_context_str}] */') } } else { g.writeln('#else') } if node.is_expr && !branch.is_comptime_err { len := branch.stmts.len if len > 0 { last := branch.stmts.last() if last is ast.ExprStmt { if len > 1 { g.indent++ g.writeln('{') g.stmts(branch.stmts[..len - 1]) g.set_current_pos_as_last_stmt_pos() prev_skip_stmt_pos := g.skip_stmt_pos g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') g.stmt(last) g.writeln('builtin___result_ok(&(${g.base_type(inferred_typ)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(inferred_typ)}));') g.writeln('}') } else { g.write('\t${tmp_var} = ') g.stmt(last) } g.skip_stmt_pos = prev_skip_stmt_pos g.writeln(';') g.write_defer_stmts(branch.scope, false, branch.pos) g.writeln('}') g.indent-- } else { g.indent++ g.set_current_pos_as_last_stmt_pos() prev_skip_stmt_pos := g.skip_stmt_pos g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') g.stmt(last) g.writeln('builtin___result_ok(&(${g.base_type(inferred_typ)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(inferred_typ)}));') g.writeln('}') } else { g.write('${tmp_var} = ') g.stmt(last) } g.skip_stmt_pos = prev_skip_stmt_pos g.writeln(';') g.write_defer_stmts(branch.scope, false, branch.pos) g.indent-- } } else if last is ast.Return { if last.exprs.len > 0 { g.write('${tmp_var} = ') g.expr(last.exprs[0]) g.writeln(';') g.write_defer_stmts(branch.scope, false, branch.pos) } } } } else if is_true.val || g.pref.output_cross_c { g.stmts(branch.stmts) g.write_defer_stmts(branch.scope, false, branch.pos) } } g.writeln('#endif') if node.is_expr { g.write('${line}${tmp_var}') } }