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

implement RFC 1192 inclusive ranges #30884

Merged
merged 19 commits into from
Mar 6, 2016
Merged

Conversation

durka
Copy link
Contributor

@durka durka commented Jan 13, 2016

This PR implements RFC 1192, which is triple-dot syntax for inclusive range expressions. The new stuff is behind two feature gates (one for the syntax and one for the std::ops types). This replaces the deprecated functionality in std::iter. Along the way I simplified the desugaring for all ranges.

This is my first contribution to rust which changes more than one character outside of a test or comment, so please review carefully! Some of the individual commit messages have more of my notes. Also thanks for putting up with my dumb questions in #rust-internals.

Notes

  • For implementing std::ops::RangeInclusive, I took @Stebalien's suggestion from RFC for inclusive ranges with ... rfcs#1192 (comment). It seemed to me to make the implementation easier and increase type safety. If that stands, the RFC should be amended to avoid confusion.
  • I also kind of like @glaebhoerl's idea, which is unified inclusive/exclusive range syntax something like x>..=y. We can experiment with this while everything is behind a feature gate.
  • There are a couple of FIXMEs left (see the last commit). I didn't know what to do about RangeArgument and I haven't added Index impls yet. Those should be discussed/finished before merging.

cc @gankro since you complained
cc #27777 #30877 #1192 rust-lang/rfcs#1254
relevant to #28237 (tracking issue)

@rust-highfive
Copy link
Collaborator

r? @aturon

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

@Stebalien
Copy link
Contributor

There's actually an rfc for that: rust-lang/rfcs#1320

The current proposed syntax is actually:

pub enum RangeInclusive<T> {
  Empty {
    at: T,
  },
  NonEmpty {
    start: T,
    end: T,
  }
}

This way, we don't loose any information or throw anything away (see the discussion on the RFC). It also means we should also be able to implement From<Range<T>> for RangeInclusive<T>.

@durka
Copy link
Contributor Author

durka commented Jan 13, 2016

@Stebalien oh I completely missed that RFC! I will add that field and impl later tonight.

@durka
Copy link
Contributor Author

durka commented Jan 14, 2016

Fixed tidy issues. The things @Stebalien brought up will need to wait until tomorrow. But those only affect the libcore part, not the rest of the PR. What's the policy on accepting RFC amendments?

@durka
Copy link
Contributor Author

durka commented Jan 14, 2016

The failing test is the one that ensures HIR node IDs are reproducible. For debugging I removed some custom Debug impls in favor of #[derive]s which will print the ID. I isolated the difference (full output here):

$ diff -us hir{1,2}
--- hir1    2016-01-14 12:06:39.000000000 -0500
+++ hir2    2016-01-14 12:06:47.000000000 -0500
@@ -16,8 +16,8 @@
                                             ),
                                             Spanned {
                                                 node: Ident {
-                                                    name: _result(81),
-                                                    unhygienic_name: _result(81)
+                                                    name: _result(83),
+                                                    unhygienic_name: _result(83)
                                                 },
                                                 span: Span { lo: BytePos(0), hi: BytePos(0), expn_id: ExpnId(4294967295) }
                                             },
@@ -192,8 +192,8 @@
                                                                     ),
                                                                     Spanned {
                                                                         node: Ident {
-                                                                            name: iter(72),
-                                                                            unhygienic_name: iter(72)
+                                                                            name: iter(82),
+                                                                            unhygienic_name: iter(82)
                                                                         },
                                                                         span: Span { lo: BytePos(0), hi: BytePos(0), expn_id: ExpnId(4294967295) }
                                                                     },
