Skip to content

eq_unspanned is incorrect #141522

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

Closed
workingjubilee opened this issue May 24, 2025 · 7 comments · Fixed by #141570
Closed

eq_unspanned is incorrect #141522

workingjubilee opened this issue May 24, 2025 · 7 comments · Fixed by #141570
Assignees
Labels
C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@workingjubilee
Copy link
Member

workingjubilee commented May 24, 2025

The following function is incorrect:

pub fn eq_unspanned(&self, other: &TokenStream) -> bool {
let mut iter1 = self.iter();
let mut iter2 = other.iter();
for (tt1, tt2) in iter::zip(&mut iter1, &mut iter2) {
if !tt1.eq_unspanned(tt2) {
return false;
}
}
iter1.next().is_none() && iter2.next().is_none()
}

Zipped iterators with uneven lengths will still consume an item from both sides of the zip on their final iteration (where one will return None and one will return Some). This allows for both iterators to be exhausted even though their lengths are unequal.

@workingjubilee workingjubilee added the C-bug Category: This is a bug. label May 24, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label May 24, 2025
@workingjubilee workingjubilee added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels May 24, 2025
@workingjubilee
Copy link
Member Author

h/t @jameysharp for finding this

@compiler-errors
Copy link
Member

This seems to only be used in tests, clippy, and rustdoc. It should probably be removed tbh.

@chenyukang
Copy link
Member

I am not sure if I misunderstood, I think this function is correct.

iter::zip(&mut iter1, &mut iter2) will only iterate as long as both iterators yield values, i.e., up to the length of the shorter iterator, see the type in for.

Image

@jameysharp
Copy link
Contributor

This is surprisingly tricky because I've discovered this function would work as intended on some iterators, and I don't know if this is one of them. But here's an example where zip iterates once, where one iterator yields two values but the other only yields one, and yet after zip returns None, both iterators also return None:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=268f9af2bf4b61d9bd13f75cc905dca3

This happens because the default implementation of next() for zip first calls next on the left iterator, and then on the right, so by the time it discovers that there is no next element in the second iterator it has already consumed an element from the first iterator.

But on TrustedRandomAccess iterators, zip is specialized to know exactly how many elements it can return, and doesn't consume from either iterator after that point. So zip's behavior is observably different depending on which specialization you get, which is interesting.

I don't know which category TokenStream::iter() falls into. But at minimum it seems fragile to me.

@chenyukang
Copy link
Member

This is surprisingly tricky because I've discovered this function would work as intended on some iterators, and I don't know if this is one of them. But here's an example where zip iterates once, where one iterator yields two values but the other only yields one, and yet after zip returns None, both iterators also return None:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=268f9af2bf4b61d9bd13f75cc905dca3

This happens because the default implementation of next() for zip first calls next on the left iterator, and then on the right, so by the time it discovers that there is no next element in the second iterator it has already consumed an element from the first iterator.

But on TrustedRandomAccess iterators, zip is specialized to know exactly how many elements it can return, and doesn't consume from either iterator after that point. So zip's behavior is observably different depending on which specialization you get, which is interesting.

I don't know which category TokenStream::iter() falls into. But at minimum it seems fragile to me.

You're right, I misunderstood it.

@chenyukang
Copy link
Member

The subtle part is only the arguments order:

fn cmp(arr1: &[i32], arr2: &[i32]) -> bool {
    let mut xi = arr1.into_iter();
    let mut yi = arr2.into_iter();

    for (x, y) in std::iter::zip(&mut xi, &mut yi) {
        if x != y {
            return false;
        }
    }
    xi.next().is_none() && yi.next().is_none()
}

fn main() {
    let arr1 = [1];
    let arr2 = [1, 2];

    eprintln!("res1: {:?}", cmp(&arr1, &arr2));   // return `false`
    eprintln!("res2: {:?}", cmp(&arr2, &arr1));   // return `true`
}

@workingjubilee
Copy link
Member Author

@the8472 I feel like I ought to poke you at this point.

chenyukang added a commit to chenyukang/rust that referenced this issue Jun 4, 2025
…, r=workingjubilee

Fix incorrect eq_unspanned in TokenStream

Fixes rust-lang#141522

r? `@workingjubilee`

should we remove this function?
since it's used in several places, i'd prefer to keep it.
rust-bors bot added a commit that referenced this issue Jun 4, 2025
Fix incorrect eq_unspanned in TokenStream

Fixes #141522

r? `@workingjubilee`

should we remove this function?
since it's used in several places, i'd prefer to keep it.

try-job: i686-msvc-1
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jun 4, 2025
…, r=workingjubilee

Fix incorrect eq_unspanned in TokenStream

Fixes rust-lang#141522

r? `@workingjubilee`

should we remove this function?
since it's used in several places, i'd prefer to keep it.
@bors bors closed this as completed in 0736a03 Jun 4, 2025
rust-timer added a commit that referenced this issue Jun 4, 2025
Rollup merge of #141570 - chenyukang:yukang-fix-eq_unspanned, r=workingjubilee

Fix incorrect eq_unspanned in TokenStream

Fixes #141522

r? ``@workingjubilee``

should we remove this function?
since it's used in several places, i'd prefer to keep it.
flip1995 pushed a commit to flip1995/rust-clippy that referenced this issue Jun 13, 2025
…ngjubilee

Fix incorrect eq_unspanned in TokenStream

Fixes rust-lang/rust#141522

r? ``@workingjubilee``

should we remove this function?
since it's used in several places, i'd prefer to keep it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants