diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index f2055608aa9d2..c37736f137df9 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -8,8 +8,9 @@ use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::{self, Display, Write}; -use std::{cmp, iter}; +use std::iter; +use itertools::Either; use rustc_data_structures::fx::FxIndexMap; use rustc_lexer::{Cursor, FrontmatterAllowed, LiteralKind, TokenKind}; use rustc_span::BytePos; @@ -134,151 +135,322 @@ fn can_merge(class1: Option, class2: Option, text: &str) -> bool { } } +#[derive(Debug)] +struct ClassInfo { + class: Class, + /// If `Some`, then it means the tag was opened and needs to be closed. + closing_tag: Option<&'static str>, + /// Set to `true` by `exit_elem` to signal that all the elements of this class have been pushed. + /// + /// The class will be closed and removed from the stack when the next non-mergeable item is + /// pushed. When it is removed, the closing tag will be written if (and only if) + /// `self.closing_tag` is `Some`. + pending_exit: bool, +} + +impl ClassInfo { + fn new(class: Class, closing_tag: Option<&'static str>) -> Self { + Self { class, closing_tag, pending_exit: closing_tag.is_some() } + } + + fn close_tag(&self, out: &mut W) { + if let Some(closing_tag) = self.closing_tag { + out.write_str(closing_tag).unwrap(); + } + } + + fn is_open(&self) -> bool { + self.closing_tag.is_some() + } +} + +/// This represents the stack of HTML elements. For example a macro expansion +/// will contain other elements which might themselves contain other elements +/// (like macros). +/// +/// This allows to easily handle HTML tags instead of having a more complicated +/// state machine to keep track of which tags are open. +#[derive(Debug)] +struct ClassStack { + open_classes: Vec, +} + +impl ClassStack { + fn new() -> Self { + Self { open_classes: Vec::new() } + } + + fn enter_elem( + &mut self, + out: &mut W, + href_context: &Option>, + new_class: Class, + closing_tag: Option<&'static str>, + ) { + if let Some(current_class) = self.open_classes.last_mut() { + if can_merge(Some(current_class.class), Some(new_class), "") { + current_class.pending_exit = false; + return; + } else if current_class.pending_exit { + current_class.close_tag(out); + self.open_classes.pop(); + } + } + let mut class_info = ClassInfo::new(new_class, closing_tag); + if closing_tag.is_none() { + if matches!(new_class, Class::Decoration(_) | Class::Original) { + // Even if a whitespace characters follows, we need to open the class right away + // as these characters are part of the element. + // FIXME: Should we instead add a new boolean field to `ClassInfo` to force a + // non-open tag to be added if another one comes before it's open? + write!(out, "", new_class.as_html()).unwrap(); + class_info.closing_tag = Some(""); + } else if new_class.get_span().is_some() + && let Some(closing_tag) = + string_without_closing_tag(out, "", Some(class_info.class), href_context, false) + && !closing_tag.is_empty() + { + class_info.closing_tag = Some(closing_tag); + } + } + + self.open_classes.push(class_info); + } + + /// This sets the `pending_exit` field to `true`. Meaning that if we try to push another stack + /// which is not compatible with this one, it will exit the current one before adding the new + /// one. + fn exit_elem(&mut self) { + let current_class = + self.open_classes.last_mut().expect("`exit_elem` called on empty class stack"); + if !current_class.pending_exit { + current_class.pending_exit = true; + return; + } + // If the current class was already closed, it means we are actually closing its parent. + self.open_classes.pop(); + let current_class = + self.open_classes.last_mut().expect("`exit_elem` called on empty class stack parent"); + current_class.pending_exit = true; + } + + fn last_class(&self) -> Option { + self.open_classes.last().map(|c| c.class) + } + + fn last_class_is_open(&self) -> bool { + if let Some(last) = self.open_classes.last() { + last.is_open() + } else { + // If there is no class, then it's already open. + true + } + } + + fn close_last_if_needed(&mut self, out: &mut W) { + if let Some(last) = self.open_classes.pop_if(|class| class.pending_exit && class.is_open()) + { + last.close_tag(out); + } + } + + fn push( + &mut self, + out: &mut W, + href_context: &Option>, + class: Option, + text: Cow<'_, str>, + needs_escape: bool, + ) { + // If the new token cannot be merged with the currently open `Class`, we close the `Class` + // if possible. + if !can_merge(self.last_class(), class, &text) { + self.close_last_if_needed(out) + } + + let current_class = self.last_class(); + + // If we have a `Class` that hasn't been "open" yet (ie, we received only an `EnterSpan` + // event), we need to open the `Class` before going any further so the new token will be + // written inside it. + if class.is_none() && !self.last_class_is_open() { + if let Some(current_class_info) = self.open_classes.last_mut() { + let class_s = current_class_info.class.as_html(); + if !class_s.is_empty() { + write!(out, "").unwrap(); + } + current_class_info.closing_tag = Some(""); + } + } + + let current_class_is_open = self.open_classes.last().is_some_and(|c| c.is_open()); + let can_merge = can_merge(class, current_class, &text); + let should_open_tag = !current_class_is_open || !can_merge; + + let text = + if needs_escape { Either::Left(&EscapeBodyText(&text)) } else { Either::Right(text) }; + + let closing_tag = + string_without_closing_tag(out, &text, class, href_context, should_open_tag); + if class.is_some() && should_open_tag && closing_tag.is_none() { + panic!( + "called `string_without_closing_tag` with a class but no closing tag was returned" + ); + } else if let Some(closing_tag) = closing_tag + && !closing_tag.is_empty() + { + // If this is a link, we need to close it right away and not open a new `Class`, + // otherwise extra content would go into the `` HTML tag. + if closing_tag == "" { + out.write_str(closing_tag).unwrap(); + // If the current `Class` is not compatible with this one, we create a new `Class`. + } else if let Some(class) = class + && !can_merge + { + self.enter_elem(out, href_context, class, Some("")); + // Otherwise, we consider the actual `Class` to have been open. + } else if let Some(current_class_info) = self.open_classes.last_mut() { + current_class_info.closing_tag = Some(""); + } + } + } + + /// This method closes all open tags and returns the list of `Class` which were not already + /// closed (ie `pending_exit` set to `true`). + /// + /// It is used when starting a macro expansion: we need to close all HTML tags and then to + /// reopen them inside the newly created expansion HTML tag. Same goes when we close the + /// expansion. + fn empty_stack(&mut self, out: &mut W) -> Vec { + let mut classes = Vec::with_capacity(self.open_classes.len()); + + // We close all open tags and only keep the ones that were not already waiting to be closed. + while let Some(class_info) = self.open_classes.pop() { + class_info.close_tag(out); + if !class_info.pending_exit { + classes.push(class_info.class); + } + } + classes + } +} + /// This type is used as a conveniency to prevent having to pass all its fields as arguments into /// the various functions (which became its methods). struct TokenHandler<'a, 'tcx, F: Write> { out: &'a mut F, - /// It contains the closing tag and the associated `Class`. - closing_tags: Vec<(&'static str, Class)>, - /// This is used because we don't automatically generate the closing tag on `ExitSpan` in - /// case an `EnterSpan` event with the same class follows. - pending_exit_span: Option, - /// `current_class` and `pending_elems` are used to group HTML elements with same `class` - /// attributes to reduce the DOM size. - current_class: Option, + class_stack: ClassStack, /// We need to keep the `Class` for each element because it could contain a `Span` which is /// used to generate links. - pending_elems: Vec<(Cow<'a, str>, Option)>, href_context: Option>, - write_line_number: fn(&mut F, u32, &'static str), + write_line_number: fn(u32) -> String, + line: u32, + max_lines: u32, } impl std::fmt::Debug for TokenHandler<'_, '_, F> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TokenHandler") - .field("closing_tags", &self.closing_tags) - .field("pending_exit_span", &self.pending_exit_span) - .field("current_class", &self.current_class) - .field("pending_elems", &self.pending_elems) - .finish() + f.debug_struct("TokenHandler").field("class_stack", &self.class_stack).finish() } } -impl TokenHandler<'_, '_, F> { - fn handle_exit_span(&mut self) { - // We can't get the last `closing_tags` element using `pop()` because `closing_tags` is - // being used in `write_pending_elems`. - let class = self.closing_tags.last().expect("ExitSpan without EnterSpan").1; - // We flush everything just in case... - self.write_pending_elems(Some(class)); +impl<'a, F: Write> TokenHandler<'a, '_, F> { + fn handle_backline(&mut self) -> Option { + self.line += 1; + if self.line < self.max_lines { + return Some((self.write_line_number)(self.line)); + } + None + } - exit_span(self.out, self.closing_tags.pop().expect("ExitSpan without EnterSpan").0); - self.pending_exit_span = None; + fn push_token_without_backline_check( + &mut self, + class: Option, + text: Cow<'a, str>, + needs_escape: bool, + ) { + self.class_stack.push(self.out, &self.href_context, class, text, needs_escape); } - /// Write all the pending elements sharing a same (or at mergeable) `Class`. - /// - /// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged - /// with the elements' class, then we simply write the elements since the `ExitSpan` event will - /// close the tag. - /// - /// Otherwise, if there is only one pending element, we let the `string` function handle both - /// opening and closing the tag, otherwise we do it into this function. - /// - /// It returns `true` if `current_class` must be set to `None` afterwards. - fn write_pending_elems(&mut self, current_class: Option) -> bool { - if self.pending_elems.is_empty() { - return false; - } - if let Some((_, parent_class)) = self.closing_tags.last() - && can_merge(current_class, Some(*parent_class), "") + fn push_token(&mut self, class: Option, text: Cow<'a, str>) { + if text == "\n" + && let Some(backline) = self.handle_backline() { - for (text, class) in self.pending_elems.iter() { - string( - self.out, - EscapeBodyText(text), - *class, - &self.href_context, - false, - self.write_line_number, - ); - } + self.out.write_str(&text).unwrap(); + self.out.write_str(&backline).unwrap(); } else { - // We only want to "open" the tag ourselves if we have more than one pending and if the - // current parent tag is not the same as our pending content. - let close_tag = if self.pending_elems.len() > 1 - && let Some(current_class) = current_class - // `PreludeTy` can never include more than an ident so it should not generate - // a wrapping `span`. - && !matches!(current_class, Class::PreludeTy(_)) - { - Some(enter_span(self.out, current_class, &self.href_context)) - } else { - None - }; - // To prevent opening a macro expansion span being closed right away because - // the currently open item is replaced by a new class. - let last_pending = - self.pending_elems.pop_if(|(_, class)| *class == Some(Class::Expansion)); - for (text, class) in self.pending_elems.iter() { - string( - self.out, - EscapeBodyText(text), - *class, - &self.href_context, - close_tag.is_none(), - self.write_line_number, - ); - } - if let Some(close_tag) = close_tag { - exit_span(self.out, close_tag); - } - if let Some((text, class)) = last_pending { - string( - self.out, - EscapeBodyText(&text), - class, - &self.href_context, - close_tag.is_none(), - self.write_line_number, - ); - } + self.push_token_without_backline_check(class, text, true); + } + } + + fn start_expansion(&mut self) { + // We close all open tags. + let classes = self.class_stack.empty_stack(self.out); + + // We start the expansion tag. + self.class_stack.enter_elem(self.out, &self.href_context, Class::Expansion, None); + self.push_token_without_backline_check( + Some(Class::Expansion), + Cow::Owned(format!( + "", + self.line, + )), + false, + ); + + // We re-open all tags that didn't have `pending_exit` set to `true`. + for class in classes.into_iter().rev() { + self.class_stack.enter_elem(self.out, &self.href_context, class, None); } - self.pending_elems.clear(); - true } - #[inline] - fn write_line_number(&mut self, line: u32, extra: &'static str) { - (self.write_line_number)(self.out, line, extra); + fn add_expanded_code(&mut self, expanded_code: &ExpandedCode) { + self.push_token_without_backline_check( + None, + Cow::Owned(format!("{}", expanded_code.code)), + false, + ); + self.class_stack.enter_elem(self.out, &self.href_context, Class::Original, None); + } + + fn close_expansion(&mut self) { + // We close all open tags. + let classes = self.class_stack.empty_stack(self.out); + + // We re-open all tags without expansion-related ones. + for class in classes.into_iter().rev() { + if !matches!(class, Class::Expansion | Class::Original) { + self.class_stack.enter_elem(self.out, &self.href_context, class, None); + } + } } } impl Drop for TokenHandler<'_, '_, F> { /// When leaving, we need to flush all pending data to not have missing content. fn drop(&mut self) { - if self.pending_exit_span.is_some() { - self.handle_exit_span(); - } else { - self.write_pending_elems(self.current_class); - } + self.class_stack.empty_stack(self.out); } } -fn write_scraped_line_number(out: &mut impl Write, line: u32, extra: &'static str) { +fn scraped_line_number(line: u32) -> String { // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr // Do not show "1 2 3 4 5 ..." in web search results. - write!(out, "{extra}{line}",).unwrap(); + format!("{line}") } -fn write_line_number(out: &mut impl Write, line: u32, extra: &'static str) { +fn line_number(line: u32) -> String { // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr // Do not show "1 2 3 4 5 ..." in web search results. - write!(out, "{extra}{line}",).unwrap(); + format!("{line}") } -fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) { - out.write_str(extra).unwrap(); +fn empty_line_number(_: u32) -> String { + String::new() } fn get_next_expansion( @@ -292,80 +464,24 @@ fn get_next_expansion( fn get_expansion<'a, W: Write>( token_handler: &mut TokenHandler<'_, '_, W>, expanded_codes: &'a [ExpandedCode], - line: u32, span: Span, ) -> Option<&'a ExpandedCode> { - if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) { - let (closing, reopening) = if let Some(current_class) = token_handler.current_class - && let class = current_class.as_html() - && !class.is_empty() - { - ("", format!("")) - } else { - ("", String::new()) - }; - let id = format!("expand-{line}"); - token_handler.pending_elems.push(( - Cow::Owned(format!( - "{closing}\ -\ - {reopening}", - )), - Some(Class::Expansion), - )); - Some(expanded_code) - } else { - None - } -} - -fn start_expansion(out: &mut Vec<(Cow<'_, str>, Option)>, expanded_code: &ExpandedCode) { - out.push(( - Cow::Owned(format!( - "{}", - expanded_code.code, - )), - Some(Class::Expansion), - )); + let expanded_code = get_next_expansion(expanded_codes, token_handler.line, span)?; + token_handler.start_expansion(); + Some(expanded_code) } fn end_expansion<'a, W: Write>( token_handler: &mut TokenHandler<'_, '_, W>, expanded_codes: &'a [ExpandedCode], - expansion_start_tags: &[(&'static str, Class)], - line: u32, span: Span, ) -> Option<&'a ExpandedCode> { - if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) { - // We close the current "original" content. - token_handler.pending_elems.push((Cow::Borrowed(""), Some(Class::Expansion))); - return Some(expanded_code); - } - - let skip = iter::zip(token_handler.closing_tags.as_slice(), expansion_start_tags) - .position(|(tag, start_tag)| tag != start_tag) - .unwrap_or_else(|| cmp::min(token_handler.closing_tags.len(), expansion_start_tags.len())); - - let tags = iter::chain( - expansion_start_tags.iter().skip(skip), - token_handler.closing_tags.iter().skip(skip), - ); - - let mut elem = Cow::Borrowed(""); - - for (tag, _) in tags.clone() { - elem.to_mut().push_str(tag); - } - for (_, class) in tags { - write!(elem.to_mut(), "", class.as_html()).unwrap(); + token_handler.class_stack.exit_elem(); + let expansion = get_next_expansion(expanded_codes, token_handler.line, span); + if expansion.is_none() { + token_handler.close_expansion(); } - - token_handler.pending_elems.push((elem, Some(Class::Expansion))); - None + expansion } #[derive(Clone, Copy)] @@ -417,29 +533,29 @@ pub(super) fn write_code( if src.contains('\r') { src.replace("\r\n", "\n").into() } else { Cow::Borrowed(src) }; let mut token_handler = TokenHandler { out, - closing_tags: Vec::new(), - pending_exit_span: None, - current_class: None, - pending_elems: Vec::with_capacity(20), href_context, write_line_number: match line_info { Some(line_info) => { if line_info.is_scraped_example { - write_scraped_line_number + scraped_line_number } else { - write_line_number + line_number } } None => empty_line_number, }, + line: 0, + max_lines: u32::MAX, + class_stack: ClassStack::new(), }; - let (mut line, max_lines) = if let Some(line_info) = line_info { - token_handler.write_line_number(line_info.start_line, ""); - (line_info.start_line, line_info.max_lines) - } else { - (0, u32::MAX) - }; + if let Some(line_info) = line_info { + token_handler.line = line_info.start_line - 1; + token_handler.max_lines = line_info.max_lines; + if let Some(text) = token_handler.handle_backline() { + token_handler.push_token_without_backline_check(None, Cow::Owned(text), false); + } + } let (expanded_codes, file_span) = match token_handler.href_context.as_ref().and_then(|c| { let expanded_codes = c.context.shared.expanded_codes.get(&c.file_span.lo())?; @@ -448,114 +564,53 @@ pub(super) fn write_code( Some((expanded_codes, file_span)) => (expanded_codes.as_slice(), file_span), None => (&[] as &[ExpandedCode], DUMMY_SP), }; - let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, line, file_span); - token_handler.write_pending_elems(None); - let mut expansion_start_tags = Vec::new(); + let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, file_span); Classifier::new( &src, token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP), decoration_info, ) - .highlight(&mut |span, highlight| { - match highlight { - Highlight::Token { text, class } => { - // If we received a `ExitSpan` event and then have a non-compatible `Class`, we - // need to close the ``. - let need_current_class_update = if let Some(pending) = - token_handler.pending_exit_span - && !can_merge(Some(pending), class, text) - { - token_handler.handle_exit_span(); - true - // If the two `Class` are different, time to flush the current content and start - // a new one. - } else if !can_merge(token_handler.current_class, class, text) { - token_handler.write_pending_elems(token_handler.current_class); - true - } else { - token_handler.current_class.is_none() - }; + .highlight(&mut |span, highlight| match highlight { + Highlight::Token { text, class } => { + token_handler.push_token(class, Cow::Borrowed(text)); - if need_current_class_update { - token_handler.current_class = class.map(Class::dummy); + if text == "\n" { + if current_expansion.is_none() { + current_expansion = get_expansion(&mut token_handler, expanded_codes, span); } - if text == "\n" { - line += 1; - if line < max_lines { - token_handler - .pending_elems - .push((Cow::Borrowed(text), Some(Class::Backline(line)))); - } - if current_expansion.is_none() { - current_expansion = - get_expansion(&mut token_handler, expanded_codes, line, span); - expansion_start_tags = token_handler.closing_tags.clone(); - } - if let Some(ref current_expansion) = current_expansion - && current_expansion.span.lo() == span.hi() - { - start_expansion(&mut token_handler.pending_elems, current_expansion); - } - } else { - token_handler.pending_elems.push((Cow::Borrowed(text), class)); - - let mut need_end = false; - if let Some(ref current_expansion) = current_expansion { - if current_expansion.span.lo() == span.hi() { - start_expansion(&mut token_handler.pending_elems, current_expansion); - } else if current_expansion.end_line == line - && span.hi() >= current_expansion.span.hi() - { - need_end = true; - } - } - if need_end { - current_expansion = end_expansion( - &mut token_handler, - expanded_codes, - &expansion_start_tags, - line, - span, - ); - } + if let Some(ref current_expansion) = current_expansion + && current_expansion.span.lo() == span.hi() + { + token_handler.add_expanded_code(current_expansion); } - } - Highlight::EnterSpan { class } => { - let mut should_add = true; - if let Some(pending_exit_span) = token_handler.pending_exit_span { - if class.is_equal_to(pending_exit_span) { - should_add = false; - } else { - token_handler.handle_exit_span(); - } - } else { - // We flush everything just in case... - if token_handler.write_pending_elems(token_handler.current_class) { - token_handler.current_class = None; + } else { + let mut need_end = false; + if let Some(ref current_expansion) = current_expansion { + if current_expansion.span.lo() == span.hi() { + token_handler.add_expanded_code(current_expansion); + } else if current_expansion.end_line == token_handler.line + && span.hi() >= current_expansion.span.hi() + { + need_end = true; } } - if should_add { - let closing_tag = - enter_span(token_handler.out, class, &token_handler.href_context); - token_handler.closing_tags.push((closing_tag, class)); + if need_end { + current_expansion = end_expansion(&mut token_handler, expanded_codes, span); } - - token_handler.current_class = None; - token_handler.pending_exit_span = None; } - Highlight::ExitSpan => { - token_handler.current_class = None; - token_handler.pending_exit_span = Some( - token_handler - .closing_tags - .last() - .as_ref() - .expect("ExitSpan without EnterSpan") - .1, - ); - } - }; + } + Highlight::EnterSpan { class } => { + token_handler.class_stack.enter_elem( + token_handler.out, + &token_handler.href_context, + class, + None, + ); + } + Highlight::ExitSpan => { + token_handler.class_stack.exit_elem(); + } }); } @@ -585,9 +640,10 @@ enum Class { PreludeVal(Span), QuestionMark, Decoration(&'static str), - Backline(u32), /// Macro expansion. Expansion, + /// "original" code without macro expansion. + Original, } impl Class { @@ -605,17 +661,6 @@ impl Class { } } - /// If `self` contains a `Span`, it'll be replaced with `DUMMY_SP` to prevent creating links - /// on "empty content" (because of the attributes merge). - fn dummy(self) -> Self { - match self { - Self::Self_(_) => Self::Self_(DUMMY_SP), - Self::Macro(_) => Self::Macro(DUMMY_SP), - Self::Ident(_) => Self::Ident(DUMMY_SP), - s => s, - } - } - /// Returns the css class expected by rustdoc for each `Class`. fn as_html(self) -> &'static str { match self { @@ -636,8 +681,8 @@ impl Class { Class::PreludeVal(_) => "prelude-val", Class::QuestionMark => "question-mark", Class::Decoration(kind) => kind, - Class::Backline(_) => "", - Class::Expansion => "", + Class::Expansion => "expansion", + Class::Original => "original", } } @@ -662,12 +707,22 @@ impl Class { | Self::Lifetime | Self::QuestionMark | Self::Decoration(_) - | Self::Backline(_) + | Self::Original | Self::Expansion => None, } } } +impl fmt::Display for Class { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let html = self.as_html(); + if html.is_empty() { + return Ok(()); + } + write!(f, " class=\"{html}\"") + } +} + #[derive(Debug)] enum Highlight<'a> { Token { text: &'a str, class: Option }, @@ -745,12 +800,15 @@ impl<'a> PeekIter<'a> { None } } + + fn stop_peeking(&mut self) { + self.peek_pos = 0; + } } impl<'a> Iterator for PeekIter<'a> { type Item = (TokenKind, &'a str); fn next(&mut self) -> Option { - self.peek_pos = 0; if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() } } } @@ -1130,31 +1188,35 @@ impl<'src> Classifier<'src> { LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number, }, TokenKind::GuardedStrPrefix => return no_highlight(sink), - TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => { + TokenKind::Ident | TokenKind::RawIdent + if self.peek_non_whitespace() == Some(TokenKind::Bang) => + { self.in_macro = true; let span = new_span(before, text, file_span); sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) }); sink(span, Highlight::Token { text, class: None }); return; } - TokenKind::Ident => match get_real_ident_class(text, false) { - None => match text { - "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)), - "Some" | "None" | "Ok" | "Err" => { - Class::PreludeVal(new_span(before, text, file_span)) - } - // "union" is a weak keyword and is only considered as a keyword when declaring - // a union type. - "union" if self.check_if_is_union_keyword() => Class::KeyWord, - _ if self.in_macro_nonterminal => { - self.in_macro_nonterminal = false; - Class::MacroNonTerminal - } - "self" | "Self" => Class::Self_(new_span(before, text, file_span)), - _ => Class::Ident(new_span(before, text, file_span)), - }, - Some(c) => c, - }, + TokenKind::Ident => { + match get_real_ident_class(text, false) { + None => match text { + "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)), + "Some" | "None" | "Ok" | "Err" => { + Class::PreludeVal(new_span(before, text, file_span)) + } + // "union" is a weak keyword and is only considered as a keyword when declaring + // a union type. + "union" if self.check_if_is_union_keyword() => Class::KeyWord, + _ if self.in_macro_nonterminal => { + self.in_macro_nonterminal = false; + Class::MacroNonTerminal + } + "self" | "Self" => Class::Self_(new_span(before, text, file_span)), + _ => Class::Ident(new_span(before, text, file_span)), + }, + Some(c) => c, + } + } TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => { Class::Ident(new_span(before, text, file_span)) } @@ -1179,68 +1241,20 @@ impl<'src> Classifier<'src> { self.tokens.peek().map(|(token_kind, _text)| *token_kind) } - fn check_if_is_union_keyword(&mut self) -> bool { - while let Some(kind) = self.tokens.peek_next().map(|(token_kind, _text)| token_kind) { - if *kind == TokenKind::Whitespace { - continue; + fn peek_non_whitespace(&mut self) -> Option { + while let Some((token_kind, _)) = self.tokens.peek_next() { + if *token_kind != TokenKind::Whitespace { + let token_kind = *token_kind; + self.tokens.stop_peeking(); + return Some(token_kind); } - return *kind == TokenKind::Ident; } - false + self.tokens.stop_peeking(); + None } -} - -/// Called when we start processing a span of text that should be highlighted. -/// The `Class` argument specifies how it should be highlighted. -fn enter_span( - out: &mut impl Write, - klass: Class, - href_context: &Option>, -) -> &'static str { - string_without_closing_tag(out, "", Some(klass), href_context, true).expect( - "internal error: enter_span was called with Some(klass) but did not return a \ - closing HTML tag", - ) -} -/// Called at the end of a span of highlighted text. -fn exit_span(out: &mut impl Write, closing_tag: &str) { - out.write_str(closing_tag).unwrap(); -} - -/// Called for a span of text. If the text should be highlighted differently -/// from the surrounding text, then the `Class` argument will be a value other -/// than `None`. -/// -/// The following sequences of callbacks are equivalent: -/// ```plain -/// enter_span(Foo), string("text", None), exit_span() -/// string("text", Foo) -/// ``` -/// -/// The latter can be thought of as a shorthand for the former, which is more -/// flexible. -/// -/// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function -/// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then -/// generate a link for this element (which corresponds to where its definition is located). -fn string( - out: &mut W, - text: EscapeBodyText<'_>, - klass: Option, - href_context: &Option>, - open_tag: bool, - write_line_number_callback: fn(&mut W, u32, &'static str), -) { - if let Some(Class::Backline(line)) = klass { - write_line_number_callback(out, line, "\n"); - } else if let Some(Class::Expansion) = klass { - // This has already been escaped so we get the text to write it directly. - out.write_str(text.0).unwrap(); - } else if let Some(closing_tag) = - string_without_closing_tag(out, text, klass, href_context, open_tag) - { - out.write_str(closing_tag).unwrap(); + fn check_if_is_union_keyword(&mut self) -> bool { + self.peek_non_whitespace().is_some_and(|kind| kind == TokenKind::Ident) } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 5f72064f0a8ce..820b2392e07ce 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -6,6 +6,7 @@ #![feature(ascii_char)] #![feature(ascii_char_variants)] #![feature(assert_matches)] +#![feature(box_into_inner)] #![feature(box_patterns)] #![feature(debug_closure_helpers)] #![feature(file_buffered)]