Skip to content
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

disallow enum to enum conversion (require passing through ord) #294

Closed
timotheecour opened this issue Dec 2, 2020 · 19 comments · Fixed by nim-lang/Nim#16351
Closed

disallow enum to enum conversion (require passing through ord) #294

timotheecour opened this issue Dec 2, 2020 · 19 comments · Fixed by nim-lang/Nim#16351

Comments

@timotheecour
Copy link
Member

proposal

when true:
  type Koo = enum k1, k2
  type Goo = enum g1, g2
  discard k2.Goo # make this illegal (currently legal)
  discard k2.ord.Goo # this stays legal

rationale: this seems error prone and it's more explicit to pass through ord (and should result in same C code)

@Araq
Copy link
Member

Araq commented Dec 4, 2020

"Accepted RFC" here means "let's try and see what it breaks".

@timotheecour
Copy link
Member Author

"let's try and see what it breaks"

=> "only" 2 packages, more precisely 2 LOC in total.

@Araq
Copy link
Member

Araq commented Dec 14, 2020

Still needs a backwards compat switch but aside from that, ready to go.

@cooldome
Copy link
Member

IMO, explicit conversions should be allowed. Implicit conversions not allowed.

@Araq
Copy link
Member

Araq commented Dec 14, 2020

The RFC only targets explicit conversions as there are no implicit enum conversions in Nim.

timotheecour added a commit to timotheecour/Nim that referenced this issue Dec 14, 2020
timotheecour added a commit to timotheecour/Nim that referenced this issue Dec 17, 2020
timotheecour added a commit to timotheecour/Nim that referenced this issue Mar 16, 2021
timotheecour added a commit to timotheecour/Nim that referenced this issue Mar 18, 2021
timotheecour added a commit to timotheecour/Nim that referenced this issue Apr 2, 2021
Araq pushed a commit to nim-lang/Nim that referenced this issue Apr 3, 2021
* fix nim-lang/RFCs#294 ; disallow enum <=> enum conversion
* fix the runnableExamples that was the instigator of this RFC
* legacy -d:nimLegacyConvEnumEnum
* use -d:nimLegacyConvEnumEnum in important_package nimgame2
* add test for enum cast
* improve changelog
* add changelog: Changes affecting backward compatibility
* cleanup changelog
* fix changelog
@alaviss
Copy link

alaviss commented May 3, 2021

May I know how this RFC was accepted without much reasoning?

I'm worried about us introducing tiny breaking changes on every minor release.

@timotheecour
Copy link
Member Author

timotheecour commented May 3, 2021

what makes you think it was done without much reasoning?
the previous behavior was error prone:

when true:
  type Koo = enum k1, k2
  type Goo = enum g1, g2, g3
  var a = g3
  var b = a.Koo
  echo b

before nim-lang/Nim#16351:
nim c main # no error/warning
nim r main # RangeDefect
nim r -d:danger main # 2 (invalid data!)

after nim-lang/Nim#16351:
nim c main # error:

Error: type mismatch:
 got 'Goo' for 'a' [enum declared in /Users/timothee/git_clone/nim/timn/tests/nim/all/t12239.nim(7, 8)]
 but expected 'Koo = enum' [enum declared in /Users/timothee/git_clone/nim/timn/tests/nim/all/t12239.nim(6, 8)]
    var b = a.Koo

nim c -d:nimLegacyConvEnumEnum main # Warning: enum to enum conversion is now deprecated

this change (and -d:nimLegacyConvEnumEnum) is explained in details in the changelog, and the proper fix (beside the workaround -d:nimLegacyConvEnumEnum) is backward compatible: explicit conversion through ord via var b = a.ord.Koo

(and see nim-lang/Nim#17928 which mentions -d:nimLegacyConvEnumEnum in the error message)

@Clyybber
Copy link

Clyybber commented May 3, 2021

Copying my reply from nim-lang/Nim#16351 (comment) :

I'm not sure anymore wether this change is a good idea, the user is already explicit when writing foo.Bar, having to add .ord in there is unneccessary syntax salt.
I was originally positive about this change because two enums might not have any meaningful relation and this would make conversions between them more explicit, but thinking more about it I don't think it fits well with the rest of the language; conversions from distinct T to T or vice versa don't have this extra explicitness either, even though the value of a distinct T might not have any meaningful relation to it's value as T.

@timotheecour
Copy link
Member Author

unneccessary syntax salt

the added syntax salt doesn't matter if this case occurs so rarely, and if it prevents illegal unintended conversions in the common case, eg where user intended conversion from (say) an int and instead got a silent conversion from another unrelated enum.

Note that nim is strict about other conversions (apart from distinct that is), this case was AFAIK an oversight/inconsistency.

when true:
  type A = object
    a0: int
  type B = object
    b0: int
  var a = A(a0: 1)
  var b = a.B # error

@Clyybber
Copy link

Clyybber commented May 3, 2021

Note that nim is strict about other conversions (apart from distinct that is), this case was AFAIK an oversight/inconsistency.

when true:
  type A = object
    a0: int
  type B = object
    b0: int
  var a = A(a0: 1)
  var b = a.B # error

objects are the odd one out here, I think it's reasonable to argue that your snippet should actually work (if the objects are structurally equivalent, but it's not implemented yet).
Regardless I think objects are not the best thing to compare this to, a better comparision is to integer types, for which narrowing conversions are allowed too, if explicit.

