c3lang / c3c

Compiler for the C3 language
GNU Lesser General Public License v3.0
2.57k stars 148 forks source link

enum with values / distinct const #1129

Open lerno opened 6 months ago

lerno commented 6 months ago

Enums changed with #428 from classic C enums to ordinal based enums.

This left a hole in the language, not the least in how to define C enums that aren't strict ordinals.

Early ideas included an attribute – which is bad because an attribute should be affecting the entire implementation of the enum. As well as just work around it with distinct type + sub module.

Later distinct was proposed for this, but worked poorly as it was not a keyword at the time. However, currently something like

module baz;
distinct const Foo : int
{
  ABC = 3,
  BCE = 123
}

Could be considered.

Questions remain in regards to semantic and usage. For example, is the usage: baz::ABC or Foo.ABC or Foo::ABC. The first case considers the code mere shorthand for:

module baz;
distinct Foo = inline int;
Foo ABC = 3;
Foo BCE = 123;

The second is close to enum style, but the question is then whether this is desirable giving the difference in semantics. The final variant Foo::ABC would give it a unique look, clearly indicating it is from a "const set", but then unlike Foo.ABC with the obvious inference of .ABC it would not seem like it should implement inference.

distinct const while not requiring a new set of keywords is fairly long, and the question is whether this is good.

Other syntactic alternatives would be:

constset Foo1 : int
{ 
  ABC = 3,
  BCE = 123
}
const enum Foo2 : int
{  
  ABC = 3,
  BCE = 123
}
enumconst Foo3 : int
{  
  ABC = 3,
  BCE = 123
}

All of those suppose a separate kind of types. The initial option with baz::ABC retains the option of just modelling this as constants.

cbuttner commented 6 months ago

Just some thoughts. A question to ask is whether this would just be some syntactic sugar where it just wraps come constants in a namespace, or would it pull some more weight.

Would there be auto-incrementing like C enums? Would it be a new kind of type for reflection purposes, where I can get an array of all the constants? If so, it seems to me that distinct const might not be a very expressive or unique name. constset could be the right idea here. I'm also noticing that distinct const implies the existence of def const.

With foo::ABC we almost have classic C enums, but how would that help with naming collisions within the same module. Foo.ABC is more consistent with "accessing something from a type", but Foo::ABC is indeed more distinct which could also have benefits. Kind of has that "constants in a module/namespace" feel to it. I don't see why it couldn't do inference in either case, I doubt there are any expectations that only enums should do that. After all module paths are also inferred.

lerno commented 6 months ago

Auto-incrementation could happen, although I don't see all that much use for it to be honest. When you're using values that's usually what you want for everything, barring the case where you're reserving 0

It could be possible that you could get a list of all the constants in some way using reflection, but there are nuances here. It's obviously not possible to do a name <=> value mapping, so at compile time, what is passed around if we get a compile time reference to such a thing? Like for enums we could provide a list of all the names and a list for all the values. Such a thing could exist both at compile time and runtime without any problems as long as it isn't extensible (i.e. possible to add more constants at a later time)

One thing to note is how such compile time capabilities interact with the forward/backwards compatibility of enums.

It is common in frameworks to see the equivalent of this:

// Framework 5.0
enum Foo
{
  FOO_ONE = 3,
  FOO_TWO = 100,
 } 
 // Framework 6.0
 enum Foo
 {
   FOO_ONE = 3,
   FOO_TEST = 100,
   FOO_TWO = FOO_TEST @deprecated("use FOO_TEST"),
   FOO_HELLO = -10239
 }

If we think about how this interacts with an application that bound to the 5.0 framework. If the app encoded the list of values somewhere, it will see -10239 and not recognize it. This is fine if it's a switch. Typically you'd add a default. But what if code was like this:

Foo f = get_foo_from_system();
String[] all_foos = Foo.names(); // Compile time generated from 5.0
int[] all_foo_values = Foo.values(); // Compile time generated from 5.0
foreach (i, val : all_foo_values) 
{
   if (val == f) return all_foos[i];
}
return "Invalid foo"

So some care has to be taken as to what the guarantees are and so on. This is similar for enums, but may be more surprising for a user in this case.

Regarding name collisions foo::ABC would not prevent same module collisions, but in the case where it's replicating a C API the constant would be namespaced anyway.

The argument against inference for Foo::ABC is that it would somewhat violate expectations. But yes, it could be supported.

cbuttner commented 6 months ago

