Flags enum

Quirin Schroll qs.il.paperinik at gmail.com
Thu Jun 27 19:03:01 UTC 2024


Defining an `enum` type whose members have specific bits set is a 
common task. Therefore, the language should make that easy to do 
correctly and hard to do incorrectly.

The main purpose is to make defining and maintaining flags 
correctly easy. In particular, for normal enums, adding flags 
anywhere except the end is error prone, as subsequent values need 
to be updated. In the

I propose to add an attribute `@flags` that can be applied to 
`enum` types only.

A `@flags enum` only different from normal a normal enum on the 
definition side.

In particular on the definition side:
* It must have some unsigned integer type as its underlying type, 
the default is `uint`.
* It is an error to have the first member unassigned. This is 
because some flag enums have a neutral element with value `0`, 
and for others, that makes no sense and they have the first 
member with value `1`. The language should not make assumptions; 
requiring the programmer to write `= 0` or `= 1` explicitly is 
not a big ask.
* It is an error to assign `0` to any member except the first.
* If the first member is `0`, there must be at least one other 
member,
and the second member must be unassigned, which gives it the 
value `1`.
* Members with explicit values must have an 
[`OrExpression`](https://dlang.org/spec/expression.html#OrExpression) of pairwise different, previously defined constants (possibly just one constant).
* Members without initializers, generally speaking, progress in 
powers of 2.
* As a special exception, the last member may be assigned the 
underlying type’s `max` (either as `uint.max` or `-1`), for the 
purpose of expressing an invalid non-zero value.

In particular, members without initializers follow these rules:
* If it is the second member and the first member has value `0`, 
it has value `1`.
* If the previous member is has no initializer, it has value 
double the previous member.
* If the previous member has a non-zero non-power-of-2 value, 
consider the member before that.

All in all, this means that power-of-2 members have their values 
implicitly assigned, except for possible doppelgängers, and 
members with an initializer are ignored for the purpose of value 
progression as their purpose is to be an abbreviation for 
prepackaged options.

If a flags enum has an invalid value, it is its `init`; otherwise 
the `init` is zero, even if a neutral option does not exist.

```d
@flags enum WindowOptions : ubyte
{
     empty = 0,

     titleBar, // 1
     statusBar, // 2
     progressBar = statusBar, // doppelgänger
     minimizeButton, // 4
     maximizeButton, // 8
     closeButton, // 16
     standardButtons = minimizeButton | maximizeButton | 
closeButton, // prepackaged combo
     defaultButtons = standardButtons,
     helpButton, // 32
     dialogButtons = closeButton | helpButton,
     allButtons = defaultButtons | dialogButtons, // closeButton 
overlaps, but okay

     invalid = -1
}
```

```d
@flags enum Options : ubyte
{
     invalid = -1, // error, can only start with 1 or 0, -1 goes 
to the end
     b = 2, // error: can’t skip 1
     c = 6, // error, can’t assign values directly
}
```


More information about the dip.ideas mailing list