Skip to main content

wdl_format/token/
post.rs

1//! Postprocessed tokens.
2//!
3//! Generally speaking, unless you are working with the internals of code
4//! formatting, you're not going to be working with these.
5
6use std::collections::HashMap;
7use std::fmt::Display;
8use std::rc::Rc;
9
10use wdl_ast::DIRECTIVE_COMMENT_PREFIX;
11use wdl_ast::DIRECTIVE_DELIMITER;
12use wdl_ast::DOC_COMMENT_PREFIX;
13use wdl_ast::Directive;
14use wdl_ast::SyntaxKind;
15
16use crate::Comment;
17use crate::Config;
18use crate::Indent;
19use crate::PreToken;
20use crate::SPACE;
21use crate::Token;
22use crate::TokenStream;
23use crate::Trivia;
24use crate::TriviaBlankLineSpacingPolicy;
25
26/// [`PostToken`]s that precede an inline comment.
27const INLINE_COMMENT_PRECEDING_TOKENS: [PostToken; 2] = [PostToken::Space, PostToken::Space];
28
29/// A postprocessed token.
30#[derive(Clone, Eq, PartialEq)]
31pub enum PostToken {
32    /// A space.
33    Space,
34
35    /// A newline.
36    Newline,
37
38    /// One indentation.
39    Indent,
40
41    /// A temporary indent.
42    ///
43    /// This is added after a [`PostToken::Indent`] during the formatting of
44    /// command sections.
45    TempIndent(Rc<String>),
46
47    /// A string literal.
48    Literal(Rc<String>),
49
50    /// A doc comment block.
51    Documentation {
52        /// The current indent level.
53        num_indents: usize,
54        /// The contents of the doc comment block.
55        contents: Rc<String>,
56    },
57
58    /// A directive comment.
59    Directive {
60        /// The current indent level.
61        num_indents: usize,
62        /// The directive.
63        directive: Rc<Directive>,
64    },
65}
66
67impl std::fmt::Debug for PostToken {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            Self::Space => write!(f, "<SPACE>"),
71            Self::Newline => write!(f, "<NEWLINE>"),
72            Self::Indent => write!(f, "<INDENT>"),
73            Self::TempIndent(value) => write!(f, "<TEMP_INDENT@{value}>"),
74            Self::Literal(value) => write!(f, "<LITERAL@{value}>"),
75            Self::Directive { directive, .. } => write!(f, "<DIRECTIVE@{directive:?}>"),
76            Self::Documentation { contents, .. } => write!(f, "<DOCUMENTATION@{contents}>"),
77        }
78    }
79}
80
81impl Token for PostToken {
82    /// Returns a displayable version of the token.
83    fn display<'a>(&'a self, config: &'a Config) -> impl Display + 'a {
84        /// A displayable version of a [`PostToken`].
85        struct Display<'a> {
86            /// The token to display.
87            token: &'a PostToken,
88            /// The configuration to use.
89            config: &'a Config,
90        }
91
92        fn write_indents(
93            f: &mut std::fmt::Formatter<'_>,
94            indent: &Indent,
95            num_indents: usize,
96        ) -> std::fmt::Result {
97            for _ in 0usize..num_indents {
98                write!(f, "{indent}")?;
99            }
100            Ok(())
101        }
102
103        impl std::fmt::Display for Display<'_> {
104            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105                match self.token {
106                    PostToken::Space => write!(f, "{SPACE}"),
107                    PostToken::Newline => write!(f, "{}", self.config.newline_style.as_str()),
108                    PostToken::Indent => {
109                        write!(f, "{indent}", indent = self.config.indent)
110                    }
111                    PostToken::TempIndent(value) => write!(f, "{value}"),
112                    PostToken::Literal(value) => write!(f, "{value}"),
113                    PostToken::Documentation {
114                        num_indents,
115                        contents: markdown,
116                    } => {
117                        let prefix = DOC_COMMENT_PREFIX;
118                        write!(f, "{prefix}")?;
119                        let mut lines = markdown.lines().peekable();
120                        while let Some(cur) = lines.next() {
121                            write!(f, "{cur}")?;
122                            if lines.peek().is_some() {
123                                write!(f, "{}", self.config.newline_style.as_str())?;
124                                write_indents(f, &self.config.indent, *num_indents)?;
125                                write!(f, "{prefix}")?;
126                            }
127                        }
128                        Ok(())
129                    }
130                    PostToken::Directive {
131                        num_indents,
132                        directive,
133                    } => {
134                        let mut prefix = format!("{} ", DIRECTIVE_COMMENT_PREFIX);
135                        match &**directive {
136                            Directive::Except(exceptions) => {
137                                prefix.push_str("except");
138                                prefix.push_str(DIRECTIVE_DELIMITER);
139                                prefix.push(' ');
140                                let mut rules: Vec<String> =
141                                    exceptions.iter().cloned().map(|e| e.name).collect();
142                                rules.sort();
143                                write!(f, "{prefix}")?;
144                                if let Some(max) = self.config.max_line_length.get() {
145                                    let indent_width = self.config.indent.num() * num_indents;
146                                    let start_width = indent_width + prefix.len();
147                                    let mut remaining = max.saturating_sub(start_width);
148                                    let mut written_to_cur_line = 0usize;
149                                    for rule in rules {
150                                        let cur_len = rule.len();
151                                        if written_to_cur_line == 0 {
152                                            write!(f, "{rule}")?;
153                                            remaining = remaining.saturating_sub(cur_len);
154                                            written_to_cur_line += 1;
155                                        } else if remaining.saturating_sub(cur_len + 2) > 0 {
156                                            // NOTE: the `+ 2` accounts for
157                                            // the `", "` separator written
158                                            // before each subsequent rule.
159                                            write!(f, ", {rule}")?;
160                                            remaining = remaining.saturating_sub(cur_len + 2);
161                                            written_to_cur_line += 1;
162                                        } else {
163                                            // Current rule does not fit
164                                            write!(f, "{}", self.config.newline_style.as_str())?;
165                                            write_indents(f, &self.config.indent, *num_indents)?;
166                                            write!(f, "{prefix}{rule}")?;
167                                            written_to_cur_line = 1;
168                                            remaining = max.saturating_sub(start_width + cur_len);
169                                        }
170                                    }
171                                    Ok(())
172                                } else {
173                                    write!(f, "{rules}", rules = rules.join(", "))
174                                }
175                            }
176                        }
177                    }
178                }
179            }
180        }
181
182        Display {
183            token: self,
184            config,
185        }
186    }
187}
188
189impl PostToken {
190    /// Gets the width of the [`PostToken`].
191    ///
192    /// This is used to determine how much space the token takes up _within a
193    /// single line_ for the purposes of respecting the maximum line length.
194    /// As such, newlines are considered zero-width tokens. Similarly, doc
195    /// comments and directive comments are considered zero-width as they always
196    /// appear on their own lines.
197    fn width(&self, config: &crate::Config) -> usize {
198        match self {
199            Self::Space => SPACE.len(), // 1 character
200            Self::Newline => 0,
201            Self::Indent => config.indent.num(),
202            Self::TempIndent(value) => value.len(),
203            Self::Literal(value) => value.len(),
204            Self::Directive { .. } => 0,
205            Self::Documentation { .. } => 0,
206        }
207    }
208}
209
210impl TokenStream<PostToken> {
211    /// Gets the maximum width of the [`TokenStream`].
212    ///
213    /// This is suitable to call if the stream represents multiple lines.
214    fn max_width(&self, config: &Config) -> usize {
215        let mut max: usize = 0;
216        let mut cur_width: usize = 0;
217        for token in self.iter() {
218            cur_width += token.width(config);
219            if token == &PostToken::Newline {
220                max = max.max(cur_width);
221                cur_width = 0;
222            }
223        }
224        max.max(cur_width)
225    }
226
227    /// Gets the width of the last line of the [`TokenStream`].
228    fn last_line_width(&self, config: &Config) -> usize {
229        let mut width = 0;
230        for token in self.iter().rev() {
231            if token == &PostToken::Newline {
232                break;
233            }
234            width += token.width(config);
235        }
236        width
237    }
238}
239
240/// A line break.
241enum LineBreak {
242    /// A line break that can be inserted before a token.
243    Before,
244    /// A line break that can be inserted after a token.
245    After,
246}
247
248/// Returns whether a token can be line broken.
249fn can_be_line_broken(kind: SyntaxKind) -> Option<LineBreak> {
250    match kind {
251        SyntaxKind::CloseBrace
252        | SyntaxKind::CloseBracket
253        | SyntaxKind::CloseParen
254        | SyntaxKind::CloseHeredoc
255        | SyntaxKind::Assignment
256        | SyntaxKind::Plus
257        | SyntaxKind::Minus
258        | SyntaxKind::Asterisk
259        | SyntaxKind::Slash
260        | SyntaxKind::Percent
261        | SyntaxKind::Exponentiation
262        | SyntaxKind::Equal
263        | SyntaxKind::NotEqual
264        | SyntaxKind::Less
265        | SyntaxKind::LessEqual
266        | SyntaxKind::Greater
267        | SyntaxKind::GreaterEqual
268        | SyntaxKind::LogicalAnd
269        | SyntaxKind::LogicalOr
270        | SyntaxKind::AfterKeyword
271        | SyntaxKind::AsKeyword
272        | SyntaxKind::IfKeyword
273        | SyntaxKind::ElseKeyword
274        | SyntaxKind::ThenKeyword => Some(LineBreak::Before),
275        SyntaxKind::OpenBrace
276        | SyntaxKind::OpenBracket
277        | SyntaxKind::OpenParen
278        | SyntaxKind::OpenHeredoc
279        | SyntaxKind::Colon
280        | SyntaxKind::PlaceholderOpen
281        | SyntaxKind::Comma => Some(LineBreak::After),
282        _ => None,
283    }
284}
285
286/// Gets the corresponding [`SyntaxKind`] that should be line broken in tandem
287/// with the provided [`SyntaxKind`].
288fn tandem_line_break(kind: SyntaxKind) -> Option<SyntaxKind> {
289    match kind {
290        SyntaxKind::OpenBrace => Some(SyntaxKind::CloseBrace),
291        SyntaxKind::OpenBracket => Some(SyntaxKind::CloseBracket),
292        SyntaxKind::OpenParen => Some(SyntaxKind::CloseParen),
293        SyntaxKind::OpenHeredoc => Some(SyntaxKind::CloseHeredoc),
294        SyntaxKind::PlaceholderOpen => Some(SyntaxKind::CloseBrace),
295        _ => None,
296    }
297}
298
299/// Tokens that should be allowed to be interrupted.
300///
301/// If one of these tokens comes after an interruption, the typical extra level
302/// of indentation that appears after interrupts won't be inserted.
303fn allow_interruption(kind: SyntaxKind) -> bool {
304    matches!(
305        kind,
306        SyntaxKind::OpenBrace
307            | SyntaxKind::OpenBracket
308            | SyntaxKind::OpenParen
309            | SyntaxKind::OpenHeredoc
310            | SyntaxKind::CloseBrace
311            | SyntaxKind::CloseBracket
312            | SyntaxKind::CloseParen
313            | SyntaxKind::CloseHeredoc
314            | SyntaxKind::IfKeyword
315            | SyntaxKind::ElseKeyword
316    )
317}
318
319/// Tracks a tandem break.
320struct TandemBreak {
321    /// The [`SyntaxKind`] which opened this tandem break.
322    pub open: SyntaxKind,
323    /// The [`SyntaxKind`] which will close this tandem break.
324    pub close: SyntaxKind,
325    /// Token depth since opening the break.
326    ///
327    /// The close break is only added when `depth == 0`.
328    /// This is incremented by one for every token matching `open` after the
329    /// break is initiated. It is decremented by one for every token
330    /// matching `close` after the break is initiated.
331    pub depth: usize,
332}
333
334/// Current position in a line.
335#[derive(Default, Eq, PartialEq)]
336enum LinePosition {
337    /// The start of a line.
338    #[default]
339    StartOfLine,
340
341    /// The middle of a line.
342    MiddleOfLine,
343}
344
345/// A postprocessor of [tokens](PreToken).
346#[derive(Default)]
347pub struct Postprocessor {
348    /// The current position in the line.
349    position: LinePosition,
350
351    /// The current indentation level.
352    indent_level: usize,
353
354    /// Whether the current line has been interrupted by trivia.
355    interrupted: bool,
356
357    /// The current trivial blank line spacing policy.
358    line_spacing_policy: TriviaBlankLineSpacingPolicy,
359
360    /// Temporary indentation to add.
361    temp_indent: Option<Rc<String>>,
362}
363
364impl Postprocessor {
365    /// Runs the postprocessor.
366    pub fn run(&mut self, input: TokenStream<PreToken>, config: &Config) -> TokenStream<PostToken> {
367        let mut output = TokenStream::<PostToken>::default();
368        let mut buffer = TokenStream::<PreToken>::default();
369
370        for token in input {
371            match token {
372                PreToken::LineEnd => {
373                    self.flush(&buffer, &mut output, config);
374                    self.trim_whitespace(&mut output);
375                    output.push(PostToken::Newline);
376
377                    buffer.clear();
378                    self.interrupted = false;
379                    self.position = LinePosition::StartOfLine;
380                }
381                _ => {
382                    buffer.push(token);
383                }
384            }
385        }
386
387        output
388    }
389
390    /// Takes a step of a [`PreToken`] stream and processes the appropriate
391    /// [`PostToken`]s.
392    fn step(
393        &mut self,
394        token: PreToken,
395        next: Option<&PreToken>,
396        stream: &mut TokenStream<PostToken>,
397    ) {
398        if stream.is_empty() {
399            self.interrupted = false;
400            self.position = LinePosition::StartOfLine;
401            self.indent(stream);
402        }
403        match token {
404            PreToken::BlankLine => {
405                self.blank_line(stream);
406            }
407            PreToken::LineEnd => {
408                self.interrupted = false;
409                self.end_line(stream);
410            }
411            PreToken::WordEnd => {
412                stream.trim_end(&PostToken::Space);
413
414                if self.position == LinePosition::MiddleOfLine {
415                    stream.push(PostToken::Space);
416                } else {
417                    // We're at the start of a line, so we don't need to add a
418                    // space.
419                }
420            }
421            PreToken::IndentStart => {
422                self.indent_level += 1;
423                self.end_line(stream);
424            }
425            PreToken::IndentEnd => {
426                self.indent_level = self.indent_level.saturating_sub(1);
427                self.end_line(stream);
428            }
429            PreToken::LineSpacingPolicy(policy) => {
430                self.line_spacing_policy = policy;
431            }
432            PreToken::Literal(value, kind) => {
433                assert!(!kind.is_trivia());
434
435                // This is special handling for inserting the empty string.
436                // We remove any indentation or spaces from the end of the
437                // stream before adding the empty string as a literal.
438                if value.is_empty() {
439                    self.trim_last_line(stream);
440                }
441
442                if self.interrupted && allow_interruption(kind) {
443                    self.pop_indent(stream);
444                }
445
446                stream.push(PostToken::Literal(value));
447                self.position = LinePosition::MiddleOfLine;
448            }
449            PreToken::Trivia(trivia) => match trivia {
450                Trivia::BlankLine => match self.line_spacing_policy {
451                    TriviaBlankLineSpacingPolicy::Always => {
452                        self.blank_line(stream);
453                    }
454                    TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks => {
455                        if matches!(next, Some(&PreToken::Trivia(Trivia::Comment(_)))) {
456                            self.blank_line(stream);
457                        }
458                    }
459                },
460                Trivia::Comment(comment) => {
461                    match comment {
462                        Comment::Preceding(value) => {
463                            if self.position == LinePosition::MiddleOfLine {
464                                self.interrupted = true;
465                                self.end_line(stream);
466                                if let Some(PreToken::Literal(_, next_kind)) = next
467                                    && allow_interruption(*next_kind)
468                                {
469                                    self.pop_indent(stream);
470                                }
471                            }
472                            stream.push(PostToken::Literal(value));
473                        }
474                        Comment::Inline(value) => {
475                            assert!(self.position == LinePosition::MiddleOfLine);
476                            if let Some(next) = next
477                                && next != &PreToken::LineEnd
478                            {
479                                self.interrupted = true;
480                            }
481                            self.trim_last_line(stream);
482                            for token in INLINE_COMMENT_PRECEDING_TOKENS.iter() {
483                                stream.push(token.clone());
484                            }
485                            stream.push(PostToken::Literal(value));
486                        }
487                        Comment::Documentation(contents) => {
488                            if self.position == LinePosition::MiddleOfLine {
489                                self.interrupted = true;
490                                self.end_line(stream);
491                            }
492                            stream.push(PostToken::Documentation {
493                                num_indents: self.indent_level,
494                                contents,
495                            });
496                        }
497                        Comment::Directive(directive) => {
498                            if self.position == LinePosition::MiddleOfLine {
499                                self.interrupted = true;
500                                self.end_line(stream);
501                            }
502                            stream.push(PostToken::Directive {
503                                num_indents: self.indent_level,
504                                directive,
505                            });
506                        }
507                    }
508                    self.position = LinePosition::MiddleOfLine;
509                    self.end_line(stream);
510                }
511            },
512            PreToken::TempIndentStart(bash_indent) => {
513                self.temp_indent = Some(bash_indent);
514            }
515            PreToken::TempIndentEnd => {
516                self.temp_indent = None;
517            }
518        }
519    }
520
521    /// Flushes the `in_stream` buffer to the `out_stream`.
522    fn flush(
523        &mut self,
524        in_stream: &TokenStream<PreToken>,
525        out_stream: &mut TokenStream<PostToken>,
526        config: &Config,
527    ) {
528        assert!(!self.interrupted);
529        assert!(self.position == LinePosition::StartOfLine);
530        let mut post_buffer = TokenStream::<PostToken>::default();
531        let mut pre_buffer = in_stream.iter().peekable();
532        let starting_indent = self.indent_level;
533        let starting_temp_indent = self.temp_indent.clone();
534        while let Some(token) = pre_buffer.next() {
535            let next = pre_buffer.peek().copied();
536            self.step(token.clone(), next, &mut post_buffer);
537        }
538
539        // If all lines are short enough, we can just add the post_buffer to the
540        // out_stream and be done.
541        if config.max_line_length.get().is_none()
542            || post_buffer.max_width(config) <= config.max_line_length.get().unwrap()
543        {
544            out_stream.extend(post_buffer);
545            return;
546        }
547
548        // At least one line in the post_buffer is too long.
549        // We iterate through the in_stream to find potential line breaks,
550        // and then we iterate through the in_stream again to actually insert
551        // them in the proper places.
552
553        let max_length = config.max_line_length.get().unwrap();
554
555        let mut potential_line_breaks: HashMap<usize, SyntaxKind> = HashMap::new();
556        for (i, token) in in_stream.iter().enumerate() {
557            if let PreToken::Literal(_, kind) = token {
558                match can_be_line_broken(*kind) {
559                    Some(LineBreak::Before) => {
560                        potential_line_breaks.insert(i, *kind);
561                    }
562                    Some(LineBreak::After) => {
563                        potential_line_breaks.insert(i + 1, *kind);
564                    }
565                    None => {}
566                }
567            }
568        }
569
570        if potential_line_breaks.is_empty() {
571            // There are no potential line breaks, so we can't do anything.
572            out_stream.extend(post_buffer);
573            return;
574        }
575
576        // Set up the buffers for the second pass.
577        post_buffer.clear();
578        let mut pre_buffer = in_stream.iter().enumerate().peekable();
579
580        // Reset self.
581        self.interrupted = false;
582        self.position = LinePosition::StartOfLine;
583        self.temp_indent = starting_temp_indent;
584        self.indent_level = starting_indent;
585
586        let mut break_stack: Vec<TandemBreak> = Vec::new();
587
588        while let Some((i, token)) = pre_buffer.next() {
589            let mut cache = None;
590            if let Some(break_kind) = potential_line_breaks.get(&i) {
591                // Check if we need a break to match a prior tandem break
592                if let Some(top_of_stack) = break_stack.last_mut() {
593                    if *break_kind == top_of_stack.close {
594                        if top_of_stack.depth > 0 {
595                            top_of_stack.depth -= 1;
596                        } else {
597                            break_stack.pop();
598                            self.indent_level -= 1;
599                            self.end_line(&mut post_buffer);
600                        }
601                    } else if *break_kind == top_of_stack.open {
602                        top_of_stack.depth += 1;
603                    }
604                }
605                // Cache the current state so we can revert to it if
606                // necessary.
607                cache = Some(post_buffer.clone());
608            }
609
610            self.step(
611                token.clone(),
612                pre_buffer.peek().map(|(_, v)| &**v),
613                &mut post_buffer,
614            );
615
616            if let Some(cache) = cache
617                && post_buffer.last_line_width(config) > max_length
618            {
619                // The line is too long after the next step. Revert to the
620                // cached state and insert a line break.
621                post_buffer = cache;
622                self.interrupted = true;
623                self.end_line(&mut post_buffer);
624                self.step(
625                    token.clone(),
626                    pre_buffer.peek().map(|(_, v)| &**v),
627                    &mut post_buffer,
628                );
629
630                // Check if this introduces a tandem break
631                // SAFETY: if cache is Some(_) this step must have a potential line break
632                let break_kind = potential_line_breaks.get(&i).unwrap();
633                if let Some(also_break_on) = tandem_line_break(*break_kind) {
634                    let tandem_break = TandemBreak {
635                        open: *break_kind,
636                        close: also_break_on,
637                        depth: 0,
638                    };
639                    break_stack.push(tandem_break);
640                    self.indent_level += 1;
641                }
642            }
643        }
644
645        // reduce indent for breaks never added
646        for _ in break_stack {
647            self.indent_level = self.indent_level.saturating_sub(1);
648        }
649        out_stream.extend(post_buffer);
650    }
651
652    /// Trims any and all whitespace from the end of the stream.
653    fn trim_whitespace(&self, stream: &mut TokenStream<PostToken>) {
654        stream.trim_while(|token| {
655            matches!(
656                token,
657                PostToken::Space
658                    | PostToken::Newline
659                    | PostToken::Indent
660                    | PostToken::TempIndent(_)
661            )
662        });
663    }
664
665    /// Trims spaces and indents (and not newlines) from the end of the stream.
666    fn trim_last_line(&self, stream: &mut TokenStream<PostToken>) {
667        stream.trim_while(|token| {
668            matches!(
669                token,
670                PostToken::Space | PostToken::Indent | PostToken::TempIndent(_)
671            )
672        });
673    }
674
675    /// Ends the current line without resetting the interrupted flag.
676    ///
677    /// Removes any trailing spaces or indents and adds a newline only if state
678    /// is not [`LinePosition::StartOfLine`]. State is then set to
679    /// [`LinePosition::StartOfLine`]. Finally, indentation is added. Safe to
680    /// call multiple times in a row.
681    fn end_line(&mut self, stream: &mut TokenStream<PostToken>) {
682        self.trim_last_line(stream);
683        if self.position != LinePosition::StartOfLine {
684            stream.push(PostToken::Newline);
685        }
686        self.position = LinePosition::StartOfLine;
687        self.indent(stream);
688    }
689
690    /// Pushes the current indentation level to the stream.
691    ///
692    /// This should only be called when the state is
693    /// [`LinePosition::StartOfLine`]. This does not change the state
694    /// and is safe to call multiple times in a row.
695    fn indent(&self, stream: &mut TokenStream<PostToken>) {
696        assert!(self.position == LinePosition::StartOfLine);
697
698        self.trim_last_line(stream);
699
700        let level = if self.interrupted {
701            self.indent_level + 1
702        } else {
703            self.indent_level
704        };
705
706        for _ in 0..level {
707            stream.push(PostToken::Indent);
708        }
709
710        if let Some(ref temp_indent) = self.temp_indent {
711            stream.push(PostToken::TempIndent(temp_indent.clone()));
712        }
713    }
714
715    /// Creates a blank line and then indents.
716    fn blank_line(&mut self, stream: &mut TokenStream<PostToken>) {
717        self.trim_whitespace(stream);
718        if !stream.is_empty() {
719            stream.push(PostToken::Newline);
720        }
721        stream.push(PostToken::Newline);
722        self.position = LinePosition::StartOfLine;
723        self.indent(stream);
724    }
725
726    /// Pop exactly one indent off the end of the stream.
727    ///
728    /// If the last token is not [`PostToken::Indent`] or
729    /// [`PostToken::TempIndent`], this is a no-op.
730    fn pop_indent(&mut self, stream: &mut TokenStream<PostToken>) {
731        if matches!(
732            stream.0.last(),
733            Some(&PostToken::Indent) | Some(&PostToken::TempIndent(_))
734        ) {
735            let popped = stream.0.pop().unwrap();
736            // We don't actually want to pop the TempIndent token,
737            // but rather a regular Indent token before the temp indent.
738            if matches!(popped, PostToken::TempIndent(_)) {
739                stream.0.pop_if(|t| matches!(t, PostToken::Indent));
740                // Restore the popped TempIndent
741                stream.0.push(popped);
742            }
743        }
744    }
745}