Yeah, good points. Now I'm questioning what else reflection would even be useful for.

lerno commented 6 months ago

I think it's mostly automating debug things.

lerno commented 6 months ago

Possibly some mapping.

data-man commented 6 months ago

This is almost the most awaited feature for me, thank you! :) Some thoughts:

  1. OrdinalEnum or OrdEnum.
  2. Support simple expressions (or even macros) in enum's fields. This will make porting from other languages easier.
  3. Less restrictive naming - allow mixed case in field names. Also to make porting easier.

In addition to point 1: what if ordinal enum becomes the default enum and current enum will be renamed to EnumExt or something similar? Or make enum more universal and let the compiler decide which enum is declared.

lerno commented 6 months ago

I tentatively tried implementing this, but as far as I can tell, the benefit seems small over enums with associated values:

constset Foo : int
{
  ABC = 3,
  BCE = 123
}

enum Bar : int(int val)
{
   ABC(3),
   BCE(123)
}

// With constset
extern fn void some_c_foo(Foo f); 

// With enum + associated value
extern fn void _some_c_bar(int val) @extern(some_c_bar) @local;
macro void some_c_bar(Bar bar) => _some_c_bar(bar.val);
lerno commented 6 months ago

The advantage is somewhat greater when going the other direction:

// With constset
extern fn Foo c_foo(); 
...
switch (c_foo())
{
    case Foo.ABC: ...
    case Foo.BCE: ...
    ...
}

// With enum + associated value
extern fn int c_bar();
...
switch (c_bar())
{
    case Bar.ABC.val: ...
    case Bar.BCE.val: ...
}
lerno commented 6 months ago

We could imagine supercharging associated values, in which case we'd get this:

enum Bar : int(inline int val)
{
   ABC(3),
   BCE(123)
}
extern fn void some_c_bar(int val);
...
some_c_bar(Bar.ABC);

However this loses some type safety. Which might be acceptable though.

lerno commented 6 months ago

But we could go further with this, although at a greater cost to the grammar:

// Imagine the associated type can be used as a pseudo-type:
extern fn void some_c_bar(Bar.val val);
...
some_c_bar(ABC);

This is much more complex to handle, but still somewhat possible. If we think about it from an "ease of learning", I think this might be going overboard too. Unless we have usecases that are not when interfacing with C.

lerno commented 6 months ago

It's fairly straightforward to construct a macro @enum_by_value that would do something like:

extern fn int _c_bar() @extern("c_bar") @local;
macro Bar c_bar() => @enum_by_value(Bar, val, c_bar());

Which then would convert an int to Bar like the constset version without any extra magic.

lerno commented 6 months ago

To add a bit to this, why do I say that it's "complex"? Well, for any variant of this that is anything but a convenient way of creating a distinct type + a list of constants for this type we require a new kind of type. This type must be checked for correct semantics everywhere, which means it adds to the general complexity of the semantic checker in a negative way. If it's merely a shorthand for:

module baz;
distinct Foo = inline int;
Foo ABC = 3;
Foo BCE = 123;

Then we're fine, because no extra type is needed, but then we don't get Foo.ABC or Foo::ABC either!

This is why I bring up the associated values for enums. They are incredibly useful in some cases, but largely they seem overlooked. While they could be removed from the language, there isn't actually that much extra complexity in supporting the feature itself.

So if this feature could accommodate the "distinct const" use-case as well, that would be a double win.

I've already outlined some way it could be done, but to add one more:

enum Bar : int(inline int val)
{
   ABC(3),
   BCE(123)
}
extern fn void some_c_bar(inline Bar b);
...
some_c_bar(ABC);

Now the way this will work from the language's perspective is this:

  1. When it sees "inline Bar", it understands that the actual underlying type should be the inline parameter of Bar, so in this case int. There is no restriction here by the way. It could equally well be some struct.
  2. When passed an argument, the type check will be against Bar however.

The somewhat confusing result though is that the type of b here isn't the type of Bar, but rather the type of the inline parameter.

Rather than using inline this could be using attributes:

extern fn void some_c_bar(Bar b @access(val));

Such a feature could in that case be more generally applied, and possible to use with any type. It is less obvious though.

Without attribute it could look something like:

extern fn void some_c_bar(Bar.val b);

although this can certainly run into some grammar difficulties.

It should also be noted that something like @access and inline can be more generally applied, e.g:

 // This macro would then work on any enum with an inline parameter
 macro void abc(inline b)
 { ... }
 // This macro could extract "val" from any type:
 macro void abc(b @access(val))
 { ... }

