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 #[allow(deprecated)]
347 if never!(
348 close_span.anchor.ast_id == FIXUP_DUMMY_AST_ID
349 || open_span.anchor.ast_id == FIXUP_DUMMY_AST_ID
350 ) {
351 let span = |file_id| Span {
352 range: TextRange::empty(TextSize::new(0)),
353 anchor: SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID },
354 ctx: SyntaxContext::root(span::Edition::Edition2015),
355 };
356 tt.set_top_subtree_delimiter_span(tt::DelimSpan {
357 open: span(open_span.anchor.file_id),
358 close: span(close_span.anchor.file_id),
359 });
360 }
361 reverse_fixups_(tt, undo_info);
362}
363
364fn reverse_fixups_(tt: &mut TopSubtree, undo_info: &[TopSubtree]) {
365 transform_tt(tt, |tt| match tt {
366 tt::TokenTree::Leaf(leaf) => {
367 let span = leaf.span();
368 let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID;
369 let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
370 if !is_real_leaf && !is_replaced_node {
371 return TransformTtAction::remove();
372 }
373
374 if !is_real_leaf {
375 let original = &undo_info[u32::from(leaf.span().range.start()) as usize];
377 TransformTtAction::ReplaceWith(original.view().strip_invisible())
378 } else {
379 TransformTtAction::Keep
381 }
382 }
383 tt::TokenTree::Subtree(tt) => {
384 if tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID
388 || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID
389 {
390 return TransformTtAction::remove();
391 }
392 TransformTtAction::Keep
393 }
394 });
395}
396
397#[cfg(test)]
398mod tests {
399 use expect_test::{Expect, expect};
400 use span::{Edition, EditionedFileId, FileId};
401 use syntax::TextRange;
402 use syntax_bridge::DocCommentDesugarMode;
403
404 use crate::{
405 fixup::reverse_fixups,
406 span_map::{RealSpanMap, SpanMap},
407 tt,
408 };
409
410 fn check_leaf_eq(a: &tt::Leaf, b: &tt::Leaf) -> bool {
413 match (a, b) {
414 (tt::Leaf::Literal(a), tt::Leaf::Literal(b)) => a.text_and_suffix == b.text_and_suffix,
415 (tt::Leaf::Punct(a), tt::Leaf::Punct(b)) => a.char == b.char,
416 (tt::Leaf::Ident(a), tt::Leaf::Ident(b)) => a.sym == b.sym,
417 _ => false,
418 }
419 }
420
421 fn check_subtree_eq(a: &tt::TopSubtree, b: &tt::TopSubtree) -> bool {
422 let a = a.view().as_token_trees().iter_flat_tokens();
423 let b = b.view().as_token_trees().iter_flat_tokens();
424 a.len() == b.len() && std::iter::zip(a, b).all(|(a, b)| check_tt_eq(&a, &b))
425 }
426
427 fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool {
428 match (a, b) {
429 (tt::TokenTree::Leaf(a), tt::TokenTree::Leaf(b)) => check_leaf_eq(a, b),
430 (tt::TokenTree::Subtree(a), tt::TokenTree::Subtree(b)) => {
431 a.delimiter.kind == b.delimiter.kind
432 }
433 _ => false,
434 }
435 }
436
437 #[track_caller]
438 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, mut expect: Expect) {
439 let parsed = syntax::SourceFile::parse(ra_fixture, span::Edition::CURRENT);
440 let span_map = SpanMap::RealSpanMap(&RealSpanMap::absolute(EditionedFileId::new(
441 FileId::from_raw(0),
442 Edition::CURRENT,
443 )));
444 let fixups = super::fixup_syntax(
445 span_map,
446 &parsed.syntax_node(),
447 span_map.span_for_range(TextRange::empty(0.into())),
448 DocCommentDesugarMode::Mbe,
449 );
450 let mut tt = syntax_bridge::syntax_node_to_token_tree_modified(
451 &parsed.syntax_node(),
452 span_map,
453 fixups.append,
454 fixups.remove,
455 span_map.span_for_range(TextRange::empty(0.into())),
456 DocCommentDesugarMode::Mbe,
457 |_, _| (true, Vec::new()),
458 );
459
460 let actual = format!("{tt}\n");
461
462 expect.indent(false);
463 expect.assert_eq(&actual);
464
465 let (parse, _) = syntax_bridge::token_tree_to_syntax_node(
467 &tt,
468 syntax_bridge::TopEntryPoint::MacroItems,
469 &mut |_| parser::Edition::CURRENT,
470 );
471 assert!(
472 parse.errors().is_empty(),
473 "parse has syntax errors. parse tree:\n{:#?}",
474 parse.syntax_node()
475 );
476
477 for x in tt.token_trees().iter_flat_tokens() {
480 match x {
481 ::tt::TokenTree::Leaf(::tt::Leaf::Punct(punct)) => {
482 assert!(!matches!(punct.char, '{' | '}' | '(' | ')' | '[' | ']'))
483 }
484 _ => (),
485 }
486 }
487
488 reverse_fixups(&mut tt, &fixups.undo_info);
489
490 let original_as_tt = syntax_bridge::syntax_node_to_token_tree(
493 &parsed.syntax_node(),
494 span_map,
495 span_map.span_for_range(TextRange::empty(0.into())),
496 DocCommentDesugarMode::Mbe,
497 );
498 assert!(
499 check_subtree_eq(&tt, &original_as_tt),
500 "different token tree:\n{tt:?}\n\n{original_as_tt:?}"
501 );
502 }
503
504 #[test]
505 fn just_for_token() {
506 check(
507 r#"
508fn foo() {
509 for
510}
511"#,
512 expect![[r#"
513fn foo () {for _ in __ra_fixup {}}
514"#]],
515 )
516 }
517
518 #[test]
519 fn for_no_iter_pattern() {
520 check(
521 r#"
522fn foo() {
523 for {}
524}
525"#,
526 expect![[r#"
527fn foo () {for _ in __ra_fixup {}}
528"#]],
529 )
530 }
531
532 #[test]
533 fn for_no_body() {
534 check(
535 r#"
536fn foo() {
537 for bar in qux
538}
539"#,
540 expect![[r#"
541fn foo () {for bar in qux {}}
542"#]],
543 )
544 }
545
546 #[test]
548 fn for_no_pat() {
549 check(
550 r#"
551fn foo() {
552 for in qux {
553
554 }
555}
556"#,
557 expect![[r#"
558fn foo () {__ra_fixup}
559"#]],
560 )
561 }
562
563 #[test]
564 fn match_no_expr_no_arms() {
565 check(
566 r#"
567fn foo() {
568 match
569}
570"#,
571 expect![[r#"
572fn foo () {match __ra_fixup {}}
573"#]],
574 )
575 }
576
577 #[test]
578 fn match_expr_no_arms() {
579 check(
580 r#"
581fn foo() {
582 match it {
583
584 }
585}
586"#,
587 expect![[r#"
588fn foo () {match it {}}
589"#]],
590 )
591 }
592
593 #[test]
594 fn match_no_expr() {
595 check(
596 r#"
597fn foo() {
598 match {
599 _ => {}
600 }
601}
602"#,
603 expect![[r#"
604fn foo () {match __ra_fixup {}}
605"#]],
606 )
607 }
608
609 #[test]
610 fn incomplete_field_expr_1() {
611 check(
612 r#"
613fn foo() {
614 a.
615}
616"#,
617 expect![[r#"
618fn foo () {a . __ra_fixup}
619"#]],
620 )
621 }
622
623 #[test]
624 fn incomplete_field_expr_2() {
625 check(
626 r#"
627fn foo() {
628 a.;
629}
630"#,
631 expect![[r#"
632fn foo () {a .__ra_fixup ;}
633"#]],
634 )
635 }
636
637 #[test]
638 fn incomplete_field_expr_3() {
639 check(
640 r#"
641fn foo() {
642 a.;
643 bar();
644}
645"#,
646 expect![[r#"
647fn foo () {a .__ra_fixup ; bar () ;}
648"#]],
649 )
650 }
651
652 #[test]
653 fn incomplete_let() {
654 check(
655 r#"
656fn foo() {
657 let it = a
658}
659"#,
660 expect![[r#"
661fn foo () {let it = a ;}
662"#]],
663 )
664 }
665
666 #[test]
667 fn incomplete_field_expr_in_let() {
668 check(
669 r#"
670fn foo() {
671 let it = a.
672}
673"#,
674 expect![[r#"
675fn foo () {let it = a . __ra_fixup ;}
676"#]],
677 )
678 }
679
680 #[test]
681 fn field_expr_before_call() {
682 check(
684 r#"
685fn foo() {
686 a.b
687 bar();
688}
689"#,
690 expect![[r#"
691fn foo () {a . b ; bar () ;}
692"#]],
693 )
694 }
695
696 #[test]
697 fn extraneous_comma() {
698 check(
699 r#"
700fn foo() {
701 bar(,);
702}
703"#,
704 expect![[r#"
705fn foo () {__ra_fixup ;}
706"#]],
707 )
708 }
709
710 #[test]
711 fn fixup_if_1() {
712 check(
713 r#"
714fn foo() {
715 if a
716}
717"#,
718 expect![[r#"
719fn foo () {if a {}}
720"#]],
721 )
722 }
723
724 #[test]
725 fn fixup_if_2() {
726 check(
727 r#"
728fn foo() {
729 if
730}
731"#,
732 expect![[r#"
733fn foo () {if __ra_fixup {}}
734"#]],
735 )
736 }
737
738 #[test]
739 fn fixup_if_3() {
740 check(
741 r#"
742fn foo() {
743 if {}
744}
745"#,
746 expect![[r#"
747fn foo () {if __ra_fixup {} {}}
748"#]],
749 )
750 }
751
752 #[test]
753 fn fixup_while_1() {
754 check(
755 r#"
756fn foo() {
757 while
758}
759"#,
760 expect![[r#"
761fn foo () {while __ra_fixup {}}
762"#]],
763 )
764 }
765
766 #[test]
767 fn fixup_while_2() {
768 check(
769 r#"
770fn foo() {
771 while foo
772}
773"#,
774 expect![[r#"
775fn foo () {while foo {}}
776"#]],
777 )
778 }
779 #[test]
780 fn fixup_while_3() {
781 check(
782 r#"
783fn foo() {
784 while {}
785}
786"#,
787 expect![[r#"
788fn foo () {while __ra_fixup {}}
789"#]],
790 )
791 }
792
793 #[test]
794 fn fixup_loop() {
795 check(
796 r#"
797fn foo() {
798 loop
799}
800"#,
801 expect![[r#"
802fn foo () {loop {}}
803"#]],
804 )
805 }
806
807 #[test]
808 fn fixup_path() {
809 check(
810 r#"
811fn foo() {
812 path::
813}
814"#,
815 expect![[r#"
816fn foo () {path :: __ra_fixup}
817"#]],
818 )
819 }
820
821 #[test]
822 fn fixup_record_ctor_field() {
823 check(
824 r#"
825fn foo() {
826 R { f: }
827}
828"#,
829 expect![[r#"
830fn foo () {R {f : __ra_fixup}}
831"#]],
832 )
833 }
834
835 #[test]
836 fn no_fixup_record_ctor_field() {
837 check(
838 r#"
839fn foo() {
840 R { f: a }
841}
842"#,
843 expect![[r#"
844fn foo () {R {f : a}}
845"#]],
846 )
847 }
848
849 #[test]
850 fn fixup_arg_list() {
851 check(
852 r#"
853fn foo() {
854 foo(a
855}
856"#,
857 expect![[r#"
858fn foo () {foo (a)}
859"#]],
860 );
861 check(
862 r#"
863fn foo() {
864 bar.foo(a
865}
866"#,
867 expect![[r#"
868fn foo () {bar . foo (a)}
869"#]],
870 );
871 }
872
873 #[test]
874 fn fixup_closure() {
875 check(
876 r#"
877fn foo() {
878 ||
879}
880"#,
881 expect![[r#"
882fn foo () {|| __ra_fixup}
883"#]],
884 );
885 }
886
887 #[test]
888 fn fixup_regression_() {
889 check(
890 r#"
891fn foo() {
892 {}
893 {}
894}
895"#,
896 expect![[r#"
897fn foo () {{} {}}
898"#]],
899 );
900 }
901}