rethink spec for enums

currently, enum types don't actually exist at all in the spec, they're essentially just a different syntax for creating an alias to the enum which also happens to create a few defs. this leads to a strange interpretation of eg. type a = enum int { A }; type b = (...a | int); which doesn't match what harec does. the spec also implies that the types of enum values should be the underlying type for the enum, which is wrong

Assigned to
1 year, 1 month ago
8 months ago
design harec spec

~sebsite 1 year, 1 month ago

I think the example you gave involving unwrapping the enum shouldn't compile, since enums don't make sense when used outside of a type alias. The spec should be updated to say enum values assume the type of the alias being created in the declaration, but outside of this I'm not sure there's actually a problem here, unless we want to actually rethink enums in general and allow anonymous enums (which ftr I wouldn't be opposed to)

~ecs 1 year, 1 month ago

in that case we should update harec to reflect the spec, by dropping STORAGE_ENUM

~sebsite 1 year, 1 month ago

Adding onto this: enums currently have a bit of an identity crisis, in that they simultaneously want to just be aliases for their underlying type, and also a distinct thing with their own semantics. For example, an exhaustive switch on an enum value only requires that all of its defs have cases, which is problematic for e.g. a "flags" value, where enum defs may be OR'd together.

We may be able to take some inspiration from Zig here: https://ziglang.org/documentation/master/#Non-exhaustive-enum

~ecs 1 year, 1 month ago

i tentatively like zig's thing

~autumnull 1 year, 1 month ago

i had an idea earlier today, which is that it might make sense to have a bitflag keyword, which would work like enum except:

  • enumeration values would count as 1<<N rather than N
  • exhaustivity checking would be different, probably being the same as the underlying type, and then enums could have proper exhaustivity checking where only each member must be covered.

~sebsite 10 months ago*

Some more notes here:

For enum types in the spec (and in harec):

  • They shouldn't be integer types (so you can't do any kind of arithmetic on them)
  • They shouldn't be assignable from integer types (though they should be castable to/from their underlying type)
  • The invariant is that a variable with an enum type must have the value of one of the enum's members, however, we should still require that the implementation checks if an "unreachable" case is reached when switching on an enum, since forcing an invalid value into it is still very doable. (Ditto for match, even when exhaustivity is implemented there. That's unrelated to this ticket but I figured I'd mention it.)
  • As mentioned above: "enum types" should only be usable as type aliases; unwrapping an enum alias should be illegal.
  • Duplicate members are permitted. Only one such member needs to (or is even allowed to) appear as a switch case.

As for bitflag, the idea has been really growing on me. I think it's the best option here honestly. This is basically how I imagine it working:

  • It's not an integer type, but it's still assignable from iconst (similar to how integers are assignable from rconst for example).
  • Certain arithmetic operators may be used on it: &, |, ^, and ~.
  • As autumn said, member values count as 1<<N instead of N (by default). Put another way, enums add one to the value, bitflags left shift one.
  • Similarly to enums, bitflags can only be used through type aliases.
  • Duplicate members are permitted; same logic as enums here.
  • The set of "legal" values is only values with member bits set; all other bits must be zero. This means that e.g. if a bitflag has six members (all assigned to their default), assigning a variable with said type to 1 << 6 would be a compile error*. This also matters for switch exhaustivity: bits that aren't defined as members of the bitflag type don't need to be accounted for within an exhaustive switch expression.
  • Furthermore, the above point would affect the meaning of the ~ operator: it would only touch the bits that are part of the bitflag, leaving other bits untouched. The rationale here is stuff like reserved bits which must always be zero. I'm willing to concede this (and the above point) though if people aren't a fan of it.

* As of now it wouldn't be a compile error, it would just silently do the wrong thing. As is the case with stuff like let x: u32 = 1 << 32; which is allowed, and I think it shouldn't be. This is especially bad with variable-width integers like int and size, because it means something may work on a 64-bit target for example, but still compile on a 32-bit target but just silently do the wrong thing, when it should just refuse to compile. This is how assignability already works with non-iconst integer types (you can't assign to an int from an i64, but you can from an i32, unless int is smaller, in which case it errors, and I think that's reasonable behavior which should apply to iconst as well).

~sebsite 10 months ago

One more note: we should consider making enums (and bitflags if we implement those) not have an implicit underlying type, so it always needs to be explicitly named.

~sebsite 8 months ago

As of now the most up to date RFC relating to this is https://lists.sr.ht/~sircmpwn/hare-rfc/<CVBDKURF0QLM.1LYLCH0HZA0EA%40notmylaptop>.

Whether or not enums are types themselves is still unclear, and enums still need to be refined in the spec either way.

Register here or Log in to comment, or comment via email.