Another thing in favour of `@access` though is that we typically use these only for external functions:
```c
// Today
extern fn void _some_c_bar(int b) @inline @local;
fn void some_c_bar(Bar b) => some_c_bar(b.val);

// Access idea
extern fn void some_c_bar(Bar b @access(val));
cbuttner commented 6 months ago

Even though this:

module baz;
distinct Foo = inline int;
const Foo ABC = 3;
const Foo BCE = 123;

is technically a solution, it has the following issues:

So to me this is a granularity/module fragmentation issue. I'd rather have Foo be part of a larger module than having to open a module just for a few constants.

Instead what I'm doing right now is just prefix the names as you would do in C:

distinct Foo = inline int;
const Foo FOO_ABC = 3;
const Foo FOO_BCE = 123;

Which has the (minor) problem that you still have to qualify with at least one module when using the constants from another module so the names get even longer. So the original proposal of

distinct const Foo : inline int
{
  ABC = 3,
  BCE = 123
}

for shortening names and usage to Foo::ABC or Foo.ABC makes sense to me at the moment. It would also not require a new type and consequently keep complexity low.

cbuttner commented 6 months ago

The enums with associated values is interesting as well, but it looks less intuitive, and it's usefulness seems to be limited to C interop.

It's potentially more error prone too, since you might be tempted to cast the enum to int to get its real value instead of using foo.val.

lerno commented 6 months ago

Unfortunately Foo::ABC and Foo.ABC both require modeling it as a separate type.

lerno commented 6 months ago

@cbuttner Associated values as such has been in the language from the beginning and is very useful for certain use-cases. So the problem here is more whether we can leverage them.

I think the question you point out is valid: what happens with (int)myfoo? Currently, this is shorthand for myfoo.ordinal. If this change with "inline" is used however, it might be useful to entirely disallow casts on enums and strictly require . access. So rather than (int)myfoo always require myfoo.ordinal, which could get shortened to myfoo.ord.

In that case there is no confusion as to what casts means, since they aren't used.

data-man commented 6 months ago

the benefit seems small over enums with associated values

Benefit №1:

enum Foo : int
{
  A = 3,
  B, // B == 4 here
  C = A + 10,
  D = 1 << 4,
  E = 1 << 5,
}

Benefit №2: As I see in asm output, enums' values are now accessed by pointer. Correct me if I am wrong. I think their direct values should be used.

Also: enum Bar : int(int val)

What exactly the first int here? It's confused.

Associated values as such has been in the language from the beginning

Previously, there were the usual normal enumerations. :)

And sorry, I'm against new keywords or complicating syntax.

lerno commented 6 months ago

Benefit №1: Is the automatic numbering here of some essential use? I don't see it to be honest

enum Foo : int(int val)
{
A(3),
B(4),
C(A.val + 10),
D(1 << 4),
E(1 << 5),
}

As I see in asm output, enums' values are now accessed by pointer. Correct me if I am wrong. I think their direct values should be used.

When we're talking about the associated value yes. It is because the implementation is essentially one of:

int[5] Foo_val = { 3, 4, 13, 16, 32 };

So that retrieving the mapping is one of indexing into this variable. If you do something like

int x = Foo.A.val;

Then this will actually automatically get folded into int x = 3;.The only time the array is actually used is if you need the lookup at runtime. E.g.

Foo a = get_foo();
int x = a.val;

enum Bar : int(int val) What exactly the first int here? It's confused.

The first int is the storage type of the enum.

Previously, there were the usual normal enumerations. :)

Associated values were always there, they just costed more (a function call was needed) and had more problems (how do you get the name of an enum from a number when that number matches two values?)

So even if we say "let's go back to C enums", that would also mean that everything you get for the enums today: enum sets, runtime and compile time name reflection, associated values etc – they all have to go away.

And conversely, if we say "let's add C style enums too", they have exactly the problem of having to be yet another type to be handled in the type system. There is no free lunch here, just trade offs.

lerno commented 6 months ago

Note here that we could add this:

enum Foo : int(int val)
{
   ABC = 1,
   BCD = 3,
}

As mere syntax sugar for

enum Foo : int(int val)
{
   ABC(1),
   BCD(3),
}

I would agree that the ABC(1) form leaves something to be desired visually, although it's fairly consistent.

That said, this would be consistent as well:

enum Foo : int { int val, double x }
{
   ABC = { 1, 0.0 },
   BCD = { 3, 0.4 },
}

Such a form could perhaps more naturally have the shorthand

enum Foo : int { int val }
{
   ABC = 1,
   BCD = 3,
}
data-man commented 6 months ago

What about this:

C-style enum:

enum Foo : int
{
   ABC = 1,
   BCD = 3,
   EFG
}

Associated enum:

enum Foo : ( int val, double x )
{
   ABC { 1, 0.0 },
   BCD { 3, 0.4 }
}
lerno commented 6 months ago

As I said, keeping both C style enums and the regular ones is going to have a prohibitively high complexity cost, so this would need to be implemented as a type of enum with associated values, but where there is some convenience to let it be used similar to C enums.

data-man commented 6 months ago

Ok. Let's remove associated enums completely. They can be replaced by structs:

import std::io;

struct Value
{
    int a;
    double d;
}

struct Foo
{
    Value a;
    Value b;
}

fn void main()
{
    const Foo FOO = { {1, 0}, {2, 2} };
    io::printfn("FOO.a.a %s", FOO.a.a);
    io::printfn("FOO.a.d %s", FOO.a.d);
    io::printfn("FOO.b.a %s", FOO.b.a);
    io::printfn("FOO.b.d %s", FOO.b.d);
}
lerno commented 6 months ago

No they can't that doesn't make sense even. There is no "associated enum" either. There's just enums backed by ordinals and enums that are glorified constants i.e. C enums.

The point is that ordinal enums support:

  1. Proper exhaustive switching
  2. Error checking on "invalid" values
  3. Runtime reflection
  4. Compile time reflection
  5. Inference

They are not getting removed, so please stop suggesting this over and over again.

It just happens that associated values are easily implemented by ordinal based enums.

For some reason you seem to insist that ordinal enums are like Swifts so-called "enums", which are actually just tagged unions but with broken naming. The ordinal enums have zero to do with tagged unions.

Whether ordinal enums have associated values or not is entirely separate to whether enums should be ordinal based or just random constants.

So again, let me state in no unclear terms that the ordinal enums are not being removed. Nor will they be considered a special case of enums. They are the normal form of enums and that is final.

What this issue is discussing, is how to support "enums as a collection of random constants" which C also allows.

From trying to implement it, it's 100% clear that anything beyond the "syntax sugar for constants" solution will require adding a new kind of type to the language. This is something that I found prohibitively costly. For that reason I am trying to find a way for "enum as a collection of random constants" to piggyback on ordinal enum. Using the associated values seems like an easy way to do this.

lerno commented 6 months ago
enum Foo : int { int val }
{
   ABC = { 1 },
   BCD = { 3 },
}

Is ambiguous, so some alternatives are

enum Foo : { int val } int
{
   ABC = { 1 },
   BCD = { 3 },
}

enum Foo (int val) int
{
   ABC = { 1 },
   BCD = { 3 },
}

Which have the advantage of allowing eliding the ordinal storage type, e.g.

enum Foo : { int val }
{
   ABC = { 1 },
   BCD = { 3 },
}
lerno commented 6 months ago

Here is a real example of an enum with associated value:

enum Dir : char(int[<2>] dir)
{
  NORTH({  0, -1 }),
  EAST ({  1,  0 }),
  SOUTH({  0,  1 }),
  WEST ({ -1,  0 }),
}

We could imagine it being:

enum Dir : { int[<2>] dir } char
{
  NORTH = {  0, -1 },
  EAST  = {  1,  0 },
  SOUTH = {  0,  1 },
  WEST  = { -1,  0 },
}

Dir x = ...
location += x.dir;
cbuttner commented 6 months ago

Unfortunately Foo::ABC and Foo.ABC both require modeling it as a separate type.

Ah okay, I didn't know the cost was this high. So what's left is the original idea about the syntax sugar. That would still be an improvement since it makes a bundle of related constants more obvious to a reader, and external tools can parse it as a bundle too.

I'm not against leveraging enums either. It's clear that runtime table lookups and accessing values with .val doesn't make it a one-to-one replacement for constants, but the use cases are still visible.

I'm not sure about the inline idea though, it seems kinda quirky to me and not explicit enough. The @access one is also lacking some clarity about the involved types. How about something like this instead?

extern fn void some_c_bar(int val @from(Bar));
// Or
extern fn void some_c_bar(int foo @from(Bar.val));

Now that looks like a special case of parameter destructuring to me. Might still be too quirky though, I don't know, I leave that up to you.

lerno commented 6 months ago

The feature is kind of hard to judge the scope of to be honest: regardless of what syntax is used, this is the kind of feature you then could leverage for other types as well. It's a very niche feature, but at the same time fairly complex in the possible uses.

If we consider something like extern fn void some_c_bar(int val @from(Bar));, would this then mean that you can use it both with an integer and a "Bar" value for example? Is it applicable also on structs? If not, then why not?

There's a reason for the "inline inline" feature:

Substructs allows something like:

struct Abc
{
  inline Baz b;
  int x;
}

To be used both where Baz and where Abc is wanted. Essentially it will implicitly create a .b access where needed.

If we just wanted extern fn void some_c_bar(int val) to work, then enum Bar : int(inline int val) would do that. However, you could also use the normal int. That is, there is no type-checking happening.

The idea with

extern fn void some_c_bar(inline Bar val);

Was to then to essentially say that "you have to use a Bar, but it will actually pass on the inlined value of Bar, rather than the ordinal"

OdnetninI commented 6 months ago

This seems pretty cool:

enum Dir : { int[<2>] dir } char
{
  NORTH = {  0, -1 },
  EAST  = {  1,  0 },
  SOUTH = {  0,  1 },
  WEST  = { -1,  0 },
}

Dir x = ...
location += x.dir;

I understand the complexity of adding a new type, so I do not see any issue on trying to fit this feature inside the already existing enum type, and relying on it.

What I really do not understand is the inline feature. So it is intended to make the type reemplazable with one of its members... So, what happens in this scenario:

enum Baz : { int val }
{
   ABC = { 1 },
   BCD = { 3 },
}

struct Foo {
  inline Baz a;
}

struct Abc
{
  inline Baz b;
  inline Foo c;
  int x;
}

I guess, it will be a compiler error.... However, I feel like 'inline' is a different thing and probably we should discuss it in another place. I do not see it relevant for the enum / const feature.

lerno commented 6 months ago

So, to explain inline... I can show an example that doesn't use enums, but structs, since anything using inline is affected:

struct Foo
{
  inline double d;
  int x;
}  

fn double foo(inline Foo bar) 
{
   io::printn($typeof(bar).nameof);
   return bar;
}  

fn void test()
{
   Foo x = { 3.3, 2 };
   foo(x); // Will print "double" and return 3.3
   // foo(3.3); <- error!
}   

What happens here is that fn double foo(inline Foo bar) is the equivalent of:

macro double foo(Foo bar)
{
   return __foo(bar.d);
}
fn double __foo(double d)
{
   io::printn($typeof(d).nameof);
   return d;
}

Essentially the type checking is against the type, but internally the value used is that of the inline field.

This means that we can go from this:

distinct Foo = inline int;
Foo ABC = 3;
Foo BCE = 123;

extern fn void foo(Foo f);

To

enum Foo : { inline int val }
{
   ABC = 3,
   BCE = 123
}
extern fn void foo(inline Foo f);

Which is almost C syntax, while still keeping all of the benefits of enums. Note here that foo(ABC) will pass the int value 3 to foo.

However, we can have arbitrary values, so like this:

fn int[<2>] new_location(int[<2>] pos, inline Dir move_dir)
{
    // move_dir is implicitly "move_dir.dir"
    return pos + move_dir;
}

While less useful, it can have some interesting uses.

lerno commented 6 months ago

Please try out the new feature above which is now in dev

OdnetninI commented 6 months ago

I am having some issues with the new version. I will write an small summary here to see if it helps, I will detail them later where they belongs to.

Some when converting from char to an enum with basetype char: enum Tag : { char tag }, and much later doing a Tag t = (Tag)function_that_returns_char(args);

Also it is trying to link the binary outside the directory... `Failed to link executable '../build/test' using command ... Just removing the '../build/' from the cmd and executing it manually, it works.

