checker,cgen: add comptime match support

This commit is contained in:
kbkpbot 2025-08-25 21:40:09 +08:00
parent be31491834
commit 6d25f4db9d
22 changed files with 923 additions and 273 deletions

View file

@ -1262,6 +1262,7 @@ pub mut:
@[minify]
pub struct MatchExpr {
pub:
is_comptime bool
tok_kind token.Kind
pos token.Pos
comments []Comment // comments before the first branch

View file

@ -969,6 +969,15 @@ fn (mut c Checker) comptime_if_to_ifdef(name string) !string {
return error('bad os ifdef name "${name}"')
}
// check if `ident` is a function generic, such as `T`
fn (mut c Checker) is_generic_ident(ident string) bool {
if !isnil(c.table.cur_fn) && 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 {
@ -978,6 +987,10 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type {
|| 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`
idx := c.table.cur_fn.generic_names.index(cond.name)
return c.table.cur_concrete_types[idx]
} else if var := cond.scope.find_var(cond.name) {
// var
checked_type = c.unwrap_generic(var.typ)
@ -1042,6 +1055,35 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type {
}
}
fn (mut c Checker) check_compatible_types(left_type ast.Type, left_name string, expr ast.Expr) bool {
if expr is ast.ComptimeType {
return c.type_resolver.is_comptime_type(left_type, expr as ast.ComptimeType)
} else if expr is ast.TypeNode {
typ := c.get_expr_type(expr)
right_type := c.unwrap_generic(typ)
right_sym := c.table.sym(right_type)
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 left_type.has_flag(.option) == right_type.has_flag(.option)
&& c.table.does_type_implement_interface(left_type, right_type)
}
if right_sym.info is ast.FnType && c.comptime.comptime_for_method_var == left_name {
return c.table.fn_signature(right_sym.info.func,
skip_receiver: true
type_only: true
) == c.table.fn_signature(c.comptime.comptime_for_method,
skip_receiver: true
type_only: true
)
} else {
return 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 {`
@ -1141,46 +1183,10 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) (
}
// iter the `type_array`, for `is` and `!is`, it has only one element
for expr in type_array {
if expr is ast.ComptimeType {
is_true = c.type_resolver.is_comptime_type(left_type,
expr as ast.ComptimeType)
is_true = c.check_compatible_types(left_type, left_name, expr)
if is_true {
break
}
} else if expr is ast.TypeNode {
typ := c.get_expr_type(expr)
right_type := c.unwrap_generic(typ)
right_sym := c.table.sym(right_type)
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 {
is_true =
left_type.has_flag(.option) == right_type.has_flag(.option)
&& c.table.does_type_implement_interface(left_type, right_type)
if is_true {
break
}
}
if right_sym.info is ast.FnType
&& c.comptime.comptime_for_method_var == left_name {
is_true = c.table.fn_signature(right_sym.info.func,
skip_receiver: true
type_only: true
) == c.table.fn_signature(c.comptime.comptime_for_method,
skip_receiver: true
type_only: true
)
if is_true {
break
}
} else {
is_true = left_type == right_type
if is_true {
break
}
}
}
}
is_true = if cond.op in [.key_in, .key_is] { is_true } else { !is_true }
sb.write_string('${is_true}')

View file

@ -18,14 +18,27 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
c.expected_expr_type = ast.void_type
}
}
cond_type := c.expr(mut node.cond)
mut cond_type := ast.void_type
if node.is_comptime {
// for field.name and generic type `T`
if node.cond is ast.SelectorExpr {
c.expr(mut node.cond)
}
cond_type = c.get_expr_type(node.cond)
} else {
cond_type = c.expr(mut node.cond)
}
// we setting this here rather than at the end of the method
// since it is used in c.match_exprs() it saves checking twice
node.cond_type = ast.mktyp(cond_type)
if (node.cond is ast.Ident && node.cond.is_mut)
|| (node.cond is ast.SelectorExpr && node.cond.is_mut) {
if node.is_comptime {
c.error('`\$match` condition `${node.cond}` can not be mutable', node.cond.pos())
} else {
c.fail_if_immutable(mut node.cond)
}
}
if !c.ensure_type_exists(node.cond_type, node.pos) {
return ast.void_type
}
@ -42,7 +55,164 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
mut nbranches_with_return := 0
mut nbranches_without_return := 0
mut must_be_option := false
comptime_branch_context_str := if node.is_comptime { c.gen_branch_context_string() } else { '' }
mut comptime_match_branch_result := false
mut comptime_match_found_branch := false
mut comptime_match_cond_value := ''
if node.is_comptime {
if node.cond in [ast.ComptimeType, ast.TypeNode]
|| (node.cond is ast.Ident && (c.is_generic_ident(node.cond.name))) {
// must be a type `$match`
c.inside_x_matches_type = true
} else {
// a value `$match`, eval the `node.cond` first
c.expr(mut node.cond)
if !c.type_resolver.is_generic_param_var(node.cond) {
match mut node.cond {
ast.StringLiteral {
comptime_match_cond_value = node.cond.val
}
ast.IntegerLiteral {
comptime_match_cond_value = node.cond.val.str()
}
ast.BoolLiteral {
comptime_match_cond_value = node.cond.val.str()
}
ast.Ident {
mut cond_expr := c.find_definition(node.cond) or {
c.error(err.msg(), node.cond.pos)
return ast.void_type
}
match mut cond_expr {
ast.StringLiteral {
comptime_match_cond_value = cond_expr.val
}
ast.IntegerLiteral {
comptime_match_cond_value = cond_expr.val.str()
}
ast.BoolLiteral {
comptime_match_cond_value = cond_expr.val.str()
}
else {
c.error('`${node.cond}` is not a string/int/bool literal.',
node.cond.pos)
return ast.void_type
}
}
}
ast.SelectorExpr {
if c.comptime.inside_comptime_for && node.cond.field_name in ['name', 'typ'] {
// hack: `typ` is just for bypass the error test, because we don't know it is a type match or a value match righ now
comptime_match_cond_value = c.comptime.comptime_for_field_value.name
} else {
c.error('`${node.cond}` is not `\$for` field.name.', node.cond.pos)
return ast.void_type
}
}
else {
c.error('`\$match` cond only support string/int/bool/ident.',
node.cond.pos())
return ast.void_type
}
}
}
}
}
for mut branch in node.branches {
if node.is_comptime {
// `idx_str` is composed of two parts:
// The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json`
// The second part indicates the branch's location in the source file.
// This format must match what is in `cgen`.
idx_str := comptime_branch_context_str + '|${c.file.path}|${branch.pos}|'
mut c_str := ''
if !branch.is_else {
if c.inside_x_matches_type {
// type $match
for expr in branch.exprs {
if mut node.cond is ast.ComptimeType {
// $match $int { a {}
branch_type := c.get_expr_type(expr)
comptime_match_branch_result = c.type_resolver.is_comptime_type(branch_type,
node.cond)
c_str = '${expr} == ${c.table.type_to_str(branch_type)}'
} else {
// $match a { $int {}
comptime_match_branch_result = c.check_compatible_types(node.cond_type,
'${node.cond}', expr)
c_str = '${c.table.type_to_str(node.cond_type)} == ${expr}'
}
if comptime_match_branch_result {
break
}
}
} else {
// value $match
for mut expr in branch.exprs {
match mut expr {
ast.Ident {
mut branch_expr := c.find_definition(expr) or {
c.error(err.msg(), expr.pos)
return ast.void_type
}
match mut branch_expr {
ast.StringLiteral {
comptime_match_branch_result = branch_expr.val == comptime_match_cond_value
}
ast.IntegerLiteral {
comptime_match_branch_result = branch_expr.val.str() == comptime_match_cond_value
}
ast.BoolLiteral {
comptime_match_branch_result = branch_expr.val.str() == comptime_match_cond_value
}
else {
c.error('`${expr}` is not a string/int/bool literal.',
expr.pos)
return ast.void_type
}
}
c_str = '${node.cond} == ${expr.name}'
}
ast.SelectorExpr {}
ast.StringLiteral {
c_str = '${node.cond} == ${expr}'
comptime_match_branch_result = comptime_match_cond_value == expr.val
}
ast.IntegerLiteral {
c_str = '${node.cond} == ${expr.val}'
comptime_match_branch_result = comptime_match_cond_value == expr.val.str()
}
ast.BoolLiteral {
c_str = '${node.cond} == ${expr.val}'
comptime_match_branch_result = comptime_match_cond_value == expr.val.str()
}
else {
c.error('`\$match` branch only support string/int/bool types',
node.cond.pos())
return ast.void_type
}
}
if comptime_match_branch_result {
break
}
}
}
if comptime_match_branch_result {
comptime_match_found_branch = true
}
// set `comptime_is_true` which can be used by `cgen`
c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{
val: comptime_match_branch_result
c_str: c_str
}
} else {
comptime_match_branch_result = !comptime_match_found_branch
c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{
val: comptime_match_branch_result
c_str: ''
}
}
}
if node.is_expr {
c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type)
} else {
@ -326,6 +496,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
c.expected_type = node.expected_type
cond_sym := c.table.sym(node.cond_type)
mut enum_ref_checked := false
mut is_comptime_value_match := false
// branch_exprs is a histogram of how many times
// an expr was used in the match
mut branch_exprs := map[string]int{}
@ -335,7 +506,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
for k, mut expr in branch.exprs {
mut key := ''
// TODO: investigate why enums are different here:
if expr !is ast.EnumVal {
if expr !is ast.EnumVal && !(node.is_comptime && expr is ast.ComptimeType) {
// ensure that the sub expressions of the branch are actually checked, before anything else:
_ := c.expr(mut expr)
}
@ -351,7 +522,8 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
&& node.cond.field_name == 'typ'
}
}
if mut expr is ast.TypeNode && cond_sym.is_primitive() && !is_comptime {
if mut expr is ast.TypeNode && cond_sym.is_primitive() && !is_comptime
&& !node.is_comptime {
c.error('matching by type can only be done for sum types, generics, interfaces, `${node.cond}` is none of those',
branch.pos)
}
@ -412,7 +584,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
}
continue
}
is_type_node := expr is ast.TypeNode
is_type_node := expr is ast.TypeNode || expr is ast.ComptimeType
match mut expr {
ast.TypeNode {
key = c.table.type_to_str(expr.typ)
@ -439,7 +611,58 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
c.expected_type = node.cond_type
if is_type_node {
c.inside_x_matches_type = true
} else {
if node.is_comptime {
is_comptime_value_match = true
}
}
if node.is_comptime {
if is_type_node {
if is_comptime_value_match {
// type branch in a value match
c.error('can not matching a type in a value `\$match`', expr.pos())
return
}
} else if c.inside_x_matches_type {
// value branch in a type match
if expr in [ast.IntegerLiteral, ast.BoolLiteral, ast.StringLiteral] {
c.error('can not matching a value in a type `\$match`', expr.pos())
return
}
}
if !is_type_node {
// value check should match cond's type
if expr is ast.IntegerLiteral {
if mut node.cond is ast.ComptimeType {
if node.cond.kind != .int {
c.error('can not matching a int value(`${expr}`) in a non int type `\$match`, `${node.cond}` type is `${node.cond.kind}`',
expr.pos())
return
}
} else if node.cond_type !in ast.integer_type_idxs {
c.error('can not matching a int value(`${expr}`) in a non int type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`',
expr.pos())
return
}
} else if expr is ast.BoolLiteral && node.cond_type != ast.bool_type {
c.error('can not matching a bool value(`${expr}`) in a non bool type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`',
expr.pos())
return
} else if expr is ast.StringLiteral {
if mut node.cond is ast.ComptimeType {
if node.cond.kind != .string {
c.error('can not matching a string value(`${expr}`) in a non string type `\$match`, `${node.cond}` type is `${node.cond.kind}`',
expr.pos())
return
}
} else if node.cond_type != ast.string_type {
c.error('can not matching a string value(`${expr}`) in a non string type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`',
expr.pos())
return
}
}
}
} else {
expr_type := c.expr(mut expr)
if expr_type.idx() == 0 {
// parser failed, stop checking
@ -480,6 +703,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
expect_str := c.table.type_to_str(node.cond_type)
c.error('cannot match `${expect_str}` with `${expr_str}`', expr.pos())
}
}
branch_exprs[key] = val + 1
}
// when match is type matching, then register smart cast for every branch
@ -596,7 +820,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
}
return
}
if has_else {
if has_else || node.is_comptime {
return
}
mut err_details := 'match must be exhaustive'

View file

@ -0,0 +1,14 @@
vlib/v/checker/tests/comptime_match_cond_cannot_mut.vv:3:9: error: `x` is mut and may have changed since its definition
1 | fn main() {
2 | mut x := 1
3 | $match x {
| ^
4 | 1 {
5 | println('1')
vlib/v/checker/tests/comptime_match_cond_cannot_mut.vv:13:13: error: `$match` condition `y` can not be mutable
11 |
12 | y := 100
13 | $match mut y {
| ^
14 | 100 {
15 | println('100')

View file

@ -0,0 +1,18 @@
fn main() {
mut x := 1
$match x {
1 {
println('1')
}
2 {
println('2')
}
}
y := 100
$match mut y {
100 {
println('100')
}
}
}

View file

@ -0,0 +1,14 @@
vlib/v/checker/tests/comptime_match_value_different_type.vv:7:3: error: can not matching a string value(`'100'`) in a non string type `$match`, `x` type is `int`
5 | println('int 100')
6 | }
7 | '100' {
| ~~~~~
8 | println('string 100')
9 | }
vlib/v/checker/tests/comptime_match_value_different_type.vv:18:3: error: can not matching a string value(`'1'`) in a non string type `$match`, `$int` type is `int`
16 | println('IntegerLiteral')
17 | }
18 | '1' {
| ~~~
19 | println('StringLiteral')
20 | }

View file

@ -0,0 +1,25 @@
fn match_value_different_type() {
x := 100
$match x {
100 {
println('int 100')
}
'100' {
println('string 100')
}
}
}
fn match_type_different_literal() {
$match $int {
1 {
println('IntegerLiteral')
}
'1' {
println('StringLiteral')
}
true {
println('BoolLiteral')
}
}
}

View file

@ -0,0 +1,14 @@
vlib/v/checker/tests/comptime_match_value_type_mix_check.vv:9:3: error: can not matching a type in a value `$match`
7 | println('value check')
8 | }
9 | int {
| ~~~
10 | println('type check')
11 | }
vlib/v/checker/tests/comptime_match_value_type_mix_check.vv:21:3: error: can not matching a value in a type `$match`
19 | println('type check')
20 | }
21 | 100 {
| ~~~
22 | println('value check')
23 | }

View file

@ -0,0 +1,25 @@
module main
fn type_check_in_a_value_match() {
x := 100
$match x {
100 {
println('value check')
}
int {
println('type check')
}
}
}
fn value_check_in_a_type_match() {
x := 100
$match x {
int {
println('type check')
}
100 {
println('value check')
}
}
}

View file

@ -2842,7 +2842,7 @@ pub fn (mut f Fmt) map_init(node ast.MapInit) {
f.write('}')
}
fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool) {
fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool, is_comptime bool) {
if !branch.is_else {
// normal branch
f.is_mbranch_expr = true
@ -2864,8 +2864,12 @@ fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool) {
f.is_mbranch_expr = false
} else {
// else branch
if is_comptime {
f.write('\$else')
} else {
f.write('else')
}
}
if branch.stmts.len == 0 {
f.writeln(' {}')
} else {
@ -2888,7 +2892,8 @@ fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool) {
}
pub fn (mut f Fmt) match_expr(node ast.MatchExpr) {
f.write('match ')
dollar := if node.is_comptime { '$' } else { '' }
f.write('${dollar}match ')
f.expr(node.cond)
f.writeln(' {')
f.indent++
@ -2913,10 +2918,10 @@ pub fn (mut f Fmt) match_expr(node ast.MatchExpr) {
else_idx = i
continue
}
f.match_branch(branch, single_line)
f.match_branch(branch, single_line, node.is_comptime)
}
if else_idx >= 0 {
f.match_branch(node.branches[else_idx], single_line)
f.match_branch(node.branches[else_idx], single_line, node.is_comptime)
}
f.indent--
f.write('}')

View file

@ -3823,8 +3823,12 @@ fn (mut g Gen) expr(node_ ast.Expr) {
g.map_init(node)
}
ast.MatchExpr {
if node.is_comptime {
g.comptime_match(node)
} else {
g.match_expr(node)
}
}
ast.NodeError {}
ast.Nil {
g.write('((void*)0)')

View file

@ -946,193 +946,102 @@ fn (mut g Gen) comptime_selector_type(node ast.SelectorExpr) ast.Type {
return node.expr_type
}
fn (mut g Gen) comptime_if_to_ifdef(name string, is_comptime_option bool) !string {
match name {
// platforms/os-es:
'windows' {
return '_WIN32'
fn (mut g Gen) comptime_match(node ast.MatchExpr) {
tmp_var := g.new_tmp_var()
is_opt_or_result := node.return_type.has_option_or_result()
line := if node.is_expr {
stmt_str := g.go_before_last_stmt()
g.write(util.tabs(g.indent))
styp := g.styp(node.return_type)
g.writeln('${styp} ${tmp_var};')
stmt_str
} else {
''
}
'ios' {
return '__TARGET_IOS__'
mut comptime_branch_context_str := g.gen_branch_context_string()
mut is_true := ast.ComptTimeCondResult{}
for i, branch in node.branches {
// `idx_str` is composed of two parts:
// The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json`
// The second part indicates the branch's location in the source file.
// This format must match what is in `checker`.
idx_str := comptime_branch_context_str + '|${g.file.path}|${branch.pos}|'
if comptime_is_true := g.table.comptime_is_true[idx_str] {
// `g.table.comptime_is_true` are the branch condition results set by `checker`
is_true = comptime_is_true
} else {
g.error('checker error: match branch result idx string not found => [${idx_str}]',
branch.pos)
return
}
'macos' {
return '__APPLE__'
if !branch.is_else {
if i == 0 {
g.write('#if ')
} else {
g.write('#elif ')
}
'mach' {
return '__MACH__'
// directly use `checker` evaluate results
g.writeln('${is_true.val}')
$if debug_comptime_branch_context ? {
g.writeln('/* | generic=[${comptime_branch_context_str}] */')
}
'darwin' {
return '__DARWIN__'
} else {
g.writeln('#else')
}
'hpux' {
return '__HPUX__'
if node.is_expr {
len := branch.stmts.len
if len > 0 {
last := branch.stmts.last() as ast.ExprStmt
if len > 1 {
g.indent++
g.writeln('{')
g.stmts(branch.stmts[..len - 1])
g.set_current_pos_as_last_stmt_pos()
prev_skip_stmt_pos := g.skip_stmt_pos
g.skip_stmt_pos = true
if is_opt_or_result {
tmp_var2 := g.new_tmp_var()
g.write('{ ${g.base_type(node.return_type)} ${tmp_var2} = ')
g.stmt(last)
g.writeln('_result_ok(&(${g.base_type(node.return_type)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(node.return_type)}));')
g.writeln('}')
} else {
g.write('\t${tmp_var} = ')
g.stmt(last)
}
'gnu' {
return '__GNU__'
g.skip_stmt_pos = prev_skip_stmt_pos
g.writeln2(';', '}')
g.indent--
} else {
g.indent++
g.set_current_pos_as_last_stmt_pos()
prev_skip_stmt_pos := g.skip_stmt_pos
g.skip_stmt_pos = true
if is_opt_or_result {
tmp_var2 := g.new_tmp_var()
g.write('{ ${g.base_type(node.return_type)} ${tmp_var2} = ')
g.stmt(last)
g.writeln('_result_ok(&(${g.base_type(node.return_type)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(node.return_type)}));')
g.writeln('}')
} else {
g.write('${tmp_var} = ')
g.stmt(last)
}
'qnx' {
return '__QNX__'
}
'linux' {
return '__linux__'
}
'serenity' {
return '__serenity__'
}
'plan9' {
return '__plan9__'
}
'vinix' {
return '__vinix__'
}
'freebsd' {
return '__FreeBSD__'
}
'openbsd' {
return '__OpenBSD__'
}
'netbsd' {
return '__NetBSD__'
}
'bsd' {
return '__BSD__'
}
'dragonfly' {
return '__DragonFly__'
}
'android' {
return '__ANDROID__'
}
'termux' {
// Note: termux is running on Android natively so __ANDROID__ will also be defined
return '__TERMUX__'
}
'solaris' {
return '__sun'
}
'haiku' {
return '__HAIKU__'
}
//
'js' {
return '_VJS'
}
'wasm32_emscripten' {
return '__EMSCRIPTEN__'
}
'native' {
return '_VNATIVE' // when using the native backend, cgen is inactive
}
// compilers:
'gcc' {
return '__V_GCC__'
}
'tinyc' {
return '__TINYC__'
}
'clang' {
return '__clang__'
}
'mingw' {
return '__MINGW32__'
}
'msvc' {
return '_MSC_VER'
}
'cplusplus' {
return '__cplusplus'
}
// other:
'threads' {
return '__VTHREADS__'
}
'gcboehm' {
return '_VGCBOEHM'
}
'debug' {
return '_VDEBUG'
}
'prod' {
return '_VPROD'
}
'profile' {
return '_VPROFILE'
}
'test' {
return '_VTEST'
}
'glibc' {
return '__GLIBC__'
}
'prealloc' {
return '_VPREALLOC'
}
'no_bounds_checking' {
return 'CUSTOM_DEFINE_no_bounds_checking'
}
'freestanding' {
return '_VFREESTANDING'
}
'autofree' {
return '_VAUTOFREE'
}
// architectures:
'amd64' {
return '__V_amd64'
}
'aarch64', 'arm64' {
return '__V_arm64'
}
'arm32' {
return '__V_arm32'
}
'i386' {
return '__V_x86'
}
'rv64', 'riscv64' {
return '__V_rv64'
}
'rv32', 'riscv32' {
return '__V_rv32'
}
's390x' {
return '__V_s390x'
}
'ppc64le' {
return '__V_ppc64le'
}
'loongarch64' {
return '__V_loongarch64'
}
// bitness:
'x64' {
return 'TARGET_IS_64BIT'
}
'x32' {
return 'TARGET_IS_32BIT'
}
// endianness:
'little_endian' {
return 'TARGET_ORDER_IS_LITTLE'
}
'big_endian' {
return 'TARGET_ORDER_IS_BIG'
}
'fast_math' {
if g.pref.ccompiler_type == .msvc {
// turned on by: `-cflags /fp:fast`
return '_M_FP_FAST'
}
// turned on by: `-cflags -ffast-math`
return '__FAST_MATH__'
}
else {
if is_comptime_option
|| (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) {
return 'CUSTOM_DEFINE_${name}'
}
return error('bad os ifdef name "${name}"') // should never happen, caught in the checker
g.skip_stmt_pos = prev_skip_stmt_pos
g.writeln(';')
g.indent--
}
}
return error('none')
} else {
if is_true.val || g.pref.output_cross_c {
g.stmts(branch.stmts)
}
}
}
g.writeln('#endif')
if node.is_expr {
g.write('${line}${tmp_var}')
}
}

View file

@ -128,6 +128,9 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr {
.key_if {
return p.if_expr(true, false)
}
.key_match {
return p.match_expr(true)
}
else {
return p.unexpected_with_pos(p.peek_tok.pos(),
got: '`$`'
@ -184,7 +187,7 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr {
if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) {
node = p.call_expr(p.language, p.mod)
} else {
node = p.match_expr()
node = p.match_expr(false)
}
}
.key_select {

View file

@ -244,8 +244,12 @@ fn (mut p Parser) is_match_sumtype_type() bool {
&& next_next_tok.lit.len > 0 && next_next_tok.lit[0].is_capital()))
}
fn (mut p Parser) match_expr() ast.MatchExpr {
match_first_pos := p.tok.pos()
fn (mut p Parser) match_expr(is_comptime bool) ast.MatchExpr {
mut match_first_pos := p.tok.pos()
if is_comptime {
p.next() // `$`
match_first_pos = p.prev_tok.pos().extend(p.tok.pos())
}
old_inside_match := p.inside_match
p.inside_match = true
p.check(.key_match)
@ -265,6 +269,15 @@ fn (mut p Parser) match_expr() ast.MatchExpr {
p.open_scope()
// final else
mut is_else := false
if is_comptime {
if p.tok.kind == .key_else {
p.error('use `\$else` instead of `else` in compile-time `match` branches')
return ast.MatchExpr{}
}
if p.tok.kind != .rcbr && p.peek_tok.kind == .key_else {
p.check(.dollar)
}
}
if p.tok.kind == .key_else {
is_else = true
p.next()
@ -382,6 +395,7 @@ fn (mut p Parser) match_expr() ast.MatchExpr {
// return ast.StructInit{}
pos.update_last_line(p.prev_tok.line_nr)
return ast.MatchExpr{
is_comptime: is_comptime
branches: branches
cond: cond
is_sum_type: is_sum_type

View file

@ -957,6 +957,15 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
.key_for {
return p.comptime_for()
}
.key_match {
mut pos := p.tok.pos()
expr := p.match_expr(true)
pos.update_last_line(p.prev_tok.line_nr)
return ast.ExprStmt{
expr: expr
pos: pos
}
}
.name {
// handles $dbg directly without registering token
if p.peek_tok.lit == 'dbg' {

View file

@ -0,0 +1,62 @@
fn test_comptime_match_assign() {
os := 'windows'
x := $match os {
'linux' { 'linux' }
'windows' { 'windows' }
$else { 'unknown' }
}
assert x == 'windows'
i := 123
y := $match i {
1 { '1' }
2 { '2' }
123 { '123' }
$else { 'unknown' }
}
assert y == '123'
j := true
z := $match j {
true { 'T' }
false { 'F' }
}
assert z == 'T'
}
fn test_comptime_match_assign_reverse() {
os1 := 'windows'
os2 := 'linux'
os3 := 'macos'
x := $match 'windows' {
os1 { 'w' }
os2 { 'l' }
os3 { 'm' }
$else { 'unknown' }
}
assert x == 'w'
b1 := true
b2 := false
b3 := true
y := $match false {
b1 { 'b1' }
b2 { 'b2' }
b3 { 'b3' }
$else { 'unknown' }
}
assert y == 'b2'
i1 := 123
i2 := 245
i3 := 1023
z := $match 1024 {
i1 { '123' }
i2 { '245' }
i3 { '1023' }
$else { 'unknown' }
}
assert z == 'unknown'
}

View file

@ -0,0 +1,47 @@
struct My {
a int
b f64
c string
}
fn test_comptime_match_for_field_type() {
x := My{}
mut result := ''
$for f in x.fields {
f_name := f.name
$match f.typ {
int {
result += '${f_name}=int,'
}
f64 {
result += '${f_name}=f64,'
}
string {
result += '${f_name}=string,'
}
$else {
result += '${f_name}=unknown,'
}
}
}
assert result == 'a=int,b=f64,c=string,'
}
fn test_comptime_match_for_field_type_reverse() {
x := My{}
a := 100
mut result := ''
$for f in x.fields {
f_name := f.name
$match $int {
f.typ {
result += '${f_name}=int,'
}
}
}
assert result == 'a=int,'
}

View file

@ -0,0 +1,27 @@
struct My {
a int
b string
c f64
}
fn test_comptime_match_for_field_value() {
x := My{}
mut result := ''
$for f in x.fields {
$match f.name {
'a' {
result += 'a'
}
'b' {
result += 'b'
}
'c' {
result += 'c'
}
$else {
result += '0'
}
}
}
assert result == 'abc'
}

View file

@ -0,0 +1,40 @@
fn func[T](val T) string {
mut result := ''
$match T {
int {
result += 'int'
}
f64 {
result += 'f64'
}
string {
result += 'string'
}
$else {
result += 'unknown'
}
}
$match val {
int {
result += ',int'
}
f64 {
result += ',f64'
}
string {
result += ',string'
}
$else {
result += ',unknown'
}
}
return result
}
fn test_comptime_match_generic_type() {
assert func(100) == 'int,int'
assert func(1.1) == 'f64,f64'
assert func('1') == 'string,string'
assert func(`a`) == 'unknown,unknown'
}

View file

@ -0,0 +1,22 @@
// This is a duplicate test of `comptime_match_type_test.v`, but with `$match` instead of a `match`
struct Test {
a int
b []int
c map[int]string
d []?int
}
fn test_main() {
mut i := 1
$for f in Test.fields {
type_name := typeof(f.$(f.name)).name
$match f.typ {
int { assert i == 1, '1. ${f.name} is ${type_name}' }
[]int { assert i == 2, '2. ${f.name} is ${type_name}' }
map[int]string { assert i == 3, '3. ${f.name} is ${type_name}' }
[]?int { assert i == 4, '4. ${f.name} is ${type_name}' }
$else {}
}
i++
}
}

View file

@ -0,0 +1,53 @@
fn test_comptime_match_type_check() {
x := 100
mut result := ''
$match x {
f64 {
result += 'f64'
}
u32 {
result += 'u32'
}
$int {
result += '\$int'
}
int, i32 {
result += 'int'
}
$else {
result += 'unknown'
}
}
assert result == '\$int'
}
fn test_comptime_match_type_check_reverse() {
a := 100
b := 200
c := 300
x := '123'
y := u64(22)
z := 1.2
mut result := ''
$match $int {
a, b {
result += 'a|b'
}
c {
result += 'c'
}
x {
result += 'x'
}
y {
result += 'y'
}
z {
result += 'z'
}
}
assert result == 'a|b'
}

View file

@ -0,0 +1,114 @@
const version = 123
const other = 456
fn test_comptime_match_value_check() {
x := version
mut result := ''
$match x {
1 {
result += 'v1'
}
2 {
result += 'v2'
}
3 {
result += 'v3'
}
123 {
result += 'v123'
}
$else {
result += 'unknown'
}
}
assert result == 'v123'
result = ''
y := true
$match y {
true {
result += 'true'
}
false {
result += 'false'
}
}
assert result == 'true'
result = ''
z := 'abc'
$match z {
'123' {
result += 'a'
}
'abc' {
result += 'b'
}
$else {
result += 'c'
}
}
assert result == 'b'
}
fn test_comptime_match_value_check_reverse() {
x := version
y := 124
z := 125
mut result := ''
$match 124 {
x {
result += 'x'
}
y {
result += 'y'
}
z {
result += 'z'
}
}
assert result == 'y'
result = ''
a := true
b := true
c := false
$match true {
a {
result += 'a'
}
b {
result += 'b'
}
c {
result += 'c'
}
$else {
result += 'else'
}
}
assert result == 'a'
result = ''
s1 := '123'
s2 := 'abc'
s3 := 'kml'
$match 'abc' {
s1 {
result += 'a'
}
s2 {
result += 'b'
}
s3 {
result += 'c'
}
$else {
result += 'else'
}
}
assert result == 'b'
}