checker: refactor comptime_if_branch (#14259)
parent
990afe37e1
commit
db185598d2
|
@ -381,19 +381,25 @@ fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.inside_ct_attr = true
|
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
|
c.inside_ct_attr = false
|
||||||
node.ct_evaled = true
|
node.ct_evaled = true
|
||||||
return node.ct_skip
|
return node.ct_skip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ComptimeBranchSkipState {
|
||||||
|
eval
|
||||||
|
skip
|
||||||
|
unknown
|
||||||
|
}
|
||||||
|
|
||||||
// comptime_if_branch checks the condition of a compile-time `if` branch. It returns `true`
|
// 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)
|
// 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
|
// TODO: better error messages here
|
||||||
match cond {
|
match cond {
|
||||||
ast.BoolLiteral {
|
ast.BoolLiteral {
|
||||||
return !cond.val
|
return if cond.val { .eval } else { .skip }
|
||||||
}
|
}
|
||||||
ast.ParExpr {
|
ast.ParExpr {
|
||||||
return c.comptime_if_branch(cond.expr, pos)
|
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 {
|
if cond.op != .not {
|
||||||
c.error('invalid `\$if` condition', cond.pos)
|
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 {
|
ast.PostfixExpr {
|
||||||
if cond.op != .question {
|
if cond.op != .question {
|
||||||
c.error('invalid \$if postfix operator', cond.pos)
|
c.error('invalid \$if postfix operator', cond.pos)
|
||||||
} else if cond.expr is ast.Ident {
|
} 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 {
|
} else {
|
||||||
c.error('invalid `\$if` condition', cond.pos)
|
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 {
|
.and {
|
||||||
l := c.comptime_if_branch(cond.left, cond.pos)
|
l := c.comptime_if_branch(cond.left, cond.pos)
|
||||||
r := c.comptime_if_branch(cond.right, 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 {
|
.logical_or {
|
||||||
l := c.comptime_if_branch(cond.left, cond.pos)
|
l := c.comptime_if_branch(cond.left, cond.pos)
|
||||||
r := c.comptime_if_branch(cond.right, 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 {
|
.key_is, .not_is {
|
||||||
if cond.left is ast.TypeNode && cond.right is ast.TypeNode {
|
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.expr(cond.left)
|
||||||
// c.error('`$sym.name` is not an interface', cond.right.pos())
|
// 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 {
|
} else if cond.left is ast.TypeNode && cond.right is ast.ComptimeType {
|
||||||
left := cond.left as ast.TypeNode
|
left := cond.left as ast.TypeNode
|
||||||
checked_type := c.unwrap_generic(left.typ)
|
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] {
|
} else if cond.left in [ast.SelectorExpr, ast.TypeNode] {
|
||||||
// `$if method.@type is string`
|
// `$if method.@type is string`
|
||||||
c.expr(cond.left)
|
c.expr(cond.left)
|
||||||
return false
|
return .unknown
|
||||||
} else {
|
} else {
|
||||||
c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check',
|
c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check',
|
||||||
cond.left.pos())
|
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)
|
right_type := c.expr(cond.right)
|
||||||
expr := c.find_definition(cond.left) or {
|
expr := c.find_definition(cond.left) or {
|
||||||
c.error(err.msg(), cond.left.pos)
|
c.error(err.msg(), cond.left.pos)
|
||||||
return false
|
return .unknown
|
||||||
}
|
}
|
||||||
if !c.check_types(right_type, left_type) {
|
if !c.check_types(right_type, left_type) {
|
||||||
left_name := c.table.type_to_str(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
|
// until `v.eval` is stable, I can't think of a better way to do this
|
||||||
different := expr.str() != cond.right.str()
|
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 {
|
} else {
|
||||||
c.error('invalid `\$if` condition: ${cond.left.type_name()}1',
|
c.error('invalid `\$if` condition: ${cond.left.type_name()}1',
|
||||||
cond.pos)
|
cond.pos)
|
||||||
|
@ -481,51 +516,81 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) bool {
|
||||||
ast.Ident {
|
ast.Ident {
|
||||||
cname := cond.name
|
cname := cond.name
|
||||||
if cname in valid_comptime_if_os {
|
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 {
|
if !c.pref.output_cross_c {
|
||||||
target_os := c.pref.os.str().to_lower()
|
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 {
|
} 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 {
|
} else if cname in valid_comptime_if_platforms {
|
||||||
if cname == 'aarch64' {
|
if cname == 'aarch64' {
|
||||||
c.note('use `arm64` instead of `aarch64`', pos)
|
c.note('use `arm64` instead of `aarch64`', pos)
|
||||||
}
|
}
|
||||||
match cname {
|
match cname {
|
||||||
'amd64' { return c.pref.arch != .amd64 }
|
'amd64' { return if c.pref.arch == .amd64 { .eval } else { .skip } }
|
||||||
'i386' { return c.pref.arch != .i386 }
|
'i386' { return if c.pref.arch == .i386 { .eval } else { .skip } }
|
||||||
'aarch64' { return c.pref.arch != .arm64 }
|
'aarch64' { return if c.pref.arch == .arm64 { .eval } else { .skip } }
|
||||||
'arm64' { return c.pref.arch != .arm64 }
|
'arm64' { return if c.pref.arch == .arm64 { .eval } else { .skip } }
|
||||||
'arm32' { return c.pref.arch != .arm32 }
|
'arm32' { return if c.pref.arch == .arm32 { .eval } else { .skip } }
|
||||||
'rv64' { return c.pref.arch != .rv64 }
|
'rv64' { return if c.pref.arch == .rv64 { .eval } else { .skip } }
|
||||||
'rv32' { return c.pref.arch != .rv32 }
|
'rv32' { return if c.pref.arch == .rv32 { .eval } else { .skip } }
|
||||||
else { return false }
|
else { return .unknown }
|
||||||
}
|
}
|
||||||
} else if cname in valid_comptime_if_cpu_features {
|
} else if cname in valid_comptime_if_cpu_features {
|
||||||
return false
|
return .unknown
|
||||||
} else if cname in valid_comptime_if_other {
|
} else if cname in valid_comptime_if_other {
|
||||||
match cname {
|
match cname {
|
||||||
'apk' { return !c.pref.is_apk }
|
'apk' {
|
||||||
'js' { return !c.pref.backend.is_js() }
|
return if c.pref.is_apk { .eval } else { .skip }
|
||||||
'debug' { return !c.pref.is_debug }
|
}
|
||||||
'prod' { return !c.pref.is_prod }
|
'js' {
|
||||||
'profile' { return !c.pref.is_prof }
|
return if c.pref.backend.is_js() { .eval } else { .skip }
|
||||||
'test' { return !c.pref.is_test }
|
}
|
||||||
'glibc' { return false } // TODO
|
'debug' {
|
||||||
'threads' { return c.table.gostmts == 0 }
|
return if c.pref.is_debug { .eval } else { .skip }
|
||||||
'prealloc' { return !c.pref.prealloc }
|
}
|
||||||
'no_bounds_checking' { return cname !in c.pref.compile_defines_all }
|
'prod' {
|
||||||
'freestanding' { return !c.pref.is_bare || c.pref.output_cross_c }
|
return if c.pref.is_prod { .eval } else { .skip }
|
||||||
'interpreter' { c.pref.backend != .interpret }
|
}
|
||||||
else { return false }
|
'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 {
|
} else if cname !in c.pref.compile_defines_all {
|
||||||
if cname == 'linux_or_macos' {
|
if cname == 'linux_or_macos' {
|
||||||
c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead',
|
c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead',
|
||||||
cond.pos)
|
cond.pos)
|
||||||
return false
|
return .unknown
|
||||||
}
|
}
|
||||||
// `$if some_var {}`, or `[if user_defined_tag] fn abc(){}`
|
// `$if some_var {}`, or `[if user_defined_tag] fn abc(){}`
|
||||||
typ := c.unwrap_generic(c.expr(cond))
|
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 {
|
if !c.inside_ct_attr {
|
||||||
c.error('unknown var: `$cname`', pos)
|
c.error('unknown var: `$cname`', pos)
|
||||||
}
|
}
|
||||||
return false
|
return .unknown
|
||||||
}
|
}
|
||||||
expr := c.find_obj_definition(cond.obj) or {
|
expr := c.find_obj_definition(cond.obj) or {
|
||||||
c.error(err.msg(), cond.pos)
|
c.error(err.msg(), cond.pos)
|
||||||
return false
|
return .unknown
|
||||||
}
|
}
|
||||||
if !c.check_types(typ, ast.bool_type) {
|
if !c.check_types(typ, ast.bool_type) {
|
||||||
type_name := c.table.type_to_str(typ)
|
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
|
// 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 {
|
ast.ComptimeCall {
|
||||||
if cond.is_pkgconfig {
|
if cond.is_pkgconfig {
|
||||||
mut m := pkgconfig.main([cond.args_var]) or {
|
mut m := pkgconfig.main([cond.args_var]) or {
|
||||||
c.error(err.msg(), cond.pos)
|
c.error(err.msg(), cond.pos)
|
||||||
return true
|
return .skip
|
||||||
}
|
}
|
||||||
m.run() or { return true }
|
m.run() or { return .skip }
|
||||||
}
|
}
|
||||||
|
return .eval
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
c.error('invalid `\$if` condition', pos)
|
c.error('invalid `\$if` condition', pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return .unknown
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
||||||
node.typ = ast.void_type
|
node.typ = ast.void_type
|
||||||
mut nbranches_with_return := 0
|
mut nbranches_with_return := 0
|
||||||
mut nbranches_without_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 found_branch := false // Whether a matching branch was found- skip the rest
|
||||||
mut is_comptime_type_is_expr := false // if `$if T is string`
|
mut is_comptime_type_is_expr := false // if `$if T is string`
|
||||||
for i in 0 .. node.branches.len {
|
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.has_else || i < node.branches.len - 1 {
|
||||||
if node.is_comptime {
|
if node.is_comptime {
|
||||||
should_skip = c.comptime_if_branch(branch.cond, branch.pos)
|
skip_state = c.comptime_if_branch(branch.cond, branch.pos)
|
||||||
node.branches[i].pkg_exist = !should_skip
|
node.branches[i].pkg_exist = if skip_state == .eval { true } else { false }
|
||||||
} else {
|
} else {
|
||||||
// check condition type is boolean
|
// check condition type is boolean
|
||||||
c.expected_type = ast.bool_type
|
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 {
|
if branch.cond.right is ast.ComptimeType && left is ast.TypeNode {
|
||||||
is_comptime_type_is_expr = true
|
is_comptime_type_is_expr = true
|
||||||
checked_type := c.unwrap_generic(left.typ)
|
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 {
|
} else {
|
||||||
got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ)
|
got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ)
|
||||||
sym := c.table.sym(got_type)
|
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_comptime_type_is_expr = true
|
||||||
// is interface
|
// is interface
|
||||||
checked_type := c.unwrap_generic(left.typ)
|
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)
|
got_type)
|
||||||
|
{
|
||||||
|
.eval
|
||||||
|
} else {
|
||||||
|
.skip
|
||||||
|
}
|
||||||
} else if left is ast.TypeNode {
|
} else if left is ast.TypeNode {
|
||||||
is_comptime_type_is_expr = true
|
is_comptime_type_is_expr = true
|
||||||
left_type := c.unwrap_generic(left.typ)
|
left_type := c.unwrap_generic(left.typ)
|
||||||
if left_type != got_type {
|
skip_state = if left_type == got_type { .eval } else { .skip }
|
||||||
should_skip = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,10 +106,10 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
||||||
cur_skip_flags := c.skip_flags
|
cur_skip_flags := c.skip_flags
|
||||||
if found_branch {
|
if found_branch {
|
||||||
c.skip_flags = true
|
c.skip_flags = true
|
||||||
} else if should_skip {
|
} else if skip_state == .skip {
|
||||||
c.skip_flags = true
|
c.skip_flags = true
|
||||||
should_skip = false // Reset the value of `should_skip` for the next branch
|
skip_state = .unknown // Reset the value of `skip_state` for the next branch
|
||||||
} else if !is_comptime_type_is_expr {
|
} else if !is_comptime_type_is_expr && skip_state == .eval {
|
||||||
found_branch = true // If a branch wasn't skipped, the rest must be
|
found_branch = true // If a branch wasn't skipped, the rest must be
|
||||||
}
|
}
|
||||||
if c.fn_level == 0 && c.pref.output_cross_c {
|
if c.fn_level == 0 && c.pref.output_cross_c {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#if defined(__GLIBC__)
|
||||||
|
x = 2;
|
||||||
|
#else
|
||||||
|
x = 3;
|
||||||
|
#endif
|
|
@ -0,0 +1,9 @@
|
||||||
|
fn main() {
|
||||||
|
mut x := 1
|
||||||
|
$if glibc {
|
||||||
|
x = 2
|
||||||
|
} $else {
|
||||||
|
x = 3
|
||||||
|
}
|
||||||
|
println('done')
|
||||||
|
}
|
Loading…
Reference in New Issue