mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
checker,cgen: add comptime match support
This commit is contained in:
parent
be31491834
commit
6d25f4db9d
22 changed files with 923 additions and 273 deletions
|
@ -1262,9 +1262,10 @@ pub mut:
|
|||
@[minify]
|
||||
pub struct MatchExpr {
|
||||
pub:
|
||||
tok_kind token.Kind
|
||||
pos token.Pos
|
||||
comments []Comment // comments before the first branch
|
||||
is_comptime bool
|
||||
tok_kind token.Kind
|
||||
pos token.Pos
|
||||
comments []Comment // comments before the first branch
|
||||
pub mut:
|
||||
cond Expr
|
||||
branches []MatchBranch
|
||||
|
|
|
@ -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,45 +1183,9 @@ 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)
|
||||
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 = 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 }
|
||||
|
|
|
@ -18,13 +18,26 @@ 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) {
|
||||
c.fail_if_immutable(mut node.cond)
|
||||
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,46 +611,98 @@ 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
|
||||
}
|
||||
}
|
||||
expr_type := c.expr(mut expr)
|
||||
if expr_type.idx() == 0 {
|
||||
// parser failed, stop checking
|
||||
return
|
||||
}
|
||||
expr_type_sym := c.table.sym(expr_type)
|
||||
if cond_type_sym.kind == .interface {
|
||||
// TODO
|
||||
// This generates a memory issue with TCC
|
||||
// Needs to be checked later when TCC errors are fixed
|
||||
// Current solution is to move expr.pos() to its own statement
|
||||
// c.type_implements(expr_type, c.expected_type, expr.pos())
|
||||
expr_pos := expr.pos()
|
||||
if c.type_implements(expr_type, c.expected_type, expr_pos) {
|
||||
if !expr_type.is_any_kind_of_pointer() && !c.inside_unsafe {
|
||||
if expr_type_sym.kind != .interface {
|
||||
c.mark_as_referenced(mut &branch.exprs[k], 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 if cond_type_sym.info is ast.SumType {
|
||||
if expr_type !in cond_type_sym.info.variants {
|
||||
} else {
|
||||
expr_type := c.expr(mut expr)
|
||||
if expr_type.idx() == 0 {
|
||||
// parser failed, stop checking
|
||||
return
|
||||
}
|
||||
expr_type_sym := c.table.sym(expr_type)
|
||||
if cond_type_sym.kind == .interface {
|
||||
// TODO
|
||||
// This generates a memory issue with TCC
|
||||
// Needs to be checked later when TCC errors are fixed
|
||||
// Current solution is to move expr.pos() to its own statement
|
||||
// c.type_implements(expr_type, c.expected_type, expr.pos())
|
||||
expr_pos := expr.pos()
|
||||
if c.type_implements(expr_type, c.expected_type, expr_pos) {
|
||||
if !expr_type.is_any_kind_of_pointer() && !c.inside_unsafe {
|
||||
if expr_type_sym.kind != .interface {
|
||||
c.mark_as_referenced(mut &branch.exprs[k], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if cond_type_sym.info is ast.SumType {
|
||||
if expr_type !in cond_type_sym.info.variants {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
sumtype_variant_names := cond_type_sym.info.variants.map(c.table.type_to_str_using_aliases(it,
|
||||
{}))
|
||||
suggestion := util.new_suggestion(expr_str, sumtype_variant_names)
|
||||
c.error(suggestion.say('`${expect_str}` has no variant `${expr_str}`'),
|
||||
expr.pos())
|
||||
}
|
||||
} else if cond_type_sym.info is ast.Alias && expr_type_sym.info is ast.Struct {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
sumtype_variant_names := cond_type_sym.info.variants.map(c.table.type_to_str_using_aliases(it,
|
||||
{}))
|
||||
suggestion := util.new_suggestion(expr_str, sumtype_variant_names)
|
||||
c.error(suggestion.say('`${expect_str}` has no variant `${expr_str}`'),
|
||||
c.error('cannot match alias type `${expect_str}` with `${expr_str}`',
|
||||
expr.pos())
|
||||
} else if !c.check_types(expr_type, node.cond_type) && !is_comptime {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
c.error('cannot match `${expect_str}` with `${expr_str}`', expr.pos())
|
||||
}
|
||||
} else if cond_type_sym.info is ast.Alias && expr_type_sym.info is ast.Struct {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
c.error('cannot match alias type `${expect_str}` with `${expr_str}`',
|
||||
expr.pos())
|
||||
} else if !c.check_types(expr_type, node.cond_type) && !is_comptime {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
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
|
||||
}
|
||||
|
@ -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'
|
||||
|
|
14
vlib/v/checker/tests/comptime_match_cond_cannot_mut.out
Normal file
14
vlib/v/checker/tests/comptime_match_cond_cannot_mut.out
Normal 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')
|
18
vlib/v/checker/tests/comptime_match_cond_cannot_mut.vv
Normal file
18
vlib/v/checker/tests/comptime_match_cond_cannot_mut.vv
Normal 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')
|
||||
}
|
||||
}
|
||||
}
|
14
vlib/v/checker/tests/comptime_match_value_different_type.out
Normal file
14
vlib/v/checker/tests/comptime_match_value_different_type.out
Normal 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 | }
|
25
vlib/v/checker/tests/comptime_match_value_different_type.vv
Normal file
25
vlib/v/checker/tests/comptime_match_value_different_type.vv
Normal 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')
|
||||
}
|
||||
}
|
||||
}
|
14
vlib/v/checker/tests/comptime_match_value_type_mix_check.out
Normal file
14
vlib/v/checker/tests/comptime_match_value_type_mix_check.out
Normal 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 | }
|
25
vlib/v/checker/tests/comptime_match_value_type_mix_check.vv
Normal file
25
vlib/v/checker/tests/comptime_match_value_type_mix_check.vv
Normal 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')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,7 +2864,11 @@ fn (mut f Fmt) match_branch(branch ast.MatchBranch, single_line bool) {
|
|||
f.is_mbranch_expr = false
|
||||
} else {
|
||||
// else branch
|
||||
f.write('else')
|
||||
if is_comptime {
|
||||
f.write('\$else')
|
||||
} else {
|
||||
f.write('else')
|
||||
}
|
||||
}
|
||||
if branch.stmts.len == 0 {
|
||||
f.writeln(' {}')
|
||||
|
@ -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('}')
|
||||
|
|
|
@ -3823,7 +3823,11 @@ fn (mut g Gen) expr(node_ ast.Expr) {
|
|||
g.map_init(node)
|
||||
}
|
||||
ast.MatchExpr {
|
||||
g.match_expr(node)
|
||||
if node.is_comptime {
|
||||
g.comptime_match(node)
|
||||
} else {
|
||||
g.match_expr(node)
|
||||
}
|
||||
}
|
||||
ast.NodeError {}
|
||||
ast.Nil {
|
||||
|
|
|
@ -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 {
|
||||
''
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
'ios' {
|
||||
return '__TARGET_IOS__'
|
||||
}
|
||||
'macos' {
|
||||
return '__APPLE__'
|
||||
}
|
||||
'mach' {
|
||||
return '__MACH__'
|
||||
}
|
||||
'darwin' {
|
||||
return '__DARWIN__'
|
||||
}
|
||||
'hpux' {
|
||||
return '__HPUX__'
|
||||
}
|
||||
'gnu' {
|
||||
return '__GNU__'
|
||||
}
|
||||
'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'
|
||||
if !branch.is_else {
|
||||
if i == 0 {
|
||||
g.write('#if ')
|
||||
} else {
|
||||
g.write('#elif ')
|
||||
}
|
||||
// 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}'
|
||||
// directly use `checker` evaluate results
|
||||
g.writeln('${is_true.val}')
|
||||
$if debug_comptime_branch_context ? {
|
||||
g.writeln('/* | generic=[${comptime_branch_context_str}] */')
|
||||
}
|
||||
} else {
|
||||
g.writeln('#else')
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
g.skip_stmt_pos = prev_skip_stmt_pos
|
||||
g.writeln(';')
|
||||
g.indent--
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if is_true.val || g.pref.output_cross_c {
|
||||
g.stmts(branch.stmts)
|
||||
}
|
||||
return error('bad os ifdef name "${name}"') // should never happen, caught in the checker
|
||||
}
|
||||
}
|
||||
return error('none')
|
||||
g.writeln('#endif')
|
||||
if node.is_expr {
|
||||
g.write('${line}${tmp_var}')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' {
|
||||
|
|
62
vlib/v/tests/comptime/comptime_match_assign_test.v
Normal file
62
vlib/v/tests/comptime/comptime_match_assign_test.v
Normal 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'
|
||||
}
|
47
vlib/v/tests/comptime/comptime_match_for_field_type_test.v
Normal file
47
vlib/v/tests/comptime/comptime_match_for_field_type_test.v
Normal 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,'
|
||||
}
|
27
vlib/v/tests/comptime/comptime_match_for_field_value_test.v
Normal file
27
vlib/v/tests/comptime/comptime_match_for_field_value_test.v
Normal 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'
|
||||
}
|
40
vlib/v/tests/comptime/comptime_match_generic_type_test.v
Normal file
40
vlib/v/tests/comptime/comptime_match_generic_type_test.v
Normal 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'
|
||||
}
|
22
vlib/v/tests/comptime/comptime_match_type_2_test.v
Normal file
22
vlib/v/tests/comptime/comptime_match_type_2_test.v
Normal 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++
|
||||
}
|
||||
}
|
53
vlib/v/tests/comptime/comptime_match_type_check_test.v
Normal file
53
vlib/v/tests/comptime/comptime_match_type_check_test.v
Normal 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'
|
||||
}
|
114
vlib/v/tests/comptime/comptime_match_value_check_test.v
Normal file
114
vlib/v/tests/comptime/comptime_match_value_check_test.v
Normal 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'
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue