From 02c3af2432cf8f764f450080b73a86d48965c5b1 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 29 Oct 2022 06:32:20 +0300 Subject: [PATCH] all: add support for `enum Xyz as u64 {` + tests (#16246) --- doc/docs.md | 4 +- make.bat | 2 +- vlib/sokol/gfx/enums.v | 2 +- vlib/v/ast/ast.v | 1 + vlib/v/checker/checker.v | 160 ++++++++++++++---- vlib/v/checker/tests/enum_err.out | 4 +- .../tests/enum_field_name_duplicate_err.out | 2 +- vlib/v/checker/tests/enum_field_overflow.out | 11 +- .../tests/enum_field_value_overflow.out | 87 +++++++++- .../tests/enum_field_value_overflow.vv | 64 +++++++ vlib/v/fmt/fmt.v | 6 +- vlib/v/gen/c/auto_str_methods.v | 4 +- vlib/v/gen/c/cgen.v | 38 ++++- vlib/v/gen/native/tests/enum.vv | 5 +- vlib/v/gen/native/tests/enum.vv.out | 1 + vlib/v/parser/parser.v | 23 ++- vlib/v/tests/enum_bitfield_64bit_test.v | 124 ++++++++++++++ .../enum_explicit_size_big_and_small_test.v | 83 +++++++++ 18 files changed, 567 insertions(+), 54 deletions(-) create mode 100644 vlib/v/tests/enum_bitfield_64bit_test.v create mode 100644 vlib/v/tests/enum_explicit_size_big_and_small_test.v diff --git a/doc/docs.md b/doc/docs.md index 21396f980d..59feee51df 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3237,7 +3237,7 @@ You can see the complete ### Enums ```v -enum Color { +enum Color as u8 { red green blue @@ -3254,6 +3254,8 @@ match color { } ``` +The enum type can be any integer type, but can be ommited, if it is `int`: `enum Color {`. + Enum match must be exhaustive or have an `else` branch. This ensures that if a new enum field is added, it's handled everywhere in the code. diff --git a/make.bat b/make.bat index 2c7f20c0a0..feb5845fcf 100644 --- a/make.bat +++ b/make.bat @@ -205,7 +205,7 @@ if exist "%InstallDir%\Common7\Tools\vsdevcmd.bat" ( set ObjFile=.v.c.obj echo ^> Attempting to build v_win.c with MSVC -cl.exe /volatile:ms /Fo%ObjFile% /O2 /MD /D_VBOOTSTRAP vc\v_win.c user32.lib kernel32.lib advapi32.lib shell32.lib /link /nologo /out:v.exe /incremental:no +cl.exe /volatile:ms /Fo%ObjFile% /W0 /MD /D_VBOOTSTRAP vc\v_win.c user32.lib kernel32.lib advapi32.lib shell32.lib /link /nologo /out:v.exe /incremental:no if %ERRORLEVEL% NEQ 0 ( echo In some cases, compile errors happen because of the MSVC compiler version cl.exe diff --git a/vlib/sokol/gfx/enums.v b/vlib/sokol/gfx/enums.v index 575627b23c..c25f88fa6e 100644 --- a/vlib/sokol/gfx/enums.v +++ b/vlib/sokol/gfx/enums.v @@ -118,7 +118,7 @@ pub enum ImageType { _num } -pub enum CubeFace { +pub enum CubeFace as u32 { pos_x neg_x pos_y diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 27d2881a5d..5f6eea6276 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1164,6 +1164,7 @@ pub: comments []Comment // comments before the first EnumField fields []EnumField // all the enum fields attrs []Attr // attributes of enum declaration + typ Type // the default is `int`; can be changed by `enum Big as u64 { a = 5 }` pos token.Pos } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index e8923e7d10..91d693693f 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1401,7 +1401,9 @@ pub fn (mut c Checker) const_decl(mut node ast.ConstDecl) { pub fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { c.check_valid_pascal_case(node.name, 'enum name', node.pos) - mut seen := []i64{cap: node.fields.len} + mut useen := []u64{} + mut iseen := []i64{} + mut seen_enum_field_names := map[string]int{} if node.fields.len == 0 { c.error('enum cannot be empty', node.pos) } @@ -1410,35 +1412,114 @@ pub fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { c.error('`builtin` module cannot have enums', node.pos) } */ + mut enum_imin := i64(0) + mut enum_imax := i64(0) + mut enum_umin := u64(0) + mut enum_umax := u64(0) + mut signed := true + senum_type := c.table.type_to_str(node.typ) + match node.typ { + ast.i8_type { + signed, enum_imin, enum_imax = true, -128, 0x7F + } + ast.i16_type { + signed, enum_imin, enum_imax = true, -32_768, 0x7FFF + } + ast.int_type { + signed, enum_imin, enum_imax = true, -2_147_483_648, 0x7FFF_FFFF + } + ast.i64_type { + signed, enum_imin, enum_imax = true, i64(-9223372036854775807 - 1), i64(0x7FFF_FFFF_FFFF_FFFF) + } + // + ast.u8_type { + signed, enum_umin, enum_umax = false, 0, 0xFF + } + ast.u16_type { + signed, enum_umin, enum_umax = false, 0, 0xFFFF + } + ast.u32_type { + signed, enum_umin, enum_umax = false, 0, 0xFFFF_FFFF + } + ast.u64_type { + signed, enum_umin, enum_umax = false, 0, 0xFFFF_FFFF_FFFF_FFFF + } + else { + c.error('`$senum_type` is not one of `i8`,`i16`,`int`,`i64`,`u8`,`u16`,`u32`,`u64`', + node.pos) + } + } + if enum_imin > 0 { + // ensure that the minimum value is negative, even with msvc, which has a bug that makes -2147483648 positive ... + enum_imin *= -1 + } for i, mut field in node.fields { if !c.pref.experimental && util.contains_capital(field.name) { // TODO C2V uses hundreds of enums with capitals, remove -experimental check once it's handled c.error('field name `$field.name` cannot contain uppercase letters, use snake_case instead', field.pos) } - for j in 0 .. i { - if field.name == node.fields[j].name { - c.error('field name `$field.name` duplicate', field.pos) - } + if _ := seen_enum_field_names[field.name] { + c.error('duplicate enum field name `$field.name`', field.pos) } + seen_enum_field_names[field.name] = i if field.has_expr { match mut field.expr { ast.IntegerLiteral { - val := field.expr.val.i64() - if val < checker.int_min || val > checker.int_max { - c.error('enum value `$val` overflows int', field.expr.pos) - } else if !c.pref.translated && !c.file.is_translated && !node.is_multi_allowed - && i64(val) in seen { - c.error('enum value `$val` already exists', field.expr.pos) + mut overflows := false + mut uval := u64(0) + mut ival := i64(0) + if signed { + val := field.expr.val.i64() + ival = val + if val < enum_imin || val >= enum_imax { + c.error('enum value `$field.expr.val` overflows the enum type `$senum_type`, values of which have to be in [$enum_imin, $enum_imax]', + field.expr.pos) + overflows = true + } + } else { + val := field.expr.val.u64() + uval = val + if val >= enum_umax { + c.error('enum value `$field.expr.val` overflows the enum type `$senum_type`, values of which have to be in [$enum_umin, $enum_umax]', + field.expr.pos) + overflows = true + } + } + if !overflows && !c.pref.translated && !c.file.is_translated + && !node.is_multi_allowed { + if (signed && ival in iseen) || (!signed && uval in useen) { + c.error('enum value `$field.expr.val` already exists', field.expr.pos) + } + } + if signed { + iseen << ival + } else { + useen << uval } - seen << i64(val) } - ast.PrefixExpr {} + ast.PrefixExpr { + dump(field.expr) + } ast.InfixExpr { // Handle `enum Foo { x = 1 + 2 }` c.infix_expr(mut field.expr) } - // ast.ParExpr {} // TODO allow `.x = (1+2)` + ast.ParExpr { + c.expr(field.expr) + } + ast.CastExpr { + fe_type := c.cast_expr(mut field.expr) + if node.typ != fe_type { + sfe_type := c.table.type_to_str(fe_type) + c.error('the type of the enum value `$sfe_type` != the enum type itself `$senum_type`', + field.expr.pos) + } + if !fe_type.is_pure_int() { + c.error('the type of an enum value must be an integer type, like i8, u8, int, u64 etc.', + field.expr.pos) + } + } else { if mut field.expr is ast.Ident { if field.expr.language == .c { @@ -1449,21 +1530,38 @@ pub fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { if pos.pos == 0 { pos = field.pos } - c.error('default value for enum has to be an integer', pos) + c.error('the default value for an enum has to be an integer', pos) } } } else { - if seen.len > 0 { - last := seen[seen.len - 1] - if last == checker.int_max { - c.error('enum value overflows', field.pos) - } else if !c.pref.translated && !c.file.is_translated && !node.is_multi_allowed - && last + 1 in seen { - c.error('enum value `${last + 1}` already exists', field.pos) + if signed { + if iseen.len > 0 { + ilast := iseen[iseen.len - 1] + if ilast == enum_imax { + c.error('enum value overflows type `$senum_type`, which has a maximum value of $enum_imax', + field.pos) + } else if !c.pref.translated && !c.file.is_translated && !node.is_multi_allowed + && ilast + 1 in iseen { + c.error('enum value `${ilast + 1}` already exists', field.pos) + } + iseen << ilast + 1 + } else { + iseen << 0 } - seen << last + 1 } else { - seen << 0 + if useen.len > 0 { + ulast := useen[useen.len - 1] + if ulast == enum_umax { + c.error('enum value overflows type `$senum_type`, which has a maximum value of $enum_umax', + field.pos) + } else if !c.pref.translated && !c.file.is_translated && !node.is_multi_allowed + && ulast + 1 in useen { + c.error('enum value `${ulast + 1}` already exists', field.pos) + } + useen << ulast + 1 + } else { + useen << 0 + } } } } @@ -2637,21 +2735,25 @@ pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { if to_sym.kind == .enum_ { if mut node.expr is ast.IntegerLiteral { enum_typ_name := c.table.get_type_name(to_type) - node_val := node.expr.val.int() + node_val := node.expr.val.i64() if enum_decl := c.table.enum_decls[to_sym.name] { mut in_range := false if enum_decl.is_flag { // if a flag enum has 4 variants, the maximum possible value would have all 4 flags set (0b1111) - max_val := (1 << enum_decl.fields.len) - 1 - in_range = node_val >= 0 && node_val <= max_val + max_val := (u64(1) << enum_decl.fields.len) - 1 + in_range = node_val >= 0 && u64(node_val) <= max_val } else { - mut enum_val := 0 + mut enum_val := i64(0) for enum_field in enum_decl.fields { // check if the field of the enum value is an integer literal if enum_field.expr is ast.IntegerLiteral { - enum_val = enum_field.expr.val.int() + enum_val = enum_field.expr.val.i64() + } else if comptime_value := c.eval_comptime_const_expr(enum_field.expr, + 0) + { + enum_val = comptime_value.i64() or { -1 } } if node_val == enum_val { in_range = true diff --git a/vlib/v/checker/tests/enum_err.out b/vlib/v/checker/tests/enum_err.out index 739305d3b9..669f2d25a1 100644 --- a/vlib/v/checker/tests/enum_err.out +++ b/vlib/v/checker/tests/enum_err.out @@ -1,5 +1,5 @@ -vlib/v/checker/tests/enum_err.vv:4:13: error: default value for enum has to be an integer - 2 | +vlib/v/checker/tests/enum_err.vv:4:13: error: the default value for an enum has to be an integer + 2 | 3 | enum Color { 4 | green = 'green' | ~~~~~~~ diff --git a/vlib/v/checker/tests/enum_field_name_duplicate_err.out b/vlib/v/checker/tests/enum_field_name_duplicate_err.out index 6f1de8d93a..9f705a71c9 100644 --- a/vlib/v/checker/tests/enum_field_name_duplicate_err.out +++ b/vlib/v/checker/tests/enum_field_name_duplicate_err.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/enum_field_name_duplicate_err.vv:5:2: error: field name `green` duplicate +vlib/v/checker/tests/enum_field_name_duplicate_err.vv:5:2: error: duplicate enum field name `green` 3 | yellow 4 | blue 5 | green diff --git a/vlib/v/checker/tests/enum_field_overflow.out b/vlib/v/checker/tests/enum_field_overflow.out index 26f58437f4..c51d6580c4 100644 --- a/vlib/v/checker/tests/enum_field_overflow.out +++ b/vlib/v/checker/tests/enum_field_overflow.out @@ -1,6 +1,13 @@ -vlib/v/checker/tests/enum_field_overflow.vv:4:2: error: enum value overflows +vlib/v/checker/tests/enum_field_overflow.vv:3:10: error: enum value `2147483647` overflows the enum type `int`, values of which have to be in [-2147483648, 2147483647] + 1 | enum Color { + 2 | red + 3 | green = 2147483647 + | ~~~~~~~~~~ + 4 | blue + 5 | } +vlib/v/checker/tests/enum_field_overflow.vv:4:2: error: enum value overflows type `int`, which has a maximum value of 2147483647 2 | red 3 | green = 2147483647 4 | blue | ~~~~ - 5 | } \ No newline at end of file + 5 | } diff --git a/vlib/v/checker/tests/enum_field_value_overflow.out b/vlib/v/checker/tests/enum_field_value_overflow.out index c6bb9c0a10..a13678b4ec 100644 --- a/vlib/v/checker/tests/enum_field_value_overflow.out +++ b/vlib/v/checker/tests/enum_field_value_overflow.out @@ -1,7 +1,90 @@ -vlib/v/checker/tests/enum_field_value_overflow.vv:3:10: error: enum value `2147483648` overflows int +vlib/v/checker/tests/enum_field_value_overflow.vv:3:10: error: enum value `2147483648` overflows the enum type `int`, values of which have to be in [-2147483648, 2147483647] 1 | enum Color { 2 | red 3 | green = 2147483648 | ~~~~~~~~~~ 4 | blue - 5 | } \ No newline at end of file + 5 | } +vlib/v/checker/tests/enum_field_value_overflow.vv:7:1: error: `string` is not one of `i8`,`i16`,`int`,`i64`,`u8`,`u16`,`u32`,`u64` + 5 | } + 6 | + 7 | enum ColorString as string { + | ~~~~~~~~~~~~~~~~ + 8 | red + 9 | green = 'abc' +vlib/v/checker/tests/enum_field_value_overflow.vv:9:10: error: the default value for an enum has to be an integer + 7 | enum ColorString as string { + 8 | red + 9 | green = 'abc' + | ~~~~~ + 10 | blue + 11 | } +vlib/v/checker/tests/enum_field_value_overflow.vv:10:2: error: enum value overflows type `string`, which has a maximum value of 0 + 8 | red + 9 | green = 'abc' + 10 | blue + | ~~~~ + 11 | } + 12 | +vlib/v/checker/tests/enum_field_value_overflow.vv:15:10: error: enum value `128` overflows the enum type `i8`, values of which have to be in [-128, 127] + 13 | enum ColorI8 as i8 { + 14 | red + 15 | green = 128 + | ~~~ + 16 | blue + 17 | } +vlib/v/checker/tests/enum_field_value_overflow.vv:21:10: error: enum value `32769` overflows the enum type `i16`, values of which have to be in [-32768, 32767] + 19 | enum ColorI16 as i16 { + 20 | red + 21 | green = 32769 + | ~~~~~ + 22 | blue + 23 | } +vlib/v/checker/tests/enum_field_value_overflow.vv:27:10: error: enum value `2147483648` overflows the enum type `int`, values of which have to be in [-2147483648, 2147483647] + 25 | enum ColorI32 { + 26 | red + 27 | green = 2147483648 + | ~~~~~~~~~~ + 28 | blue + 29 | } +vlib/v/checker/tests/enum_field_value_overflow.vv:33:10: error: enum value `9223372036854775808` overflows the enum type `i64`, values of which have to be in [-9223372036854775808, 9223372036854775807] + 31 | enum ColorI64 as i64 { + 32 | red + 33 | green = 9223372036854775808 + | ~~~~~~~~~~~~~~~~~~~ + 34 | blue + 35 | } +vlib/v/checker/tests/enum_field_value_overflow.vv:34:2: error: enum value overflows type `i64`, which has a maximum value of 9223372036854775807 + 32 | red + 33 | green = 9223372036854775808 + 34 | blue + | ~~~~ + 35 | } + 36 | +vlib/v/checker/tests/enum_field_value_overflow.vv:56:10: error: enum value `256` overflows the enum type `u8`, values of which have to be in [0, 255] + 54 | // These should work however, since the type is unsigned: + 55 | enum ColorU8ShouldFail as u8 { + 56 | green = 256 + | ~~~ + 57 | } + 58 | +vlib/v/checker/tests/enum_field_value_overflow.vv:60:10: error: enum value `65536` overflows the enum type `u16`, values of which have to be in [0, 65535] + 58 | + 59 | enum ColorU16ShouldFail as u16 { + 60 | green = 65536 + | ~~~~~ + 61 | } + 62 | +vlib/v/checker/tests/enum_field_value_overflow.vv:64:10: error: enum value `4294967296` overflows the enum type `u32`, values of which have to be in [0, 4294967295] + 62 | + 63 | enum ColorU32ShouldFail as u32 { + 64 | green = 4294967296 + | ~~~~~~~~~~ + 65 | } + 66 | +vlib/v/checker/tests/enum_field_value_overflow.vv:68:10: error: enum value `18446744073709551616` overflows the enum type `u64`, values of which have to be in [0, 18446744073709551615] + 66 | + 67 | enum ColorU64ShouldFail as u64 { + 68 | green = 18446744073709551616 + | ~~~~~~~~~~~~~~~~~~~~ + 69 | } diff --git a/vlib/v/checker/tests/enum_field_value_overflow.vv b/vlib/v/checker/tests/enum_field_value_overflow.vv index cb9fb9af08..13802116c3 100644 --- a/vlib/v/checker/tests/enum_field_value_overflow.vv +++ b/vlib/v/checker/tests/enum_field_value_overflow.vv @@ -3,3 +3,67 @@ enum Color { green = 2147483648 blue } + +enum ColorString as string { + red + green = 'abc' + blue +} + +enum ColorI8 as i8 { + red + green = 128 + blue +} + +enum ColorI16 as i16 { + red + green = 32769 + blue +} + +enum ColorI32 { + red + green = 2147483648 + blue +} + +enum ColorI64 as i64 { + red + green = 9223372036854775808 + blue +} + +// These should work however, since the type is unsigned: +enum ColorU8ShouldWork as u8 { + green = 128 +} + +enum ColorU16ShouldWork as u16 { + green = 32769 +} + +enum ColorU32ShouldWork as u32 { + green = 2147483648 +} + +enum ColorU64ShouldWork as u64 { + green = 9223372036854775808 +} + +// These should work however, since the type is unsigned: +enum ColorU8ShouldFail as u8 { + green = 256 +} + +enum ColorU16ShouldFail as u16 { + green = 65536 +} + +enum ColorU32ShouldFail as u32 { + green = 4294967296 +} + +enum ColorU64ShouldFail as u64 { + green = 18446744073709551616 +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index b520739d5a..0f348ea53c 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -938,7 +938,11 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) { if node.is_pub { f.write('pub ') } - name := node.name.after('.') + mut name := node.name.after('.') + if node.typ != ast.int_type { + senum_type := f.table.type_to_str_using_aliases(node.typ, f.mod2alias) + name += ' as $senum_type' + } if node.fields.len == 0 && node.pos.line_nr == node.pos.last_line { f.writeln('enum $name {}\n') return diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index b4230f3dcc..6353593a0e 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -306,8 +306,10 @@ fn (mut g Gen) gen_str_for_enum(info ast.Enum, styp string, str_fn_name string) 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;') + g.auto_str_funcs.writeln('\tu64 zit = (u64)it;') for i, val in info.vals { - g.auto_str_funcs.writeln('\tif (it & (1 << $i)) { if (!first) { ret = string__plus(ret, _SLIT(" | "));} ret = string__plus(ret, _SLIT(".$val")); first = 0;}') + mask := u64(1) << i + g.auto_str_funcs.writeln('\tif (zit & 0x${mask:016x}U) { if (!first) { ret = string__plus(ret, _SLIT(" | "));} ret = string__plus(ret, _SLIT(".$val")); first = 0;}') } g.auto_str_funcs.writeln('\tret = string__plus(ret, _SLIT("}"));') g.auto_str_funcs.writeln('\treturn ret;') diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 93bb24934c..c69c2cbfea 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3677,6 +3677,33 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { fn (mut g Gen) enum_decl(node ast.EnumDecl) { enum_name := util.no_dots(node.name) is_flag := node.is_flag + if g.pref.ccompiler == 'msvc' { + mut last_value := '0' + enum_typ_name := g.table.get_type_name(node.typ) + g.enum_typedefs.writeln('typedef $enum_typ_name $enum_name;') + for i, field in node.fields { + g.enum_typedefs.write_string('\t#define ${enum_name}__$field.name ') + g.enum_typedefs.write_string('(') + if is_flag { + g.enum_typedefs.write_string((u64(1) << i).str()) + g.enum_typedefs.write_string('ULL') + } else if field.has_expr { + expr_str := g.expr_string(field.expr) + g.enum_typedefs.write_string(expr_str) + last_value = expr_str + } else { + if i != 0 { + last_value += '+1' + } + g.enum_typedefs.write_string(last_value) + } + g.enum_typedefs.writeln(')') + } + return + } + if node.typ != ast.int_type { + g.enum_typedefs.writeln('#pragma pack(push, 1)') + } g.enum_typedefs.writeln('typedef enum {') mut cur_enum_expr := '' mut cur_enum_offset := 0 @@ -3690,8 +3717,9 @@ fn (mut g Gen) enum_decl(node ast.EnumDecl) { cur_enum_offset = 0 } else if is_flag { g.enum_typedefs.write_string(' = ') - cur_enum_expr = '1 << $i' - g.enum_typedefs.write_string((1 << i).str()) + cur_enum_expr = 'u64(1) << $i' + g.enum_typedefs.write_string((u64(1) << i).str()) + g.enum_typedefs.write_string('U') cur_enum_offset = 0 } cur_value := if cur_enum_offset > 0 { @@ -3702,7 +3730,11 @@ fn (mut g Gen) enum_decl(node ast.EnumDecl) { g.enum_typedefs.writeln(', // $cur_value') cur_enum_offset++ } - g.enum_typedefs.writeln('} $enum_name;\n') + packed_attribute := if node.typ != ast.int_type { '__attribute__((packed))' } else { '' } + g.enum_typedefs.writeln('} $packed_attribute $enum_name;') + if node.typ != ast.int_type { + g.enum_typedefs.writeln('#pragma pack(pop)\n') + } } fn (mut g Gen) enum_expr(node ast.Expr) { diff --git a/vlib/v/gen/native/tests/enum.vv b/vlib/v/gen/native/tests/enum.vv index 642d1078a6..9e8292632c 100644 --- a/vlib/v/gen/native/tests/enum.vv +++ b/vlib/v/gen/native/tests/enum.vv @@ -31,7 +31,7 @@ fn match_test() { assert false } } -// println(color) + // println(color) assert num == 3 } @@ -43,7 +43,7 @@ fn (_ Bar) baz() {} fn test_enum_variant_and_method_name_clash() { x := Bar.baz -// println(x) + // println(x) x.baz() } @@ -73,4 +73,5 @@ fn main() { match_test() test_enum_variant_and_method_name_clash() value_test() + println('OK') } diff --git a/vlib/v/gen/native/tests/enum.vv.out b/vlib/v/gen/native/tests/enum.vv.out index e69de29bb2..d86bac9de5 100644 --- a/vlib/v/gen/native/tests/enum.vv.out +++ b/vlib/v/gen/native/tests/enum.vv.out @@ -0,0 +1 @@ +OK diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index a86eae1668..405b603904 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -3665,8 +3665,14 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { return ast.EnumDecl{} } name := p.prepend_mod(enum_name) + mut enum_type := ast.int_type + if p.tok.kind == .key_as { + p.next() + enum_type = p.parse_type() + } p.check(.lcbr) enum_decl_comments := p.eat_comments() + senum_type := p.table.get_type_name(enum_type) mut vals := []string{} // mut default_exprs := []ast.Expr{} mut fields := []ast.EnumField{} @@ -3698,8 +3704,8 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { is_flag := p.attrs.contains('flag') is_multi_allowed := p.attrs.contains('_allow_multiple_values') if is_flag { - if fields.len > 32 { - p.error('when an enum is used as bit field, it must have a max of 32 fields') + if fields.len > 64 { + p.error('when an enum is used as bit field, it must have a max of 64 fields') return ast.EnumDecl{} } for f in fields { @@ -3712,12 +3718,12 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { pubfn := if p.mod == 'main' { 'fn' } else { 'pub fn' } p.codegen(' // -[inline] $pubfn ( e &$enum_name) is_empty() bool { return int(*e) == 0 } -[inline] $pubfn ( e &$enum_name) has(flag $enum_name) bool { return (int(*e) & (int(flag))) != 0 } -[inline] $pubfn ( e &$enum_name) all(flag $enum_name) bool { return (int(*e) & (int(flag))) == int(flag) } -[inline] $pubfn (mut e $enum_name) set(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) | (int(flag))) } } -[inline] $pubfn (mut e $enum_name) clear(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) & ~(int(flag))) } } -[inline] $pubfn (mut e $enum_name) toggle(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) ^ (int(flag))) } } +[inline] $pubfn ( e &$enum_name) is_empty() bool { return ${senum_type}(*e) == 0 } +[inline] $pubfn ( e &$enum_name) has(flag $enum_name) bool { return (${senum_type}(*e) & (${senum_type}(flag))) != 0 } +[inline] $pubfn ( e &$enum_name) all(flag $enum_name) bool { return (${senum_type}(*e) & (${senum_type}(flag))) == ${senum_type}(flag) } +[inline] $pubfn (mut e $enum_name) set(flag $enum_name) { unsafe{ *e = ${enum_name}(${senum_type}(*e) | (${senum_type}(flag))) } } +[inline] $pubfn (mut e $enum_name) clear(flag $enum_name) { unsafe{ *e = ${enum_name}(${senum_type}(*e) & ~(${senum_type}(flag))) } } +[inline] $pubfn (mut e $enum_name) toggle(flag $enum_name) { unsafe{ *e = ${enum_name}(${senum_type}(*e) ^ (${senum_type}(flag))) } } // ') } @@ -3741,6 +3747,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { enum_decl := ast.EnumDecl{ name: name + typ: enum_type is_pub: is_pub is_flag: is_flag is_multi_allowed: is_multi_allowed diff --git a/vlib/v/tests/enum_bitfield_64bit_test.v b/vlib/v/tests/enum_bitfield_64bit_test.v new file mode 100644 index 0000000000..74c7106299 --- /dev/null +++ b/vlib/v/tests/enum_bitfield_64bit_test.v @@ -0,0 +1,124 @@ +// TODO: fix the formatting here, when VFMT ON/OFF is working + +[flag] +enum PawnsBoard as u64 { + a8 + b8 + c8 + d8 + e8 + f8 + g8 + h8 + a7 + b7 + c7 + d7 + e7 + f7 + g7 + h7 + a6 + b6 + c6 + d6 + e6 + f6 + g6 + h6 + a5 + b5 + c5 + d5 + e5 + f5 + g5 + h5 + a4 + b4 + c4 + d4 + e4 + f4 + g4 + h4 + a3 + b3 + c3 + d3 + e3 + f3 + g3 + h3 + a2 + b2 + c2 + d2 + e2 + f2 + g2 + h2 + a1 + b1 + c1 + d1 + e1 + f1 + g1 + h1 +} + +fn test_flag_enum_with_64_value_bits() { + last_value := PawnsBoard.h1 + dump(u64(last_value)) + assert u64(last_value) == 0x8000_0000_0000_0000 + + wb := PawnsBoard.a2 | .b2 | .c2 | .d2 | .e2 | .f2 | .g2 | .h2 + dump(wb) + dump(u64(wb)) + assert u64(wb) == 71776119061217280 + assert '${u64(wb):064b}' == '0000000011111111000000000000000000000000000000000000000000000000' + assert '$wb' == 'PawnsBoard{.a2 | .b2 | .c2 | .d2 | .e2 | .f2 | .g2 | .h2}' + + bb := PawnsBoard.a7 | .b7 | .c7 | .d7 | .e7 | .f7 | .g7 | .h7 + dump(bb) + dump(u64(bb)) + assert u64(bb) == 65280 + assert '${u64(bb):064b}' == '0000000000000000000000000000000000000000000000001111111100000000' + assert '$bb' == 'PawnsBoard{.a7 | .b7 | .c7 | .d7 | .e7 | .f7 | .g7 | .h7}' + + if false { + eprintln('----------------------------------------------') + for x in [PawnsBoard.a1, .a2, .a3, .a4, .a5, .a6, .a7, .a8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.b1, .b2, .b3, .b4, .b5, .b6, .b7, .b8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.c1, .c2, .c3, .c4, .c5, .c6, .c7, .c8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.d1, .d2, .d3, .d4, .d5, .d6, .d7, .d8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.e1, .e2, .e3, .e4, .e5, .e6, .e7, .e8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.f1, .f2, .f3, .f4, .f5, .f6, .f7, .f8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.g1, .g2, .g3, .g4, .g5, .g6, .g7, .g8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + eprintln('----------------------------------------------') + for x in [PawnsBoard.h1, .h2, .h3, .h4, .h5, .h6, .h7, .h8] { + eprintln('>> x: $x | hex value: ${u64(x).hex()}') + } + } +} diff --git a/vlib/v/tests/enum_explicit_size_big_and_small_test.v b/vlib/v/tests/enum_explicit_size_big_and_small_test.v new file mode 100644 index 0000000000..e4ba5ded6e --- /dev/null +++ b/vlib/v/tests/enum_explicit_size_big_and_small_test.v @@ -0,0 +1,83 @@ +enum SmallEnum as i8 { + a = -1 + b = -4 + c + d = 5 + e +} + +enum BigEnum as u64 { + a = 0xABCD_EF09_1234_5678 + b = 0xFFFF_FFFF_FFFF_FFF0 + c + d = 5 + e +} + +enum BigIEnum as i64 { + a = -999_999_999_999 + b = -900_000_000_000 + c + d = 900_000_000_000 + e +} + +fn test_small_enum() { + dump(sizeof(SmallEnum)) + $if tinyc { + // TODO: TCC currently ignores `__attribute__((packed))` for enums, and uses an int instead, even with -fshort-enums :-| + assert sizeof(SmallEnum) == 4 + } $else { + assert sizeof(SmallEnum) == 1 + } + dump(i8(SmallEnum.a)) + dump(i8(SmallEnum.b)) + dump(i8(SmallEnum.c)) + dump(i8(SmallEnum.d)) + dump(i8(SmallEnum.e)) + assert i8(SmallEnum.a) == -1 + assert i8(SmallEnum.b) == -4 + assert i8(SmallEnum.c) == -3 + assert i8(SmallEnum.d) == 5 + assert i8(SmallEnum.e) == 6 +} + +fn test_big_enum() { + assert sizeof(BigIEnum) == 8 + assert BigEnum.a.str() == 'a' + assert BigEnum.b.str() == 'b' + assert BigEnum.c.str() == 'c' + assert BigEnum.d.str() == 'd' + assert BigEnum.e.str() == 'e' + dump(u64(BigEnum.a)) + dump(u64(BigEnum.b)) + dump(u64(BigEnum.c)) + dump(u64(BigEnum.d)) + dump(u64(BigEnum.e)) + assert sizeof(BigEnum) == 8 + assert u64(BigEnum.a) == 0xABCD_EF09_1234_5678 + assert u64(BigEnum.b) == 0xFFFF_FFFF_FFFF_FFF0 + assert u64(BigEnum.c) == 0xFFFF_FFFF_FFFF_FFF1 + assert u64(BigEnum.d) == 5 + assert u64(BigEnum.e) == 6 +} + +fn test_big_ienum() { + assert sizeof(BigIEnum) == 8 + assert BigIEnum.a.str() == 'a' + assert BigIEnum.b.str() == 'b' + assert BigIEnum.c.str() == 'c' + assert BigIEnum.d.str() == 'd' + assert BigIEnum.e.str() == 'e' + dump(i64(BigIEnum.a)) + dump(i64(BigIEnum.b)) + dump(i64(BigIEnum.c)) + dump(i64(BigIEnum.d)) + dump(i64(BigIEnum.e)) + assert sizeof(BigIEnum) == 8 + assert i64(BigIEnum.a) == -999999999999 + assert i64(BigIEnum.b) == -900000000000 + assert i64(BigIEnum.c) == -899999999999 + assert i64(BigIEnum.d) == 900000000000 + assert i64(BigIEnum.e) == 900000000001 +}