Change:
rustc crashes on the following input, found by :
searched nightlies: from nightly-2021-01-01 to nightly-2021-12-24 regressed nightly: nightly-2021-10-02 searched commits: from to regressed commit: <details<summarybisected with <a href=''cargo-bisect-rustc</av0.6.0</summaryHost triple: x86_64-unknown-linux-gnu Reproduce with: </details
The regression happened in . cc
label regression-from-stable-to-stable
Assigning priority as discussed in the of the Prioritization Working Group. label -I-prioritize +P-low
I've been taking a look at this. It's pretty interesting. I'm not entirely sure I'll be able to work it out, but I figure I may as well assign myself for the time being while I attempt to solve it.
Another test case:
The problem seems to reside within the crate , in the files and . The function in has a macro, , which is in charge of looking for foreign substitutions - that is, someone using printf or shell style formatting rather than Rust's style. It's a macro so it can be generic over the two types of substitution. The first time this macro is called, for printf substitutions, it finds two (I think "%%" and "%", though I'm not sure it's exactly that). The code that actually detects the substitutions is in . When parsing the substitution, in the function , it is somehow finding that the second substitution has a boundary in the middle of the character. I'm not sure exactly how yet. This gets turned into a span in . The span is malformed, as it splits a character in two, so the second the code tries to use it for anything it trips an assertion and causes this ICE. The remaining problem is figuring out what's going wrong in . This sort of string manipulation is not my forte. I'll probably take another few cracks at it though.
Something is broken about . This change makes the ICE go away (but probably breaks other things):
When returns here: it seems like we should increment by , but we actually only increment it by 2:
yes, I get the exact same error. . The error disappears if you add a new line after the , so maybe your text editor did that automatically.
That was it, thanks. I'm going to unassign myself from this. I do hope my exploration ends up being helpful to whoever fixes it though. if you want to fix this yourself, you certainly seem further along than I was, though you shouldn't feel any pressure to do so if you don't want to.
I put up a PR for a
I opened a new issue for your bug, because it's not exactly the same as this one.

Discussion:
pub enum Substitution<'a> { /// A formatted output substitution with its internal byte offset. Format(Format<'a>), <del> /// A literal `%%` escape. Escape, </del> <ins> /// A literal `%%` escape, with its start and end indices. Escape((usize, usize)), </ins> } impl<'a> Substitution<'a> { pub fn as_str(&self) -> &str { match *self { Substitution::Format(ref fmt) => fmt.span, <del> Substitution::Escape => "%%", </del> <ins> Substitution::Escape(_) => "%%", </ins> } } pub fn position(&self) -> Option<InnerSpan> { match *self { Substitution::Format(ref fmt) => Some(fmt.position), <del> _ => None, </del> <ins> Substitution::Escape((start, end)) => Some(InnerSpan::new(start, end)), </ins> } } pub fn set_position(&mut self, start: usize, end: usize) { <del> if let Substitution::Format(ref mut fmt) = self { fmt.position = InnerSpan::new(start, end); </del> <ins> match self { Substitution::Format(ref mut fmt) => fmt.position = InnerSpan::new(start, end), Substitution::Escape(ref mut pos) => *pos = (start, end), </ins> } }

pub fn translate(&self) -> Result<String, Option<String>> { match *self { Substitution::Format(ref fmt) => fmt.translate(), <del> Substitution::Escape => Err(None), </del> <ins> Substitution::Escape(_) => Err(None), </ins> } } }

fn next(&mut self) -> Option<Self::Item> { let (mut sub, tail) = parse_next_substitution(self.s)?; self.s = tail; <del> match sub { Substitution::Format(_) => { if let Some(inner_span) = sub.position() { sub.set_position(inner_span.start + self.pos, inner_span.end + self.pos); self.pos += inner_span.end; } } Substitution::Escape => self.pos += 2, </del> <ins> if let Some(InnerSpan { start, end }) = sub.position() { sub.set_position(start + self.pos, end + self.pos); self.pos += end; </ins> } Some(sub) }

let at = { let start = s.find('%')?; if let '%' = s[start + 1..].chars().next()? { <del> return Some((Substitution::Escape, &s[start + 2..])); </del> <ins> return Some((Substitution::Escape((start, start + 2)), &s[start + 2..])); </ins> } Cur::new_at(s, start)

fn test_escape() { assert_eq!(pns("has no escapes"), None); assert_eq!(pns("has no escapes, either %"), None); <del> assert_eq!(pns("*so* has a %% escape"), Some((S::Escape, " escape"))); assert_eq!(pns("%% leading escape"), Some((S::Escape, " leading escape"))); assert_eq!(pns("trailing escape %%"), Some((S::Escape, ""))); </del> <ins> assert_eq!(pns("*so* has a %% escape"), Some((S::Escape((11, 13)), " escape"))); assert_eq!(pns("%% leading escape"), Some((S::Escape((0, 2)), " leading escape"))); assert_eq!(pns("trailing escape %%"), Some((S::Escape((16, 18)), ""))); </ins> } #[test]

<ins> // check-fail pub fn main() { println!("🦀%%%", 0) } //~ ERROR argument never used </ins>

<ins> error: argument never used --> $DIR/issue-92267.rs:3:34 | LL | pub fn main() { println!("🦀%%%", 0) } |                                   ^ argument never used | note: format specifiers use curly braces, and the conversion specifier ` ` is unknown or unsupported --> $DIR/issue-92267.rs:3:30 | LL | pub fn main() { println!("🦀%%%", 0) } |                               ^^ = note: printf formatting not supported; see the documentation for `std::fmt` error: aborting due to previous error </ins>