diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 78e67c6c47..e925fc4382 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -381,19 +381,25 @@ fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool { } } c.inside_ct_attr = true - node.ct_skip = c.comptime_if_branch(node.ct_expr, node.pos) + 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) bool { +fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBranchSkipState { // TODO: better error messages here match cond { ast.BoolLiteral { - return !cond.val + return if cond.val { .eval } else { .skip } } ast.ParExpr { return c.comptime_if_branch(cond.expr, pos) @@ -402,13 +408,20 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { if cond.op != .not { c.error('invalid `\$if` condition', cond.pos) } - return !c.comptime_if_branch(cond.right, 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 cond.expr.name !in c.pref.compile_defines_all + return if cond.expr.name in c.pref.compile_defines_all { .eval } else { .skip } } else { c.error('invalid `\$if` condition', cond.pos) } @@ -418,12 +431,18 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { .and { l := c.comptime_if_branch(cond.left, cond.pos) r := c.comptime_if_branch(cond.right, cond.pos) - return l || r // skip (return true) if at least one should be skipped + 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) - return l && r // skip (return true) only if both should be skipped + 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 { @@ -433,15 +452,19 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { c.expr(cond.left) // c.error('`$sym.name` is not an interface', cond.right.pos()) } - return false + 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 c.table.is_comptime_type(checked_type, cond.right) + 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 false + return .unknown } else { c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check', cond.left.pos()) @@ -456,7 +479,7 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { right_type := c.expr(cond.right) expr := c.find_definition(cond.left) or { c.error(err.msg(), cond.left.pos) - return false + return .unknown } if !c.check_types(right_type, left_type) { left_name := c.table.type_to_str(left_type) @@ -467,7 +490,19 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { // :) // 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 { different } else { !different } + 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) @@ -481,51 +516,81 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { ast.Ident { cname := cond.name if cname in valid_comptime_if_os { - mut is_os_target_different := false + mut is_os_target_equal := true if !c.pref.output_cross_c { target_os := c.pref.os.str().to_lower() - is_os_target_different = cname != target_os + is_os_target_equal = cname == target_os } - return is_os_target_different + return if is_os_target_equal { .eval } else { .skip } } else if cname in valid_comptime_if_compilers { - return pref.cc_from_string(cname) != c.pref.ccompiler_type + 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 c.pref.arch != .amd64 } - 'i386' { return c.pref.arch != .i386 } - 'aarch64' { return c.pref.arch != .arm64 } - 'arm64' { return c.pref.arch != .arm64 } - 'arm32' { return c.pref.arch != .arm32 } - 'rv64' { return c.pref.arch != .rv64 } - 'rv32' { return c.pref.arch != .rv32 } - else { return false } + '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 false + return .unknown } else if cname in valid_comptime_if_other { match cname { - 'apk' { return !c.pref.is_apk } - 'js' { return !c.pref.backend.is_js() } - 'debug' { return !c.pref.is_debug } - 'prod' { return !c.pref.is_prod } - 'profile' { return !c.pref.is_prof } - 'test' { return !c.pref.is_test } - 'glibc' { return false } // TODO - 'threads' { return c.table.gostmts == 0 } - 'prealloc' { return !c.pref.prealloc } - 'no_bounds_checking' { return cname !in c.pref.compile_defines_all } - 'freestanding' { return !c.pref.is_bare || c.pref.output_cross_c } - 'interpreter' { c.pref.backend != .interpret } - else { return false } + '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 } + } + 'glibc' { + return .unknown + } // TODO + '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 false + return .unknown } // `$if some_var {}`, or `[if user_defined_tag] fn abc(){}` typ := c.unwrap_generic(c.expr(cond)) @@ -534,11 +599,11 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { if !c.inside_ct_attr { c.error('unknown var: `$cname`', pos) } - return false + return .unknown } expr := c.find_obj_definition(cond.obj) or { c.error(err.msg(), cond.pos) - return false + return .unknown } if !c.check_types(typ, ast.bool_type) { type_name := c.table.type_to_str(typ) @@ -546,21 +611,22 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool { } // :) // until `v.eval` is stable, I can't think of a better way to do this - return !(expr as ast.BoolLiteral).val + 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 true + return .skip } - m.run() or { return true } + m.run() or { return .skip } } + return .eval } else { c.error('invalid `\$if` condition', pos) } } - return false + return .unknown } diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index bc2613e10a..05648fb399 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -30,7 +30,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { node.typ = ast.void_type mut nbranches_with_return := 0 mut nbranches_without_return := 0 - mut should_skip := false // Whether the current branch should be skipped + mut skip_state := ComptimeBranchSkipState.unknown mut found_branch := false // Whether a matching branch was found- skip the rest mut is_comptime_type_is_expr := false // if `$if T is string` for i in 0 .. node.branches.len { @@ -41,8 +41,8 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { } if !node.has_else || i < node.branches.len - 1 { if node.is_comptime { - should_skip = c.comptime_if_branch(branch.cond, branch.pos) - node.branches[i].pkg_exist = !should_skip + skip_state = c.comptime_if_branch(branch.cond, branch.pos) + node.branches[i].pkg_exist = if skip_state == .eval { true } else { false } } else { // check condition type is boolean c.expected_type = ast.bool_type @@ -67,7 +67,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { if branch.cond.right is ast.ComptimeType && left is ast.TypeNode { is_comptime_type_is_expr = true checked_type := c.unwrap_generic(left.typ) - should_skip = !c.table.is_comptime_type(checked_type, branch.cond.right as ast.ComptimeType) + skip_state = if c.table.is_comptime_type(checked_type, branch.cond.right as ast.ComptimeType) { + .eval + } else { + .skip + } } else { got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ) sym := c.table.sym(got_type) @@ -84,14 +88,17 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { is_comptime_type_is_expr = true // is interface checked_type := c.unwrap_generic(left.typ) - should_skip = !c.table.does_type_implement_interface(checked_type, + skip_state = if c.table.does_type_implement_interface(checked_type, got_type) + { + .eval + } else { + .skip + } } else if left is ast.TypeNode { is_comptime_type_is_expr = true left_type := c.unwrap_generic(left.typ) - if left_type != got_type { - should_skip = true - } + skip_state = if left_type == got_type { .eval } else { .skip } } } } @@ -99,10 +106,10 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { cur_skip_flags := c.skip_flags if found_branch { c.skip_flags = true - } else if should_skip { + } else if skip_state == .skip { c.skip_flags = true - should_skip = false // Reset the value of `should_skip` for the next branch - } else if !is_comptime_type_is_expr { + skip_state = .unknown // Reset the value of `skip_state` for the next branch + } else if !is_comptime_type_is_expr && skip_state == .eval { found_branch = true // If a branch wasn't skipped, the rest must be } if c.fn_level == 0 && c.pref.output_cross_c { diff --git a/vlib/v/gen/c/testdata/comp_if_unknown.c.must_have b/vlib/v/gen/c/testdata/comp_if_unknown.c.must_have new file mode 100644 index 0000000000..e32240c61d --- /dev/null +++ b/vlib/v/gen/c/testdata/comp_if_unknown.c.must_have @@ -0,0 +1,5 @@ +#if defined(__GLIBC__) +x = 2; +#else +x = 3; +#endif diff --git a/vlib/v/gen/c/testdata/comp_if_unknown.vv b/vlib/v/gen/c/testdata/comp_if_unknown.vv new file mode 100644 index 0000000000..a1fdbab1e4 --- /dev/null +++ b/vlib/v/gen/c/testdata/comp_if_unknown.vv @@ -0,0 +1,9 @@ +fn main() { + mut x := 1 + $if glibc { + x = 2 + } $else { + x = 3 + } + println('done') +}