Some cast with distinct do not work correctly. I will send you the bug through discord tonight.

lerno commented 6 months ago

Well that should be an error.

lerno commented 6 months ago

Looking deeper at this I think I know what it is – not what I expected.

lerno commented 6 months ago

@OdnetninI you can try the latest now and see what problems persist.

OdnetninI commented 6 months ago

Now I get useful information:

ERROR: 'Attempting to convert 10 to enum 'Tag' failed as the value exceeds the max ordinal (5).'

But in my enum I have:

enum Tag : { char tag }
{
  ...
  TEST = 0x0a,
  ...
}

I have a total of 6 constants... so I guess the issue is there.

OdnetninI commented 6 months ago

Forget about the ../build/ issue, I solved it.

lerno commented 6 months ago

Yes, the conversion is always by ordinal.

OdnetninI commented 6 months ago

But that doesn't feel right. If a constant exists for that value, the conversion should be valid. In the exampled I showed, there is a constant for value 10, so the conversion should be allowed.

lerno commented 6 months ago

No, you misunderstand what = 10 means. This means setting Tag.TEST.tag to 10. Not Tag.TEST.ordinal to 10

OdnetninI commented 6 months ago

Oh! okay okay, sorry for the confusion. Now I got it working.

lerno commented 6 months ago

