checker, cgen: allow | between bitfield enum values, autogenerate a more specific .str method for them too (#8856)

pull/8839/head^2
Nicolas Sauzede 2021-02-20 20:51:54 +01:00 committed by GitHub
parent cc565b22a9
commit f67a4c3ee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 17 deletions

View File

@ -960,8 +960,19 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`',
infix_expr.pos)
} else if left.kind == .enum_ && right.kind == .enum_ && infix_expr.op !in [.ne, .eq] {
c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed',
infix_expr.pos)
left_enum := left.info as table.Enum
right_enum := right.info as table.Enum
if left_enum.is_flag && right_enum.is_flag {
// `[flag]` tagged enums are a special case that allow also `|` and `&` binary operators
if infix_expr.op !in [.pipe, .amp] {
c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed',
infix_expr.pos)
}
} else {
// Regular enums
c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed',
infix_expr.pos)
}
}
// sum types can't have any infix operation except of "is", is is checked before and doesn't reach this
if c.table.type_kind(left_type) == .sum_type {

View File

@ -1,13 +1,34 @@
vlib/v/checker/tests/enum_op_err.vv:8:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed
6 |
6 |
7 | fn main() {
8 | println(Color.red > Color.green)
| ^
9 | println(Color.red + Color.green)
10 | }
10 | println(Color.red && Color.green)
vlib/v/checker/tests/enum_op_err.vv:9:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed
7 | fn main() {
8 | println(Color.red > Color.green)
9 | println(Color.red + Color.green)
| ^
10 | }
10 | println(Color.red && Color.green)
11 | println(Color.red | Color.green)
vlib/v/checker/tests/enum_op_err.vv:10:20: error: left operand for `&&` is not a boolean
8 | println(Color.red > Color.green)
9 | println(Color.red + Color.green)
10 | println(Color.red && Color.green)
| ~~
11 | println(Color.red | Color.green)
12 | println(Color.red & Color.green)
vlib/v/checker/tests/enum_op_err.vv:11:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed
9 | println(Color.red + Color.green)
10 | println(Color.red && Color.green)
11 | println(Color.red | Color.green)
| ^
12 | println(Color.red & Color.green)
13 | }
vlib/v/checker/tests/enum_op_err.vv:12:20: error: only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed
10 | println(Color.red && Color.green)
11 | println(Color.red | Color.green)
12 | println(Color.red & Color.green)
| ^
13 | }

View File

@ -7,4 +7,7 @@ enum Color {
fn main() {
println(Color.red > Color.green)
println(Color.red + Color.green)
println(Color.red && Color.green)
println(Color.red | Color.green)
println(Color.red & Color.green)
}

View File

@ -0,0 +1,20 @@
vlib/v/checker/tests/enum_op_flag_err.vv:9:24: error: only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed
7 |
8 | fn main() {
9 | println(FilePerm.read > FilePerm.write)
| ^
10 | println(FilePerm.write + FilePerm.exec)
11 | println(FilePerm.write && FilePerm.exec)
vlib/v/checker/tests/enum_op_flag_err.vv:10:25: error: only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed
8 | fn main() {
9 | println(FilePerm.read > FilePerm.write)
10 | println(FilePerm.write + FilePerm.exec)
| ^
11 | println(FilePerm.write && FilePerm.exec)
12 | }
vlib/v/checker/tests/enum_op_flag_err.vv:11:25: error: left operand for `&&` is not a boolean
9 | println(FilePerm.read > FilePerm.write)
10 | println(FilePerm.write + FilePerm.exec)
11 | println(FilePerm.write && FilePerm.exec)
| ~~
12 | }

View File

@ -0,0 +1,12 @@
[flag]
enum FilePerm {
read
write
exec
}
fn main() {
println(FilePerm.read > FilePerm.write)
println(FilePerm.write + FilePerm.exec)
println(FilePerm.write && FilePerm.exec)
}

View File

@ -573,19 +573,31 @@ fn (mut g Gen) gen_str_for_enum(info table.Enum, styp string, str_fn_name string
s := util.no_dots(styp)
g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto')
g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { /* gen_str_for_enum */')
g.auto_str_funcs.writeln('\tswitch(it) {')
// Only use the first multi value on the lookup
mut seen := []string{len: info.vals.len}
for val in info.vals {
if info.is_multi_allowed && val in seen {
continue
} else if info.is_multi_allowed {
seen << val
// Enums tagged with `[flag]` are special in that they can be a combination of enum values
if info.is_flag {
clean_name := util.strip_main_name(styp.replace('__', '.'))
g.auto_str_funcs.writeln('\tstring ret = _SLIT("$clean_name{");')
g.auto_str_funcs.writeln('\tint first = 1;')
for i, val in info.vals {
g.auto_str_funcs.writeln('\tif (it & (1 << $i)) {if (!first) {ret = string_add(ret, _SLIT(" | "));} ret = string_add(ret, _SLIT(".$val")); first = 0;}')
}
g.auto_str_funcs.writeln('\t\tcase ${s}_$val: return _SLIT("$val");')
g.auto_str_funcs.writeln('\tret = string_add(ret, _SLIT("}"));')
g.auto_str_funcs.writeln('\treturn ret;')
} else {
g.auto_str_funcs.writeln('\tswitch(it) {')
// Only use the first multi value on the lookup
mut seen := []string{len: info.vals.len}
for val in info.vals {
if info.is_multi_allowed && val in seen {
continue
} else if info.is_multi_allowed {
seen << val
}
g.auto_str_funcs.writeln('\t\tcase ${s}_$val: return _SLIT("$val");')
}
g.auto_str_funcs.writeln('\t\tdefault: return _SLIT("unknown enum value");')
g.auto_str_funcs.writeln('\t}')
}
g.auto_str_funcs.writeln('\t\tdefault: return _SLIT("unknown enum value");')
g.auto_str_funcs.writeln('\t}')
g.auto_str_funcs.writeln('}')
}

View File

@ -26,7 +26,7 @@ fn test_enum_bitfield() {
assert a.perm.has(.execute)
assert !a.perm.has(.write)
assert !a.perm.has(.other)
mut b := BfPermission(int(BfPermission.read) | int(BfPermission.execute))
mut b := BfPermission.read | BfPermission.execute
assert int(b) == 1 + 0 + 4 + 0
assert b.has(.read)
assert b.has(.execute)
@ -42,4 +42,11 @@ fn test_enum_bitfield() {
assert int(b) == 0 + 2 + 0 + 8
assert !b.has(.read)
assert !b.has(.execute)
mut c := BfPermission.read
c.set(.write | .execute)
assert c.has(.read | .write | .execute)
assert !c.has(.other)
c.toggle(.write | .other)
assert '$c' == 'BfPermission{.read | .execute | .other}'
}