diff --git a/vlib/compiler/compile_errors.v b/vlib/compiler/compile_errors.v index 4d0a8f89c4..b095a87b2f 100644 --- a/vlib/compiler/compile_errors.v +++ b/vlib/compiler/compile_errors.v @@ -258,4 +258,6 @@ const ( and_or_error = 'use `()` to make the boolean expression clear\n' + 'for example: `(a && b) || c` instead of `a && b || c`' + +err_modify_bitfield = 'to modify a bitfield flag use the methods: set, clear, toggle. and to check for flag use: has' ) diff --git a/vlib/compiler/comptime.v b/vlib/compiler/comptime.v index 14321be008..69ef51d477 100644 --- a/vlib/compiler/comptime.v +++ b/vlib/compiler/comptime.v @@ -469,3 +469,26 @@ fn (p mut Parser) comptime_if_block(name string) { p.genln('#endif') } } + +fn (p mut Parser) gen_enum_flag_methods(typ mut Type) { + for method in ['set', 'clear', 'toggle', 'has'] { + typ.methods << Fn{ + name: method, + typ: if method == 'has' { 'bool' } else { 'void' } + args: [Var{typ: typ.name, is_mut: true, is_arg:true}, Var{typ: typ.name, is_arg: true}] + is_method: true + is_public: true + receiver_typ: typ.name + } + } + p.v.vgen_buf.writeln(' +pub fn (e mut $typ.name) set(flag $typ.name) { *e = int(*e) | (1 << int(flag)) } +pub fn (e mut $typ.name) clear(flag $typ.name) { *e = int(*e) &~ (1 << int(flag)) } +pub fn (e mut $typ.name) toggle(flag $typ.name) { *e = int(*e) ^ (1 << int(flag)) } +pub fn (e &$typ.name) has(flag $typ.name) bool { return int(*e)&(1 << int(flag)) != 0 }' +) + p.cgen.fns << 'void ${typ.name}_set($typ.name *e, $typ.name flag);' + p.cgen.fns << 'void ${typ.name}_clear($typ.name *e, $typ.name flag);' + p.cgen.fns << 'void ${typ.name}_toggle($typ.name *e, $typ.name flag);' + p.cgen.fns << 'bool ${typ.name}_has($typ.name *e, $typ.name flag);' +} diff --git a/vlib/compiler/enum.v b/vlib/compiler/enum.v index 0de279613f..8c11ef13cb 100644 --- a/vlib/compiler/enum.v +++ b/vlib/compiler/enum.v @@ -57,14 +57,23 @@ fn (p mut Parser) enum_decl(no_name bool) { } val++ } - p.table.register_type(Type { + is_flag := p.attr == 'flag' + if is_flag && fields.len > 32 { + p.error('when an enum is used as bit field, it must have a max of 32 fields') + } + mut T := Type { name: enum_name mod: p.mod parent: 'int' cat: .enum_ enum_vals: fields.clone() is_public: is_pub - }) + is_flag: is_flag + } + if is_flag && !p.first_pass() { + p.gen_enum_flag_methods(mut T) + } + p.table.register_type(T) p.check(.rcbr) p.fgenln('\n') } diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index 7bfa27bbb9..b0606c3856 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -1449,6 +1449,10 @@ fn ($v.name mut $v.typ) $p.cur_fn.name (...) { } } else if !p.builtin_mod && !p.check_types_no_throw(expr_type, p.assigned_type) { + t := p.table.find_type( p.assigned_type) + if t.cat == .enum_ && t.is_flag { + p.error_with_token_index(err_modify_bitfield, errtok) + } p.error_with_token_index( 'cannot use type `$expr_type` as type `$p.assigned_type` in assignment', errtok) } if (is_str || is_ustr) && tok == .plus_assign && !p.is_js { @@ -2792,6 +2796,11 @@ fn (p mut Parser) attribute() { p.attr = '' return } + else if p.tok == .key_enum { + p.enum_decl(false) + p.attr = '' + return + } p.error_with_token_index('bad attribute usage', attr_token_idx) } diff --git a/vlib/compiler/struct.v b/vlib/compiler/struct.v index 15d0524cd7..2290e0e748 100644 --- a/vlib/compiler/struct.v +++ b/vlib/compiler/struct.v @@ -285,6 +285,10 @@ fn (p mut Parser) struct_init(typ string) string { p.error('no such field: "$field" in type $typ') break } + tt := p.table.find_type(f.typ) + if tt.is_flag { + p.error(err_modify_bitfield) + } inited_fields << field p.gen_struct_field_init(field) p.check(.colon) @@ -361,6 +365,10 @@ fn (p mut Parser) struct_init(typ string) string { if !p.check_types_no_throw(expr_typ, ffield.typ) { p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ') } + tt := p.table.find_type(ffield.typ) + if tt.is_flag { + p.error(err_modify_bitfield) + } if i < T.fields.len - 1 { if p.tok != .comma { p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)') diff --git a/vlib/compiler/table.v b/vlib/compiler/table.v index 9459a39efe..8552ec5493 100644 --- a/vlib/compiler/table.v +++ b/vlib/compiler/table.v @@ -111,7 +111,7 @@ mut: // This information is needed in the first pass. is_placeholder bool gen_str bool // needs `.str()` method generation - + is_flag bool // enum bitfield flag } struct TypeNode { @@ -502,7 +502,7 @@ fn (p mut Parser) add_method(type_name string, f Fn) { } // TODO table.typesmap[type_name].methods << f mut t := p.table.typesmap[type_name] - if f.name != 'str' && f in t.methods { + if f.name != 'str' && f in t.methods { p.error('redefinition of method `${type_name}.$f.name`') } t.methods << f diff --git a/vlib/compiler/tests/enum_bitfield_test.v b/vlib/compiler/tests/enum_bitfield_test.v new file mode 100644 index 0000000000..ef8987e6ef --- /dev/null +++ b/vlib/compiler/tests/enum_bitfield_test.v @@ -0,0 +1,34 @@ +[flag] +enum BfPermission { + read + write + execute + other +} + +struct BfFile { +mut: + perm BfPermission +} + +fn test_enum_bitfield() { + mut a := BfFile{} + a.perm.set(.read) + a.perm.set(.write) + a.perm.toggle(.execute) + a.perm.clear(.write) + //a.perm.set(.other) + + assert a.perm.has(.read) + assert a.perm.has(.execute) + assert !a.perm.has(.write) + assert !a.perm.has(.other) + + mut b := BfPermission.read // TODO: this does nothing currenty just sets the type + b.set(.write) + b.set(.other) + assert b.has(.write) + assert b.has(.other) + assert !b.has(.read) + assert !b.has(.execute) +}