// 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 checker import math import os import v.ast import v.pref import v.token import v.util import v.pkgconfig import v.type_resolver import v.errors import strings @[ignore_overflow] fn comptime_power_i64(base i64, exponent i64) i64 { mut exp := exponent mut power := base mut value := i64(1) if exp < 0 { if base == 0 { return -1 } return if base * base != 1 { 0 } else { if exp & 1 > 0 { base } else { 1 } } } for exp > 0 { if exp & 1 > 0 { value *= power } power *= power exp >>= 1 } return value } fn comptime_power_f64(base f64, exponent f64) f64 { return math.pow(base, exponent) } fn comptime_power_value(left ast.ComptTimeConstValue, right ast.ComptTimeConstValue) ?ast.ComptTimeConstValue { if left_i := left.i64() { if right_i := right.i64() { return comptime_power_i64(left_i, right_i) } } if left_f := left.f64() { if right_f := right.f64() { return comptime_power_f64(left_f, right_f) } } return none } fn comptime_compare_i64_values(op token.Kind, left i64, right i64) ?bool { return match op { .eq { left == right } .ne { left != right } .gt { left > right } .lt { left < right } .ge { left >= right } .le { left <= right } else { none } } } fn comptime_compare_f64_values(op token.Kind, left f64, right f64) ?bool { return match op { .eq { left == right } .ne { left != right } .gt { left > right } .lt { left < right } .ge { left >= right } .le { left <= right } else { none } } } fn comptime_compare_string_values(op token.Kind, left string, right string) ?bool { return match op { .eq { left == right } .ne { left != right } else { none } } } struct ComptimeComparisonResult { val bool keep_stmts bool } fn (mut c Checker) eval_comptime_type_meta_value(typ ast.Type, field_name string) ?ast.ComptTimeConstValue { base_type := c.unwrap_generic(typ) match field_name { 'name' { return c.table.sym(base_type).name } 'idx', 'typ' { return i64(int(base_type)) } 'unaliased_typ' { return i64(int(c.table.unaliased_type(base_type))) } 'indirections' { return i64(base_type.nr_muls()) } 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type' { resolved_type := c.type_resolver.typeof_field_type(base_type, field_name) if resolved_type != ast.no_type { return i64(int(c.unwrap_generic(resolved_type))) } } else {} } return none } fn (c &Checker) is_generic_type_expr_ident(name string) bool { return util.is_generic_type_name(name) && c.table.cur_fn != unsafe { nil } && name in c.table.cur_fn.generic_names } fn (c &Checker) is_comptime_type_expr(expr ast.Expr) bool { return match expr { ast.ParExpr { c.is_comptime_type_expr(expr.expr) } ast.TypeNode { true } ast.TypeOf { true } ast.Ident { c.is_generic_type_expr_ident(expr.name) } ast.SelectorExpr { is_array_init_type_expr_field(expr.field_name) && c.is_comptime_type_expr(expr.expr) } else { false } } } fn (mut c Checker) comptime_call_type_expr_type(mut expr ast.Expr) ast.Type { match mut expr { ast.ParExpr { return c.comptime_call_type_expr_type(mut expr.expr) } ast.TypeNode { return c.recheck_concrete_type(expr.typ) } ast.TypeOf { if expr.is_type { return c.recheck_concrete_type(expr.typ) } if expr.typ == 0 || expr.typ == ast.void_type || expr.typ == ast.no_type { expr.typ = c.expr(mut expr.expr) } resolved_type := c.recheck_concrete_type(expr.typ) if resolved_type != 0 && resolved_type != ast.void_type && resolved_type != ast.no_type { return resolved_type } return c.recheck_concrete_type(c.type_resolver.typeof_type(expr.expr, expr.typ)) } ast.ArrayInit { if expr.elem_type_expr !is ast.EmptyExpr { c.resolve_array_init_elem_type_expr(mut expr) return expr.typ } return c.expr(mut expr) } ast.SelectorExpr { if is_array_init_type_expr_field(expr.field_name) { base_type := c.comptime_call_type_expr_type(mut expr.expr) resolved := c.type_resolver.typeof_field_type(base_type, expr.field_name) if resolved != ast.no_type { return c.recheck_concrete_type(resolved) } } c.expr(mut expr) return c.recheck_concrete_type(c.get_expr_type(expr)) } ast.Ident { if c.is_generic_type_expr_ident(expr.name) { return c.table.find_type(expr.name).set_flag(.generic) } return c.recheck_concrete_type(c.get_expr_type(expr)) } else { return c.recheck_concrete_type(c.expr(mut expr)) } } } fn (mut c Checker) eval_comptime_type_selector_value(expr ast.SelectorExpr) ?ast.ComptTimeConstValue { if expr.expr is ast.Ident && c.table.cur_fn != unsafe { nil } { idx := c.table.cur_fn.generic_names.index(expr.expr.name) if idx >= 0 && idx < c.table.cur_concrete_types.len { return c.eval_comptime_type_meta_value(c.table.cur_concrete_types[idx], expr.field_name) } } if expr.expr is ast.TypeOf { mut resolved_type := c.recheck_concrete_type(expr.expr.typ) if resolved_type == ast.void_type || resolved_type == ast.no_type { resolved_type = c.recheck_concrete_type(expr.name_type) } if resolved_type == ast.void_type || resolved_type == ast.no_type { resolved_type = c.type_resolver.typeof_type(expr.expr.expr, expr.name_type) } if resolved_type != ast.void_type && resolved_type != ast.no_type { return c.eval_comptime_type_meta_value(resolved_type, expr.field_name) } } return none } fn (c &Checker) comptime_expr_needs_multi_pass(expr ast.Expr) bool { return match expr { ast.TypeOf { true } ast.Ident { (c.table.cur_fn != unsafe { nil } && expr.name in c.table.cur_fn.generic_names) || (expr.obj is ast.Var && expr.obj.typ.has_flag(.generic)) || expr.ct_expr } ast.SelectorExpr { expr.expr is ast.TypeOf || (expr.expr is ast.Ident && c.table.cur_fn != unsafe { nil } && expr.expr.name in c.table.cur_fn.generic_names) || c.comptime_expr_needs_multi_pass(expr.expr) } ast.InfixExpr { c.comptime_expr_needs_multi_pass(expr.left) || c.comptime_expr_needs_multi_pass(expr.right) } ast.CastExpr { c.comptime_expr_needs_multi_pass(expr.expr) } ast.IndexExpr { c.comptime_expr_needs_multi_pass(expr.left) || c.comptime_expr_needs_multi_pass(expr.index) } ast.ParExpr { c.comptime_expr_needs_multi_pass(expr.expr) } ast.PostfixExpr { c.comptime_expr_needs_multi_pass(expr.expr) } ast.PrefixExpr { c.comptime_expr_needs_multi_pass(expr.right) } else { false } } } fn (mut c Checker) try_eval_comptime_comparison(mut left ast.Expr, mut right ast.Expr, op token.Kind) ?ComptimeComparisonResult { start_errors := c.nr_errors left_type := c.unwrap_generic(c.expr(mut left)) right_type := c.unwrap_generic(c.expr(mut right)) if c.nr_errors > start_errors { return none } left_value := c.eval_comptime_const_expr(left, 0)? right_value := c.eval_comptime_const_expr(right, 0)? keep_stmts := c.comptime_expr_needs_multi_pass(left) || c.comptime_expr_needs_multi_pass(right) if left_type == ast.string_type || right_type == ast.string_type { return ComptimeComparisonResult{ val: comptime_compare_string_values(op, left_value.string()?, right_value.string()?)? keep_stmts: keep_stmts } } if left_type.is_float() || right_type.is_float() { return ComptimeComparisonResult{ val: comptime_compare_f64_values(op, left_value.f64()?, right_value.f64()?)? keep_stmts: keep_stmts } } return ComptimeComparisonResult{ val: comptime_compare_i64_values(op, left_value.i64()?, right_value.i64()?)? keep_stmts: keep_stmts } } fn (mut c Checker) is_string_array_type(typ ast.Type) bool { final_typ := c.table.unaliased_type(c.unwrap_generic(typ)) sym := c.table.final_sym(final_typ) if sym.info is ast.Array { return c.table.unaliased_type(sym.info.elem_type) == ast.string_type } return false } fn (mut c Checker) check_comptime_method_string_auto_expand(mut node ast.ComptimeCall) bool { if c.comptime.comptime_for_method == unsafe { nil } || node.args.len == 0 || node.args.any(it.expr is ast.ArrayDecompose) { return false } method := c.comptime.comptime_for_method if method.params.len - 1 < node.args.len { return false } last_arg := node.args.last() if !c.is_string_array_type(last_arg.typ) { return false } next_param := method.params[node.args.len].typ if c.is_string_array_type(next_param) { return false } c.error('to auto-expand `[]string` arguments in comptime method calls, use `...${last_arg.expr}`', last_arg.pos) return true } fn (mut c Checker) check_comptime_method_call_args(mut node ast.ComptimeCall) { if c.comptime.comptime_for_method == unsafe { nil } { return } if node.args.any(it.expr is ast.ArrayDecompose) { return } method := c.comptime.comptime_for_method if method.params.len == 0 { return } nr_method_args := method.params.len - 1 if !method.is_variadic && node.args.len > nr_method_args { return } receiver_name := c.table.type_to_str(c.unwrap_generic(method.receiver_type).set_nr_muls(0)) for i in 0 .. node.args.len { mut arg := node.args[i] param := if method.is_variadic && i >= nr_method_args - 1 { method.params.last() } else if i + 1 < method.params.len { method.params[i + 1] } else { break } arg = c.implicit_mut_call_arg(param, arg) node.args[i] = arg param_share := param.typ.share() if arg.is_mut { to_lock, pos := c.fail_if_immutable(mut arg.expr) if !param.is_mut { tok := arg.share.str() c.error('`${method.name}` parameter `${param.name}` is not `${tok}`, `${tok}` is not needed`', arg.expr.pos()) continue } if param_share != arg.share { c.error('wrong shared type `${arg.share.str()}`, expected: `${param_share.str()}`', arg.expr.pos()) } if to_lock != '' && param_share != .shared_t { c.error('${to_lock} is `shared` and must be `lock`ed to be passed as `mut`', pos) } } else if param.is_mut { tok := param.specifier() c.error('method `${method.name}` parameter `${param.name}` is `${tok}`, so use `${tok} ${arg.expr}` instead', arg.expr.pos()) continue } else { c.fail_if_unreadable(arg.expr, arg.typ, 'argument') } c.check_expected_call_arg(arg.typ, c.unwrap_generic(param.typ), method.language, arg) or { if arg.typ != ast.void_type { c.error('${err.msg()} in argument ${i + 1} to `${receiver_name}.${method.name}`', arg.pos) } } } } fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { if node.left !is ast.EmptyExpr { node.left_type = c.expr(mut node.left) } if node.kind == .compile_error { mut err_pos := node.pos // During generic rechecks, report the instantiation site instead of the // `$compile_error()` directive inside the generic body. if c.fn_level > 0 && c.table.cur_fn != unsafe { nil } { call_key := c.build_generic_call_key(c.table.cur_fn.fkey(), c.table.cur_concrete_types) if pos := c.generic_call_positions[call_key] { err_pos = pos } } c.error(c.comptime_call_msg(node), err_pos) return ast.void_type } else if node.kind == .compile_warn { c.warn(c.comptime_call_msg(node), node.pos) return ast.void_type } if node.kind == .env { env_value := util.resolve_env_value("\$env('${node.args_var}')", false) or { c.error(err.msg(), node.env_pos) return ast.string_type } node.env_value = env_value return ast.string_type } if node.kind == .d { node.resolve_compile_value(c.pref.compile_values) or { c.error(err.msg(), node.pos) return ast.void_type } return node.result_type } if node.kind in [.zero, .new] { if node.args.len != 1 { c.error('`\$${node.method_name}()` expects 1 type argument', node.pos) return ast.void_type } mut type_expr := node.args[0].expr resolved_type := c.comptime_call_type_expr_type(mut type_expr) node.args[0].expr = type_expr if resolved_type == ast.void_type || resolved_type == ast.no_type { c.error('`\$${node.method_name}()` expects a valid type expression', node.args[0].pos) return ast.void_type } node.args[0].typ = resolved_type node.result_type = if node.kind == .new { resolved_type.ref() } else { resolved_type } return node.result_type } if node.kind == .embed_file { if node.args.len == 1 { embed_arg := node.args[0] mut raw_path := '' if embed_arg.expr is ast.AtExpr { mut expr := embed_arg.expr c.at_expr(mut expr) raw_path = expr.val } if embed_arg.expr is ast.StringLiteral { raw_path = embed_arg.expr.val } else if embed_arg.expr is ast.Ident { if var := c.fn_scope.find_var(embed_arg.expr.name) { if var.expr is ast.StringLiteral { raw_path = var.expr.val } } } mut escaped_path := raw_path.replace('/', os.path_separator) // Validate that the epath exists, and that it is actually a file. if escaped_path == '' { c.error('supply a valid relative or absolute file path to the file to embed, that is known at compile time', node.pos) return ast.string_type } escaped_path = c.resolve_pseudo_variables(escaped_path, node.pos) or { return ast.string_type } abs_path := os.real_path(escaped_path) // check absolute path first if !os.exists(abs_path) { // ... look relative to the source file: escaped_path = os.real_path(os.join_path_single(os.dir(c.file.path), escaped_path)) if !os.exists(escaped_path) { c.error('"${escaped_path}" does not exist so it cannot be embedded', node.pos) return ast.string_type } if !os.is_file(escaped_path) { c.error('"${escaped_path}" is not a file so it cannot be embedded', node.pos) return ast.string_type } } else { escaped_path = abs_path } node.embed_file.rpath = raw_path node.embed_file.apath = escaped_path } // c.file.embedded_files << node.embed_file if node.embed_file.compression_type !in ast.valid_comptime_compression_types { supported := ast.valid_comptime_compression_types.map('.${it}').join(', ') c.error('not supported compression type: .${node.embed_file.compression_type}. supported: ${supported}', node.pos) } return c.table.find_type('v.embed_file.EmbedFileData') } if node.is_template { // TODO: assoc parser bug save_cur_fn := c.table.cur_fn pref_ := *c.pref pref2 := &pref.Preferences{ ...pref_ is_template: true } mut c2 := new_checker(c.table, pref2) c2.comptime_call_pos = node.pos.pos template_parser_errors := node.veb_tmpl.errors.clone() template_parser_warnings := node.veb_tmpl.warnings.clone() template_parser_notices := node.veb_tmpl.notices.clone() c2.check(mut node.veb_tmpl) // Cache template file content for error display using the relative path // The template_paths contains full paths, but errors use relative paths from node.veb_tmpl.path if node.veb_tmpl.template_paths.len > 0 { // Cache the main template file main_template_path := node.veb_tmpl.template_paths[0] if content := os.read_file(main_template_path) { util.set_source_for_path(node.veb_tmpl.path, content) } } // Fix error line numbers using template line mapping line_map := node.veb_tmpl.template_line_map if line_map.len > 0 { for i, err in c2.errors { if err.pos.line_nr >= 0 && err.pos.line_nr < line_map.len { line_info := line_map[err.pos.line_nr] c2.errors[i] = errors.Error{ message: err.message details: err.details file_path: line_info.tmpl_path pos: token.Pos{ ...err.pos line_nr: line_info.tmpl_line } reporter: err.reporter call_stack: err.call_stack } } } for i, warn in c2.warnings { if warn.pos.line_nr >= 0 && warn.pos.line_nr < line_map.len { line_info := line_map[warn.pos.line_nr] c2.warnings[i] = errors.Warning{ message: warn.message details: warn.details file_path: line_info.tmpl_path pos: token.Pos{ ...warn.pos line_nr: line_info.tmpl_line } reporter: warn.reporter call_stack: warn.call_stack } } } for i, notice in c2.notices { if notice.pos.line_nr >= 0 && notice.pos.line_nr < line_map.len { line_info := line_map[notice.pos.line_nr] c2.notices[i] = errors.Notice{ message: notice.message details: notice.details file_path: line_info.tmpl_path pos: token.Pos{ ...notice.pos line_nr: line_info.tmpl_line } reporter: notice.reporter call_stack: notice.call_stack } } } } c.warnings << template_parser_warnings c.errors << template_parser_errors c.notices << template_parser_notices c.nr_warnings += template_parser_warnings.len c.nr_errors += template_parser_errors.len c.nr_notices += template_parser_notices.len c.warnings << c2.warnings c.errors << c2.errors c.notices << c2.notices c.nr_warnings += c2.nr_warnings c.nr_errors += c2.nr_errors c.nr_notices += c2.nr_notices c.table.cur_fn = save_cur_fn } if node.kind == .html { ret_sym := c.table.sym(c.table.cur_fn.return_type) if ret_sym.cname != 'veb__Result' { c.error("`\$veb.html()` must be called inside a web method, e.g. `fn (mut app App) foo(mut ctx Context) veb.Result { return \$veb.html('index.html') }`", node.pos) } rtyp := c.table.find_type('veb.Result') node.result_type = rtyp return rtyp } if node.method_name == 'method' { if c.inside_anon_fn && 'method' !in c.cur_anon_fn.inherited_vars.map(it.name) { c.error('undefined ident `method` in the anonymous function', node.pos) } for i, mut arg in node.args { // check each arg expression node.args[i].typ = c.expr(mut arg.expr) } if c.check_comptime_method_string_auto_expand(mut node) { return ast.void_type } c.check_comptime_method_call_args(mut node) c.markused_comptimecall(mut node) c.stmts_ending_with_expression(mut node.or_block.stmts, c.expected_or_type) return c.type_resolver.get_type(node) } if node.kind == .res { if !c.inside_defer { c.error('`res` can only be used in defer blocks', node.pos) return ast.void_type } if c.fn_return_type == ast.void_type { c.error('`res` can only be used in functions that returns something', node.pos) return ast.void_type } sym := c.table.sym(c.fn_return_type) if c.fn_return_type.has_flag(.result) { c.error('`res` cannot be used in functions that returns a Result', node.pos) return ast.void_type } if sym.info is ast.MultiReturn { if node.args_var == '' { c.error('`res` requires an index of the returned value', node.pos) return ast.void_type } idx := node.args_var.int() if idx < 0 || idx >= sym.info.types.len { c.error('index ${idx} out of range of ${sym.info.types.len} return types', node.pos) return ast.void_type } return sym.info.types[idx] } return c.fn_return_type } if node.is_template { return ast.string_type } // s.$my_str() v := node.scope.find_var(node.method_name) or { c.error('unknown identifier `${node.method_name}`', node.method_pos) return ast.void_type } if v.typ != ast.string_type { s := c.expected_msg(v.typ, ast.string_type) c.error('invalid string method call: ${s}', node.method_pos) return ast.void_type } // note: we should use a compile-time evaluation function rather than handle here // mut variables will not work after init mut method_name := '' if v.expr is ast.StringLiteral { method_name = v.expr.val } else { c.error('todo: not a string literal', node.method_pos) } left_type := c.unwrap_generic(node.left_type) left_sym := c.table.sym(left_type) f := left_sym.find_method(method_name) or { c.error('could not find method `${method_name}`', node.method_pos) return ast.void_type } c.mark_fn_decl_as_referenced(f.fkey()) c.markused_comptime_call(true, '${int(left_type)}.${method_name}') node.result_type = f.return_type return f.return_type } fn (mut c Checker) comptime_call_msg(node ast.ComptimeCall) string { return if node.args_var.len > 0 { node.args_var } else if value := c.eval_comptime_const_expr(node.args[0].expr, -1) { value.string() or { '' } } else { '' } } fn (mut c Checker) comptime_selector_method_value(mut node ast.ComptimeSelector) ast.Type { if c.comptime.comptime_for_method == unsafe { nil } { c.error('compile time method access can only be used when iterating over `T.methods`', node.field_expr.pos()) return ast.void_type } method := c.comptime.comptime_for_method node.is_method = true fn_type := c.type_resolver.get_comptime_selector_type(node, ast.void_type) node.typ = c.unwrap_generic(fn_type) c.mark_fn_decl_as_referenced(method.fkey()) c.markused_comptime_call(true, '${int(method.params[0].typ)}.${method.name}') receiver := c.unwrap_generic(method.params[0].typ) if receiver.nr_muls() > 0 && !c.inside_unsafe { rec_sym := c.table.sym(receiver.set_nr_muls(0)) if !rec_sym.is_heap() { suggestion := if rec_sym.kind == .struct { 'declaring `${rec_sym.name}` as `@[heap]`' } else { 'wrapping the `${rec_sym.name}` object in a `struct` declared as `@[heap]`' } c.error('method `${c.table.type_to_str(receiver.idx_type())}.${method.name}` cannot be used as a variable outside `unsafe` blocks as its receiver might refer to an object stored on stack. Consider ${suggestion}.', node.left.pos().extend(node.pos)) } } c.table.used_features.anon_fn = true return fn_type } fn (mut c Checker) comptime_selector(mut node ast.ComptimeSelector) ast.Type { node.left_type = c.expr(mut node.left) mut expr_type := c.unwrap_generic(c.expr(mut node.field_expr)) if c.type_resolver.info.is_comptime_method_selector(node.field_expr) { return c.comptime_selector_method_value(mut node) } expr_sym := c.table.sym(expr_type) if expr_type != ast.string_type { c.error('expected `string` instead of `${expr_sym.name}` (e.g. `field.name`)', node.field_expr.pos()) } if mut node.field_expr is ast.SelectorExpr { left_pos := node.field_expr.expr.pos() if c.type_resolver.type_map.len == 0 { c.error('compile time field access can only be used when iterating over `T.fields`', left_pos) } node.is_name = node.field_expr.field_name == 'name' if mut node.field_expr.expr is ast.Ident { node.typ_key = if c.comptime.comptime_for_field_value.name != '' { '${node.field_expr.expr.name}.typ|${c.comptime.comptime_for_field_value.name}' } else { '${node.field_expr.expr.name}.typ' } } expr_type = c.type_resolver.get_comptime_selector_type(node, ast.void_type) if expr_type != ast.void_type { if node.or_block.kind == .propagate_option { return expr_type.clear_flag(.option) } return expr_type } expr_name := node.field_expr.expr.str() if expr_name in c.type_resolver.type_map { return c.type_resolver.get_ct_type_or_default(expr_name, ast.void_type) } c.error('unknown `\$for` variable `${expr_name}`', left_pos) } else { c.error('expected selector expression e.g. `$(field.name)`', node.field_expr.pos()) } return ast.void_type } fn (mut c Checker) comptime_for(mut node ast.ComptimeFor) { mut typ := if node.expr !is ast.EmptyExpr { node.typ = c.expr(mut node.expr) c.unwrap_generic(node.typ) } else if node.typ != ast.void_type { c.unwrap_generic(node.typ) } else { node.typ = c.expr(mut node.expr) c.unwrap_generic(node.typ) } if node.expr !is ast.EmptyExpr { resolved_typ := c.type_resolver.get_type(node.expr) if resolved_typ != ast.void_type { node.typ = resolved_typ typ = c.unwrap_generic(node.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) return } else if sym.kind == .void { c.error('only known compile-time variables can be used', node.typ_pos) return } if node.kind == .fields { if sym.kind in [.struct, .interface] { mut fields := []ast.StructField{} match sym.info { ast.Struct { fields = sym.info.fields.clone() } ast.Interface { fields = sym.info.fields.clone() } else { c.error('iterating over .fields is supported only for structs and interfaces, and ${sym.name} is neither', node.typ_pos) return } } has_different_types := fields.len > 1 && !fields.all(c.check_basic(it.typ, fields[0].typ)) if fields.len == 0 { // force eval `node.stmts` to set their types fields << ast.StructField{ typ: ast.error_type } } for field in fields { c.push_new_comptime_info() prev_inside_x_matches_type := c.inside_x_matches_type c.comptime.inside_comptime_for = true if c.field_data_type == 0 { c.field_data_type = c.table.find_type('FieldData') } c.comptime.comptime_for_field_value = field c.comptime.comptime_for_field_var = node.val_var resolved_field_typ := c.unwrap_generic(field.typ) c.type_resolver.update_ct_type(node.val_var, c.field_data_type) c.type_resolver.update_ct_type('${node.val_var}.typ', resolved_field_typ) c.comptime.comptime_for_field_type = resolved_field_typ c.comptime.has_different_types = has_different_types c.stmts(mut node.stmts) if resolved_field_typ != ast.no_type { unwrapped_expr_type := c.unwrap_generic(resolved_field_typ) tsym := c.table.sym(unwrapped_expr_type) c.markused_comptimefor(mut node, unwrapped_expr_type) if tsym.kind == .array_fixed { info := tsym.info as ast.ArrayFixed if !info.is_fn_ret { // for dumping fixed array we must register the fixed array struct to return from function c.table.find_or_register_array_fixed(info.elem_type, info.size, info.size_expr, true) } } } c.inside_x_matches_type = prev_inside_x_matches_type c.pop_comptime_info() } } else if node.typ != ast.void_type && c.table.generic_type_names(node.typ).len == 0 && sym.kind != .placeholder { c.error('iterating over .fields is supported only for structs and interfaces, and ${sym.name} is neither', node.typ_pos) return } } else if node.kind == .values { if sym.kind == .enum { if sym.info is ast.Enum { for _ in sym.info.vals { c.push_new_comptime_info() c.comptime.inside_comptime_for = true if c.enum_data_type == 0 { c.enum_data_type = c.table.find_type('EnumData') } c.comptime.comptime_for_enum_var = node.val_var c.type_resolver.update_ct_type(node.val_var, c.enum_data_type) c.type_resolver.update_ct_type('${node.val_var}.typ', node.typ) c.stmts(mut node.stmts) c.pop_comptime_info() } } } else { c.error('iterating over .values is supported only for enums, and ${sym.name} is not an enum', node.typ_pos) return } } else if node.kind == .methods { mut methods := c.table.get_type_methods(typ) if methods.len == 0 { // force eval `node.stmts` to set their types methods << ast.Fn{} } for method in methods { c.push_new_comptime_info() c.comptime.inside_comptime_for = true c.comptime.comptime_for_method = unsafe { &method } c.comptime.comptime_for_method_var = node.val_var c.comptime.comptime_for_method_ret_type = method.return_type c.type_resolver.update_ct_type('${node.val_var}.return_type', method.return_type) if method.params.len > 0 { for j, arg in method.params[1..] { c.type_resolver.update_ct_type('${node.val_var}.args[${j}].typ', arg.typ.idx()) } } c.stmts(mut node.stmts) c.pop_comptime_info() } } else if node.kind == .params { if !(sym.kind == .function || sym.name == 'FunctionData') { c.error('iterating over `.params` is supported only for functions, and `${sym.name}` is not a function', node.typ_pos) return } func := if sym.info is ast.FnType { &sym.info.func } else { c.comptime.comptime_for_method } mut params := if func.is_method { func.params[1..] } else { func.params } if params.len == 0 { // force eval `node.stmts` to set their types params << ast.Param{} } // example: fn (mut d MyStruct) add(x int, y int) string // `d` is params[0], `x` is params[1], `y` is params[2] // so we at least has one param (`d`) for method for param in params { c.push_new_comptime_info() c.comptime.inside_comptime_for = true c.comptime.comptime_for_method_param_var = node.val_var c.type_resolver.update_ct_type('${node.val_var}.typ', param.typ) c.stmts(mut node.stmts) c.pop_comptime_info() } } else if node.kind == .attributes { mut attrs := c.table.get_attrs(sym) if attrs.len == 0 { // force eval `node.stmts` to set their types attrs << ast.Attr{} } for attr in attrs { c.push_new_comptime_info() c.comptime.inside_comptime_for = true c.comptime.comptime_for_attr_var = node.val_var c.comptime.comptime_for_attr_value = attr c.stmts(mut node.stmts) c.pop_comptime_info() } } else if node.kind == .variants { if c.variant_data_type == 0 { c.variant_data_type = c.table.find_type('VariantData') } mut variants := []ast.Type{} if c.comptime.comptime_for_field_var != '' && typ == c.field_data_type { sumtype_sym := c.table.sym(c.comptime.comptime_for_field_type) if sumtype_sym.kind == .sum_type { variants = (sumtype_sym.info as ast.SumType).variants.clone() } } else if sym.kind != .sum_type { c.error('${sym.name} is not Sum type to use with .variants', node.typ_pos) } else { variants = (sym.info as ast.SumType).variants.clone() } for variant in variants { c.push_new_comptime_info() c.comptime.inside_comptime_for = true c.comptime.comptime_for_variant_var = node.val_var c.type_resolver.update_ct_type(node.val_var, c.variant_data_type) c.type_resolver.update_ct_type('${node.val_var}.typ', variant) c.stmts(mut node.stmts) c.pop_comptime_info() } } else { c.stmts(mut node.stmts) } } fn (mut c Checker) find_comptime_eval_fn(node ast.CallExpr) ?ast.Fn { if f := c.table.find_fn(node.name) { return f } if node.mod != '' && !node.name.contains('.') { if f := c.table.find_fn('${node.mod}.${node.name}') { return f } } if !node.name.contains('.') { if f := c.table.find_fn('${c.mod}.${node.name}') { return f } if f := c.table.find_fn('builtin.${node.name}') { return f } } return none } fn (c &Checker) find_comptime_eval_fn_decl(func ast.Fn) ?ast.FnDecl { if func.source_fn != unsafe { nil } { fn_decl := unsafe { &ast.FnDecl(func.source_fn) } if fn_decl != unsafe { nil } { return *fn_decl } } if c.file != unsafe { nil } { for stmt in c.file.stmts { if stmt is ast.FnDecl && stmt.name == func.name { return stmt } } } return none } fn (mut c Checker) eval_comptime_fn_decl_value_with_locals(fn_decl ast.FnDecl, nlevel int, local_values map[string]ast.ComptTimeConstValue) ?ast.ComptTimeConstValue { mut stmts := fn_decl.stmts.clone() if stmts.len == 1 && stmts[0] is ast.Block { unsafe_block := stmts[0] as ast.Block if unsafe_block.is_unsafe { stmts = unsafe_block.stmts.clone() } } if stmts.len != 1 { return none } stmt := stmts[0] match stmt { ast.Return { if stmt.exprs.len != 1 { return none } return c.eval_comptime_const_expr_with_locals(stmt.exprs[0], nlevel + 1, local_values) } ast.ExprStmt { return c.eval_comptime_const_expr_with_locals(stmt.expr, nlevel + 1, local_values) } else { return none } } } fn (mut c Checker) eval_comptime_fn_call_expr_with_locals(node ast.CallExpr, nlevel int, local_values map[string]ast.ComptTimeConstValue) ?ast.ComptTimeConstValue { if node.is_method || node.is_fn_var || node.is_fn_a_const { return none } func := c.find_comptime_eval_fn(node) or { return none } if !func.attrs.contains('comptime') { return none } if func.is_method || func.is_variadic || func.is_c_variadic || func.no_body || func.generic_names.len > 0 || func.params.len != node.args.len { return none } fn_decl := c.find_comptime_eval_fn_decl(func) or { return none } mut local_args := map[string]ast.ComptTimeConstValue{} for idx, param in func.params { arg_value := c.eval_comptime_const_expr_with_locals(node.args[idx].expr, nlevel + 1, local_values) or { return none } local_args[param.name] = arg_value } return c.eval_comptime_fn_decl_value_with_locals(fn_decl, nlevel + 1, local_args) } // comptime const eval fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.ComptTimeConstValue { return c.eval_comptime_const_expr_with_locals(expr, nlevel, map[string]ast.ComptTimeConstValue{}) } fn (mut c Checker) eval_comptime_const_expr_with_locals(expr ast.Expr, nlevel int, local_values map[string]ast.ComptTimeConstValue) ?ast.ComptTimeConstValue { if nlevel > 100 { // protect against a too deep comptime eval recursion return none } match expr { ast.ParExpr { return c.eval_comptime_const_expr_with_locals(expr.expr, nlevel + 1, local_values) } ast.EnumVal { enum_name := if expr.enum_name == '' { c.table.type_to_str(c.expected_type) } else { expr.enum_name } if val := c.table.find_enum_field_val(enum_name, expr.val) { return val } } ast.SizeOf { s, _ := c.table.type_size(c.unwrap_generic(expr.typ)) return i64(s) } ast.FloatLiteral { x := expr.val.f64() return x } ast.IntegerLiteral { x := expr.val.u64() if x > 9223372036854775807 { return x } return expr.val.i64() } ast.StringLiteral { return util.smart_quote(expr.val, expr.is_raw) } ast.StringInterLiteral { if nlevel < 0 { mut sb := strings.new_builder(20) for i, val in expr.vals { sb.write_string(val) if e := expr.exprs[i] { if value := c.eval_comptime_const_expr_with_locals(e, nlevel + 1, local_values) { sb.write_string(value.string() or { '' }) } else { c.error('unsupport expr `${e.str()}`', e.pos()) } } } return sb.str() } } ast.CharLiteral { runes := expr.val.runes() if runes.len > 0 { return runes[0] } return none } ast.Ident { if value := local_values[expr.name] { return value } if expr.obj is ast.ConstField { // an existing constant? return c.eval_comptime_const_expr_with_locals(expr.obj.expr, nlevel + 1, local_values) } if c.table.cur_fn != unsafe { nil } { idx := c.table.cur_fn.generic_names.index(expr.name) if typ := c.table.cur_concrete_types[idx] { sym := c.table.sym(typ) return sym.str() } } } ast.SelectorExpr { if value := c.eval_comptime_type_selector_value(expr) { return value } } ast.CastExpr { cast_expr_value := c.eval_comptime_const_expr_with_locals(expr.expr, nlevel + 1, local_values) or { return none } if expr.typ == ast.i8_type { return cast_expr_value.i8() or { return none } } if expr.typ == ast.i16_type { return cast_expr_value.i16() or { return none } } if expr.typ == ast.i32_type { return cast_expr_value.i32() or { return none } } if expr.typ == ast.i64_type { return cast_expr_value.i64() or { return none } } if expr.typ == ast.int_type { return cast_expr_value.i64() or { return none } } // if expr.typ == ast.u8_type { return cast_expr_value.u8() or { return none } } if expr.typ == ast.u16_type { return cast_expr_value.u16() or { return none } } if expr.typ == ast.u32_type { return cast_expr_value.u32() or { return none } } if expr.typ == ast.u64_type { return cast_expr_value.u64() or { return none } } // if expr.typ == ast.f32_type { return cast_expr_value.f32() or { return none } } if expr.typ == ast.f64_type { return cast_expr_value.f64() or { return none } } if expr.typ == ast.voidptr_type || expr.typ == ast.nil_type { ptrvalue := cast_expr_value.voidptr() or { return none } return ast.ComptTimeConstValue(ptrvalue) } } ast.CallExpr { return c.eval_comptime_fn_call_expr_with_locals(expr, nlevel, local_values) } ast.InfixExpr { left := c.eval_comptime_const_expr_with_locals(expr.left, nlevel + 1, local_values)? saved_expected_type := c.expected_type if expr.left is ast.EnumVal { c.expected_type = expr.left.typ } else if expr.left is ast.InfixExpr { mut infixexpr := expr for { if infixexpr.left is ast.InfixExpr { infixexpr = infixexpr.left as ast.InfixExpr } else { break } } if mut infixexpr.left is ast.EnumVal { c.expected_type = infixexpr.left.typ } } right := c.eval_comptime_const_expr_with_locals(expr.right, nlevel + 1, local_values)? c.expected_type = saved_expected_type if expr.op == .power { return comptime_power_value(left, right) } if left is string && right is string { match expr.op { .plus { return left + right } else { return none } } } else if left is u32 && right is i64 { match expr.op { .plus { return i64(left) + right } .minus { return i64(left) - right } .mul { return i64(left) * right } .div { return i64(left) / right } .mod { return i64(left) % right } .xor { return i64(left) ^ right } .pipe { return i64(left) | right } .amp { return i64(left) & right } .left_shift { return i64(u64(left) << right) } .right_shift { return i64(u64(left) >> right) } .unsigned_right_shift { return i64(u64(left) >>> right) } else { return none } } } else if left is i64 && right is u32 { match expr.op { .plus { return left + i64(right) } .minus { return left - i64(right) } .mul { return left * i64(right) } .div { return left / i64(right) } .mod { return left % i64(right) } .xor { return left ^ i64(right) } .pipe { return left | i64(right) } .amp { return left & i64(right) } .left_shift { return i64(u64(left) << i64(right)) } .right_shift { return i64(u64(left) >> i64(right)) } .unsigned_right_shift { return i64(u64(left) >>> i64(right)) } else { return none } } } else if left is u32 && right is u32 { match expr.op { .plus { return i64(left) + i64(right) } .minus { return i64(left) - i64(right) } .mul { return i64(left) * i64(right) } .div { return i64(left) / i64(right) } .mod { return i64(left) % i64(right) } .xor { return i64(left) ^ i64(right) } .pipe { return i64(left) | i64(right) } .amp { return i64(left) & i64(right) } .left_shift { return i64(u64(left) << right) } .right_shift { return i64(u64(left) >> right) } .unsigned_right_shift { return i64(u64(left) >>> right) } else { return none } } } else if left is u64 && right is i64 { match expr.op { .plus { return i64(left) + i64(right) } .minus { return i64(left) - i64(right) } .mul { return i64(left) * i64(right) } .div { return i64(left) / i64(right) } .mod { return i64(left) % i64(right) } .xor { return i64(left) ^ i64(right) } .pipe { return i64(left) | i64(right) } .amp { return i64(left) & i64(right) } .left_shift { return i64(u64(left) << i64(right)) } .right_shift { return i64(u64(left) >> i64(right)) } .unsigned_right_shift { return i64(u64(left) >>> i64(right)) } else { return none } } } else if left is i64 && right is u64 { match expr.op { .plus { return i64(left) + i64(right) } .minus { return i64(left) - i64(right) } .mul { return i64(left) * i64(right) } .div { return i64(left) / i64(right) } .mod { return i64(left) % i64(right) } .xor { return i64(left) ^ i64(right) } .pipe { return i64(left) | i64(right) } .amp { return i64(left) & i64(right) } .left_shift { return i64(u64(left) << i64(right)) } .right_shift { return i64(u64(left) >> i64(right)) } .unsigned_right_shift { return i64(u64(left) >>> i64(right)) } else { return none } } } else if left is u64 && right is u64 { match expr.op { .plus { return left + right } .minus { return left - right } .mul { return left * right } .div { return left / right } .mod { return left % right } .xor { return left ^ right } .pipe { return left | right } .amp { return left & right } .left_shift { return left << right } .right_shift { return left >> right } .unsigned_right_shift { return left >>> right } else { return none } } } else if left is i64 && right is i64 { match expr.op { .plus { return left + right } .minus { return left - right } .mul { return left * right } .div { return left / right } .mod { return left % right } .xor { return left ^ right } .pipe { return left | right } .amp { return left & right } .left_shift { return i64(u64(left) << right) } .right_shift { return i64(u64(left) >> right) } .unsigned_right_shift { return i64(u64(left) >>> right) } else { return none } } } else if left is u8 && right is u8 { match expr.op { .plus { return left + right } .minus { return left - right } .mul { return left * right } .div { return left / right } .mod { return left % right } .xor { return left ^ right } .pipe { return left | right } .amp { return left & right } .left_shift { return left << right } .right_shift { return left >> right } .unsigned_right_shift { return left >>> right } else { return none } } } } ast.IfExpr { if !expr.is_comptime { return none } for i in 0 .. expr.branches.len { mut branch := expr.branches[i] if !expr.has_else || i < expr.branches.len - 1 { mut sb := strings.new_builder(256) is_true, _ := c.comptime_if_cond(mut branch.cond, mut sb) if is_true { last_stmt := branch.stmts.last() if last_stmt is ast.ExprStmt { return c.eval_comptime_const_expr_with_locals(last_stmt.expr, nlevel + 1, local_values) } } } else { last_stmt := branch.stmts.last() if last_stmt is ast.ExprStmt { return c.eval_comptime_const_expr_with_locals(last_stmt.expr, nlevel + 1, local_values) } } } } // ast.ArrayInit {} // ast.PrefixExpr { // c.note('prefixexpr: ${expr}', expr.pos) // } else { // eprintln('>>> nlevel: ${nlevel} | another ${expr.type_name()} | ${expr} ') return none } } return none } fn (mut c Checker) verify_veb_params_for_method(node &ast.Fn) (bool, int, int) { margs := node.params.len - 1 // first arg is the receiver/this // if node.attrs.len == 0 || (node.attrs.len == 1 && node.attrs[0].name == 'post') { if node.attrs.len == 0 { // allow non custom routed methods, with 1:1 mapping return true, -1, margs } mut context_params := 0 if node.params.len > 1 { for param in node.params[1..] { if c.has_veb_context(param.typ) { context_params++ continue } param_sym := c.table.final_sym(param.typ) if !(param_sym.is_string() || param_sym.is_number() || param_sym.is_float() || param_sym.kind == .bool) { c.error('invalid type `${param_sym.name}` for parameter `${param.name}` in veb app method `${node.name}` (only strings, numbers, and bools are allowed)', param.pos) } } } mut route_attributes := 0 for a in node.attrs { if a.name.starts_with('/') { route_attributes += a.name.count(':') } } return route_attributes == margs - context_params, route_attributes, margs - context_params } fn (mut c Checker) verify_all_veb_routes() { if c.veb_gen_types.len == 0 { return } c.table.used_features.used_veb_types = c.veb_gen_types typ_veb_result := c.table.find_type('veb.Result') old_file := c.file for vgt in c.veb_gen_types { sym_app := c.table.sym(vgt) for m in sym_app.methods { if m.return_type == typ_veb_result { is_ok, nroute_attributes, nargs := c.verify_veb_params_for_method(m) if !is_ok { f := unsafe { &ast.FnDecl(m.source_fn) } if f == unsafe { nil } { continue } if f.return_type == typ_veb_result && f.receiver.typ == m.params[0].typ && f.name == m.name && !f.attrs.contains('post') { c.change_current_file(f.source_file) // setup of file path for the warning c.warn('mismatched parameters count between veb method `${sym_app.name}.${m.name}` (${nargs}) and route attribute ${m.attrs} (${nroute_attributes})', f.pos) } } } } } c.change_current_file(old_file) } fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool { if node.ct_evaled { return node.ct_skip } if mut node.ct_expr is ast.Ident { if node.ct_opt { if node.ct_expr.name in ast.valid_comptime_not_user_defined { c.error('option `@[if expression ?]` tags, can be used only for user defined identifiers', node.pos) node.ct_skip = true } else { node.ct_skip = node.ct_expr.name !in c.pref.compile_defines } node.ct_evaled = true return node.ct_skip } else { if node.ct_expr.name !in ast.valid_comptime_not_user_defined { c.note('`@[if ${node.ct_expr.name}]` is deprecated. Use `@[if ${node.ct_expr.name} ?]` instead', node.pos) node.ct_skip = node.ct_expr.name !in c.pref.compile_defines node.ct_evaled = true return node.ct_skip } else { if node.ct_expr.name in c.pref.compile_defines { // explicitly allow custom user overrides with `-d linux` for example, for easier testing: node.ct_skip = false node.ct_evaled = true return node.ct_skip } } } } c.inside_ct_attr = true mut sb := strings.new_builder(256) is_true, _ := c.comptime_if_cond(mut node.ct_expr, mut sb) node.ct_skip = !is_true c.inside_ct_attr = false node.ct_evaled = true return node.ct_skip } // check if `ident` is a function generic, such as `T` fn (mut c Checker) is_generic_ident(ident string) bool { if c.table.cur_fn != unsafe { nil } && ident in c.table.cur_fn.generic_names && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len { return true } return false } fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { match cond { ast.Ident { mut checked_type := ast.void_type if c.comptime.inside_comptime_for && (cond.name == c.comptime.comptime_for_variant_var || cond.name == c.comptime.comptime_for_method_param_var || cond.name == c.comptime.comptime_for_field_var) { // struct field return c.type_resolver.get_type_from_comptime_var(cond) } else if c.is_generic_ident(cond.name) { // generic type `T` if c.table.cur_fn != unsafe { nil } { idx := c.table.cur_fn.generic_names.index(cond.name) if idx >= 0 && idx < c.table.cur_concrete_types.len { concrete_type := c.table.cur_concrete_types[idx] if concrete_type != 0 { return concrete_type } } } type_idx := c.table.find_type_idx(cond.name) return if type_idx == 0 { ast.void_type } else { ast.new_type(type_idx).set_flag(.generic) } } else if cond.name in c.type_resolver.type_map { return c.type_resolver.get_ct_type_or_default(cond.name, ast.void_type) } else if var := cond.scope.find_var(cond.name) { // var checked_type = c.unwrap_generic(var.typ) if var.smartcasts.len > 0 { checked_type = c.unwrap_generic(c.visible_var_type_for_read(var)) } } return checked_type } ast.TypeNode { return c.unwrap_generic(cond.typ) } ast.SelectorExpr { if c.comptime.inside_comptime_for && cond.field_name in ['typ', 'unaliased_typ', 'indirections', 'pointee_type', 'payload_type', 'variant_types'] && cond.expr is ast.Ident && (cond.expr.name == c.comptime.comptime_for_variant_var || cond.expr.name == c.comptime.comptime_for_method_param_var || cond.expr.name == c.comptime.comptime_for_field_var) { typ := c.type_resolver.get_type_from_comptime_var(cond.expr as ast.Ident) if cond.field_name == 'unaliased_typ' { return c.table.unaliased_type(typ) } else if cond.field_name in ['pointee_type', 'payload_type', 'variant_types'] { return c.type_resolver.typeof_field_type(typ, cond.field_name) } // for `indirections` we also return the `typ` return typ } if cond.name_type != 0 && cond.field_name in ['key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type', 'variant_types'] { return c.type_resolver.typeof_field_type(cond.name_type, cond.field_name) } if cond.gkind_field in [.typ, .indirections, .unaliased_typ] { if cond.expr is ast.Ident { generic_name := cond.expr.name if c.table.cur_fn != unsafe { nil } && generic_name in c.table.cur_fn.generic_names { idx := c.table.cur_fn.generic_names.index(generic_name) if idx >= 0 && idx < c.table.cur_concrete_types.len { concrete_type := c.table.cur_concrete_types[idx] if cond.gkind_field == .unaliased_typ { return c.table.unaliased_type(concrete_type) } return concrete_type } } } unwrapped := c.unwrap_generic(cond.name_type) if cond.gkind_field == .unaliased_typ { if unwrapped.idx() == 0 || unwrapped.has_flag(.generic) { return unwrapped } return c.table.unaliased_type(unwrapped) } return unwrapped } else { if cond.expr is ast.TypeOf { return c.type_resolver.typeof_field_type(c.type_resolver.typeof_type(cond.expr.expr, cond.name_type), cond.field_name) } name := '${cond.expr}.${cond.field_name}' if name in c.type_resolver.type_map { return c.type_resolver.get_ct_type_or_default(name, ast.void_type) } else { return c.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 } } } fn (mut c Checker) check_compatible_types(left_type ast.Type, left_name string, expr ast.Expr) bool { mut resolved_left_type := c.unwrap_generic(left_type) if expr is ast.ComptimeType { return c.type_resolver.is_comptime_type(resolved_left_type, expr as ast.ComptimeType) } else if expr is ast.TypeNode { typ := c.get_expr_type(expr) right_type := c.unwrap_generic(typ) mut right_sym := c.table.sym(right_type) if right_sym.kind == .generic_inst { gi := right_sym.info as ast.GenericInst if gi.parent_idx > 0 && gi.parent_idx < c.table.type_symbols.len && c.table.type_symbols[gi.parent_idx].kind == .interface && !gi.concrete_types.any(c.type_has_unresolved_generic_parts(it)) { c.table.generic_insts_to_concrete() right_sym = c.table.sym(right_type) } } if c.type_resolver.bind_matching_generic_type(resolved_left_type, right_type) { return true } if right_sym.kind == .placeholder || right_type.has_flag(.generic) { c.error('unknown type `${right_sym.name}`', expr.pos) } if right_sym.kind == .interface && right_sym.info is ast.Interface { return resolved_left_type.has_flag(.option) == right_type.has_flag(.option) && c.table.does_type_implement_interface(resolved_left_type, right_type) } if right_sym.info is ast.FnType && c.comptime.comptime_for_method_var != '' && c.comptime.comptime_for_method != unsafe { nil } && c.comptime.comptime_for_method_var == left_name { right_fn_type := right_sym.info as ast.FnType return c.table.fn_signature(right_fn_type.func, skip_receiver: true type_only: true ) == c.table.fn_signature(c.comptime.comptime_for_method, skip_receiver: true type_only: true ) } left_unaliased_type := c.table.fully_unaliased_type(resolved_left_type) right_unaliased_type := c.table.fully_unaliased_type(right_type) left_unaliased_sym := c.table.sym(left_unaliased_type) right_unaliased_sym := c.table.sym(right_unaliased_type) if left_unaliased_sym.info is ast.FnType && right_unaliased_sym.info is ast.FnType { same_flags := left_unaliased_type.nr_muls() == right_unaliased_type.nr_muls() && left_unaliased_type.has_flag(.option) == right_unaliased_type.has_flag(.option) && left_unaliased_type.has_flag(.result) == right_unaliased_type.has_flag(.result) && left_unaliased_type.has_flag(.shared_f) == right_unaliased_type.has_flag(.shared_f) && left_unaliased_type.has_flag(.atomic_f) == right_unaliased_type.has_flag(.atomic_f) return same_flags && c.table.fn_signature(left_unaliased_sym.info.func, skip_receiver: true type_only: true ) == c.table.fn_signature(right_unaliased_sym.info.func, skip_receiver: true type_only: true ) } else { return resolved_left_type == right_type } } return false } // comptime_if_cond evaluate the `cond` and return (`is_true`, `keep_stmts`) // `is_true` is the evaluate result of `cond`; // `keep_stmts` meaning the branch is a `multi pass branch`, we should keep the branch stmts even `is_true` is false, such as `$if T is int {` fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) (bool, bool) { mut should_record_ident := false mut is_user_ident := false mut ident_name := '' defer { if should_record_ident { // record current cond result for debugging if is_user_ident { c.ct_user_defines[ident_name] = $res(0) } else { c.ct_system_defines[ident_name] = $res(0) } } } mut is_true := false match mut cond { ast.BoolLiteral { c.expr(mut cond) is_true = cond.val sb.write_string('${is_true}') return is_true, false } ast.ParExpr { sb.write_string('(') is_true_result, multi_pass_stmts := c.comptime_if_cond(mut cond.expr, mut sb) sb.write_string(')') return is_true_result, multi_pass_stmts } ast.PrefixExpr { if cond.op != .not { c.error('invalid \$if prefix operator, only allow `!`.', cond.pos) return false, false } sb.write_string(cond.op.str()) is_true_result, multi_pass_stmts := c.comptime_if_cond(mut cond.right, mut sb) return !is_true_result, multi_pass_stmts } ast.PostfixExpr { if cond.op != .question { c.error('invalid \$if postfix operator, only allow `?`.', cond.pos) return false, false } if cond.expr !is ast.Ident { c.error('invalid \$if postfix condition, only allow `Indent`.', cond.expr.pos()) return false, false } cname := (cond.expr as ast.Ident).name // record current cond result for debugging should_record_ident = true is_user_ident = true ident_name = cname sb.write_string('defined(CUSTOM_DEFINE_${cname})') is_true = cname in c.pref.compile_defines return is_true, false } ast.InfixExpr { match cond.op { .and, .logical_or { l, d1 := c.comptime_if_cond(mut cond.left, mut sb) sb.write_string(' ${cond.op} ') r, d2 := c.comptime_if_cond(mut cond.right, mut sb) // if at least one of the cond has `keep_stmts`, we should keep stmts return if cond.op == .and { l && r } else { l || r }, d1 || d2 } .key_is, .not_is, .key_in, .not_in { // $if T is Type // $if T in [Type1, Type2] if cond.left in [ast.TypeNode, ast.Ident, ast.SelectorExpr] && ((cond.right in [ast.ComptimeType, ast.TypeNode] && cond.op in [.key_is, .not_is]) || (cond.right is ast.ArrayInit && cond.op in [.key_in, .not_in])) { c.expr(mut cond.left) // resolve left type mut left_name := '' if mut cond.left is ast.Ident { left_name = cond.left.name } else if mut cond.left is ast.SelectorExpr { left_name = '${cond.left.expr}'.all_before('.') } left_type := c.get_expr_type(cond.left) type_array := if cond.op in [.key_is, .not_is] { // construct a type array for a single `is`, `!is` [cond.right] } else { (cond.right as ast.ArrayInit).exprs } // iter the `type_array`, for `is` and `!is`, it has only one element for expr in type_array { is_true = c.check_compatible_types(left_type, left_name, expr) if is_true { break } } is_true = if cond.op in [.key_in, .key_is] { is_true } else { !is_true } sb.write_string('${is_true}') return is_true, true } if cond.left !in [ast.TypeNode, ast.Ident, ast.SelectorExpr] { c.error('invalid \$if left expr: expected a Type/Ident/SelectorExpr', cond.left.pos()) return false, false } if cond.right !in [ast.ComptimeType, ast.TypeNode] { c.error('invalid \$if right expr: expected a type', cond.right.pos()) return false, false } c.error('invalid \$if condition: is/!is/in/!in', cond.pos) return false, false } .eq, .ne, .gt, .lt, .ge, .le { match mut cond.left { ast.AtExpr { // @OS == 'linux' left_type := c.expr(mut cond.left) right_type := c.expr(mut cond.right) if !c.check_types(right_type, left_type) { left_name := c.table.type_to_str(left_type) right_name := c.table.type_to_str(right_type) c.error('mismatched types `${left_name}` and `${right_name}`', cond.pos) } left_str := cond.left.val right_str := (cond.right as ast.StringLiteral).val if cond.op == .eq { is_true = left_str == right_str } else if cond.op == .ne { is_true = left_str != right_str } else { c.error('string type only support `==` and `!=` operator', cond.pos) return false, false } sb.write_string('${is_true}') return is_true, false } ast.Ident { // $if version == 2 left_type := c.expr(mut cond.left) right_type := c.expr(mut cond.right) expr := c.find_definition(cond.left) or { c.error(err.msg(), cond.left.pos) return false, false } if !c.check_types(right_type, left_type) { left_name := c.table.type_to_str(left_type) right_name := c.table.type_to_str(right_type) c.error('mismatched types `${left_name}` and `${right_name}`', cond.pos) } match mut cond.right { ast.StringLiteral { match cond.op { .eq { is_true = expr.str() == cond.right.str() } .ne { is_true = expr.str() != cond.right.str() } else { c.error('string type only support `==` and `!=` operator', cond.pos) return false, false } } } ast.IntegerLiteral { match cond.op { .eq { is_true = expr.str().i64() == cond.right.val.i64() } .ne { is_true = expr.str().i64() != cond.right.val.i64() } .gt { is_true = expr.str().i64() > cond.right.val.i64() } .lt { is_true = expr.str().i64() < cond.right.val.i64() } .ge { is_true = expr.str().i64() >= cond.right.val.i64() } .le { is_true = expr.str().i64() <= cond.right.val.i64() } else { c.error('int type only support `==` `!=` `>` `<` `>=` and `<=` operator', cond.pos) return false, false } } } ast.BoolLiteral { match cond.op { .eq { is_true = expr.str().bool() == cond.right.val } .ne { is_true = expr.str().bool() != cond.right.val } else { c.error('bool type only support `==` and `!=` operator', cond.pos) return false, false } } } else { c.error('compare only support string int and bool type', cond.pos) return false, false } } sb.write_string('${is_true}') return is_true, false } ast.SelectorExpr { // $if field.name == 'abc' c.expr(mut cond.left) match mut cond.right { ast.StringLiteral { if cond.left.field_name == 'name' && c.comptime.inside_comptime_for { left_name := (cond.left.expr as ast.Ident).name match left_name { c.comptime.comptime_for_method_var { is_true = c.comptime.comptime_for_method.name == cond.right.val } c.comptime.comptime_for_field_var { is_true = c.comptime.comptime_for_field_value.name == cond.right.val } c.comptime.comptime_for_attr_var { is_true = c.comptime.comptime_for_attr_value.name == cond.right.val } else { c.error('.name compare only support for \$for vars', cond.pos) return false, false } } match cond.op { .eq { sb.write_string('${is_true}') return is_true, true } .ne { sb.write_string('${!is_true}') return !is_true, true } else { c.error('.name compare only support for `==` and `!=`', cond.pos) return false, false } } } else { if comparison := c.try_eval_comptime_comparison(mut cond.left, mut cond.right, cond.op) { sb.write_string(if comparison.val { '1' } else { '0' }) return comparison.val, comparison.keep_stmts } c.error('only support .name compare for \$for vars', cond.pos) return false, false } } ast.IntegerLiteral { if cond.left.field_name == 'indirections' { // field.indirections, T.indirections left_type := c.get_expr_type(ast.Expr(cond.left)) left_muls := left_type.nr_muls() match cond.op { .eq { is_true = left_muls == cond.right.val.i64() } .ne { is_true = left_muls != cond.right.val.i64() } .gt { is_true = left_muls > cond.right.val.i64() } .lt { is_true = left_muls < cond.right.val.i64() } .ge { is_true = left_muls >= cond.right.val.i64() } .le { is_true = left_muls <= cond.right.val.i64() } else { c.error('.indirections only support `==` `!=` `>` `<` `>=` and `<=` operator', cond.pos) return false, false } } sb.write_string('${is_true}') return is_true, true } else if cond.left.field_name == 'return_type' { // method.return_type left_name := (cond.left.expr as ast.Ident).name if c.comptime.inside_comptime_for && left_name == c.comptime.comptime_for_method_var { left_type_idx := c.comptime.comptime_for_method_ret_type.idx() match cond.op { .eq { is_true = left_type_idx == cond.right.val.i64() } .gt { is_true = left_type_idx > cond.right.val.i64() } .lt { is_true = left_type_idx < cond.right.val.i64() } .ge { is_true = left_type_idx >= cond.right.val.i64() } .le { is_true = left_type_idx <= cond.right.val.i64() } else { c.error('.return_type only support `==` `!=` `>` `<` `>=` and `<=` operator', cond.pos) return false, false } } sb.write_string('${is_true}') return is_true, false } else { c.error('only support .return_type compare for \$for method', cond.pos) return false, false } } else { if comparison := c.try_eval_comptime_comparison(mut cond.left, mut cond.right, cond.op) { sb.write_string(if comparison.val { '1' } else { '0' }) return comparison.val, comparison.keep_stmts } c.error('only support .indirections/.return_type compare for \$for vars and generic', cond.pos) return false, false } } ast.BoolLiteral { // field.is_pub == true mut left := ast.Expr(cond.left) l, _ := c.comptime_if_cond(mut left, mut sb) sb.write_string(' ${cond.op} ') r := (cond.right as ast.BoolLiteral).val sb.write_string('${r}') is_true = if cond.op == .eq { l == r } else { l != r } return is_true, true } else { if comparison := c.try_eval_comptime_comparison(mut cond.left, mut cond.right, cond.op) { sb.write_string(if comparison.val { '1' } else { '0' }) return comparison.val, comparison.keep_stmts } c.error('definition of `${cond.left}` is unknown at compile time', cond.pos) return false, false } } if comparison := c.try_eval_comptime_comparison(mut cond.left, mut cond.right, cond.op) { sb.write_string(if comparison.val { '1' } else { '0' }) return comparison.val, comparison.keep_stmts } c.error('invalid \$if condition: SelectorExpr', cond.pos) return false, false } ast.SizeOf { match mut cond.right { ast.IntegerLiteral { s, _ := c.table.type_size(c.unwrap_generic(cond.left.typ)) match cond.op { .eq { is_true = s == cond.right.val.i64() } .ne { is_true = s != cond.right.val.i64() } .gt { is_true = s > cond.right.val.i64() } .lt { is_true = s < cond.right.val.i64() } .ge { is_true = s >= cond.right.val.i64() } .le { is_true = s <= cond.right.val.i64() } else { c.error('sizeof() only support `==` `!=` `>` `<` `>=` and `<=` operator', cond.pos) return false, false } } sb.write_string('${is_true}') return is_true, true } else { c.error('sizeof() can only compare with int type', cond.pos) return false, false } } } else { if comparison := c.try_eval_comptime_comparison(mut cond.left, mut cond.right, cond.op) { sb.write_string(if comparison.val { '1' } else { '0' }) return comparison.val, comparison.keep_stmts } c.error('invalid \$if condition', cond.pos) return false, false } } c.error('invalid \$if condition', cond.pos) return false, false } else { c.error('invalid \$if operator: ${cond.op}', cond.pos) return false, false } } } ast.Ident { cname := cond.name // record current cond result for debugging should_record_ident = true is_user_ident = false ident_name = cname if cname in ast.valid_comptime_not_user_defined { if cname == 'threads' { is_true = c.table.gostmts > 0 } else { is_true = ast.eval_comptime_not_user_defined_ident(cname, c.pref) or { c.error(err.msg(), cond.pos) return false, false } } } else if cname !in c.pref.compile_defines_all { if cname == 'linux_or_macos' { c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead', cond.pos) return false, false } // `$if some_var {}`, or `[if user_defined_tag] fn abc(){}` mut ident_expr := ast.Expr(cond) typ := c.unwrap_generic(c.expr(mut ident_expr)) resolved_obj := if mut ident_expr is ast.Ident { ident_expr.obj } else { cond.obj } if resolved_obj !in [ast.Var, ast.ConstField, ast.GlobalField] { if !c.inside_ct_attr { c.error('unknown var: `${cname}`', cond.pos) return false, false } c.error('invalid \$if condition: unknown indent `${cname}`', cond.pos) return false, false } expr := c.find_obj_definition(resolved_obj) or { c.error(err.msg(), cond.pos) return false, false } if !c.check_types(typ, ast.bool_type) { type_name := c.table.type_to_str(typ) c.error('non-bool type `${type_name}` used as \$if condition', cond.pos) return false, false } is_true = (expr as ast.BoolLiteral).val } else if cname in c.pref.compile_defines { is_true = true } else { c.error('invalid \$if condition: unknown indent `${cname}`', cond.pos) return false, false } if ifdef := ast.comptime_if_to_ifdef(cname, c.pref) { sb.write_string('defined(${ifdef})') } else { sb.write_string('${is_true}') } return is_true, false } ast.ComptimeCall { if cond.kind == .pkgconfig { if mut m := pkgconfig.main([cond.args_var]) { if _ := m.run() { is_true = true } else { // pkgconfig not found, do not issue error, just set false is_true = false } } else { c.error(err.msg(), cond.pos) is_true = false } sb.write_string('${is_true}') return is_true, true } if cond.kind == .d { cond.resolve_compile_value(c.pref.compile_values) or { c.error(err.msg(), cond.pos) return false, false } if cond.result_type != ast.bool_type { c.error('inside \$if, only \$d() expressions that return bool are allowed', cond.pos) return false, false } is_true = cond.compile_value.bool() sb.write_string('${is_true}') return is_true, false } c.error('invalid \$if condition: unknown ComptimeCall', cond.pos) return false, false } ast.SelectorExpr { if c.comptime.comptime_for_field_var != '' && cond.expr is ast.Ident { if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_embed', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] { is_true = c.type_resolver.get_comptime_selector_bool_field(cond.field_name) sb.write_string('${is_true}') return is_true, true } c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_field_var}', cond.pos) } if c.comptime.comptime_for_attr_var != '' && cond.expr is ast.Ident { if (cond.expr as ast.Ident).name == c.comptime.comptime_for_attr_var && cond.field_name == 'has_arg' { is_true = c.comptime.comptime_for_attr_value.has_arg sb.write_string('${is_true}') return is_true, true } c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_attr_var}', cond.pos) } if c.comptime.comptime_for_method_var != '' && cond.expr is ast.Ident { if (cond.expr as ast.Ident).name == c.comptime.comptime_for_method_var && cond.field_name in ['is_variadic', 'is_c_variadic', 'is_pub', 'is_ctor_new', 'is_deprecated', 'is_noreturn', 'is_unsafe', 'is_must_use', 'is_placeholder', 'is_main', 'is_test', 'is_keep_alive', 'is_method', 'is_static_type_method', 'no_body', 'is_file_translated', 'is_conditional', 'is_expand_simple_interpolation'] { method := c.comptime.comptime_for_method is_true = match cond.field_name { 'is_variadic' { method.is_variadic } 'is_c_variadic' { method.is_c_variadic } 'is_pub' { method.is_pub } 'is_ctor_new' { method.is_ctor_new } 'is_deprecated' { method.is_deprecated } 'is_noreturn' { method.is_noreturn } 'is_unsafe' { method.is_unsafe } 'is_must_use' { method.is_must_use } 'is_placeholder' { method.is_placeholder } 'is_main' { method.is_main } 'is_test' { method.is_test } 'is_keep_alive' { method.is_keep_alive } 'is_method' { method.is_method } 'is_static_type_method' { method.is_static_type_method } 'no_body' { method.no_body } 'is_file_translated' { method.is_file_translated } 'is_conditional' { method.is_conditional } 'is_expand_simple_interpolation' { method.is_expand_simple_interpolation } else { false } } sb.write_string('${is_true}') return is_true, true } c.error('unknown field `${cond.field_name}` from ${c.comptime.comptime_for_method_var}', cond.pos) } return false, false } else { c.error('invalid \$if condition ${cond}', cond.pos()) return false, false } } c.error('invalid \$if condition ${cond}', cond.pos()) return false, false } // push_new_comptime_info saves the current comptime information fn (mut c Checker) push_new_comptime_info() { c.type_resolver.info_stack << type_resolver.ResolverInfo{ saved_type_map: c.type_resolver.type_map.clone() inside_comptime_for: c.comptime.inside_comptime_for inside_comptime_if: c.comptime.inside_comptime_if has_different_types: c.comptime.has_different_types comptime_for_variant_var: c.comptime.comptime_for_variant_var comptime_for_field_var: c.comptime.comptime_for_field_var comptime_for_field_type: c.comptime.comptime_for_field_type comptime_for_field_value: c.comptime.comptime_for_field_value comptime_for_enum_var: c.comptime.comptime_for_enum_var comptime_for_attr_var: c.comptime.comptime_for_attr_var comptime_for_attr_value: c.comptime.comptime_for_attr_value comptime_for_method_var: c.comptime.comptime_for_method_var comptime_for_method: c.comptime.comptime_for_method comptime_for_method_ret_type: c.comptime.comptime_for_method_ret_type } } // pop_comptime_info pops the current comptime information frame fn (mut c Checker) pop_comptime_info() { old := c.type_resolver.info_stack.pop() c.type_resolver.type_map = old.saved_type_map.clone() c.comptime.inside_comptime_for = old.inside_comptime_for c.comptime.inside_comptime_if = old.inside_comptime_if c.comptime.has_different_types = old.has_different_types c.comptime.comptime_for_variant_var = old.comptime_for_variant_var c.comptime.comptime_for_field_var = old.comptime_for_field_var c.comptime.comptime_for_field_type = old.comptime_for_field_type c.comptime.comptime_for_field_value = old.comptime_for_field_value c.comptime.comptime_for_enum_var = old.comptime_for_enum_var c.comptime.comptime_for_attr_var = old.comptime_for_attr_var c.comptime.comptime_for_attr_value = old.comptime_for_attr_value c.comptime.comptime_for_method_var = old.comptime_for_method_var c.comptime.comptime_for_method = old.comptime_for_method c.comptime.comptime_for_method_ret_type = old.comptime_for_method_ret_type } fn overflows_i8(val i64) bool { return val > max_i8 || val < min_i8 } fn overflows_i16(val i64) bool { return val > max_i16 || val < min_i16 } fn overflows_i32(val i64) bool { return val > max_i32 || val < min_i32 } fn overflows_u8(val i64) bool { return val > max_u8 || val < min_u8 } fn overflows_u16(val i64) bool { return val > max_u16 || val < min_u16 } fn overflows_u32(val i64) bool { return val > max_u32 || val < min_u32 }