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

improve the TryFrom implementations #43248

Merged
merged 1 commit into from
Jul 25, 2017
Merged

Conversation

llogiq
Copy link
Contributor

@llogiq llogiq commented Jul 15, 2017

This removes the need for a 128 bit storage by making use of the fact that there can be either no over/underflow, either one or both, and each time the target type suffices to hold the limit for comparison. This also means that the implementation will work in targets without 128bit support (unless it's for 128bit types, of course).

The downside is that the code looks a bit more complex.

@rust-highfive
Copy link
Collaborator

r? @brson

(rust_highfive has picked a reviewer for you, use r? to override)

@llogiq
Copy link
Contributor Author

llogiq commented Jul 15, 2017

r? @BurntSushi since you're already reviewing #43220

@oyvindln
Copy link
Contributor

oyvindln commented Jul 15, 2017

Hm, this might be a bit nicer way to do this than my attempt

@BurntSushi
Copy link
Member

Thanks @llogiq! This change makes sense to me. My primary concern here is test coverage. What do our tests look like for TryFrom and all these conversions? I don't see any tests in this specific file. Are there tests elsewhere? I'm in particular interested in this because this PR adds target specific impls, and it seems wise to have tests for that.

What do others think? cc @rust-lang/libs

@llogiq
Copy link
Contributor Author

llogiq commented Jul 15, 2017

I did not see tests either. I'll write some when I find the time.

I also think I should add #[inline] annotations to all impls, since at least with single bounds, the checks are likely as small as a function call.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 16, 2017

@BurntSushi there are tests in src/libcore/tests/num/mod.rs, but they could probably benefit from an update.

@Mark-Simulacrum Mark-Simulacrum added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 17, 2017
@BurntSushi BurntSushi added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 17, 2017
@alexcrichton
Copy link
Member

I agree with @BurntSushi in that it's kind hard to follow what's going on here reading the source code, having an exhaustive test which tests all ints going to all other ints with the same implementation of TryFrom would be nice to have indeed (ideally exericising corner cases and whatnot)

($storage:ty, $target:ty, $($source:ty),*) => {$(
// no possible bounds violation
macro_rules! try_from_unbounded {
($source:ty, $($target:ty),*) => {$(
#[unstable(feature = "try_from", issue = "33417")]
impl TryFrom<$source> for $target {
type Error = TryFromIntError;
Copy link
Member

Choose a reason for hiding this comment

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

Since this can never fail, shouldn't this be an empty type like enum NeverIntError {}?

Copy link
Member

Choose a reason for hiding this comment

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

Oh-- but I see there are cfg'd impls of this for usize based on the current target's pointer width, so those would have to be kept as TryFromIntError in order to ensure target compatibility.

Copy link
Member

Choose a reason for hiding this comment

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

I think the infallible ones are moving to a blanket impl, per #33417 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Yep-- I was suggesting enum NeverIntError {} so that this wouldn't be blocked on never_type. Once never_type and the blanket impl land, NeverIntError can be changed to an alias for !.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 18, 2017

Ok, I've added the inline annotations and copied over the tests from #43194

#[unstable(feature = "try_from", issue = "33417")]
#[inline]
impl TryFrom<$source> for $target {
Copy link
Member

Choose a reason for hiding this comment

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

[00:01:34] error[E0518]: attribute should be applied to function
[00:01:34]     --> /checkout/src/libcore/num/mod.rs:2511:9
[00:01:34]      |
[00:01:34] 2511 |         #[inline]
[00:01:34]      |         ^^^^^^^^^ requires a function
[00:01:34] ...
[00:01:34] 2588 | try_from_unbounded!(u8, u8, u16, u32, u64, u128);
[00:01:34]      | ------------------------------------------------- in this macro invocation

Yeah shouldn't this be applied to fn try_from?

let max = <$signed as FromStrRadixHelper>::max_value() as u128;
if u as u128 > max {
fn try_from(u: $source) -> Result<$target, TryFromIntError> {
if u > (<$target as FromStrRadixHelper>::max_value() as $source) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The as FromStrRadixHelper isn't actually needed. You can simply do <$target>::max_value() and avoid the extra indirection.

@scottmcm
Copy link
Member

Looks like a real failure?

Testing libstd stage2 (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu)
[01:00:21]    Compiling rand v0.0.0 (file:///checkout/src/librand)
[01:00:21]    Compiling core v0.0.0 (file:///checkout/src/libcore)
[01:00:21]    Compiling std_unicode v0.0.0 (file:///checkout/src/libstd_unicode)
[01:00:21]    Compiling alloc v0.0.0 (file:///checkout/src/liballoc)
[01:00:27]    Compiling collections v0.0.0 (file:///checkout/src/libcollections)
[01:00:27]    Compiling std v0.0.0 (file:///checkout/src/libstd)
[01:00:33] error[E0277]: the trait bound `u64: core::convert::TryFrom<isize>` is not satisfied
[01:00:33]    --> /checkout/src/libcore/../libcore/tests/num/mod.rs:349:24
[01:00:33]     |
[01:00:33] 349 |             assert_eq!(<$target as TryFrom<$source>>::try_from(max).unwrap(),
[01:00:33]     |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `core::convert::TryFrom<isize>` is not implemented for `u64`
[01:00:33] ...
[01:00:33] 383 |     test_impl_try_from_signed_to_unsigned_upper_ok! { test_try_isizeu64, isize, u64 }
[01:00:33]     |     --------------------------------------------------------------------------------- in this macro invocation
[01:00:33]     |
[01:00:33]     = help: the following implementations were found:
[01:00:33]               <u64 as core::convert::TryFrom<u16>>
[01:00:33]               <u64 as core::convert::TryFrom<i128>>
[01:00:33]               <u64 as core::convert::TryFrom<u64>>
[01:00:33]               <u64 as core::convert::TryFrom<usize>>
[01:00:33]             and 7 others

@llogiq
Copy link
Contributor Author

llogiq commented Jul 19, 2017

That's strange... Is the target pointer size not one of 16/32/64 on that build? I note that all other builds are successful.

Perhaps I should add code for this scenario.

@oyvindln
Copy link
Contributor

oyvindln commented Jul 19, 2017

It says x86_64-unknown-linux-gnu so it should be 64-bit. I don't think any of the other builds run any tests, did it work locally?

EDIT: The tests you borrowed from my pr even has a check for non 16/32/64-bit in the "assume_ptr_width" macro that wraps the pointer width specific tests, so that's definitely not the issue.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 20, 2017

OK, but then why does the error only appear on that one LLVM 3.7 builder?

@scottmcm
Copy link
Member

@llogiq Because for PRs, the other jobs are configured to do essentially nothing. You'll see "skipping, not a full build" in them.

#[cfg(target_pointer_width = "64")] try_from_both_bounded!(i128, usize);

cfg_block!{
#[cfg(target_pointer_width = "16")] {
Copy link
Member

Choose a reason for hiding this comment

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

Can these be done with inner modules instead?

#[cfg(target_pointer_width = "16")]
mod imp {
   ...
}

#[cfg(target_pointer_width = "32")]
mod imp {
   ...
}

pub use imp::*;

Copy link
Contributor

Choose a reason for hiding this comment

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

You could probably also do the same in the tests.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 21, 2017

@kennytm of course, I just reused the macro from the tests.Will wait for travis, though, so I can fix whatever it comes up with.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 21, 2017

By, the way, I'd like to leave the macro in the tests for now, as I think submodules will get their own headers in the test output, right?

@llogiq
Copy link
Contributor Author

llogiq commented Jul 21, 2017

Huh? That tidy error 1 sure looks spurious.

@oyvindln
Copy link
Contributor

[00:01:34] error: expected item after attributes

[00:01:34]     --> /checkout/src/libcore/num/mod.rs:2638:35

[00:01:34]      |

[00:01:34] 2638 | #[cfg(target_pointer_width = "16")] {

[00:01:34]      |                                   ^

[00:01:34] 

[00:01:34] error: aborting due to previous error(s)

[00:01:34] 

[00:01:34] error: Could not compile `core`.

Looks like there is an extra set of braces. I think you also forgot to pub use the module.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 22, 2017

Ah, thanks. Will fix once I get to my PC.

try_from_both_bounded!(i64, u32, u16, u8);
try_from_both_bounded!(i128, u64, u32, u16, u8);

pub use ptr_try_from_impls.*;
Copy link
Member

Choose a reason for hiding this comment

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

more typo

[00:01:56]    Compiling unwind v0.0.0 (file:///checkout/src/libunwind)
[00:01:56] error: expected one of `::`, `;`, or `as`, found `.`
[00:01:56]     --> /checkout/src/libcore/num/mod.rs:2630:27
[00:01:56]      |
[00:01:56] 2630 | pub use ptr_try_from_impls.*;
[00:01:56]      |                           ^ expected one of `::`, `;`, or `as` here

@nagisa
Copy link
Member

nagisa commented Jul 24, 2017

@llogiq will you be fixing this? The alternate PR has been ready for quite a while, although it seems to me like having #[cfg] mod is slightly more clean, so I would rather land this.

I’ll wait for this to get into landable state or till I get another triage ping on the alternate PR whichever comes sooner to take a decision here.

@nagisa
Copy link
Member

nagisa commented Jul 24, 2017

Also I feel like @oyvindln deserves some attribution in the commit message for authoring the tests.

@llogiq
Copy link
Contributor Author

llogiq commented Jul 24, 2017

@nagisa I wholeheartedly agree. The only reason I didn't merge his PR directly is I suck at git. 🙁

@oyvindln
Copy link
Contributor

@nagisa @llogiq Thanks!

@llogiq
Copy link
Contributor Author

llogiq commented Jul 24, 2017

This appears to work. I'll try to squash & add proper attribution in the commit message.

This removes the need for a 128 bit storage by making use of the fact that
there can be either no over/underflow, either one or both, and each time
the target type suffices to hold the limit for comparison.

The downside is that the code looks a bit more complex.

This test code included in this commit is from @oyvindln 's PR. They also
greatly helped fixing a number of errors I made along the way. Thanks a lot!
@llogiq
Copy link
Contributor Author

llogiq commented Jul 24, 2017

Done!

@nagisa
Copy link
Member

nagisa commented Jul 24, 2017

@bors r+

@bors
Copy link
Contributor

bors commented Jul 24, 2017

📌 Commit 72ef15e has been approved by nagisa

try_from_both_bounded!(i128, u64, u32, u16, u8);

#[unstable(feature = "try_from", issue = "33417")]
pub use self::ptr_try_from_impls::*;
Copy link
Member

Choose a reason for hiding this comment

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

What is this for? You can't (and don't need to) reexport impls.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Really? Because the pub makes them visible where the mods are not. I deem them implementation detail, so I chose to keep them private.

Copy link
Member

Choose a reason for hiding this comment

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

Impls are always visible, even if they're in private modules.

test_impl_try_from_always_ok! { test_try_u64u128, u64, u128 }
test_impl_try_from_always_ok! { test_try_u64i128, u64, i128 }

test_impl_try_from_always_ok! { test_try_u128, u128, u128 }
Copy link
Member

Choose a reason for hiding this comment

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

For consistency with the other tests this should be test_try_u128u128.

try_from_both_bounded!(isize, u8);
try_from_lower_bounded!(isize, usize, u16, u32, u64, u128);
try_from_both_bounded!(isize, i8);
try_from_unbounded!(isize, i16, i32, i64, i128);
Copy link
Member

Choose a reason for hiding this comment

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

This looks like it's missing isize to isize. It might be clearer to put the following outside these target specific modules:

try_from_unbounded!(usize, usize);
try_from_upper_bounded!(usize, isize);
try_from_lower_bounded!(isize, usize);
try_from_unbounded!(isize, isize);

cfg_block!(
#[cfg(target_pointer_width = "16")] {
test_impl_try_from_always_ok! { test_try_u16usize, u16, usize }
test_impl_try_from_always_ok! { test_try_i16isize, i16, isize }
Copy link
Member

Choose a reason for hiding this comment

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

these should be:

test_impl_try_from_always_ok! { test_try_usizeu16, usize, u16 }
test_impl_try_from_always_ok! { test_try_isizei16, isize, i16 }

also missing:

test_impl_try_from_always_ok! { test_try_usizeu32, usize, u32 }
test_impl_try_from_always_ok! { test_try_usizei32, usize, i32 }
test_impl_try_from_always_ok! { test_try_usizei64, usize, i64 }
test_impl_try_from_always_ok! { test_try_isizei32, isize, i32 }

test_impl_try_from_always_ok! { test_try_i64i128, i64, i128 }

test_impl_try_from_always_ok! { test_try_i128i128, i128, i128 }

Copy link
Member

Choose a reason for hiding this comment

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

missing:

test_impl_try_from_always_ok! { test_try_usizeusize, usize, usize }
test_impl_try_from_always_ok! { test_try_isizeisize, isize, isize }

test_impl_try_from_always_ok! { test_try_i128i128, i128, i128 }

assume_usize_width! {
test_impl_try_from_always_ok! { test_try_u8usize, u8, usize }
Copy link
Member

Choose a reason for hiding this comment

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

missing:

test_impl_try_from_always_ok! { test_try_u8isize, u8, isize }

test_impl_try_from_always_ok! { test_try_usizeu32, usize, u32 }
test_impl_try_from_always_ok! { test_try_isizei32, isize, i32 }
test_impl_try_from_always_ok! { test_try_u32usize, u32, usize }
test_impl_try_from_always_ok! { test_try_i32isize, i32, isize }
Copy link
Member

Choose a reason for hiding this comment

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

missing:

test_impl_try_from_always_ok! { test_try_u16isize, u16, isize }
test_impl_try_from_always_ok! { test_try_usizei64, usize, i64 }

test_impl_try_from_always_ok! { test_try_u32usize, u32, usize }
test_impl_try_from_always_ok! { test_try_i32isize, i32, isize }
test_impl_try_from_always_ok! { test_try_u64usize, u64, usize }
test_impl_try_from_always_ok! { test_try_i64isize, i64, isize }
Copy link
Member

Choose a reason for hiding this comment

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

missing:

test_impl_try_from_always_ok! { test_try_u16isize, u16, isize }
test_impl_try_from_always_ok! { test_try_u32isize, u32, isize }

cfg_block!(
#[cfg(target_pointer_width = "16")] {
test_impl_try_from_unsigned_to_signed_upper_err! { test_try_u32isize, u32, isize }
test_impl_try_from_unsigned_to_signed_upper_err! { test_try_u32isize, u64, isize }
Copy link
Member

Choose a reason for hiding this comment

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

this one should be:

test_impl_try_from_unsigned_to_signed_upper_err! { test_try_u16isize, u16, isize }

@bors
Copy link
Contributor

bors commented Jul 25, 2017

⌛ Testing commit 72ef15e with merge 7c46c6c...

bors added a commit that referenced this pull request Jul 25, 2017
improve the TryFrom implementations

This removes the need for a 128 bit storage by making use of the fact that there can be either no over/underflow, either one or both, and each time the target type suffices to hold the limit for comparison. This also means that the implementation will work in targets without 128bit support (unless it's for 128bit types, of course).

The downside is that the code looks a bit more complex.
@bors
Copy link
Contributor

bors commented Jul 25, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nagisa
Pushing 7c46c6c to master...

@bors bors merged commit 72ef15e into rust-lang:master Jul 25, 2017
@llogiq llogiq deleted the num-try-from branch July 25, 2017 04:18
@llogiq
Copy link
Contributor Author

llogiq commented Jul 25, 2017

@ollie27 thanks for noticing the missing tests! Do you want to do a followup PR adding them?

@ollie27
Copy link
Member

ollie27 commented Jul 25, 2017

Sure, I'll add them in a new PR.

@ollie27
Copy link
Member

ollie27 commented Jul 25, 2017

Done: #43471

@cramertj
Copy link
Member

cramertj commented Jul 25, 2017

I'm confused why this was merged-- don't the infallible TryFrom implementations here conflict with the future blanket impl for From?

@kennytm
Copy link
Member

kennytm commented Jul 25, 2017

@cramertj this can be reverted once that NumCast RFC is implemented (which was scheduled to be submitted mid-August).

@scottmcm
Copy link
Member

@cramertj the infallible impls were already there (https://doc.rust-lang.org/std/convert/trait.TryFrom.html), so AFAIK they'll be removed only when that blanket impl is added.

Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this pull request Jul 26, 2017
Add missing impl and tests for int to int TryFrom impls

These were missing from rust-lang#43248.

r? @nagisa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.