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

Support autocasting from smaller integer to large integer, or float, for vars #9618

Closed
wants to merge 11 commits into from

Conversation

asterite
Copy link
Member

Implements #9565

@BrucePerens mades a good point that if you pass an Int32 variable to a method that expects Int64, then there's no problem at all. There's no precision loss, there's no danger. In general, it seems it's safe to do this as long as the range of the value is inside the range of the target type.

With this PR you can do this:

def foo(x : Int64)
  x
end

a = 1
foo(a) # => 1
typeof(foo(a)) # => Int64

Also this:

a = [] of Int64
x = 1
a << x # okay!

The compiler remains with the unintuitive names with_literal, NumberLiteralType, etc., where the name "autocast" is probably better, because now it starts applying to things other than literals. However, I don't want to rename things yet because I'd like to merge #9501 and so I don't want to introduce an unnecessary conflict.

Copy link
Member

@bcardiff bcardiff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are performing autocast on method calls with vars, the same should apply to symbols and enums.

I would expect the following to compile before merding this PR

enum Foo
  Bar
  Baz
end

def foo(x : Foo = :bar)
  pp!({x, typeof(x)})
end

s = :baz
foo(s) # => {Baz, Foo}

In a similar manner to the failing spec stated in a comment, the following should also work to make it consistent.

s = :baz
e : Foo = :bar
e = s
pp!({e, typeof(e)}) # => {Baz, Foo}

Something that is not tested in this PR and seems possible is that not only variable expressions are autocasted if needed, but any other expression also. Maybe some specs should cover this explicitly.

case self_type = self.type?
when IntegerType
case self_type.kind
when :i8, :u8, :i16, :u16, :i32, :u32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why i64/u64 are not listed here?

With Int128 in mind, they should be autocasted. The fact that 64 bit integer can't be autocasted to a specific target type depends on the width of the target, and not to the 64 bit interger type kind. Right?

assert_type(%(
a : Int64 = 0_i64
x = 1
a = x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While building

a : Int64 = 0_i64
x = 1
a = x
a

I'm getting

In foo.ignoreme.cr:30:1

 30 | a = x
      ^
Error: type must be Int64, not (Int32 | Int64)

And the CI is seems to be complaining also https://github.com/crystal-lang/crystal/pull/9618/checks?check_run_id=885278722#step:5:2488

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't implement this because autocasting in this situation is a bit different. I will try to do it, but I honestly don't think it's very useful, or put another way: nobody puts type annotations in variables. But I'll try...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, I injected this spec because I initially thought about implementing it, but it depended on another PR that wasn't yet merged at that point.

I'll try to make it work again.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, let me just remove the spec.

This is very hard to implement. The reason is that when the compiler sees x = 1 it checks whether that can be autocasted, and since "1" can't change type it's very easy to know.

With x = a, the type of a might later change... but right now the check for "can it be autocast" happens once, and it's very hard to know that... maybe a doesn't have a type at that point. Maybe it does and it will change later on.

Can we leave this "feature" for a separate PR, or even after 1.0? I don't think this is necessary to enjoy the real benefits of this PR, which are much more common (method calls).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I'm fine not covering this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! 🎉

@asterite
Copy link
Member Author

@bcardiff

If we are performing autocast on method calls with vars, the same should apply to symbols and enums.

The title of this PR is "Support autocasting from smaller integer to large integer, or float, for vars", I never said this is for symbols and enums.

For symbols and enums this is much harder to do, I'd say "impossible" because of how difficult it is. At runtime a symbol is just an int. We can't know, at runtime, whether that symbol actually matches one of the enum members. And even if we knew that, there's no easy way to know how to translate that symbool int value into the enum int value.

@bcardiff
Copy link
Member

For symbols and enums this is much harder to do [..]

Ok, I forgot about that. But I it's harder to explain, why the same autocasting works in some cases and not in others 😞

@asterite
Copy link
Member Author

But I it's harder to explain, why the same autocasting works in some cases and not in others

Autocasting

Autocasting works in the following scenarios:

  • When casting an integer literal to any integer or float type where that integer value fits the target
  • When casting an integer type to any integer or float type where the bounds of the source type fit in the target type
  • When casting a symbol literal to an enum type where there's an enum member blah blah blah

Just 3 rules :-)

@asterite
Copy link
Member Author

Something that is not tested in this PR and seems possible is that not only variable expressions are autocasted if needed, but any other expression also. Maybe some specs should cover this explicitly.

You are right! I just added such a spec. It's actually a really good spec because I didn't realize this also applies to any expression... this is really powerful/convenient :-)

@bcardiff
Copy link
Member

I feel there is a bit of tension between the platform dependant int and allowing all this autocast, like the former is not that need if we end going to the platform dependant route for default int. But I guess it wont hurt, as long as the changes are sound.

Having any expression be autocasted might trigger expectation for expressions of type Int32? to be autocasted as Int64?. But auto-cast does not work on unions. This could be seen as a lack of completeness.

I guess the compiler performance for already compiling code is not affected by this PR since the exact method lookup is performed before the autocast, right?

@asterite
Copy link
Member Author

Yeah, I agree with you. With a single recommended Int type there's less need for autocasting like this. So if we go with that, we can probably close this PR and maybe implement it later on if really needed.

@asterite
Copy link
Member Author

However, it can help migrate existing code. If people are using Int32 right now, all calls to the std api that use Int will continue to work.

@asterite
Copy link
Member Author

I also had to do a lot of manual casts when introducing Int, before I based that code on this PR. Once I did that the changes I had to make were much fewer. I still think this is worth on its own.

@asterite asterite closed this Jul 26, 2020
@asterite asterite deleted the feature/integer-autocast branch July 26, 2020 14:48
@Sija
Copy link
Contributor

Sija commented Jul 26, 2020

@asterite Mate, what this pr-closing-spree is about? :/

@asterite
Copy link
Member Author

Old PRs, and none of these PRs are going to be merged. And I usually code in a hacky way so I prefer others to implement these things in a more robust way. I don't have time anymore to think about how to solve these things.

@BrucePerens
Copy link

BrucePerens commented Jul 26, 2020 via email

@Sija
Copy link
Contributor

Sija commented Feb 3, 2021

Could this get reopened, please?

@asterite
Copy link
Member Author

asterite commented Feb 3, 2021

Not by me :-)

@Sija
Copy link
Contributor

Sija commented Feb 3, 2021

I don't care by whom, if there's a will to push this forward any1 can copy-paste it into the new PR, that's not a problem ;) What I'd like to know is whether that would be pursued at all by any of the core team members.

@asterite
Copy link
Member Author

asterite commented Feb 3, 2021

Let's reopen it. I'd love to have this in the language.

Oh, actually, it seems it can't be reopened.

@asterite asterite restored the feature/integer-autocast branch February 3, 2021 12:54
@asterite asterite reopened this Feb 3, 2021
@BrucePerens
Copy link

BrucePerens commented Feb 3, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants