Repository navigation
Fix parse error described in #35 - #36
Conversation
This change adds a `new` fn, which uses debug assertions to validate the arguments. It also uses the non_exhaustive attribute to ensure callers have to call new instead of initializing the struct directly, and updates all affected code paths to use the new function.
After handling an excluded range, the start variable is bumped to the end of the excluded range. This can cause it to be past the end of the next parent range. Previously, the implementation would push an invalid range when that happened. This change adds a condition to prevent that from happening.
|
I've pushed the rest of the changes into the pr/edition-2024 and pr/rewrite-intersect_ranges_impl branches on my fork. Here's a permalinked comparison. It's best reviewed as separate commits, to avoid having to filter out noise from I can submit these in 3 separate PRs, merge them all into one, or something in between, as needed. |
|
Updating to edition 2024 would raise the MSRV significantly and libraries should avoid doing that unless there's something we really need in a higher Rust version. |
I really enjoyed being able to use let chains, but it sounds like it probably wouldn't fall into your definition of need, and it bumps the required Rust version up to 1.88.0. Edit: I've backported my changes to the existing MSRV, and merged them into the branch for this PR. |
This allows the compiler to instantiate a single instance of the generic intersect_ranges_impl function, rather than monomorphizing a different version for each arm of the match.
The previous implementation had at least one other bug in it, but it was written in a way that was overly hard to reason about, and less idiomatic than it could be. Rather than try to incrementally fix bugs, it felt more efficient to rewrite it as a pair of iterator transformations, one to handle exclusion, and a second one to calculate the intersections. This version should result in similar compiler output, but with more readable source code, and a style of interface that's easier to compose with other iterator functions.
|
@the-mikedavis would you mind offering updated feedback about this, when you have time? |
|
This is a complicated section of the code and I have a lot of other review on my plate so I probably won't be able to give this proper feedback for a while. At a quick glance though the new code looks much more complicated. I will probably feel more comfortable making a fix in this area of the code myself so I would recommending not investing too much time into this fix. I can reproduce the invalid ranges by reducing the example code down to: /**
* foo
*/
impl Foo {
/**
* foo
*/
/// foo
}This gives the following ranges before the parsing errors out: // Layer 0 root
LayerData {
language: Language(0),
ranges: [0..4294967295],
parent: None,
..
}
// Layer 1
LayerData {
language: Language(96),
ranges: [3..12, 34..51, 62..67],
parent: Some(Layer(0)),
..
}
// Layer 3
LayerData {
language: Language(97),
ranges: [42..46, 63..51, 62..66],
parent: Some(Layer(1)),
..
}I haven't yet dug into why |
My memory's getting a bit vague, by now, but the first commit in this PR explains the problem and supplies a three-line fix. Basically, after an exclusion, the start of the child range gets advanced to the end of There's at least one other bug in the existing That would be more correct like this: Also, it looks like an invalid range would be pushed if the first excluded range returned by the iterator were to start before
Yeah, I understand. I'd probably feel the same way if I were on the other side of a diff like this. It looked cleaner in the 2024 edition, with let chains, but either way, there's more code here than there was before. When I started looking at the I split them because I was hoping to address not only the bug itself, but also this underlying cause: code that sacrifices readability and robustness to optimize something that the compiler is going to optimize anyway.
Noted. |
With combined injections, parent ranges can be adjacent. intersect_ranges_impl had logic to merge adjacent ranges, but it ran after determining the intersection, so adjacent runs were already consumed before the merge. Say for adjacent ranges `[10..30, 30..50, 50..80]`, the output would be `[10..30, 30..80]`.
the-mikedavis
left a comment
There was a problem hiding this comment.
Ok I finally got around to giving this a proper look. Sorry for the delay. I agree the new approach moving exclude_ranges out is easier to reason about and shouldn't be a regression for performance. (I think so but I didn't do any benchmarking.)
I made a few edits for adding unit & regression tests and a change about merging adjacent ranges. I think adjacent ranges are ok in practice but we might as well merge as aggressively as we can. I also reverted the #[non_exhaustive] attribute on Range since I want to avoid a breaking change with this. I think if we really wanted to disallow Range from being constructed with backwards points we would have ::new return an Option<Self> or Result<Self>. Since it's not a critical failure for the library I think it's fine to allow constructing Range manually
|
Thanks @sruggier ! |
Here's a minimal fix for the problem documented in #35.
It looks like there are other potential problems with
intersect_range_impl, but addressing them fully is more complicated, and would be simpler if I could use let chains. That would require updating the edition to 2024, so I decided it would probably make sense to defer those changes into a separate PR or two.