-
Notifications
You must be signed in to change notification settings - Fork 221
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
Support assigning enum members in compatible scalar types (#4302) #4560
base: main
Are you sure you want to change the base?
Support assigning enum members in compatible scalar types (#4302) #4560
Conversation
@microsoft-github-policy-service agree |
1e4b96e
to
bcdcad4
Compare
Thanks for the PR @lorenzodejong! |
/azp run typespec - PR Tools |
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the contribution, however I do think we need to discuss this a little further before allowing it as it has other consequences. As much as it could make sense for default it might be a bit more tricky in other scenarios and we need to be sure we want that in the type system.
Potential issues(will only take string as example but apply to numeric enums too):
-
A decorator that expect string value will now be able to get an enum member pass to it.
a. Do we auto convert to the string or now every decorator also need to start handling enum members in those cases? . Previous discussion on this showed some concern that if there is extra info on the enum it could then be silently lost.
b. What if the decorator accept bothvalueof string | EnumMember
, this one is probably an easy choice it should just be keeping it as an EnumMember as we have precedent for such behavior. -
This also then apply to type relation in general. If you expect a certain type to be a string it could be an enum member now
-
Do we only count enums that actually have an explicit value defined or
enum Foo {a, b}
would have the string valuea
orb
like it has over json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example of a crash that this now reveal:
PR Playground vs Latest playground
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do what TypeScript does in each case. Here's what I think we should do, with the caveat that my understanding of TypeScript is limited.
- Decorators: Explicit handling of enum members to avoid silent loss of information. If a decorator can accept both
string | EnumMember
, handle the enum member as is. - Type Relations: TypeScript ensures type safety when using enum members where a specific type is expected. We should do the same in TypeSpec.
- Serialization: Explicit enum values are serialized as their string values, while implicit values are serialized as numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say that TypeScript is a little different here. Typescript enum are this weird thing that have no direct representation in JS and just map to an object with those keys/values (and are quite hated for that reason as well) so I think its easier in typescript to justify that you can pass an enum of certain value to anything that accept that kind.
For us we do treat enums members as this unique symbols. I don';t think explicit handling is a good solution. Taking in a string is the most common arg a decorator takes and it would involve breaking any decororator that does it on top of being extremely error prone.
My opinion is if you do give an explicit value we could consider using that value in places that expect that type and silently loose any additional information that might be on the member.
Regarding this
Serialization: Explicit enum values are serialized as their string values, while implicit values are serialized as numbers.
this is not what we have been doing
enum Foo {a, b}
will serialize as "a" or "b"
for json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and silently loose any additional information that might be on the member.
Would it be worthwhile and possible to emit a suppressible warning so the user is aware of the potential loss and can decide if that's what they want?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the thorough review! I had expected some concerns to pop up, especially taking my limited knowledge of the TypeSpec codebase into account.
As you can see in the proposed code, I'm explicitly converting the EnumMember
value to a string/numeric literal type. This information was actually missing in the EnumMember
symbol, however is (in my opinion) relevant to the type system in general.
Would explicitly supporting primitive (literal) types in the EnumMember.value
property perhaps easen the solution to implement this logic?
Feels like this would be a more fundamental change throughout the type system, however that would open up for more graceful handling of enum members (like the intention of this PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That does sound like a very interesting idea. If we do want to start treating some enum with values as the value it would definitely make sense to keep a reference of the literal.
I think it still would need some special handling in decorator calls to actually retrieve the value but that would be cleaner for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lorenzodejong just a little update, we talked about this yersteday in the design meeting and I think everyone was ok about doing the implicit coercion however we are not sure that the defaultValue
should remain at this point the EnumValue
and not be the string/numeric value itself as this is what we are doing here.
However doing so I believe wouldn't help your use case of trying to use enum members as reusable constant in the generated code.
If we went that way we would probably need a way to trace back if that value was coerced or not. Either with an helper or an additional property originalDefaultValue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the issue proposal with what we should do. Need to coerce the value to numeric/string value and provide a new helper to retrieve the original if needed.
Let us know if 1.
that will still solve your problem and 2.
if this is too much for you to keep implementing and we'll take over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @timotheeguerin, i think the proposed solution will indeed solve my problem! With my limited knowledge of the codebase, I'm not entirely sure about the implementation strategy so I think the best path forward is for you guys to take over the implementation part.
I will definitely stay involved with the implementation to learn more about the codebase 😄
❌ There is undocummented changes. Run The following packages have changes but are not documented.
Show changes |
You can try these changes here
|
This PR introduces support for assigning compatible enum members to a scalar type. Related issue: #4302.
As mentioned in the documentation, Enums support the following types:
The implementation of the emitter framework already allowed the assignment of these values, however the assignment was blocked by the
type-relation-checker
in the compiler package.The following assignments are supported:
The following assignments will raise an error:
The implementation works the same for all bit types of integers and floats.