Ok, cutting and pasting from Discord:

I've come full circle with this: While I like the new syntax for declaring associated values, I also think that distinct constants probably is the way to go for C compatibility. No other solution really does it justice. For example we have this from Raylib:

distinct ConfigFlag = uint;
const ConfigFlag VSYNC_HINT         = 0x00000040;
const ConfigFlag FULLSCREEN_MODE    = 0x00000002;

These are intended to be used as masks. Even with this kind of enum:

enum ConfigFlag : uint (uint val)
{
  VSYNC_HINT = 0x40,
  ...
}

Can we really use it as intended?

This doesn't work:

setConfigFlags(ConfigFlag.VSYNC_HINT | ConfigFlag.FULLSCREEN_MODE);

So the feature with inlining fields fails for this usecase.

We can't even make a reasonable wrapper method without using varargs! There's a fundamental problem here: ordinal enums are not made to be used as masks. The distinct solution works perfectly however, since there is no actual constraint that a ConfigFlag should need to map to an enum for that case.

Sure it would be possible to do:

setConfigFlags(ConfigFlag.VSYNC_HINT.mask | ConfigFlag.FULLSCREEN_MODE.mask);

You might think: "surely it's possible to add something like this?":

enum const KeyboardKey : int
{
  NULL,
  APOSTROPHE,
  COMMA,
  ...
}
enum mask ConfigFlag : uint
{
  FULLSCREEN_MODE = 2,
  WINDOW_RESIZABLE = 3,
  WINDOW_UNDECORATED = 4,
  ...
}