This isn't really about strictness IMO, the compiler was already strict, the conversion is explicit.

@Araq
Copy link
Member

Araq commented May 6, 2021

Conversions between enums are unsafe and should be done via cast, note that it can come up in generic code where you use T(x) and expect only sound conversions -- not true for enums. This RFC is justified and arguably even a language bugfix.

@Clyybber
Copy link

Clyybber commented May 7, 2021

Conversions between enums are unsafe and should be done via cast, note that it can come up in generic code where you use T(x) and expect only sound conversions -- not true for enums. This RFC is justified and arguably even a language bugfix.

For distincts a conversion from distinct T to T might change the meaning in the same way an enum to enum conversion does.
For integers a conversion from int to int8 errors, for ranges it's the same story, for enum to enum it was the same.

When we say that the conversion should be done via cast then k2.ord.Goo should be just as illegal as k2.Goo is with this RFC, which would require making integer to enum conversions illegal, which is even more of a breaking change.

@Araq
Copy link
Member

Araq commented May 7, 2021

For distincts a conversion from distinct T to T might change the meaning in the same way an enum to enum conversion does.

Not in practice, no. In theory you can argue that there should be no connection between distinct T and T but then why would .borrow exist? There is a type relation between these two, it's not a subtype relation but still. There is no such relation between unrelated enum types.

Your argument makes much more sense when you compare two different enum types to two different distinct types of the same base type, but well, then these distinct types have at least the same base type whereas an enum's base type is not int.

@Clyybber
Copy link

Clyybber commented May 7, 2021

Your argument makes much more sense when you compare two different enum types to two different distinct types of the same base type, but well, then these distinct types have at least the same base type whereas an enum's base type is not int.

Yeah, that's a better comparison. Even though an int is not an enum's base type, it can still represent all of it's values and is convertible to and from, and this RFC forces conversions to go through k2.ord.Goo or k2.int.Goo which isn't any safer than k2.Goo. In the same way explicit conversions from distinct T to another distinct T are allowed directly, and don't have to go through T first.

@Wh1teDuke
Copy link

I disagree, too verbose

rationale: this seems error prone and it's more explicit to pass through ord (and should result in same C code)

It might seem like that, but it isn't. No more error prone than myfloat.int

@Araq
Copy link
Member

Araq commented May 7, 2021

this RFC forces conversions to go through k2.ord.Goo or k2.int.Goo which isn't any safer than k2.Goo.

But it is safer, I gave an example why that is, think about a generic T(x) conversion.

@Clyybber
Copy link

Clyybber commented May 7, 2021

But it is safer, I gave an example why that is, think about a generic T(x) conversion.

But this isn't exclusive to enum, if x is an integer and T an enum it might raise too, when T is a smaller integer it might raise too, or if T is a range type it might raise too.
So if one were to have a generic T(x) conversion before this change, after this RFC this generic code would now have to become:

when typeof(x) is enum:
  T(ord(x))
else:
  T(x)

where T(ord(x)) can raise just as T(x) could.

@Araq
Copy link
Member

Araq commented May 7, 2021

This is not about raising, the conversion to a different enum is silent and can easily produce an invalid enum value! Type conversions in Nim cannot produce invalid bit patterns otherwise, in general. This implies that cast[E](5) should be enforced if 5 is not a valid representation of an enum field from E.

@alaviss
Copy link

alaviss commented May 7, 2021

the conversion to a different enum is silent and can easily produce an invalid enum value!

What do you mean by silent? It's explicit in the code, and a RangeDefect is thrown if it's out-of-bounds.

Type conversions in Nim cannot produce invalid bit patterns otherwise, in general. This implies that cast[E](5) should be enforced if 5 is not a valid representation of an enum field from E.

Wouldn't it also implies that you need to cast from int64 to int32, always?

PMunch pushed a commit to PMunch/Nim that referenced this issue Mar 28, 2022
* fix nim-lang/RFCs#294 ; disallow enum <=> enum conversion
* fix the runnableExamples that was the instigator of this RFC
* legacy -d:nimLegacyConvEnumEnum
* use -d:nimLegacyConvEnumEnum in important_package nimgame2
* add test for enum cast
* improve changelog
* add changelog: Changes affecting backward compatibility
* cleanup changelog
* fix changelog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants