First sorry if this is inconvenient but I am opening an issue/feature request here as I do not want to use github.
---
Currently items that are ordinal values in an enum are required to be in ascending order.
I would like to remove or relax that requirement so that the code below compiles:
type
unordered_enum = enum
a = 1
b = 0
Some usecases are:
These are the options I see, in order of preference and feasability:
I am not a language designer and I am not familiar with nim compiler internals.
It is difficult for me to assess the implications of option1 on the language/compiler/existing code.
So I attempted a patch for option 2.
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index c88795517..0efc3f47c 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -69,6 +69,9 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
e: PSym = nil
base: PType = nil
identToReplace: ptr PNode = nil
+ containsUnspecifiedValues = false
+ containsUnorderedValues = false
+ firstUnorderedValueIndex = 0
counter = 0
base = nil
result = newOrPrevType(tyEnum, prev, c)
@@ -121,16 +124,21 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
if i != 1:
if x != counter: incl(result.flags, tfEnumHasHoles)
if x < counter:
- localError(c.config, n[i].info, errInvalidOrderInEnumX % e.name.s)
+ if not containsUnorderedValues:
+ firstUnorderedValueIndex = i
+ containsUnorderedValues = true
x = counter
e.ast = strVal # might be nil
counter = x
of nkSym:
+ containsUnspecifiedValues = true
e = n[i].sym
of nkIdent, nkAccQuoted:
+ containsUnspecifiedValues = true
e = newSymS(skEnumField, n[i], c)
identToReplace = addr n[i]
of nkPragmaExpr:
+ containsUnspecifiedValues = true
e = newSymS(skEnumField, n[i][0], c)
pragma(c, e, n[i][1], enumFieldPragmas)
identToReplace = addr n[i][0]
@@ -162,6 +170,8 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
localError(c.config, n[i].info, errOverflowInEnumX % [e.name.s, $high(typeof(counter))])
else:
inc(counter)
+ if containsUnorderedValues and containsUnspecifiedValues:
+ localError(c.config, n[firstUnorderedValueIndex].info, errInvalidOrderInEnumX % e.name.s)
if isPure and sfExported in result.sym.flags:
addPureEnum(c, LazySym(sym: result.sym))
if tfNotNil in e.typ.flags and not hasNull:
Any comments?
I think the reason behind that unordered enums trigger a compiler error is that would break (or make unintuitive and error prone) the simple case of not declaring the values of enums:
type
unordered_enum = enum
a = 1
b = 0
c, # now what? is it 1 or 2?
As for wrapping C libraries, I know I have dealt with it in many times, just declare a Table[int, string] and sort it before writing the nim definitions.
you can implement 3. yourself with a macro. like this:
macro unordered(def) =
def.expectKind nnkTypeDef
def[2].expectKind nnkEnumTy
var fields: OrderedTable[int, NimNode]
for field in def[2][1..^1]:
field.expectKind nnkEnumFieldDef
let i =
if field[1].kind == nnkIntLit:
field[1].intVal
elif field[1].kind == nnkTupleConstr and field[1][0].kind == nnkIntLit:
field[1][0].intVal
else:
error "no int value found"; return
fields[i] = field
var sortedTy = nnkEnumTy.newTree(def[2][0])
fields.sort do(a, b: (int, NimNode)) -> int: a[0]-b[0]
for field in fields.values:
sortedTy.add(field)
result = def
result[2] = sortedTy
type
SomeEnum {.unordered.} = enum
seA = 3
seB = 1
seC = (6, "foo")
now what? is it 1 or 2?
It should be 2 as we don't support enum name aliases directly (these can be done with a separate const declaration).
Thank you for the feedback.
Regarding the mistake/unintuiveness/error-proneness, IMHO nim users should be able to understand and use unordered enums with no problems.
For example C and Python enum numbering requirements are more flexible and are not causing major usability or safety issues.
That is one known and working way of implementing option1; removing the order requirement completely.
Option2 that I implemented, is not full C enum flexibility. It allows unordered fields only when all the values are set, so it does not require aliasing and/or special automatic numbering. In this case, your example would still be not supported by the language and would result in an error.
Thank you!
Do you have an opinion on aliasing and c-style numbering? I did not need this so far but I am curious about your thoughts.
Cool glad to see that these requirements can be relaxed without any major issue. While we are it, I have a proposal that would make C bindings a bit less cumbersome. So nim sets can't be casted to C bitflags, because the enums are converted to powers of two automatically. But if there was a .flagbits pragma for enums that could influence how sets are made from this enum, that would guarantee full compatibility with C bitflags. Think about it people!
type
VkAccessFlagBits* {.size: sizeof(int32), flagbits.} = enum
IndirectCommandReadBit = 1
IndexReadBit
VertexAttributeReadBit = 4
UniformReadBit = 8
InputAttachmentReadBit = 16
ShaderReadBit = 32
ShaderWriteBit = 64
ColorAttachmentReadBit = 128
ColorAttachmentWriteBit = 256
DepthStencilAttachmentReadBit = 512
DepthStencilAttachmentWriteBit = 1024
TransferReadBit = 2048
TransferWriteBit = 4096
HostReadBit = 8192
HostWriteBit = 16384
MemoryReadBit = 32768
MemoryWriteBit = 65536
VkAccessFlags* {.size: sizeof(uint32) = set[VkAccessFlagBits]
See also
https://github.com/nim-lang/Nim/pull/23585
block: # unordered enum
block:
type
unordered_enum = enum
a = 1
b = 0
doAssert (ord(a), ord(b)) == (1, 0)
block:
type
unordered_enum = enum
a = 1
b = 0
c
doAssert (ord(a), ord(b), ord(c)) == (1, 0, 2)
block:
type
unordered_enum = enum
a = 100
b
c = 50
d
doAssert (ord(a), ord(b), ord(c), ord(d)) == (100, 101, 50, 51)
block:
type
unordered_enum = enum
a = 7
b = 6
c = 5
d
doAssert (ord(a), ord(b), ord(c), ord(d)) == (7, 6, 5, 8)
While this case gives an error
type
unordered_enum = enum
a = 1
b = 0
c
d = 2