YES it would be possible, but what is the huge benefit derived from this extra complexity in introducing two new types? In the mask case that might not even be ok because sometimes enum values in C will be something that combines two or more flags. Sometimes all of them! There would have to be even more features in the language to fix that!

And we're not really adding things orthogonal to existing features, which in general should be avoided.

So the "let's make an enum that behaves like a constant sometimes" is really dead as a feature. It shouldn't be implemented.

This leaves the status quo and something like

distinct const Foo : int
{
  ABC = 3,
  BCE = 123
}

Where this is just a shorthand for:

distinct Foo = int
const Foo ABC = 3;
const Foo BCE = 123;

But then aside from not having to type const Foo what is really the benefit? It seems marginal.

data-man commented 6 months ago

But then aside from not having to type const Foo what is really the benefit? It seems marginal.

The benefit is using an actual constant rather than a pointer in case of an associated enums.

lerno commented 6 months ago

@data-man I am suggesting using const not enum.

data-man commented 6 months ago

Ok, I've already proposed this syntax:

Enum as set of consts:

enum Foo : uint
{
   AB = 1,
   CD = 2,
   EF = 4
}

And current associated enum:

enum Foo : uint (uint val)
{
   AB = { 1 },
   CD = { 2 },
   EF = { 4 }
}

The compiler will figure out what to use.

lerno commented 6 months ago

Yes that won't happen because the first would not support Foo.associated or foo.ordinal or any such thing. They're just completely different things, so they can't have the same name. AND if they are both accepted, then that would mean there are two different types that have the exact same name.

This problem doesn't arise in C since C only has the first type, and consequently can't support things like getting the name of an enum at runtime/compile time

data-man commented 6 months ago

the first would not support Foo.associated or foo.ordinal or any such thing.

Why? I don't see any problem, because .associated is available for only associated enums. But .ordinal and .values are available during compilation.

lerno commented 6 months ago

No it doesn't work. We've been over this.

data-man commented 6 months ago

This "problem" has been successfully solved in other languages, so it's possible.

lerno commented 6 months ago

What languages do you refer to?

data-man commented 6 months ago

Odin, Zig.