From f67a4c3ee0a5dac93196c9670744ddcb0b20095b Mon Sep 17 00:00:00 2001 From: Nicolas Sauzede Date: Sat, 20 Feb 2021 20:51:54 +0100 Subject: [PATCH] checker, cgen: allow | between bitfield enum values, autogenerate a more specific .str method for them too (#8856) --- vlib/v/checker/checker.v | 15 ++++++++-- vlib/v/checker/tests/enum_op_err.out | 27 ++++++++++++++++-- vlib/v/checker/tests/enum_op_err.vv | 3 ++ vlib/v/checker/tests/enum_op_flag_err.out | 20 +++++++++++++ vlib/v/checker/tests/enum_op_flag_err.vv | 12 ++++++++ vlib/v/gen/c/auto_str_methods.v | 34 +++++++++++++++-------- vlib/v/tests/enum_bitfield_test.v | 9 +++++- 7 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 vlib/v/checker/tests/enum_op_flag_err.out create mode 100644 vlib/v/checker/tests/enum_op_flag_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 0b2e6481d2..5c78a68db8 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 { diff --git a/vlib/v/checker/tests/enum_op_err.out b/vlib/v/checker/tests/enum_op_err.out index c28e1c9f19..ecf165700f 100644 --- a/vlib/v/checker/tests/enum_op_err.out +++ b/vlib/v/checker/tests/enum_op_err.out @@ -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 | } diff --git a/vlib/v/checker/tests/enum_op_err.vv b/vlib/v/checker/tests/enum_op_err.vv index 485391077d..7fa48e4216 100644 --- a/vlib/v/checker/tests/enum_op_err.vv +++ b/vlib/v/checker/tests/enum_op_err.vv @@ -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) } diff --git a/vlib/v/checker/tests/enum_op_flag_err.out b/vlib/v/checker/tests/enum_op_flag_err.out new file mode 100644 index 0000000000..1fec5dcf80 --- /dev/null +++ b/vlib/v/checker/tests/enum_op_flag_err.out @@ -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 | } diff --git a/vlib/v/checker/tests/enum_op_flag_err.vv b/vlib/v/checker/tests/enum_op_flag_err.vv new file mode 100644 index 0000000000..0701255fff --- /dev/null +++ b/vlib/v/checker/tests/enum_op_flag_err.vv @@ -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) +} diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 86bf78fea4..0eaefe5643 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -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('}') } diff --git a/vlib/v/tests/enum_bitfield_test.v b/vlib/v/tests/enum_bitfield_test.v index 81a1507b96..f6d274eca5 100644 --- a/vlib/v/tests/enum_bitfield_test.v +++ b/vlib/v/tests/enum_bitfield_test.v @@ -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}' }