Skip to main content

ra_ap_syntax_bridge/
lib.rs

1//! Conversions between [`SyntaxNode`] and [`tt::TokenTree`].
2
3#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
4
5#[cfg(feature = "in-rust-tree")]
6extern crate rustc_driver as _;
7
8use std::{collections::VecDeque, fmt, hash::Hash};
9
10use intern::Symbol;
11use rustc_hash::{FxHashMap, FxHashSet};
12use span::{Edition, Span, SpanAnchor, SpanMap, SyntaxContext};
13use stdx::{format_to, never};
14use syntax::{
15    AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement,
16    SyntaxKind::{self, *},
17    SyntaxNode, SyntaxToken, SyntaxTreeBuilder, T, TextRange, TextSize, WalkEvent,
18    ast::{self, make::tokens::doc_comment},
19    format_smolstr,
20};
21use tt::{Punct, buffer::Cursor, token_to_literal};
22
23pub mod prettify_macro_expansion;
24mod to_parser_input;
25pub use to_parser_input::to_parser_input;
26// FIXME: we probably should re-think  `token_tree_to_syntax_node` interfaces
27pub use ::parser::TopEntryPoint;
28
29#[cfg(test)]
30mod tests;
31
32pub trait SpanMapper {
33    fn span_for(&self, range: TextRange) -> Span;
34}
35
36impl SpanMapper for SpanMap {
37    fn span_for(&self, range: TextRange) -> Span {
38        self.span_at(range.start())
39    }
40}
41
42impl<SM: SpanMapper> SpanMapper for &SM {
43    fn span_for(&self, range: TextRange) -> Span {
44        SM::span_for(self, range)
45    }
46}
47
48/// Dummy things for testing where spans don't matter.
49pub mod dummy_test_span_utils {
50
51    use span::{Span, SyntaxContext};
52
53    use super::*;
54
55    pub const DUMMY: Span = Span {
56        range: TextRange::empty(TextSize::new(0)),
57        anchor: span::SpanAnchor {
58            file_id: span::EditionedFileId::new(
59                span::FileId::from_raw(0xe4e4e),
60                span::Edition::CURRENT,
61            ),
62            ast_id: span::ROOT_ERASED_FILE_AST_ID,
63        },
64        ctx: SyntaxContext::root(Edition::CURRENT),
65    };
66
67    pub struct DummyTestSpanMap;
68
69    impl SpanMapper for DummyTestSpanMap {
70        fn span_for(&self, range: syntax::TextRange) -> Span {
71            Span {
72                range,
73                anchor: span::SpanAnchor {
74                    file_id: span::EditionedFileId::new(
75                        span::FileId::from_raw(0xe4e4e),
76                        span::Edition::CURRENT,
77                    ),
78                    ast_id: span::ROOT_ERASED_FILE_AST_ID,
79                },
80                ctx: SyntaxContext::root(Edition::CURRENT),
81            }
82        }
83    }
84}
85
86/// Doc comment desugaring differs between mbe and proc-macros.
87#[derive(Copy, Clone, PartialEq, Eq)]
88pub enum DocCommentDesugarMode {
89    /// Desugars doc comments as quoted raw strings
90    Mbe,
91    /// Desugars doc comments as quoted strings
92    ProcMacro,
93}
94
95/// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the
96/// subtree's spans.
97pub fn syntax_node_to_token_tree<SpanMap>(
98    node: &SyntaxNode,
99    map: SpanMap,
100    span: Span,
101    mode: DocCommentDesugarMode,
102) -> tt::TopSubtree
103where
104    SpanMap: SpanMapper,
105{
106    let mut c =
107        Converter::new(node, map, Default::default(), Default::default(), span, mode, |_, _| {
108            (true, Vec::new())
109        });
110    convert_tokens(&mut c)
111}
112
113/// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the
114/// subtree's spans. Additionally using the append and remove parameters, the additional tokens can
115/// be injected or hidden from the output.
116pub fn syntax_node_to_token_tree_modified<SpanMap, OnEvent>(
117    node: &SyntaxNode,
118    map: SpanMap,
119    append: FxHashMap<SyntaxElement, Vec<tt::Leaf>>,
120    remove: FxHashSet<SyntaxElement>,
121    call_site: Span,
122    mode: DocCommentDesugarMode,
123    on_enter: OnEvent,
124) -> tt::TopSubtree
125where
126    SpanMap: SpanMapper,
127    OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent<SyntaxElement>) -> (bool, Vec<tt::Leaf>),
128{
129    let mut c = Converter::new(node, map, append, remove, call_site, mode, on_enter);
130    convert_tokens(&mut c)
131}
132
133// The following items are what `rustc` macro can be parsed into :
134// link: https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libsyntax/ext/expand.rs#L141
135// * Expr(Box<ast::Expr>)                     -> token_tree_to_expr
136// * Pat(Box<ast::Pat>)                       -> token_tree_to_pat
137// * Ty(Box<ast::Ty>)                         -> token_tree_to_ty
138// * Stmts(SmallVec<[ast::Stmt; 1]>)        -> token_tree_to_stmts
139// * Items(SmallVec<[Box<ast::Item>; 1]>)     -> token_tree_to_items
140//
141// * TraitItems(SmallVec<[ast::TraitItem; 1]>)
142// * AssocItems(SmallVec<[ast::AssocItem; 1]>)
143// * ForeignItems(SmallVec<[ast::ForeignItem; 1]>
144
145/// Converts a [`tt::Subtree`] back to a [`SyntaxNode`].
146/// The produced `SpanMap` contains a mapping from the syntax nodes offsets to the subtree's spans.
147pub fn token_tree_to_syntax_node(
148    tt: &tt::TopSubtree,
149    entry_point: parser::TopEntryPoint,
150    span_to_edition: &mut dyn FnMut(SyntaxContext) -> Edition,
151) -> (Parse<SyntaxNode>, SpanMap)
152where
153    SyntaxContext: Copy + fmt::Debug + PartialEq + PartialEq + Eq + Hash,
154{
155    let buffer = tt.view().strip_invisible();
156    let parser_input = to_parser_input(buffer, span_to_edition);
157    // It matters what edition we parse with even when we escape all identifiers correctly.
158    let parser_output = entry_point.parse(&parser_input);
159    let mut tree_sink = TtTreeSink::new(buffer.cursor());
160    for event in parser_output.iter() {
161        match event {
162            parser::Step::Token { kind, n_input_tokens: n_raw_tokens } => {
163                tree_sink.token(kind, n_raw_tokens)
164            }
165            parser::Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
166                tree_sink.float_split(has_pseudo_dot)
167            }
168            parser::Step::Enter { kind } => tree_sink.start_node(kind),
169            parser::Step::Exit => tree_sink.finish_node(),
170            parser::Step::Error { msg } => tree_sink.error(msg.to_owned()),
171        }
172    }
173    tree_sink.finish()
174}
175
176/// Convert a string to a `TokenTree`. The spans of the subtree will be anchored to the provided
177/// anchor with the given context.
178pub fn parse_to_token_tree(
179    edition: Edition,
180    anchor: SpanAnchor,
181    ctx: SyntaxContext,
182    text: &str,
183) -> Option<tt::TopSubtree> {
184    let lexed = parser::LexedStr::new(edition, text);
185    if lexed.errors().next().is_some() {
186        return None;
187    }
188    let mut conv =
189        RawConverter { lexed, anchor, pos: 0, ctx, mode: DocCommentDesugarMode::ProcMacro };
190    Some(convert_tokens(&mut conv))
191}
192
193/// Convert a string to a `TokenTree`. The passed span will be used for all spans of the produced subtree.
194pub fn parse_to_token_tree_static_span(
195    edition: Edition,
196    span: Span,
197    text: &str,
198) -> Option<tt::TopSubtree> {
199    let lexed = parser::LexedStr::new(edition, text);
200    if lexed.errors().next().is_some() {
201        return None;
202    }
203    let mut conv =
204        StaticRawConverter { lexed, pos: 0, span, mode: DocCommentDesugarMode::ProcMacro };
205    Some(convert_tokens(&mut conv))
206}
207
208fn convert_tokens<C>(conv: &mut C) -> tt::TopSubtree
209where
210    C: TokenConverter,
211    C::Token: fmt::Debug,
212{
213    let mut builder =
214        tt::TopSubtreeBuilder::new(tt::Delimiter::invisible_spanned(conv.call_site()));
215
216    while let Some((token, abs_range)) = conv.bump() {
217        let tt = match token.as_leaf() {
218            // These delimiters are not actually valid punctuation, but we produce them in syntax fixup.
219            // So we need to handle them specially here.
220            Some(&tt::Leaf::Punct(Punct {
221                char: char @ ('(' | ')' | '{' | '}' | '[' | ']'),
222                span,
223                spacing: _,
224            })) => {
225                let found_expected_delimiter =
226                    builder.expected_delimiters().enumerate().find(|(_, delim)| match delim {
227                        tt::DelimiterKind::Parenthesis => char == ')',
228                        tt::DelimiterKind::Brace => char == '}',
229                        tt::DelimiterKind::Bracket => char == ']',
230                        tt::DelimiterKind::Invisible => false,
231                    });
232                if let Some((idx, _)) = found_expected_delimiter {
233                    for _ in 0..=idx {
234                        builder.close(span);
235                    }
236                    continue;
237                }
238
239                let delim = match char {
240                    '(' => tt::DelimiterKind::Parenthesis,
241                    '{' => tt::DelimiterKind::Brace,
242                    '[' => tt::DelimiterKind::Bracket,
243                    _ => panic!("unmatched closing delimiter from syntax fixup"),
244                };
245
246                // Start a new subtree
247                builder.open(delim, span);
248                continue;
249            }
250            Some(leaf) => leaf.clone(),
251            None => match token.kind(conv) {
252                // Desugar doc comments into doc attributes
253                COMMENT => {
254                    let span = conv.span_for(abs_range);
255                    conv.convert_doc_comment(&token, span, &mut builder);
256                    continue;
257                }
258                kind if kind.is_punct() && kind != UNDERSCORE => {
259                    let found_expected_delimiter =
260                        builder.expected_delimiters().enumerate().find(|(_, delim)| match delim {
261                            tt::DelimiterKind::Parenthesis => kind == T![')'],
262                            tt::DelimiterKind::Brace => kind == T!['}'],
263                            tt::DelimiterKind::Bracket => kind == T![']'],
264                            tt::DelimiterKind::Invisible => false,
265                        });
266
267                    // Current token is a closing delimiter that we expect, fix up the closing span
268                    // and end the subtree here.
269                    // We also close any open inner subtrees that might be missing their delimiter.
270                    if let Some((idx, _)) = found_expected_delimiter {
271                        for _ in 0..=idx {
272                            // FIXME: record an error somewhere if we're closing more than one tree here?
273                            builder.close(conv.span_for(abs_range));
274                        }
275                        continue;
276                    }
277
278                    let delim = match kind {
279                        T!['('] => Some(tt::DelimiterKind::Parenthesis),
280                        T!['{'] => Some(tt::DelimiterKind::Brace),
281                        T!['['] => Some(tt::DelimiterKind::Bracket),
282                        _ => None,
283                    };
284
285                    // Start a new subtree
286                    if let Some(kind) = delim {
287                        builder.open(kind, conv.span_for(abs_range));
288                        continue;
289                    }
290
291                    let spacing = match conv.peek().map(|next| next.kind(conv)) {
292                        Some(kind) if is_single_token_op(kind) => tt::Spacing::Joint,
293                        _ => tt::Spacing::Alone,
294                    };
295                    let Some(char) = token.to_char(conv) else {
296                        panic!("Token from lexer must be single char: token = {token:#?}")
297                    };
298                    // FIXME: this might still be an unmatched closing delimiter? Maybe we should assert here
299                    tt::Leaf::from(tt::Punct { char, spacing, span: conv.span_for(abs_range) })
300                }
301                kind => {
302                    macro_rules! make_ident {
303                        () => {
304                            tt::Ident {
305                                span: conv.span_for(abs_range),
306                                sym: Symbol::intern(&token.to_text(conv)),
307                                is_raw: tt::IdentIsRaw::No,
308                            }
309                            .into()
310                        };
311                    }
312                    let leaf: tt::Leaf = match kind {
313                        k if k.is_any_identifier() => {
314                            let text = token.to_text(conv);
315                            tt::Ident::new(&text, conv.span_for(abs_range)).into()
316                        }
317                        UNDERSCORE => make_ident!(),
318                        k if k.is_literal() => {
319                            let text = token.to_text(conv);
320                            let span = conv.span_for(abs_range);
321                            token_to_literal(&text, span).into()
322                        }
323                        LIFETIME_IDENT => {
324                            let apostrophe = tt::Leaf::from(tt::Punct {
325                                char: '\'',
326                                spacing: tt::Spacing::Joint,
327                                span: conv
328                                    .span_for(TextRange::at(abs_range.start(), TextSize::of('\''))),
329                            });
330                            builder.push(apostrophe);
331
332                            let ident = tt::Leaf::from(tt::Ident {
333                                sym: Symbol::intern(&token.to_text(conv)[1..]),
334                                span: conv.span_for(TextRange::new(
335                                    abs_range.start() + TextSize::of('\''),
336                                    abs_range.end(),
337                                )),
338                                is_raw: tt::IdentIsRaw::No,
339                            });
340                            builder.push(ident);
341                            continue;
342                        }
343                        _ => continue,
344                    };
345
346                    leaf
347                }
348            },
349        };
350
351        builder.push(tt);
352    }
353
354    while builder.expected_delimiters().next().is_some() {
355        // FIXME: record an error somewhere?
356        builder.close(conv.call_site());
357    }
358    builder.build_skip_top_subtree()
359}
360
361fn is_single_token_op(kind: SyntaxKind) -> bool {
362    matches!(
363        kind,
364        EQ | L_ANGLE
365            | R_ANGLE
366            | BANG
367            | AMP
368            | PIPE
369            | TILDE
370            | AT
371            | DOT
372            | COMMA
373            | SEMICOLON
374            | COLON
375            | POUND
376            | DOLLAR
377            | QUESTION
378            | PLUS
379            | MINUS
380            | STAR
381            | SLASH
382            | PERCENT
383            | CARET
384            // LIFETIME_IDENT will be split into a sequence of `'` (a single quote) and an
385            // identifier.
386            | LIFETIME_IDENT
387    )
388}
389
390/// Returns the textual content of a doc comment block as a quoted string
391/// That is, strips leading `///` (or `/**`, etc)
392/// and strips the ending `*/`
393/// And then quote the string, which is needed to convert to `tt::Literal`
394///
395/// Note that proc-macros desugar with string literals where as macro_rules macros desugar with raw string literals.
396pub fn desugar_doc_comment_text(text: &str, mode: DocCommentDesugarMode) -> (Symbol, tt::LitKind) {
397    match mode {
398        DocCommentDesugarMode::Mbe => {
399            let mut num_of_hashes = 0;
400            let mut count = 0;
401            for ch in text.chars() {
402                count = match ch {
403                    '"' => 1,
404                    '#' if count > 0 => count + 1,
405                    _ => 0,
406                };
407                num_of_hashes = num_of_hashes.max(count);
408            }
409
410            // Quote raw string with delimiters
411            (Symbol::intern(text), tt::LitKind::StrRaw(num_of_hashes))
412        }
413        // Quote string with delimiters
414        DocCommentDesugarMode::ProcMacro => {
415            (Symbol::intern(&format_smolstr!("{}", text.escape_debug())), tt::LitKind::Str)
416        }
417    }
418}
419
420fn convert_doc_comment(
421    token: &syntax::SyntaxToken,
422    span: Span,
423    mode: DocCommentDesugarMode,
424    builder: &mut tt::TopSubtreeBuilder,
425) {
426    let Some(comment) = ast::Comment::cast(token.clone()) else { return };
427    let Some(doc) = comment.kind().doc else { return };
428
429    let mk_ident = |s: &str| {
430        tt::Leaf::from(tt::Ident { sym: Symbol::intern(s), span, is_raw: tt::IdentIsRaw::No })
431    };
432
433    let mk_punct =
434        |c: char| tt::Leaf::from(tt::Punct { char: c, spacing: tt::Spacing::Alone, span });
435
436    let mk_doc_literal = |comment: &ast::Comment| {
437        let prefix_len = comment.prefix().len();
438        let mut text = &comment.text()[prefix_len..];
439
440        // Remove ending "*/"
441        if comment.kind().shape == ast::CommentShape::Block {
442            text = &text[0..text.len() - 2];
443        }
444        let (text, kind) = desugar_doc_comment_text(text, mode);
445        let lit = tt::Literal { text_and_suffix: text, span, kind, suffix_len: 0 };
446
447        tt::Leaf::from(lit)
448    };
449
450    // Make `doc="\" Comments\""
451    let meta_tkns = [mk_ident("doc"), mk_punct('='), mk_doc_literal(&comment)];
452
453    // Make `#![]`
454    builder.push(mk_punct('#'));
455    if let ast::CommentPlacement::Inner = doc {
456        builder.push(mk_punct('!'));
457    }
458    builder.open(tt::DelimiterKind::Bracket, span);
459    builder.extend(meta_tkns);
460    builder.close(span);
461}
462
463/// A raw token (straight from lexer) converter
464struct RawConverter<'a> {
465    lexed: parser::LexedStr<'a>,
466    pos: usize,
467    anchor: SpanAnchor,
468    ctx: SyntaxContext,
469    mode: DocCommentDesugarMode,
470}
471/// A raw token (straight from lexer) converter that gives every token the same span.
472struct StaticRawConverter<'a> {
473    lexed: parser::LexedStr<'a>,
474    pos: usize,
475    span: Span,
476    mode: DocCommentDesugarMode,
477}
478
479trait SrcToken<Ctx> {
480    fn kind(&self, ctx: &Ctx) -> SyntaxKind;
481
482    fn to_char(&self, ctx: &Ctx) -> Option<char>;
483
484    fn to_text(&self, ctx: &Ctx) -> SmolStr;
485
486    fn as_leaf(&self) -> Option<&tt::Leaf> {
487        None
488    }
489}
490
491trait TokenConverter: Sized {
492    type Token: SrcToken<Self>;
493
494    fn convert_doc_comment(
495        &self,
496        token: &Self::Token,
497        span: Span,
498        builder: &mut tt::TopSubtreeBuilder,
499    );
500
501    fn bump(&mut self) -> Option<(Self::Token, TextRange)>;
502
503    fn peek(&self) -> Option<Self::Token>;
504
505    fn span_for(&self, range: TextRange) -> Span;
506
507    fn call_site(&self) -> Span;
508}
509
510impl SrcToken<RawConverter<'_>> for usize {
511    fn kind(&self, ctx: &RawConverter<'_>) -> SyntaxKind {
512        ctx.lexed.kind(*self)
513    }
514
515    fn to_char(&self, ctx: &RawConverter<'_>) -> Option<char> {
516        ctx.lexed.text(*self).chars().next()
517    }
518
519    fn to_text(&self, ctx: &RawConverter<'_>) -> SmolStr {
520        ctx.lexed.text(*self).into()
521    }
522}
523
524impl SrcToken<StaticRawConverter<'_>> for usize {
525    fn kind(&self, ctx: &StaticRawConverter<'_>) -> SyntaxKind {
526        ctx.lexed.kind(*self)
527    }
528
529    fn to_char(&self, ctx: &StaticRawConverter<'_>) -> Option<char> {
530        ctx.lexed.text(*self).chars().next()
531    }
532
533    fn to_text(&self, ctx: &StaticRawConverter<'_>) -> SmolStr {
534        ctx.lexed.text(*self).into()
535    }
536}
537
538impl TokenConverter for RawConverter<'_> {
539    type Token = usize;
540
541    fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) {
542        let text = self.lexed.text(token);
543        convert_doc_comment(&doc_comment(text), span, self.mode, builder);
544    }
545
546    fn bump(&mut self) -> Option<(Self::Token, TextRange)> {
547        if self.pos == self.lexed.len() {
548            return None;
549        }
550        let token = self.pos;
551        self.pos += 1;
552        let range = self.lexed.text_range(token);
553        let range = TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?);
554
555        Some((token, range))
556    }
557
558    fn peek(&self) -> Option<Self::Token> {
559        if self.pos == self.lexed.len() {
560            return None;
561        }
562        Some(self.pos)
563    }
564
565    fn span_for(&self, range: TextRange) -> Span {
566        Span { range, anchor: self.anchor, ctx: self.ctx }
567    }
568
569    fn call_site(&self) -> Span {
570        Span { range: TextRange::empty(0.into()), anchor: self.anchor, ctx: self.ctx }
571    }
572}
573
574impl TokenConverter for StaticRawConverter<'_> {
575    type Token = usize;
576
577    fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) {
578        let text = self.lexed.text(token);
579        convert_doc_comment(&doc_comment(text), span, self.mode, builder);
580    }
581
582    fn bump(&mut self) -> Option<(Self::Token, TextRange)> {
583        if self.pos == self.lexed.len() {
584            return None;
585        }
586        let token = self.pos;
587        self.pos += 1;
588        let range = self.lexed.text_range(token);
589        let range = TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?);
590
591        Some((token, range))
592    }
593
594    fn peek(&self) -> Option<Self::Token> {
595        if self.pos == self.lexed.len() {
596            return None;
597        }
598        Some(self.pos)
599    }
600
601    fn span_for(&self, _: TextRange) -> Span {
602        self.span
603    }
604
605    fn call_site(&self) -> Span {
606        self.span
607    }
608}
609
610struct Converter<SpanMap, OnEvent> {
611    current: Option<SyntaxToken>,
612    current_leaves: VecDeque<tt::Leaf>,
613    preorder: PreorderWithTokens,
614    range: TextRange,
615    punct_offset: Option<(SyntaxToken, TextSize)>,
616    /// Used to make the emitted text ranges in the spans relative to the span anchor.
617    map: SpanMap,
618    append: FxHashMap<SyntaxElement, Vec<tt::Leaf>>,
619    remove: FxHashSet<SyntaxElement>,
620    call_site: Span,
621    mode: DocCommentDesugarMode,
622    on_event: OnEvent,
623}
624
625impl<SpanMap, OnEvent> Converter<SpanMap, OnEvent>
626where
627    OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent<SyntaxElement>) -> (bool, Vec<tt::Leaf>),
628{
629    fn new(
630        node: &SyntaxNode,
631        map: SpanMap,
632        append: FxHashMap<SyntaxElement, Vec<tt::Leaf>>,
633        remove: FxHashSet<SyntaxElement>,
634        call_site: Span,
635        mode: DocCommentDesugarMode,
636        on_enter: OnEvent,
637    ) -> Self {
638        let mut converter = Converter {
639            current: None,
640            preorder: node.preorder_with_tokens(),
641            range: node.text_range(),
642            punct_offset: None,
643            map,
644            append,
645            remove,
646            call_site,
647            current_leaves: VecDeque::new(),
648            mode,
649            on_event: on_enter,
650        };
651        converter.current = converter.next_token();
652        converter
653    }
654
655    fn next_token(&mut self) -> Option<SyntaxToken> {
656        while let Some(ev) = self.preorder.next() {
657            let (keep_event, insert_leaves) = (self.on_event)(&mut self.preorder, &ev);
658            self.current_leaves.extend(insert_leaves);
659            if !keep_event {
660                continue;
661            }
662            match ev {
663                WalkEvent::Enter(token) => {
664                    if self.remove.contains(&token) {
665                        match token {
666                            syntax::NodeOrToken::Token(_) => {
667                                continue;
668                            }
669                            node => {
670                                self.preorder.skip_subtree();
671                                if let Some(v) = self.append.remove(&node) {
672                                    self.current_leaves.extend(v);
673                                    continue;
674                                }
675                            }
676                        }
677                    } else if let syntax::NodeOrToken::Token(token) = token {
678                        return Some(token);
679                    }
680                }
681                WalkEvent::Leave(ele) => {
682                    if let Some(v) = self.append.remove(&ele) {
683                        self.current_leaves.extend(v);
684                        continue;
685                    }
686                }
687            }
688        }
689        None
690    }
691}
692
693#[derive(Debug)]
694enum SynToken {
695    Ordinary(SyntaxToken),
696    Punct { token: SyntaxToken, offset: usize },
697    Leaf(tt::Leaf),
698}
699
700impl SynToken {
701    fn token(&self) -> &SyntaxToken {
702        match self {
703            SynToken::Ordinary(it) | SynToken::Punct { token: it, offset: _ } => it,
704            SynToken::Leaf(_) => unreachable!(),
705        }
706    }
707}
708
709impl<SpanMap, OnEvent> SrcToken<Converter<SpanMap, OnEvent>> for SynToken {
710    fn kind(&self, _ctx: &Converter<SpanMap, OnEvent>) -> SyntaxKind {
711        match self {
712            SynToken::Ordinary(token) => token.kind(),
713            SynToken::Punct { token, offset: i } => {
714                SyntaxKind::from_char(token.text().chars().nth(*i).unwrap()).unwrap()
715            }
716            SynToken::Leaf(_) => {
717                never!();
718                SyntaxKind::ERROR
719            }
720        }
721    }
722    fn to_char(&self, _ctx: &Converter<SpanMap, OnEvent>) -> Option<char> {
723        match self {
724            SynToken::Ordinary(_) => None,
725            SynToken::Punct { token: it, offset: i } => it.text().chars().nth(*i),
726            SynToken::Leaf(_) => None,
727        }
728    }
729    fn to_text(&self, _ctx: &Converter<SpanMap, OnEvent>) -> SmolStr {
730        match self {
731            SynToken::Ordinary(token) | SynToken::Punct { token, offset: _ } => token.text().into(),
732            SynToken::Leaf(_) => {
733                never!();
734                "".into()
735            }
736        }
737    }
738    fn as_leaf(&self) -> Option<&tt::Leaf> {
739        match self {
740            SynToken::Ordinary(_) | SynToken::Punct { .. } => None,
741            SynToken::Leaf(it) => Some(it),
742        }
743    }
744}
745
746impl<SpanMap, OnEvent> TokenConverter for Converter<SpanMap, OnEvent>
747where
748    SpanMap: SpanMapper,
749    OnEvent: FnMut(&mut PreorderWithTokens, &WalkEvent<SyntaxElement>) -> (bool, Vec<tt::Leaf>),
750{
751    type Token = SynToken;
752    fn convert_doc_comment(
753        &self,
754        token: &Self::Token,
755        span: Span,
756        builder: &mut tt::TopSubtreeBuilder,
757    ) {
758        convert_doc_comment(token.token(), span, self.mode, builder);
759    }
760
761    fn bump(&mut self) -> Option<(Self::Token, TextRange)> {
762        if let Some((punct, offset)) = self.punct_offset.clone()
763            && usize::from(offset) + 1 < punct.text().len()
764        {
765            let offset = offset + TextSize::of('.');
766            let range = punct.text_range();
767            self.punct_offset = Some((punct.clone(), offset));
768            let range = TextRange::at(range.start() + offset, TextSize::of('.'));
769            return Some((
770                SynToken::Punct { token: punct, offset: u32::from(offset) as usize },
771                range,
772            ));
773        }
774
775        if let Some(leaf) = self.current_leaves.pop_front() {
776            return Some((SynToken::Leaf(leaf), TextRange::empty(TextSize::new(0))));
777        }
778
779        let curr = self.current.clone()?;
780        if !self.range.contains_range(curr.text_range()) {
781            return None;
782        }
783
784        self.current = self.next_token();
785        let token = if curr.kind().is_punct() {
786            self.punct_offset = Some((curr.clone(), 0.into()));
787            let range = curr.text_range();
788            let range = TextRange::at(range.start(), TextSize::of('.'));
789            (SynToken::Punct { token: curr, offset: 0_usize }, range)
790        } else {
791            self.punct_offset = None;
792            let range = curr.text_range();
793            (SynToken::Ordinary(curr), range)
794        };
795
796        Some(token)
797    }
798
799    fn peek(&self) -> Option<Self::Token> {
800        if let Some((punct, mut offset)) = self.punct_offset.clone() {
801            offset += TextSize::of('.');
802            if usize::from(offset) < punct.text().len() {
803                return Some(SynToken::Punct { token: punct, offset: usize::from(offset) });
804            }
805        }
806
807        let curr = self.current.clone()?;
808        if !self.range.contains_range(curr.text_range()) {
809            return None;
810        }
811
812        let token = if curr.kind().is_punct() {
813            SynToken::Punct { token: curr, offset: 0_usize }
814        } else {
815            SynToken::Ordinary(curr)
816        };
817        Some(token)
818    }
819
820    fn span_for(&self, range: TextRange) -> Span {
821        self.map.span_for(range)
822    }
823    fn call_site(&self) -> Span {
824        self.call_site
825    }
826}
827
828struct TtTreeSink<'a> {
829    buf: String,
830    cursor: Cursor<'a>,
831    text_pos: TextSize,
832    inner: SyntaxTreeBuilder,
833    token_map: SpanMap,
834}
835
836impl<'a> TtTreeSink<'a> {
837    fn new(cursor: Cursor<'a>) -> Self {
838        TtTreeSink {
839            buf: String::new(),
840            cursor,
841            text_pos: 0.into(),
842            inner: SyntaxTreeBuilder::default(),
843            token_map: SpanMap::empty(),
844        }
845    }
846
847    fn finish(mut self) -> (Parse<SyntaxNode>, SpanMap) {
848        self.token_map.finish();
849        (self.inner.finish(), self.token_map)
850    }
851}
852
853fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> {
854    let texts = match d {
855        tt::DelimiterKind::Parenthesis => "()",
856        tt::DelimiterKind::Brace => "{}",
857        tt::DelimiterKind::Bracket => "[]",
858        tt::DelimiterKind::Invisible => return None,
859    };
860
861    let idx = closing as usize;
862    Some(&texts[idx..texts.len() - (1 - idx)])
863}
864
865impl TtTreeSink<'_> {
866    /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween.
867    /// This occurs when a float literal is used as a field access.
868    fn float_split(&mut self, has_pseudo_dot: bool) {
869        let token_tree = self.cursor.token_tree();
870        let (text, span) = match &token_tree {
871            Some(tt::TokenTree::Leaf(tt::Leaf::Literal(
872                lit @ tt::Literal { span, kind: tt::LitKind::Float, .. },
873            ))) => (lit.text(), *span),
874            tt => unreachable!("{tt:?}"),
875        };
876        // FIXME: Span splitting
877        match text.split_once('.') {
878            Some((left, right)) => {
879                assert!(!left.is_empty());
880
881                self.inner.start_node(SyntaxKind::NAME_REF);
882                self.inner.token(SyntaxKind::INT_NUMBER, left);
883                self.inner.finish_node();
884                self.token_map.push(self.text_pos + TextSize::of(left), span);
885
886                // here we move the exit up, the original exit has been deleted in process
887                self.inner.finish_node();
888
889                self.inner.token(SyntaxKind::DOT, ".");
890                self.token_map.push(self.text_pos + TextSize::of(left) + TextSize::of("."), span);
891
892                if has_pseudo_dot {
893                    assert!(right.is_empty(), "{left}.{right}");
894                } else {
895                    assert!(!right.is_empty(), "{left}.{right}");
896                    self.inner.start_node(SyntaxKind::NAME_REF);
897                    self.inner.token(SyntaxKind::INT_NUMBER, right);
898                    self.token_map.push(self.text_pos + TextSize::of(text), span);
899                    self.inner.finish_node();
900
901                    // the parser creates an unbalanced start node, we are required to close it here
902                    self.inner.finish_node();
903                }
904                self.text_pos += TextSize::of(text);
905            }
906            None => unreachable!(),
907        }
908        self.cursor.bump();
909    }
910
911    fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) {
912        if kind == LIFETIME_IDENT {
913            n_tokens = 2;
914        }
915
916        let mut last_two = self.cursor.peek_two_leaves();
917        let mut combined_span = None;
918        'tokens: for _ in 0..n_tokens {
919            let tmp: u8;
920            if self.cursor.eof() {
921                break;
922            }
923            last_two = self.cursor.peek_two_leaves();
924            let (text, span) = loop {
925                break match self.cursor.token_tree() {
926                    Some(tt::TokenTree::Leaf(leaf)) => match leaf {
927                        tt::Leaf::Ident(ident) => {
928                            if ident.is_raw.yes() {
929                                self.buf.push_str("r#");
930                                self.text_pos += TextSize::of("r#");
931                            }
932                            let text = ident.sym.as_str();
933                            self.buf += text;
934                            self.text_pos += TextSize::of(text);
935                            combined_span = match combined_span {
936                                None => Some(ident.span),
937                                Some(prev_span) => Some(Self::merge_spans(prev_span, ident.span)),
938                            };
939                            self.cursor.bump();
940                            continue 'tokens;
941                        }
942                        tt::Leaf::Punct(punct) => {
943                            assert!(punct.char.is_ascii());
944                            tmp = punct.char as u8;
945                            let r = (
946                                std::str::from_utf8(std::slice::from_ref(&tmp)).unwrap(),
947                                punct.span,
948                            );
949                            self.cursor.bump();
950                            r
951                        }
952                        tt::Leaf::Literal(lit) => {
953                            let buf_l = self.buf.len();
954                            format_to!(self.buf, "{lit}");
955                            debug_assert_ne!(self.buf.len() - buf_l, 0);
956                            self.text_pos += TextSize::new((self.buf.len() - buf_l) as u32);
957                            combined_span = match combined_span {
958                                None => Some(lit.span),
959                                Some(prev_span) => Some(Self::merge_spans(prev_span, lit.span)),
960                            };
961                            self.cursor.bump();
962                            continue 'tokens;
963                        }
964                    },
965                    Some(tt::TokenTree::Subtree(subtree)) => {
966                        self.cursor.bump();
967                        match delim_to_str(subtree.delimiter.kind, false) {
968                            Some(it) => (it, subtree.delimiter.open),
969                            None => continue,
970                        }
971                    }
972                    None => {
973                        let parent = self.cursor.end();
974                        match delim_to_str(parent.delimiter.kind, true) {
975                            Some(it) => (it, parent.delimiter.close),
976                            None => continue,
977                        }
978                    }
979                };
980            };
981            self.buf += text;
982            self.text_pos += TextSize::of(text);
983            combined_span = match combined_span {
984                None => Some(span),
985                Some(prev_span) => Some(Self::merge_spans(prev_span, span)),
986            }
987        }
988
989        self.token_map.push(self.text_pos, combined_span.expect("expected at least one token"));
990        self.inner.token(kind, self.buf.as_str());
991        self.buf.clear();
992        // FIXME: Emitting whitespace for this is really just a hack, we should get rid of it.
993        // Add whitespace between adjoint puncts
994        if let Some([tt::Leaf::Punct(curr), tt::Leaf::Punct(next)]) = last_two {
995            // Note: We always assume the semi-colon would be the last token in
996            // other parts of RA such that we don't add whitespace here.
997            //
998            // When `next` is a `Punct` of `'`, that's a part of a lifetime identifier so we don't
999            // need to add whitespace either.
1000            if curr.spacing == tt::Spacing::Alone && curr.char != ';' && next.char != '\'' {
1001                self.inner.token(WHITESPACE, " ");
1002                self.text_pos += TextSize::of(' ');
1003                self.token_map.push(self.text_pos, curr.span);
1004            }
1005        }
1006    }
1007
1008    fn start_node(&mut self, kind: SyntaxKind) {
1009        self.inner.start_node(kind);
1010    }
1011
1012    fn finish_node(&mut self) {
1013        self.inner.finish_node();
1014    }
1015
1016    fn error(&mut self, error: String) {
1017        self.inner.error(error, self.text_pos)
1018    }
1019
1020    fn merge_spans(a: Span, b: Span) -> Span {
1021        // We don't do what rustc does exactly, rustc does something clever when the spans have different syntax contexts
1022        // but this runs afoul of our separation between `span` and `hir-expand`.
1023        Span {
1024            range: if a.ctx == b.ctx && a.anchor == b.anchor {
1025                TextRange::new(
1026                    std::cmp::min(a.range.start(), b.range.start()),
1027                    std::cmp::max(a.range.end(), b.range.end()),
1028                )
1029            } else {
1030                // Combining ranges make no sense when they come from different syntax contexts.
1031                a.range
1032            },
1033            anchor: a.anchor,
1034            ctx: a.ctx,
1035        }
1036    }
1037}