1use intern::sym;
5use rustc_hash::{FxHashMap, FxHashSet};
6use span::{
7 ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor,
8 SyntaxContext,
9};
10use stdx::never;
11use syntax::{
12 SyntaxElement, SyntaxKind, SyntaxNode, TextRange, TextSize,
13 ast::{self, AstNode, HasLoopBody},
14 match_ast,
15};
16use syntax_bridge::DocCommentDesugarMode;
17use thin_vec::ThinVec;
18use tt::{Spacing, TransformTtAction, transform_tt};
19
20use crate::{
21 span_map::SpanMap,
22 tt::{self, Ident, Leaf, Punct, TopSubtree},
23};
24
25#[derive(Debug, Default)]
29pub(crate) struct SyntaxFixups {
30 pub(crate) append: FxHashMap<SyntaxElement, Vec<Leaf>>,
31 pub(crate) remove: FxHashSet<SyntaxElement>,
32 pub(crate) undo_info: SyntaxFixupUndoInfo,
33}
34
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
37pub struct SyntaxFixupUndoInfo {
38 original: Option<ThinVec<TopSubtree>>,
39}
40
41impl SyntaxFixupUndoInfo {
42 pub(crate) const NONE: Self = SyntaxFixupUndoInfo { original: None };
43}
44
45const FIXUP_DUMMY_AST_ID: ErasedFileAstId = FIXUP_ERASED_FILE_AST_ID_MARKER;
47const FIXUP_DUMMY_RANGE: TextRange = TextRange::empty(TextSize::new(0));
48const FIXUP_DUMMY_RANGE_END: TextSize = TextSize::new(!0);
51
52pub(crate) fn fixup_syntax(
53 span_map: SpanMap<'_>,
54 node: &SyntaxNode,
55 call_site: Span,
56 mode: DocCommentDesugarMode,
57) -> SyntaxFixups {
58 let mut append = FxHashMap::<SyntaxElement, _>::default();
59 let mut remove = FxHashSet::<SyntaxElement>::default();
60 let mut preorder = node.preorder();
61 let mut original = ThinVec::new();
62 let dummy_range = FIXUP_DUMMY_RANGE;
63 let fake_span = |range| {
64 let span = span_map.span_for_range(range);
65 Span {
66 range: dummy_range,
67 anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor },
68 ctx: span.ctx,
69 }
70 };
71 while let Some(event) = preorder.next() {
72 let syntax::WalkEvent::Enter(node) = event else { continue };
73
74 let node_range = node.text_range();
75 if can_handle_error(&node) && has_error_to_handle(&node) {
76 remove.insert(node.clone().into());
77 let original_tree =
79 syntax_bridge::syntax_node_to_token_tree(&node, span_map, call_site, mode);
80 let idx = original.len() as u32;
81 original.push(original_tree);
82 let span = span_map.span_for_range(node_range);
83 let replacement = Leaf::Ident(Ident {
84 sym: sym::__ra_fixup,
85 span: Span {
86 range: TextRange::new(TextSize::new(idx), FIXUP_DUMMY_RANGE_END),
87 anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor },
88 ctx: span.ctx,
89 },
90 is_raw: tt::IdentIsRaw::No,
91 });
92 append.insert(node.clone().into(), vec![replacement]);
93 preorder.skip_subtree();
94 continue;
95 }
96 match_ast! {
98 match node {
99 ast::FieldExpr(it) => {
100 if it.name_ref().is_none() {
101 append.insert(node.clone().into(), vec![
103 Leaf::Ident(Ident {
104 sym: sym::__ra_fixup,
105 span: fake_span(node_range),
106 is_raw: tt::IdentIsRaw::No
107 }),
108 ]);
109 }
110 },
111 ast::ExprStmt(it) => {
112 let needs_semi = it.semicolon_token().is_none() && it.expr().is_some_and(|e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR);
113 if needs_semi {
114 append.insert(node.clone().into(), vec![
115 Leaf::Punct(Punct {
116 char: ';',
117 spacing: Spacing::Alone,
118 span: fake_span(node_range),
119 }),
120 ]);
121 }
122 },
123 ast::LetStmt(it) => {
124 if it.semicolon_token().is_none() {
125 append.insert(node.clone().into(), vec![
126 Leaf::Punct(Punct {
127 char: ';',
128 spacing: Spacing::Alone,
129 span: fake_span(node_range)
130 }),
131 ]);
132 }
133 },
134 ast::IfExpr(it) => {
135 if it.condition().is_none() {
136 let if_token = match it.if_token() {
138 Some(t) => t,
139 None => continue,
140 };
141 append.insert(if_token.into(), vec![
142 Leaf::Ident(Ident {
143 sym: sym::__ra_fixup,
144 span: fake_span(node_range),
145 is_raw: tt::IdentIsRaw::No
146 }),
147 ]);
148 }
149 if it.then_branch().is_none() {
150 append.insert(node.clone().into(), vec![
151 Leaf::Punct(Punct {
152 char: '{',
153 spacing: Spacing::Alone,
154 span: fake_span(node_range)
155 }),
156 Leaf::Punct(Punct {
157 char: '}',
158 spacing: Spacing::Alone,
159 span: fake_span(node_range)
160 }),
161 ]);
162 }
163 },
164 ast::WhileExpr(it) => {
165 if it.condition().is_none() {
166 let while_token = match it.while_token() {
168 Some(t) => t,
169 None => continue,
170 };
171 append.insert(while_token.into(), vec![
172 Leaf::Ident(Ident {
173 sym: sym::__ra_fixup,
174 span: fake_span(node_range),
175 is_raw: tt::IdentIsRaw::No
176 }),
177 ]);
178 }
179 if it.loop_body().is_none() {
180 append.insert(node.clone().into(), vec![
181 Leaf::Punct(Punct {
182 char: '{',
183 spacing: Spacing::Alone,
184 span: fake_span(node_range)
185 }),
186 Leaf::Punct(Punct {
187 char: '}',
188 spacing: Spacing::Alone,
189 span: fake_span(node_range)
190 }),
191 ]);
192 }
193 },
194 ast::LoopExpr(it) => {
195 if it.loop_body().is_none() {
196 append.insert(node.clone().into(), vec![
197 Leaf::Punct(Punct {
198 char: '{',
199 spacing: Spacing::Alone,
200 span: fake_span(node_range)
201 }),
202 Leaf::Punct(Punct {
203 char: '}',
204 spacing: Spacing::Alone,
205 span: fake_span(node_range)
206 }),
207 ]);
208 }
209 },
210 ast::MatchExpr(it) => {
211 if it.expr().is_none() {
212 let match_token = match it.match_token() {
213 Some(t) => t,
214 None => continue
215 };
216 append.insert(match_token.into(), vec![
217 Leaf::Ident(Ident {
218 sym: sym::__ra_fixup,
219 span: fake_span(node_range),
220 is_raw: tt::IdentIsRaw::No
221 }),
222 ]);
223 }
224 if it.match_arm_list().is_none() {
225 append.insert(node.clone().into(), vec![
227 Leaf::Punct(Punct {
228 char: '{',
229 spacing: Spacing::Alone,
230 span: fake_span(node_range)
231 }),
232 Leaf::Punct(Punct {
233 char: '}',
234 spacing: Spacing::Alone,
235 span: fake_span(node_range)
236 }),
237 ]);
238 }
239 },
240 ast::ForExpr(it) => {
241 let for_token = match it.for_token() {
242 Some(token) => token,
243 None => continue
244 };
245
246 let [pat, in_token, iter] = [
247 sym::underscore,
248 sym::in_,
249 sym::__ra_fixup,
250 ].map(|sym|
251 Leaf::Ident(Ident {
252 sym,
253 span: fake_span(node_range),
254 is_raw: tt::IdentIsRaw::No
255 }),
256 );
257
258 if it.pat().is_none() && it.in_token().is_none() && it.iterable().is_none() {
259 append.insert(for_token.into(), vec![pat, in_token, iter]);
260 } else if it.pat().is_none() {
262 append.insert(for_token.into(), vec![pat]);
263 }
264
265 if it.loop_body().is_none() {
266 append.insert(node.clone().into(), vec![
267 Leaf::Punct(Punct {
268 char: '{',
269 spacing: Spacing::Alone,
270 span: fake_span(node_range)
271 }),
272 Leaf::Punct(Punct {
273 char: '}',
274 spacing: Spacing::Alone,
275 span: fake_span(node_range)
276 }),
277 ]);
278 }
279 },
280 ast::RecordExprField(it) => {
281 if let Some(colon) = it.colon_token()
282 && it.name_ref().is_some() && it.expr().is_none() {
283 append.insert(colon.into(), vec![
284 Leaf::Ident(Ident {
285 sym: sym::__ra_fixup,
286 span: fake_span(node_range),
287 is_raw: tt::IdentIsRaw::No
288 })
289 ]);
290 }
291 },
292 ast::Path(it) => {
293 if let Some(colon) = it.coloncolon_token()
294 && it.segment().is_none() {
295 append.insert(colon.into(), vec![
296 Leaf::Ident(Ident {
297 sym: sym::__ra_fixup,
298 span: fake_span(node_range),
299 is_raw: tt::IdentIsRaw::No
300 })
301 ]);
302 }
303 },
304 ast::ClosureExpr(it) => {
305 if it.body().is_none() {
306 append.insert(node.into(), vec![
307 Leaf::Ident(Ident {
308 sym: sym::__ra_fixup,
309 span: fake_span(node_range),
310 is_raw: tt::IdentIsRaw::No
311 })
312 ]);
313 }
314 },
315 _ => (),
316 }
317 }
318 }
319 original.shrink_to_fit();
320 let needs_fixups = !append.is_empty() || !original.is_empty();
321 SyntaxFixups {
322 append,
323 remove,
324 undo_info: SyntaxFixupUndoInfo { original: needs_fixups.then_some(original) },
325 }
326}
327
328fn has_error(node: &SyntaxNode) -> bool {
329 node.children().any(|c| c.kind() == SyntaxKind::ERROR)
330}
331
332fn can_handle_error(node: &SyntaxNode) -> bool {
333 ast::Expr::can_cast(node.kind())
334}
335
336fn has_error_to_handle(node: &SyntaxNode) -> bool {
337 has_error(node) || node.children().any(|c| !can_handle_error(&c) && has_error_to_handle(&c))
338}
339
340pub(crate) fn reverse_fixups(tt: &mut TopSubtree, undo_info: &SyntaxFixupUndoInfo) {
341 let Some(undo_info) = &undo_info.original else { return };
342 let undo_info = &**undo_info;
343 let top_subtree = tt.top_subtree();
344 let open_span = top_subtree.delimiter.open;
345 let close_span = top_subtree.delimiter.close;
346 if never!(
347 close_span.anchor.ast_id == FIXUP_DUMMY_AST_ID
348 || open_span.anchor.ast_id == FIXUP_DUMMY_AST_ID
349 ) {
350 let span = |file_id| Span {
351 range: TextRange::empty(TextSize::new(0)),
352 anchor: SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID },
353 ctx: SyntaxContext::root(span::Edition::Edition2015),
354 };
355 tt.set_top_subtree_delimiter_span(tt::DelimSpan {
356 open: span(open_span.anchor.file_id),
357 close: span(close_span.anchor.file_id),
358 });
359 }
360 reverse_fixups_(tt, undo_info);
361}
362
363fn reverse_fixups_(tt: &mut TopSubtree, undo_info: &[TopSubtree]) {
364 transform_tt(tt, |tt| match tt {
365 tt::TokenTree::Leaf(leaf) => {
366 let span = leaf.span();
367 let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID;
368 let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
369 if !is_real_leaf && !is_replaced_node {
370 return TransformTtAction::remove();
371 }
372
373 if !is_real_leaf {
374 let original = &undo_info[u32::from(leaf.span().range.start()) as usize];
376 TransformTtAction::ReplaceWith(original.view().strip_invisible())
377 } else {
378 TransformTtAction::Keep
380 }
381 }
382 tt::TokenTree::Subtree(tt) => {
383 if tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
387 || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
388 {
389 return TransformTtAction::remove();
390 }
391 TransformTtAction::Keep
392 }
393 });
394}
395
396#[cfg(test)]
397mod tests {
398 use expect_test::{Expect, expect};
399 use span::{Edition, EditionedFileId, FileId};
400 use syntax::TextRange;
401 use syntax_bridge::DocCommentDesugarMode;
402
403 use crate::{
404 fixup::reverse_fixups,
405 span_map::{RealSpanMap, SpanMap},
406 tt,
407 };
408
409 fn check_leaf_eq(a: &tt::Leaf, b: &tt::Leaf) -> bool {
412 match (a, b) {
413 (tt::Leaf::Literal(a), tt::Leaf::Literal(b)) => a.text_and_suffix == b.text_and_suffix,
414 (tt::Leaf::Punct(a), tt::Leaf::Punct(b)) => a.char == b.char,
415 (tt::Leaf::Ident(a), tt::Leaf::Ident(b)) => a.sym == b.sym,
416 _ => false,
417 }
418 }
419
420 fn check_subtree_eq(a: &tt::TopSubtree, b: &tt::TopSubtree) -> bool {
421 let a = a.view().as_token_trees();
422 let b = b.view().as_token_trees();
423 a.len() == b.len()
424 && std::iter::zip(a.iter_flat_tokens(), b.iter_flat_tokens())
425 .all(|(a, b)| check_tt_eq(&a, &b))
426 }
427
428 fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool {
429 match (a, b) {
430 (tt::TokenTree::Leaf(a), tt::TokenTree::Leaf(b)) => check_leaf_eq(a, b),
431 (tt::TokenTree::Subtree(a), tt::TokenTree::Subtree(b)) => {
432 a.delimiter.kind == b.delimiter.kind
433 }
434 _ => false,
435 }
436 }
437
438 #[track_caller]
439 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, mut expect: Expect) {
440 let parsed = syntax::SourceFile::parse(ra_fixture, span::Edition::CURRENT);
441 let span_map = SpanMap::RealSpanMap(&RealSpanMap::absolute(EditionedFileId::new(
442 FileId::from_raw(0),
443 Edition::CURRENT,
444 )));
445 let fixups = super::fixup_syntax(
446 span_map,
447 &parsed.syntax_node(),
448 span_map.span_for_range(TextRange::empty(0.into())),
449 DocCommentDesugarMode::Mbe,
450 );
451 let mut tt = syntax_bridge::syntax_node_to_token_tree_modified(
452 &parsed.syntax_node(),
453 span_map,
454 fixups.append,
455 fixups.remove,
456 span_map.span_for_range(TextRange::empty(0.into())),
457 DocCommentDesugarMode::Mbe,
458 |_, _| (true, Vec::new()),
459 );
460
461 let actual = format!("{tt}\n");
462
463 expect.indent(false);
464 expect.assert_eq(&actual);
465
466 let (parse, _) = syntax_bridge::token_tree_to_syntax_node(
468 &tt,
469 syntax_bridge::TopEntryPoint::MacroItems,
470 &mut |_| parser::Edition::CURRENT,
471 );
472 assert!(
473 parse.errors().is_empty(),
474 "parse has syntax errors. parse tree:\n{:#?}",
475 parse.syntax_node()
476 );
477
478 for x in tt.token_trees().iter_flat_tokens() {
481 match x {
482 ::tt::TokenTree::Leaf(::tt::Leaf::Punct(punct)) => {
483 assert!(!matches!(punct.char, '{' | '}' | '(' | ')' | '[' | ']'))
484 }
485 _ => (),
486 }
487 }
488
489 reverse_fixups(&mut tt, &fixups.undo_info);
490
491 let original_as_tt = syntax_bridge::syntax_node_to_token_tree(
494 &parsed.syntax_node(),
495 span_map,
496 span_map.span_for_range(TextRange::empty(0.into())),
497 DocCommentDesugarMode::Mbe,
498 );
499 assert!(
500 check_subtree_eq(&tt, &original_as_tt),
501 "different token tree:\n{tt:?}\n\n{original_as_tt:?}"
502 );
503 }
504
505 #[test]
506 fn just_for_token() {
507 check(
508 r#"
509fn foo() {
510 for
511}
512"#,
513 expect![[r#"
514fn foo () {for _ in __ra_fixup {}}
515"#]],
516 )
517 }
518
519 #[test]
520 fn for_no_iter_pattern() {
521 check(
522 r#"
523fn foo() {
524 for {}
525}
526"#,
527 expect![[r#"
528fn foo () {for _ in __ra_fixup {}}
529"#]],
530 )
531 }
532
533 #[test]
534 fn for_no_body() {
535 check(
536 r#"
537fn foo() {
538 for bar in qux
539}
540"#,
541 expect![[r#"
542fn foo () {for bar in qux {}}
543"#]],
544 )
545 }
546
547 #[test]
549 fn for_no_pat() {
550 check(
551 r#"
552fn foo() {
553 for in qux {
554
555 }
556}
557"#,
558 expect![[r#"
559fn foo () {__ra_fixup}
560"#]],
561 )
562 }
563
564 #[test]
565 fn match_no_expr_no_arms() {
566 check(
567 r#"
568fn foo() {
569 match
570}
571"#,
572 expect![[r#"
573fn foo () {match __ra_fixup {}}
574"#]],
575 )
576 }
577
578 #[test]
579 fn match_expr_no_arms() {
580 check(
581 r#"
582fn foo() {
583 match it {
584
585 }
586}
587"#,
588 expect![[r#"
589fn foo () {match it {}}
590"#]],
591 )
592 }
593
594 #[test]
595 fn match_no_expr() {
596 check(
597 r#"
598fn foo() {
599 match {
600 _ => {}
601 }
602}
603"#,
604 expect![[r#"
605fn foo () {match __ra_fixup {}}
606"#]],
607 )
608 }
609
610 #[test]
611 fn incomplete_field_expr_1() {
612 check(
613 r#"
614fn foo() {
615 a.
616}
617"#,
618 expect![[r#"
619fn foo () {a . __ra_fixup}
620"#]],
621 )
622 }
623
624 #[test]
625 fn incomplete_field_expr_2() {
626 check(
627 r#"
628fn foo() {
629 a.;
630}
631"#,
632 expect![[r#"
633fn foo () {a .__ra_fixup ;}
634"#]],
635 )
636 }
637
638 #[test]
639 fn incomplete_field_expr_3() {
640 check(
641 r#"
642fn foo() {
643 a.;
644 bar();
645}
646"#,
647 expect![[r#"
648fn foo () {a .__ra_fixup ; bar () ;}
649"#]],
650 )
651 }
652
653 #[test]
654 fn incomplete_let() {
655 check(
656 r#"
657fn foo() {
658 let it = a
659}
660"#,
661 expect![[r#"
662fn foo () {let it = a ;}
663"#]],
664 )
665 }
666
667 #[test]
668 fn incomplete_field_expr_in_let() {
669 check(
670 r#"
671fn foo() {
672 let it = a.
673}
674"#,
675 expect![[r#"
676fn foo () {let it = a . __ra_fixup ;}
677"#]],
678 )
679 }
680
681 #[test]
682 fn field_expr_before_call() {
683 check(
685 r#"
686fn foo() {
687 a.b
688 bar();
689}
690"#,
691 expect![[r#"
692fn foo () {a . b ; bar () ;}
693"#]],
694 )
695 }
696
697 #[test]
698 fn extraneous_comma() {
699 check(
700 r#"
701fn foo() {
702 bar(,);
703}
704"#,
705 expect![[r#"
706fn foo () {__ra_fixup ;}
707"#]],
708 )
709 }
710
711 #[test]
712 fn fixup_if_1() {
713 check(
714 r#"
715fn foo() {
716 if a
717}
718"#,
719 expect![[r#"
720fn foo () {if a {}}
721"#]],
722 )
723 }
724
725 #[test]
726 fn fixup_if_2() {
727 check(
728 r#"
729fn foo() {
730 if
731}
732"#,
733 expect![[r#"
734fn foo () {if __ra_fixup {}}
735"#]],
736 )
737 }
738
739 #[test]
740 fn fixup_if_3() {
741 check(
742 r#"
743fn foo() {
744 if {}
745}
746"#,
747 expect![[r#"
748fn foo () {if __ra_fixup {} {}}
749"#]],
750 )
751 }
752
753 #[test]
754 fn fixup_while_1() {
755 check(
756 r#"
757fn foo() {
758 while
759}
760"#,
761 expect![[r#"
762fn foo () {while __ra_fixup {}}
763"#]],
764 )
765 }
766
767 #[test]
768 fn fixup_while_2() {
769 check(
770 r#"
771fn foo() {
772 while foo
773}
774"#,
775 expect![[r#"
776fn foo () {while foo {}}
777"#]],
778 )
779 }
780 #[test]
781 fn fixup_while_3() {
782 check(
783 r#"
784fn foo() {
785 while {}
786}
787"#,
788 expect![[r#"
789fn foo () {while __ra_fixup {}}
790"#]],
791 )
792 }
793
794 #[test]
795 fn fixup_loop() {
796 check(
797 r#"
798fn foo() {
799 loop
800}
801"#,
802 expect![[r#"
803fn foo () {loop {}}
804"#]],
805 )
806 }
807
808 #[test]
809 fn fixup_path() {
810 check(
811 r#"
812fn foo() {
813 path::
814}
815"#,
816 expect![[r#"
817fn foo () {path :: __ra_fixup}
818"#]],
819 )
820 }
821
822 #[test]
823 fn fixup_record_ctor_field() {
824 check(
825 r#"
826fn foo() {
827 R { f: }
828}
829"#,
830 expect![[r#"
831fn foo () {R {f : __ra_fixup}}
832"#]],
833 )
834 }
835
836 #[test]
837 fn no_fixup_record_ctor_field() {
838 check(
839 r#"
840fn foo() {
841 R { f: a }
842}
843"#,
844 expect![[r#"
845fn foo () {R {f : a}}
846"#]],
847 )
848 }
849
850 #[test]
851 fn fixup_arg_list() {
852 check(
853 r#"
854fn foo() {
855 foo(a
856}
857"#,
858 expect![[r#"
859fn foo () {foo (a)}
860"#]],
861 );
862 check(
863 r#"
864fn foo() {
865 bar.foo(a
866}
867"#,
868 expect![[r#"
869fn foo () {bar . foo (a)}
870"#]],
871 );
872 }
873
874 #[test]
875 fn fixup_closure() {
876 check(
877 r#"
878fn foo() {
879 ||
880}
881"#,
882 expect![[r#"
883fn foo () {|| __ra_fixup}
884"#]],
885 );
886 }
887
888 #[test]
889 fn fixup_regression_() {
890 check(
891 r#"
892fn foo() {
893 {}
894 {}
895}
896"#,
897 expect![[r#"
898fn foo () {{} {}}
899"#]],
900 );
901 }
902}