@@ -283,8 +283,8 @@
                                                                                                                 segments: [
                                                                                                                     PathSegment {
                                                                                                                         identifier: Ident {
-                                                                                                                            name: iter(72),
-                                                                                                                            unhygienic_name: iter(72)
+                                                                                                                            name: iter(82),
+                                                                                                                            unhygienic_name: iter(82)
                                                                                                                         },
                                                                                                                         parameters: AngleBracketedParameters(
                                                                                                                             AngleBracketedParameterData {
@@ -587,8 +587,8 @@
                             segments: [
                                 PathSegment {
                                     identifier: Ident {
-                                        name: _result(81),
-                                        unhygienic_name: _result(81)
+                                        name: _result(83),
+                                        unhygienic_name: _result(83)
                                     },
                                     parameters: AngleBracketedParameters(
                                         AngleBracketedParameterData {
@@ -613,4 +613,3 @@
     span: Span { lo: BytePos(0), hi: BytePos(0), expn_id: ExpnId(4294967295) },
     attrs: None
 �[00m�[37m}
-

But I don't have time to investigate right now.

@alexcrichton
Copy link
Member

Edit: I'm wrong

@durka
Copy link
Contributor Author

durka commented Jan 14, 2016

Do you mean in the HIR? I did not remove ExprRange from the AST.
On Jan 14, 2016 2:10 PM, "Alex Crichton" notifications@github.com wrote:

I think that we may want to preserve the ExprRange variant in the AST as
it will likely assist with pretty printing (e.g. it disambiguates whether
you have a literal struct or used the sugar syntax)


Reply to this email directly or view it on GitHub
#30884 (comment).

@alexcrichton
Copy link
Member

Oh gah yes, sorry about that, my mistake!

@durka
Copy link
Contributor Author

durka commented Jan 15, 2016

So just to be clear, pretty printing of the AST is not affected, but pretty
printing the HIR does show the plain struct now (I don't know under what
circumstances if any the HIR is pretty printed). This is similar to other
desugarings like for loops and if-let which are done during HIR lowering.
On Jan 14, 2016 18:20, "Alex Crichton" notifications@github.com wrote:

Oh gah yes, sorry about that, my mistake!


Reply to this email directly or view it on GitHub
#30884 (comment).

@aturon aturon assigned aturon and unassigned aturon Jan 15, 2016
@durka
Copy link
Contributor Author

durka commented Jan 15, 2016

Regarding the ID caching stuff, I need help from @nrc or someone else familiar with this code.

I believe it was only working accidentally before, but I'm not sure. "Fixing" the bug I think I found causes an ICE elsewhere. Notably, in cache_ids() here I see that gensym_key gets set but cached_id does not. This then causes later code to assume that caching is not active, and so the gensyms don't match on consecutive lowerings.

Here is a trace of some debugging output I added, with manual indentation to show nested cache_ids() calls:

cache_ids: was not in cache, next_id is 44, now id_cache[18] = 44, cached_id = 0, gensym_key = 44 (will reset)
    cache_ids: was not in cache, next_id is 44, now id_cache[20] = 44, cached_id = 0, gensym_key = 44 (will reset)
    cache_ids: resetting, now cached_id = 0, gensym_key = 0
    str_to_ident(iter) = Ident { name: iter(72), unhygienic_name: iter(72) } (not caching)
    str_to_ident(_result) = Ident { name: _result(81), unhygienic_name: _result(81) } (not caching)
cache_ids: resetting, now cached_id = 0, gensym_key = 0
cache_ids: was in cache, cached_id was 0, now id_cache[18] = 44, cached_id = 44, gensym_key = 44 (will reset)
    cache_ids: was in cache, cached_id was 44, now id_cache[20] = 44, cached_id = 44, gensym_key = 44 (will not reset)
    cache_ids, not resetting, now cached_id = 45, gensym_key = 44
    str_to_ident(iter) = Ident { name: iter(82), unhygienic_name: iter(82) } (uncached, cached_id=44)
    str_to_ident(_result) = Ident { name: _result(83), unhygienic_name: _result(83) } (uncached, cached_id=44)
cache_ids: resetting, now cached_id = 0, gensym_key = 0

This is from the test that lowers a for loop twice to check the HIR equivalence. You can see that the first call to cache_ids() did not set cached_id, so the nested call to cache_ids() decides to reset the keys, so the gensyms of iter and _result are not cached. Oops.

Okay, but I tried to fix this, by adding this line, but uncommenting that causes an ICE somewhere else (backtrace):

thread 'rustc' panicked at 'adding a def'n for node-id 200897 and data Binding(_result(53163)) but a previous def'n exists:
DefData {
    key: DefKey {
        parent: Some(DefIndex(1701)),
        disambiguated_data: DisambiguatedDefPathData {
            data: Binding(_result(53163)),
            disambiguator: 1
        }
    },
    node_id: 200897
}', src/librustc/front/map/definitions.rs:146

Wat do?

@durka
Copy link
Contributor Author

durka commented Jan 16, 2016

@Stebalien I added RangeInclusive::Empty::at and the From impl. It still isn't going to pass Travis because of the issue above, but take a look and see if I got it right.

/// An inclusive range which is only bounded above.
#[derive(Copy, Clone, PartialEq, Eq)]
#[unstable(feature = "inclusive_range", reason = "recently added, follows RFC", issue = "28237")]
pub struct RangeToInclusive<Idx> {
Copy link
Contributor

Choose a reason for hiding this comment

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

It should be possible to impl From<RangeTo> for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, should be pretty much the same.

On Sat, Jan 16, 2016 at 3:41 PM, Steven Allen notifications@github.com
wrote:

In src/libcore/ops.rs
#30884 (comment):

+impl<Idx: PartialOrd + One + Sub<Output=Idx>> From<Range> for RangeInclusive {

  • fn from(range: Range) -> RangeInclusive {
  •    use self::RangeInclusive::*;
    
  •    if range.start < range.end {
    
  •        NonEmpty { start: range.start, end: range.end - Idx::one() }
    
  •    } else {
    
  •        Empty { at: range.start } // FIXME range.start or range.end?
    
  •    }
    
  • }
    +}

+/// An inclusive range which is only bounded above.
+#[derive(Copy, Clone, PartialEq, Eq)]
+#[unstable(feature = "inclusive_range", reason = "recently added, follows RFC", issue = "28237")]
+pub struct RangeToInclusive {

It should be possible to impl From for this.


Reply to this email directly or view it on GitHub
https://github.com/rust-lang/rust/pull/30884/files#r49936902.

@Stebalien
Copy link
Contributor

@durka, looks good. However, I don't know if we want to implement Iterator directly or go though some intermediate (step etc...).

@durka
Copy link
Contributor Author

durka commented Jan 16, 2016

I note that the extant ops::Range implements Iterator directly, but there is also an impl for StepBy<Range>. That could be done for RangeInclusive as well.

@durka
Copy link
Contributor Author

durka commented Jan 16, 2016

@Stebalien what do you think about underflow on the conversions? For example what should (..0u8).into() do?

@Stebalien
Copy link
Contributor

@durka hm. Yeah, we probably shouldn't implement From<RangeTo>. This isn't a problem for From<Range> because we have an empty variant and 0..0 would map to RangeInclusive { Empty { at: 0 }}.

@durka
Copy link
Contributor Author

durka commented Jan 16, 2016

@Stebalien I suppose so. Just to be clear, the intention for the From impls is that they should produce a range over the same values, so 0..10 => 0...9? I could also imagine a world in which From converts [a, b) to [a, b] so that 0..10 => 0...10. Then these overflow problems would go away. Not quite sure how to apply the Principle of Least Surprise here.

For From<Range> it's still an issue because you could have an empty range like 5..0u8, which my current implementation would try to convert to RangeInclusive::Empty { at: 0u8-1 } which would either panic or underflow. I guess I could use saturating_sub or something.

@bors
Copy link
Contributor

bors commented Jan 17, 2016

☔ The latest upstream changes (presumably #30567) made this pull request unmergeable. Please resolve the merge conflicts.

@nrc
Copy link
Member

nrc commented Jan 17, 2016

@durka Hi, I'm not sure off the top of my head what to do. TBH, I'm not sure we need to keep all that stuff, but I need to think about that. Fixing it would be nice. Let me get back to you a bit later...

@durka
Copy link
Contributor Author

durka commented Jan 18, 2016

Rebased.

@Stebalien
Copy link
Contributor

@durka

First, I would expect the conversion should convert the endpoints to keep the same meaning. As for negative/empty ranges, 5..0u8 should be Empty { at: 5 } (an empty range starting at 5).

if end <= start {
    RangeInclusive::Empty { at: start }
} else {
    // Can't underflow as `end > start >= MIN`
    RangeInclusive::NonEmpty {
        start: start,
        end: end - 1,
}

@durka
Copy link
Contributor Author

durka commented Jan 19, 2016

Another question (foreshadowed by a FIXME buried in my code). Are we settled on NonEmpty as the variant name or is there bikeshedding left to be done? It seems slightly weird to me to have the common case named with a negation. But I can't come up with anything better.

@aturon
Copy link
Member

aturon commented Mar 4, 2016

Hi, more apologies: been away much of this week due to family catching flu. I looked at the underflow commit and left a comment. Once that's been addressed, I think this is good to go!

@durka
Copy link
Contributor Author

durka commented Mar 5, 2016

@aturon should be good to go.

@aturon
Copy link
Member

aturon commented Mar 5, 2016

@durka Great! Thanks for sticking with it, and let's talk RFC soon. :-)

@bors: r+

@bors
Copy link
Contributor

bors commented Mar 5, 2016

📌 Commit 430b3e1 has been approved by aturon

@bors
Copy link
Contributor

bors commented Mar 5, 2016

⌛ Testing commit 430b3e1 with merge deb9109...

@bors
Copy link
Contributor

bors commented Mar 5, 2016

💔 Test failed - auto-win-msvc-32-opt

@durka
Copy link
Contributor Author

durka commented Mar 6, 2016

Looks like it just timed out?

@aturon
Copy link
Member

aturon commented Mar 6, 2016

@bors: retry

bors added a commit that referenced this pull request Mar 6, 2016
This PR implements [RFC 1192](https://github.com/rust-lang/rfcs/blob/master/text/1192-inclusive-ranges.md), which is triple-dot syntax for inclusive range expressions. The new stuff is behind two feature gates (one for the syntax and one for the std::ops types). This replaces the deprecated functionality in std::iter. Along the way I simplified the desugaring for all ranges.

This is my first contribution to rust which changes more than one character outside of a test or comment, so please review carefully! Some of the individual commit messages have more of my notes. Also thanks for putting up with my dumb questions in #rust-internals.

- For implementing `std::ops::RangeInclusive`, I took @Stebalien's suggestion from rust-lang/rfcs#1192 (comment). It seemed to me to make the implementation easier and increase type safety. If that stands, the RFC should be amended to avoid confusion.
- I also kind of like @glaebhoerl's [idea](rust-lang/rfcs#1254 (comment)), which is unified inclusive/exclusive range syntax something like `x>..=y`. We can experiment with this while everything is behind a feature gate.
- There are a couple of FIXMEs left (see the last commit). I didn't know what to do about `RangeArgument` and I haven't added `Index` impls yet. Those should be discussed/finished before merging.

cc @gankro since you [complained](https://www.reddit.com/r/rust/comments/3xkfro/what_happened_to_inclusive_ranges/cy5j0yq)
cc #27777 #30877 #1192 rust-lang/rfcs#1254
relevant to #28237 (tracking issue)
@bors
Copy link
Contributor

bors commented Mar 6, 2016

⌛ Testing commit 430b3e1 with merge 8484831...

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

Successfully merging this pull request may close these issues.

None yet

7 participants