Fix or revert some return type restrictions from #10575#10857
Conversation
b46b188 to
7b83dcf
Compare
When it's not guaranteed that a wrapped IO returns `Int32`, we can't use that restriction.
7b83dcf to
fa23fb5
Compare
| # | ||
| # Returns `nil` if *obj* is not in the collection. | ||
| def index(obj) | ||
| def index(obj) : Int32? |
There was a problem hiding this comment.
#10582 added a return type restriction Int32? to the override in Array#index (https://github.com/crystal-lang/crystal/pull/10582/files#diff-a12e3cba63cd83b9e746cd9eab9d85f5ddebd05b696133cd494aa83174a48d16R2252). This method delegates to super, so it can only hold if Enumerable#index returns Int32? as well. It's either both or none. Adding the return type restriction to Enumerable doesn't seem like an issue, so I think it's best to just add the restriction there, too (instead of removing it on Array#index).
| # [1, 2, 3, 4].count { |i| i % 2 == 0 } # => 2 | ||
| # ``` | ||
| def count | ||
| def count(& : T -> _) : Int32 |
There was a problem hiding this comment.
The non-yielding overload (right below) received a Int32 return type restriction in #10582. But this restriction only holds if the yielding overload returns Int32 as well (which is in fact the case). So I figured we should just add it for consistency.
(The block type annotation is an extra, but it's directly derived from #each.)
This reverts commit 0cad8f7.
src/io/sized.cr
Outdated
| end | ||
|
|
||
| def read(slice : Bytes) : Int32 | ||
| def read(slice : Bytes) |
There was a problem hiding this comment.
Can confirm this annotation was causing an issue as well.
src/io/argf.cr
Outdated
| end | ||
|
|
||
| def read(slice : Bytes) : Int32 | ||
| def read(slice : Bytes) |
There was a problem hiding this comment.
This is actually not necessary anymore because of #10828 which already added a type cast to Int32.
That's in fact the proper resolution of these type issues (cf. #10855 (comment)).
We should either remove all restrictions (i.e. revert #10828) or cast all return types to Int32.
There was a problem hiding this comment.
Could also turn these restrictions into : Int perhaps?
There was a problem hiding this comment.
@oprypin IIRC u can't do this for the return type restrictions.
There was a problem hiding this comment.
I don't see any value in that. Either we remove the potentially erroneous restrictions again or make them true.
There was a problem hiding this comment.
I'm convinced we should have the restriction and cast to Int32. It's a non-breaking change and we had already accepted doing that in #10828. We can easily apply that to all similar methods as well.
Question is only if we do that for 1.1 or 1.2. Technically, it's okay because the restrictions are already in place and the change to make sure they hold under all circumstances is a bug fix.
There was a problem hiding this comment.
Hm I see.
In some way this is the long-term-correct approach.
But in another way, it loses us the ability to use Int64 in these cases, which might be needed one day.
The current state is indeed consistent with the rest, so that's good.
There was a problem hiding this comment.
We're set on Int32 in many places, so I don't think this is really much of an issue.
|
Could we please merge this? It would be great to get it in before the next nightly build |
|
@crystal-lang/crystallers I'd like to get another approval to make sure we agree on |
asterite
left a comment
There was a problem hiding this comment.
I'm unsure about adding these return types, I feel it's a breaking change, but let's try it. We can always revert if it breaks someone's code.
|
But @asterite in that case the code will break anyway since it's adding the restrictions that were planned for 1.1.0. Right? I skimmed the new code with casts, and I have the feeling that some of those casts can be avoided. But that's not a real issue in the end, since I assume the optimizer will get read of useless casts anyway. So let's merge it. |
| def read(slice : Bytes) : Int32 | ||
| ensure_send_continue | ||
| @io.read(slice) | ||
| @io.read(slice).to_i32 |
There was a problem hiding this comment.
Are there @io.read that does not return Int32? It seems odd to add all the to_i32 on methods that should already be returning Int32 at a first glance.
There was a problem hiding this comment.
Yes: if a shard inherits IO and returns another integer type.
There was a problem hiding this comment.
(this was the main issue that happened, and that's why the original type restrictions had to be reverted... that's why I'm still not convinced that forcing return types here isn't going to break something else, but we'll see)
There was a problem hiding this comment.
Well, it could technically break code that was explicitly expecting an Int64 return type, for example. But I don't think that's likely. Almost all source implementations in stdlib return Int32. So it would need to be very specific and selective code that doesn't at least have Int32 return type as a union member.
There was a problem hiding this comment.
Oh, ok, the abstract IO#read does not have a return type restriction and so implementors can choose any return type. Eventually on 2.0 we can make that return type explicit and the to_i32 can go away since the compiler will ensure that all implementations honor the return type.
👍 then.
Yes, those casts should be no-op if the type already matches. |
|
The added type restiction on ˋEnumerable#count(& : T ->_)ˋ seems to cause an error in crinja:https://app.circleci.com/pipelines/github/straight-shoota/crinja/721/workflows/6cd5eb17-4e34-4e00-a2a7-441d2f4f70ad/jobs/1285 This change should really be fine, because ˋEnumerable#each(& : T ->_)ˋ uses the same restriction. So, I believe the error is cause by an actual bug in the code. But it's unclear to me why this wasn't caught without the type restriction on ˋ#countˋ. It appears that some ˋ#eachˋ implementation already yields an incorrect type. It's also pretty difficult to figure out what exactly is causing this. I've already narrowed it down to an ˋIterator(Value)ˋ type and I'll keep digging. |
|
I managed to identify the origin of the error: An The correct solution would be to add a type restriction to This is technically a change that can break code, but it only identifies already broken code. So it would be fine to merge in a minor release. IMO that's not really helpful though, it contributes to hiding potential bugs in user code. And some of the other added type restriction might break broken code anyways. In any case, I'll prepare a PR to apply the necessary type restrictions on |
|
Unfortunately, adding a return type restriction to an abstract method requires all implementations to have a compatible type restriction as well. That would be a major breaking change for For now, I suppose we can just add type restrictions to |
This PR fixes some issues with return type restrictions added in the first batch of #10575.
A couple of
IO#readimplementationsneeded to removeInt32because we can't be sure that the wrapped IO implementations returnInt32(continuation of #10855). The restrictions stay, we're casting the value instead.Return type restrictions are added to a couple of
Enumerablemethods because those are delegated to from other methods which already have that restriction. This makes sure to honor that.IO#skipalways returnsNil. One implementation of an HTTP IO leaked an Int32 into other implementations as well. AddingNileverywhere avoids that.