-
Notifications
You must be signed in to change notification settings - Fork 3.4k
feat(embind): add a way to register enum values as plain string #25257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
501b164
to
fad3ab8
Compare
0c66d1a
to
b705c2e
Compare
b705c2e
to
6b09273
Compare
I splitted most of the code between the two, but I kept a common class in the TS types generation, as they are very close to one another |
6bfd0ba
to
92d9adb
Compare
92d9adb
to
bdb0eb8
Compare
Sorry for the delay here. I got some feedback from a few different projects that also want enum to behave differently and not have a On another topic, one downside I see to not using TS enums is you can compare different enums and it will NOT be an error. e.g. export type Dog = 'a'|'b';
export type Cat = 'a'|'b';
interface EmbindModule {
Dog: {a: 'a', b: 'b'};
Cat: {a: 'a', b: 'b'};
};
let module = {} as EmbindModule;
let myDog : Dog = module.Dog.a;
if (myDog == module.Cat.a) { // <--- this is not a compiler error
} vs declare enum Dog {
a = 'a',
b = 'b',
}
declare enum Cat {
a = 'a',
b = 'b',
}
let myDog : Dog = Dog.a;
if (myDog == Cat.a) { < --- compiler error
} |
Why I'm not using TS enums
I didn't find a clean way to bind the enum to a real TS enum. If I understand well, you suggest doing: // module.d.ts
declare enum Animal {
Dog = 1;
Cat = 2;
} But since this enum is only declared in a The only way to then make this enum declaration usable, is to add this at the root of // module.js, outside the module code
const Animal = {
Dog: 1,
Cat: 2,
};
// Add reverse mapping like real TS enums
Animal[1] = 'Dog';
Animal[2] = 'Cat';
export Animal; This means that:
I didn't like the idea of manually reimplementing what TS usually does under the hood. And even if I wanted to, I didn't know where to do this implementation. Thus, I went for a plain TS type. I also think that TS types have the benefit over TS enums of being easier to use and to overload. I prefer using them over TS enums in general (but this is a personal preference. TS enums are a bit clunky imo) I don't think the comparison problem you raised is that much of an issue. In the end, Why I'm using strings
We saw above that I couldn't easily implement real TS enums, so I went for: // module.d.ts
export type Animal = 'Dog' | 'Cat';
interface EmbindModule {
Animal: { Dog: 'Dog', Cat: 'Cat' },
} Would you suggest replacing it with the following implementation ? I think it's a bit strange to declare a union type of ints like this.. // module.d.ts
export type Animal = 1 | 2;
interface EmbindModule {
Animal: { Dog: 1, Cat: 2 },
} Also this is less close to real TS enums.
const enum Animal { Dog: "Dog", Cat, "Cat" };
console.log(Object.values(Animal)); // ["Dog", "Cat"]
const enum Animal { Dog: 1, Cat, 2 };
console.log(Object.values(Animal)); // ["Dog", "Cat", 1, 2] See https://www.typescriptlang.org/docs/handbook/enums.html for details Since my implementation only used strings, it was closer to a real TS string enum behaviour. Finally, using plain strings allows me to use the enum values without even needing the module: const a: Animal = "Dog"; // << doesn't need the module
// vs
const a: Animal = myModule.Animal.Dog: // << needs an instanciated module With number I would need to do: const a: Animal = 1; // << What is 1 ? Dog, Cat ?
// vs
const a: Animal = myModule.Animal.Dog; // << needs an instanciated module to be readable Possible solutionIf people need the int values, I think we can add a parameter in the enum binding (and merge all implementations inside Int enums:enum_<Animal>("Animal", enum_value_type::integer)
.value("Dog", Animal::Dog)
.value("Cat", Animal::Cat) emits: // module.d.ts
export type Animal = 1 | 2;
interface EmbindModule {
Animal: { Dog: 1, Cat: 2 },
} String enums:enum_<Animal>("Animal", enum_value_type::string)
.value("Dog", Animal::Dog)
.value("Cat", Animal::Cat) emits: // module.d.ts
export type Animal = "Dog" | "Cat";
interface EmbindModule {
Animal: { Dog: "Dog", Cat: "Cat" },
} Legacy enums (default):enum_<Animal>("Animal", enum_value_type::legacy)
.value("Dog", Animal::Dog)
.value("Cat", Animal::Cat) emits: // module.d.ts
export type AnimalValue<T extends number> {
value: T
}
export type Animal = AnimalValue<1> | AnimalValue<2>;
interface EmbindModule {
Animal: { Dog: Animal<1>, Cat: Animal<2> };
} This handles the various cases. It's not as close to TS enums, but again I don't think it's that much of a problem. |
Fix #24324
Fix #19387
Fix #18585
EDIT:
This PR adds a
string_enum_
class to be able to use enums values as plain string in javascriptstring_enum_<MyEnum>("MyEnum");
The enum is greatly simplified, since the name of a value is equivalent to the value itself.
We go from
to
This doesn't conflict with current implementation of enums, as it's whole new class
This is my first PR to emscripten, so I'm sorry in advance if it contains some obvious flaws ...
I really hope this gets into the main code, as it would really simplify enums handling