669 lines
20 KiB
V
669 lines
20 KiB
V
// Copyright (c) 2019-2022 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 v.ast
|
|
import v.pref
|
|
import v.token
|
|
import v.util
|
|
import v.pkgconfig
|
|
|
|
fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
|
|
if node.left !is ast.EmptyExpr {
|
|
node.left_type = c.expr(node.left)
|
|
}
|
|
if node.method_name == 'compile_error' {
|
|
c.error(node.args_var, node.pos)
|
|
return ast.void_type
|
|
} else if node.method_name == 'compile_warn' {
|
|
c.warn(node.args_var, node.pos)
|
|
return ast.void_type
|
|
}
|
|
if node.is_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.is_embed {
|
|
// c.file.embedded_files << node.embed_file
|
|
if node.embed_file.compression_type !in valid_comptime_compression_types {
|
|
supported := 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_idx('v.embed_file.EmbedFileData')
|
|
}
|
|
if node.is_vweb {
|
|
// TODO assoc parser bug
|
|
save_cur_fn := c.table.cur_fn
|
|
pref_ := *c.pref
|
|
pref2 := &pref.Preferences{
|
|
...pref_
|
|
is_vweb: true
|
|
}
|
|
mut c2 := new_checker(c.table, pref2)
|
|
c2.comptime_call_pos = node.pos.pos
|
|
c2.check(node.vweb_tmpl)
|
|
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.method_name == 'html' {
|
|
rtyp := c.table.find_type_idx('vweb.Result')
|
|
node.result_type = rtyp
|
|
return rtyp
|
|
}
|
|
if node.method_name == 'method' {
|
|
for i, arg in node.args {
|
|
// check each arg expression
|
|
node.args[i].typ = c.expr(arg.expr)
|
|
}
|
|
// assume string for now
|
|
return ast.string_type
|
|
}
|
|
if node.is_vweb {
|
|
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_sym := c.table.sym(c.unwrap_generic(node.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
|
|
}
|
|
node.result_type = f.return_type
|
|
return f.return_type
|
|
}
|
|
|
|
fn (mut c Checker) comptime_selector(mut node ast.ComptimeSelector) ast.Type {
|
|
node.left_type = c.expr(node.left)
|
|
expr_type := c.unwrap_generic(c.expr(node.field_expr))
|
|
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.comptime_fields_type.len == 0 {
|
|
c.error('compile time field access can only be used when iterating over `T.fields`',
|
|
left_pos)
|
|
}
|
|
expr_name := node.field_expr.expr.str()
|
|
if expr_name in c.comptime_fields_type {
|
|
return c.comptime_fields_type[expr_name]
|
|
}
|
|
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(node ast.ComptimeFor) {
|
|
typ := c.unwrap_generic(node.typ)
|
|
sym := c.table.sym(typ)
|
|
if sym.kind == .placeholder || typ.has_flag(.generic) {
|
|
c.error('unknown type `$sym.name`', node.typ_pos)
|
|
}
|
|
if node.kind == .fields {
|
|
if sym.kind == .struct_ {
|
|
sym_info := sym.info as ast.Struct
|
|
c.inside_comptime_for_field = true
|
|
for field in sym_info.fields {
|
|
c.comptime_fields_type[node.val_var] = node.typ
|
|
c.comptime_fields_default_type = field.typ
|
|
c.stmts(node.stmts)
|
|
}
|
|
c.inside_comptime_for_field = false
|
|
}
|
|
} else {
|
|
c.stmts(node.stmts)
|
|
}
|
|
}
|
|
|
|
// comptime const eval
|
|
fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?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(expr.expr, nlevel + 1)
|
|
}
|
|
// ast.EnumVal {
|
|
// c.note('>>>>>>>> expr: $expr', expr.pos)
|
|
// return expr.val.i64()
|
|
// }
|
|
ast.SizeOf {
|
|
s, _ := c.table.type_size(expr.typ)
|
|
return 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.CharLiteral {
|
|
runes := expr.val.runes()
|
|
if runes.len > 0 {
|
|
return runes[0]
|
|
}
|
|
return none
|
|
}
|
|
ast.Ident {
|
|
if expr.obj is ast.ConstField {
|
|
// an existing constant?
|
|
return c.eval_comptime_const_expr(expr.obj.expr, nlevel + 1)
|
|
}
|
|
}
|
|
ast.CastExpr {
|
|
cast_expr_value := c.eval_comptime_const_expr(expr.expr, nlevel + 1) 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.int_type {
|
|
return cast_expr_value.int() or { return none }
|
|
}
|
|
if expr.typ == ast.i64_type {
|
|
return cast_expr_value.i64() or { return none }
|
|
}
|
|
//
|
|
if expr.typ == ast.byte_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 }
|
|
}
|
|
}
|
|
ast.InfixExpr {
|
|
left := c.eval_comptime_const_expr(expr.left, nlevel + 1)?
|
|
right := c.eval_comptime_const_expr(expr.right, nlevel + 1)?
|
|
if left is string && right is string {
|
|
match expr.op {
|
|
.plus {
|
|
return 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.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_vweb_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 {
|
|
// allow non custom routed methods, with 1:1 mapping
|
|
return true, -1, margs
|
|
}
|
|
if node.params.len > 1 {
|
|
for param in node.params[1..] {
|
|
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 vweb app method `$node.name`',
|
|
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, route_attributes, margs
|
|
}
|
|
|
|
fn (mut c Checker) verify_all_vweb_routes() {
|
|
if c.vweb_gen_types.len == 0 {
|
|
return
|
|
}
|
|
c.table.used_vweb_types = c.vweb_gen_types
|
|
typ_vweb_result := c.table.find_type_idx('vweb.Result')
|
|
old_file := c.file
|
|
for vgt in c.vweb_gen_types {
|
|
sym_app := c.table.sym(vgt)
|
|
for m in sym_app.methods {
|
|
if m.return_type == typ_vweb_result {
|
|
is_ok, nroute_attributes, nargs := c.verify_vweb_params_for_method(m)
|
|
if !is_ok {
|
|
f := &ast.FnDecl(m.source_fn)
|
|
if isnil(f) {
|
|
continue
|
|
}
|
|
if f.return_type == typ_vweb_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 vweb 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 node.ct_expr is ast.Ident {
|
|
if node.ct_opt {
|
|
if node.ct_expr.name in valid_comptime_not_user_defined {
|
|
c.error('optional `[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 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
|
|
node.ct_skip = if c.comptime_if_branch(node.ct_expr, node.pos) == .skip { true } else { false }
|
|
c.inside_ct_attr = false
|
|
node.ct_evaled = true
|
|
return node.ct_skip
|
|
}
|
|
|
|
enum ComptimeBranchSkipState {
|
|
eval
|
|
skip
|
|
unknown
|
|
}
|
|
|
|
// comptime_if_branch checks the condition of a compile-time `if` branch. It returns `true`
|
|
// if that branch's contents should be skipped (targets a different os for example)
|
|
fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBranchSkipState {
|
|
// TODO: better error messages here
|
|
match cond {
|
|
ast.BoolLiteral {
|
|
return if cond.val { .eval } else { .skip }
|
|
}
|
|
ast.ParExpr {
|
|
return c.comptime_if_branch(cond.expr, pos)
|
|
}
|
|
ast.PrefixExpr {
|
|
if cond.op != .not {
|
|
c.error('invalid `\$if` condition', cond.pos)
|
|
}
|
|
reversed := c.comptime_if_branch(cond.right, cond.pos)
|
|
return if reversed == .eval {
|
|
.skip
|
|
} else if reversed == .skip {
|
|
.eval
|
|
} else {
|
|
reversed
|
|
}
|
|
}
|
|
ast.PostfixExpr {
|
|
if cond.op != .question {
|
|
c.error('invalid \$if postfix operator', cond.pos)
|
|
} else if cond.expr is ast.Ident {
|
|
return if cond.expr.name in c.pref.compile_defines_all { .eval } else { .skip }
|
|
} else {
|
|
c.error('invalid `\$if` condition', cond.pos)
|
|
}
|
|
}
|
|
ast.InfixExpr {
|
|
match cond.op {
|
|
.and {
|
|
l := c.comptime_if_branch(cond.left, cond.pos)
|
|
r := c.comptime_if_branch(cond.right, cond.pos)
|
|
if l == .unknown || r == .unknown {
|
|
return .unknown
|
|
}
|
|
return if l == .eval && r == .eval { .eval } else { .skip }
|
|
}
|
|
.logical_or {
|
|
l := c.comptime_if_branch(cond.left, cond.pos)
|
|
r := c.comptime_if_branch(cond.right, cond.pos)
|
|
if l == .unknown || r == .unknown {
|
|
return .unknown
|
|
}
|
|
return if l == .eval || r == .eval { .eval } else { .skip }
|
|
}
|
|
.key_is, .not_is {
|
|
if cond.left is ast.TypeNode && cond.right is ast.TypeNode {
|
|
// `$if Foo is Interface {`
|
|
sym := c.table.sym(cond.right.typ)
|
|
if sym.kind != .interface_ {
|
|
c.expr(cond.left)
|
|
// c.error('`$sym.name` is not an interface', cond.right.pos())
|
|
}
|
|
return .unknown
|
|
} else if cond.left is ast.TypeNode && cond.right is ast.ComptimeType {
|
|
left := cond.left as ast.TypeNode
|
|
checked_type := c.unwrap_generic(left.typ)
|
|
return if c.table.is_comptime_type(checked_type, cond.right) {
|
|
.eval
|
|
} else {
|
|
.skip
|
|
}
|
|
} else if cond.left in [ast.SelectorExpr, ast.TypeNode] {
|
|
// `$if method.@type is string`
|
|
c.expr(cond.left)
|
|
return .unknown
|
|
} else {
|
|
c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check',
|
|
cond.left.pos())
|
|
}
|
|
}
|
|
.eq, .ne {
|
|
if cond.left is ast.SelectorExpr && cond.right is ast.IntegerLiteral {
|
|
// $if method.args.len == 1
|
|
} else if cond.left is ast.Ident {
|
|
// $if version == 2
|
|
left_type := c.expr(cond.left)
|
|
right_type := c.expr(cond.right)
|
|
expr := c.find_definition(cond.left) or {
|
|
c.error(err.msg(), cond.left.pos)
|
|
return .unknown
|
|
}
|
|
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)
|
|
}
|
|
// :)
|
|
// until `v.eval` is stable, I can't think of a better way to do this
|
|
different := expr.str() != cond.right.str()
|
|
return if cond.op == .eq {
|
|
if different {
|
|
ComptimeBranchSkipState.skip
|
|
} else {
|
|
ComptimeBranchSkipState.eval
|
|
}
|
|
} else {
|
|
if different {
|
|
ComptimeBranchSkipState.eval
|
|
} else {
|
|
ComptimeBranchSkipState.skip
|
|
}
|
|
}
|
|
} else {
|
|
c.error('invalid `\$if` condition: ${cond.left.type_name()}1',
|
|
cond.pos)
|
|
}
|
|
}
|
|
else {
|
|
c.error('invalid `\$if` condition', cond.pos)
|
|
}
|
|
}
|
|
}
|
|
ast.Ident {
|
|
cname := cond.name
|
|
if cname in valid_comptime_if_os {
|
|
mut is_os_target_equal := true
|
|
if !c.pref.output_cross_c {
|
|
target_os := c.pref.os.str().to_lower()
|
|
is_os_target_equal = cname == target_os
|
|
}
|
|
return if is_os_target_equal { .eval } else { .skip }
|
|
} else if cname in valid_comptime_if_compilers {
|
|
return if pref.cc_from_string(cname) == c.pref.ccompiler_type {
|
|
.eval
|
|
} else {
|
|
.skip
|
|
}
|
|
} else if cname in valid_comptime_if_platforms {
|
|
if cname == 'aarch64' {
|
|
c.note('use `arm64` instead of `aarch64`', pos)
|
|
}
|
|
match cname {
|
|
'amd64' { return if c.pref.arch == .amd64 { .eval } else { .skip } }
|
|
'i386' { return if c.pref.arch == .i386 { .eval } else { .skip } }
|
|
'aarch64' { return if c.pref.arch == .arm64 { .eval } else { .skip } }
|
|
'arm64' { return if c.pref.arch == .arm64 { .eval } else { .skip } }
|
|
'arm32' { return if c.pref.arch == .arm32 { .eval } else { .skip } }
|
|
'rv64' { return if c.pref.arch == .rv64 { .eval } else { .skip } }
|
|
'rv32' { return if c.pref.arch == .rv32 { .eval } else { .skip } }
|
|
else { return .unknown }
|
|
}
|
|
} else if cname in valid_comptime_if_cpu_features {
|
|
return .unknown
|
|
} else if cname in valid_comptime_if_other {
|
|
match cname {
|
|
'apk' {
|
|
return if c.pref.is_apk { .eval } else { .skip }
|
|
}
|
|
'js' {
|
|
return if c.pref.backend.is_js() { .eval } else { .skip }
|
|
}
|
|
'debug' {
|
|
return if c.pref.is_debug { .eval } else { .skip }
|
|
}
|
|
'prod' {
|
|
return if c.pref.is_prod { .eval } else { .skip }
|
|
}
|
|
'profile' {
|
|
return if c.pref.is_prof { .eval } else { .skip }
|
|
}
|
|
'test' {
|
|
return if c.pref.is_test { .eval } else { .skip }
|
|
}
|
|
'musl' {
|
|
return .unknown
|
|
}
|
|
'glibc' {
|
|
return .unknown
|
|
}
|
|
'threads' {
|
|
return if c.table.gostmts > 0 { .eval } else { .skip }
|
|
}
|
|
'prealloc' {
|
|
return if c.pref.prealloc { .eval } else { .skip }
|
|
}
|
|
'no_bounds_checking' {
|
|
return if cname in c.pref.compile_defines_all { .eval } else { .skip }
|
|
}
|
|
'freestanding' {
|
|
return if c.pref.is_bare && !c.pref.output_cross_c { .eval } else { .skip }
|
|
}
|
|
'interpreter' {
|
|
return if c.pref.backend == .interpret { .eval } else { .skip }
|
|
}
|
|
else {
|
|
return .unknown
|
|
}
|
|
}
|
|
} 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 .unknown
|
|
}
|
|
// `$if some_var {}`, or `[if user_defined_tag] fn abc(){}`
|
|
typ := c.unwrap_generic(c.expr(cond))
|
|
if cond.obj !is ast.Var && cond.obj !is ast.ConstField
|
|
&& cond.obj !is ast.GlobalField {
|
|
if !c.inside_ct_attr {
|
|
c.error('unknown var: `$cname`', pos)
|
|
}
|
|
return .unknown
|
|
}
|
|
expr := c.find_obj_definition(cond.obj) or {
|
|
c.error(err.msg(), cond.pos)
|
|
return .unknown
|
|
}
|
|
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)
|
|
}
|
|
// :)
|
|
// until `v.eval` is stable, I can't think of a better way to do this
|
|
return if (expr as ast.BoolLiteral).val { .eval } else { .skip }
|
|
}
|
|
}
|
|
ast.ComptimeCall {
|
|
if cond.is_pkgconfig {
|
|
mut m := pkgconfig.main([cond.args_var]) or {
|
|
c.error(err.msg(), cond.pos)
|
|
return .skip
|
|
}
|
|
m.run() or { return .skip }
|
|
}
|
|
return .eval
|
|
}
|
|
else {
|
|
c.error('invalid `\$if` condition', pos)
|
|
}
|
|
}
|
|
return .unknown
|
|
}
|