1mod quote;
15
16use either::Either;
17use itertools::Itertools;
18use parser::{Edition, T};
19use rowan::NodeOrToken;
20use stdx::{format_to, format_to_acc, never};
21
22use crate::{
23 AstNode, SourceFile, SyntaxKind, SyntaxToken,
24 ast::{self, Param, make::quote::quote},
25 utils::is_raw_identifier,
26};
27
28pub mod ext {
33 use super::*;
34
35 pub fn simple_ident_pat(name: ast::Name) -> ast::IdentPat {
36 ast_from_text(&format!("fn f({}: ())", name.text()))
37 }
38
39 pub fn ident_path(ident: &str) -> ast::Path {
40 path_unqualified(path_segment(name_ref(ident)))
41 }
42
43 pub fn path_from_idents<'a>(
44 parts: impl std::iter::IntoIterator<Item = &'a str>,
45 ) -> Option<ast::Path> {
46 let mut iter = parts.into_iter();
47 let base = ext::ident_path(iter.next()?);
48 let path = iter.fold(base, |base, s| {
49 let path = ext::ident_path(s);
50 path_concat(base, path)
51 });
52 Some(path)
53 }
54
55 pub fn field_from_idents<'a>(
56 parts: impl std::iter::IntoIterator<Item = &'a str>,
57 ) -> Option<ast::Expr> {
58 let mut iter = parts.into_iter();
59 let base = expr_path(ext::ident_path(iter.next()?));
60 let expr = iter.fold(base, expr_field);
61 Some(expr)
62 }
63
64 pub fn expr_unit() -> ast::Expr {
65 expr_tuple([]).into()
66 }
67 pub fn expr_unreachable() -> ast::Expr {
68 expr_from_text("unreachable!()")
69 }
70 pub fn expr_todo() -> ast::Expr {
71 expr_from_text("todo!()")
72 }
73 pub fn expr_underscore() -> ast::Expr {
74 expr_from_text("_")
75 }
76 pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr {
77 expr_from_text(&format!("{ty}::default()"))
78 }
79 pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr {
80 expr_from_text(&format!("{ty}::new()"))
81 }
82 pub fn expr_self() -> ast::Expr {
83 expr_from_text("self")
84 }
85 pub fn zero_number() -> ast::Expr {
86 expr_from_text("0")
87 }
88 pub fn zero_float() -> ast::Expr {
89 expr_from_text("0.0")
90 }
91 pub fn empty_str() -> ast::Expr {
92 expr_from_text(r#""""#)
93 }
94 pub fn empty_char() -> ast::Expr {
95 expr_from_text("'\x00'")
96 }
97 pub fn default_bool() -> ast::Expr {
98 expr_from_text("false")
99 }
100 pub fn option_none() -> ast::Expr {
101 expr_from_text("None")
102 }
103 pub fn empty_block_expr() -> ast::BlockExpr {
104 block_expr(None, None)
105 }
106
107 pub fn ty_name(name: ast::Name) -> ast::Type {
108 ty_path(ident_path(&name.to_string()))
109 }
110 pub fn ty_bool() -> ast::Type {
111 ty_path(ident_path("bool"))
112 }
113 pub fn ty_option(t: ast::Type) -> ast::Type {
114 ty_from_text(&format!("Option<{t}>"))
115 }
116 pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type {
117 ty_from_text(&format!("Result<{t}, {e}>"))
118 }
119
120 pub fn token_tree_from_node(node: &ast::SyntaxNode) -> ast::TokenTree {
121 ast_from_text(&format!("todo!{node}"))
122 }
123}
124
125pub fn name(name: &str) -> ast::Name {
126 let raw_escape = raw_ident_esc(name);
127 ast_from_text(&format!("mod {raw_escape}{name};"))
128}
129pub fn name_ref(name_ref: &str) -> ast::NameRef {
130 let raw_escape = raw_ident_esc(name_ref);
131 quote! {
132 NameRef {
133 [IDENT format!("{raw_escape}{name_ref}")]
134 }
135 }
136}
137pub fn name_ref_self_ty() -> ast::NameRef {
138 quote! {
139 NameRef {
140 [Self]
141 }
142 }
143}
144fn raw_ident_esc(ident: &str) -> &'static str {
145 if is_raw_identifier(ident, Edition::CURRENT) { "r#" } else { "" }
146}
147
148pub fn lifetime(text: &str) -> ast::Lifetime {
149 let mut text = text;
150 let tmp;
151 if never!(!text.starts_with('\'')) {
152 tmp = format!("'{text}");
153 text = &tmp;
154 }
155 quote! {
156 Lifetime {
157 [LIFETIME_IDENT text]
158 }
159 }
160}
161
162pub fn ty(text: &str) -> ast::Type {
165 ty_from_text(text)
166}
167pub fn ty_placeholder() -> ast::Type {
168 ty_from_text("_")
169}
170pub fn ty_unit() -> ast::Type {
171 ty_from_text("()")
172}
173pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
174 let mut count: usize = 0;
175 let mut contents = types.into_iter().inspect(|_| count += 1).join(", ");
176 if count == 1 {
177 contents.push(',');
178 }
179
180 ty_from_text(&format!("({contents})"))
181}
182pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type {
183 ty_from_text(&if exclusive { format!("&mut {target}") } else { format!("&{target}") })
184}
185pub fn ty_path(path: ast::Path) -> ast::Type {
186 ty_from_text(&path.to_string())
187}
188fn ty_from_text(text: &str) -> ast::Type {
189 ast_from_text(&format!("type _T = {text};"))
190}
191
192pub fn ty_alias(
193 attrs: impl IntoIterator<Item = ast::Attr>,
194 ident: &str,
195 generic_param_list: Option<ast::GenericParamList>,
196 type_param_bounds: Option<ast::TypeParam>,
197 where_clause: Option<ast::WhereClause>,
198 assignment: Option<(ast::Type, Option<ast::WhereClause>)>,
199) -> ast::TypeAlias {
200 let (assignment_ty, assignment_where) = assignment.unzip();
201 let assignment_where = assignment_where.flatten();
202 quote! {
203 TypeAlias {
204 #(#attrs "\n")*
205 [type] " "
206 Name { [IDENT ident] }
207 #generic_param_list
208 #(" " [:] " " #type_param_bounds)*
209 #(" " #where_clause)*
210 #(" " [=] " " #assignment_ty)*
211 #(" " #assignment_where)*
212 [;]
213 }
214 }
215}
216
217pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
218 is_unsafe: bool,
219 abi: Option<ast::Abi>,
220 mut params: I,
221 ret_type: Option<ast::RetType>,
222) -> ast::FnPtrType {
223 let is_unsafe = is_unsafe.then_some(());
224 let first_param = params.next();
225 quote! {
226 FnPtrType {
227 #(#is_unsafe [unsafe] " ")* #(#abi " ")* [fn]
228 ['('] #first_param #([,] " " #params)* [')']
229 #(" " #ret_type)*
230 }
231 }
232}
233
234pub fn item_list(body: Option<Vec<ast::Item>>) -> ast::ItemList {
235 let is_break_braces = body.is_some();
236 let body_newline = if is_break_braces { "\n" } else { "" };
237 let body_indent = if is_break_braces { " " } else { "" };
238
239 let body = match body {
240 Some(bd) => bd.iter().map(|elem| elem.to_string()).join("\n\n "),
241 None => String::new(),
242 };
243 ast_from_text(&format!("mod C {{{body_newline}{body_indent}{body}{body_newline}}}"))
244}
245
246pub fn mod_(name: ast::Name, body: Option<ast::ItemList>) -> ast::Module {
247 let body = body.map_or(";".to_owned(), |body| format!(" {body}"));
248 ast_from_text(&format!("mod {name}{body}"))
249}
250
251pub fn assoc_item_list(body: Option<Vec<ast::AssocItem>>) -> ast::AssocItemList {
252 let is_break_braces = body.is_some();
253 let body_newline = if is_break_braces { "\n".to_owned() } else { String::new() };
254 let body_indent = if is_break_braces { " ".to_owned() } else { String::new() };
255
256 let body = match body {
257 Some(bd) => bd.iter().map(|elem| elem.to_string()).join("\n\n "),
258 None => String::new(),
259 };
260 ast_from_text(&format!("impl C for D {{{body_newline}{body_indent}{body}{body_newline}}}"))
261}
262
263fn merge_gen_params(
264 ps: Option<ast::GenericParamList>,
265 bs: Option<ast::GenericParamList>,
266) -> Option<ast::GenericParamList> {
267 match (ps, bs) {
268 (None, None) => None,
269 (None, Some(bs)) => Some(bs),
270 (Some(ps), None) => Some(ps),
271 (Some(ps), Some(bs)) => {
272 let generic_params = ps.generic_params().merge_by(bs.generic_params(), |_, b| {
274 !matches!(b, ast::GenericParam::LifetimeParam(_))
275 });
276 Some(generic_param_list(generic_params))
277 }
278 }
279}
280
281fn merge_where_clause(
282 ps: Option<ast::WhereClause>,
283 bs: Option<ast::WhereClause>,
284) -> Option<ast::WhereClause> {
285 match (ps, bs) {
286 (None, None) => None,
287 (None, Some(bs)) => Some(bs),
288 (Some(ps), None) => Some(ps),
289 (Some(ps), Some(bs)) => {
290 let preds = where_clause(std::iter::empty()).clone_for_update();
291 ps.predicates().for_each(|p| preds.add_predicate(p));
292 bs.predicates().for_each(|p| preds.add_predicate(p));
293 Some(preds)
294 }
295 }
296}
297
298pub fn impl_(
299 attrs: impl IntoIterator<Item = ast::Attr>,
300 generic_params: Option<ast::GenericParamList>,
301 generic_args: Option<ast::GenericArgList>,
302 path_type: ast::Type,
303 where_clause: Option<ast::WhereClause>,
304 body: Option<ast::AssocItemList>,
305) -> ast::Impl {
306 let attrs =
307 attrs.into_iter().fold(String::new(), |mut acc, attr| format_to_acc!(acc, "{}\n", attr));
308
309 let gen_args = generic_args.map_or_else(String::new, |it| it.to_string());
310
311 let gen_params = generic_params.map_or_else(String::new, |it| it.to_string());
312
313 let body_newline =
314 if where_clause.is_some() && body.is_none() { "\n".to_owned() } else { String::new() };
315 let where_clause = match where_clause {
316 Some(pr) => format!("\n{pr}\n"),
317 None => " ".to_owned(),
318 };
319
320 let body = body.map_or_else(|| format!("{{{body_newline}}}"), |it| it.to_string());
321 ast_from_text(&format!("{attrs}impl{gen_params} {path_type}{gen_args}{where_clause}{body}"))
322}
323
324pub fn impl_trait(
325 attrs: impl IntoIterator<Item = ast::Attr>,
326 is_unsafe: bool,
327 trait_gen_params: Option<ast::GenericParamList>,
328 trait_gen_args: Option<ast::GenericArgList>,
329 type_gen_params: Option<ast::GenericParamList>,
330 type_gen_args: Option<ast::GenericArgList>,
331 is_negative: bool,
332 path_type: ast::Type,
333 ty: ast::Type,
334 trait_where_clause: Option<ast::WhereClause>,
335 ty_where_clause: Option<ast::WhereClause>,
336 body: Option<ast::AssocItemList>,
337) -> ast::Impl {
338 let attrs =
339 attrs.into_iter().fold(String::new(), |mut acc, attr| format_to_acc!(acc, "{}\n", attr));
340 let is_unsafe = if is_unsafe { "unsafe " } else { "" };
341
342 let trait_gen_args = trait_gen_args.map(|args| args.to_string()).unwrap_or_default();
343 let type_gen_args = type_gen_args.map(|args| args.to_string()).unwrap_or_default();
344
345 let gen_params = merge_gen_params(trait_gen_params, type_gen_params)
346 .map_or_else(String::new, |it| it.to_string());
347
348 let is_negative = if is_negative { "! " } else { "" };
349
350 let body_newline =
351 if (ty_where_clause.is_some() || trait_where_clause.is_some()) && body.is_none() {
352 "\n".to_owned()
353 } else {
354 String::new()
355 };
356
357 let where_clause = merge_where_clause(ty_where_clause, trait_where_clause)
358 .map_or_else(|| " ".to_owned(), |wc| format!("\n{wc}\n"));
359
360 let body = body.map_or_else(|| format!("{{{body_newline}}}"), |it| it.to_string());
361
362 ast_from_text(&format!(
363 "{attrs}{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{body}"
364 ))
365}
366
367pub fn impl_trait_type(bounds: ast::TypeBoundList) -> ast::ImplTraitType {
368 ast_from_text(&format!("fn f(x: impl {bounds}) {{}}"))
369}
370
371pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
372 ast_from_text(&format!("type __ = {name_ref};"))
373}
374
375pub fn generic_ty_path_segment(
378 name_ref: ast::NameRef,
379 generic_args: impl IntoIterator<Item = ast::GenericArg>,
380) -> ast::PathSegment {
381 let mut generic_args = generic_args.into_iter();
382 let first_generic_arg = generic_args.next();
383 quote! {
384 PathSegment {
385 #name_ref
386 GenericArgList {
387 [<] #first_generic_arg #([,] " " #generic_args)* [>]
388 }
389 }
390 }
391}
392
393pub fn path_segment_ty(type_ref: ast::Type, trait_ref: Option<ast::PathType>) -> ast::PathSegment {
394 let text = match trait_ref {
395 Some(trait_ref) => format!("fn f(x: <{type_ref} as {trait_ref}>) {{}}"),
396 None => format!("fn f(x: <{type_ref}>) {{}}"),
397 };
398 ast_from_text(&text)
399}
400
401pub fn path_segment_self() -> ast::PathSegment {
402 ast_from_text("use self;")
403}
404
405pub fn path_segment_super() -> ast::PathSegment {
406 ast_from_text("use super;")
407}
408
409pub fn path_segment_crate() -> ast::PathSegment {
410 ast_from_text("use crate;")
411}
412
413pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
414 ast_from_text(&format!("type __ = {segment};"))
415}
416
417pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
418 ast_from_text(&format!("{qual}::{segment}"))
419}
420pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path {
422 ast_from_text(&format!("type __ = {first}::{second};"))
423}
424
425pub fn path_from_segments(
426 segments: impl IntoIterator<Item = ast::PathSegment>,
427 is_abs: bool,
428) -> ast::Path {
429 let segments = segments.into_iter().map(|it| it.syntax().clone()).join("::");
430 ast_from_text(&if is_abs {
431 format!("fn f(x: ::{segments}) {{}}")
432 } else {
433 format!("fn f(x: {segments}) {{}}")
434 })
435}
436
437pub fn join_paths(paths: impl IntoIterator<Item = ast::Path>) -> ast::Path {
438 let paths = paths.into_iter().map(|it| it.syntax().clone()).join("::");
439 ast_from_text(&format!("type __ = {paths};"))
440}
441
442pub fn path_from_text(text: &str) -> ast::Path {
444 ast_from_text(&format!("fn main() {{ let test: {text}; }}"))
445}
446
447pub fn path_from_text_with_edition(text: &str, edition: Edition) -> ast::Path {
449 ast_from_text_with_edition(&format!("fn main() {{ let test: {text}; }}"), edition)
450}
451
452pub fn use_tree_glob() -> ast::UseTree {
453 ast_from_text("use *;")
454}
455pub fn use_tree(
456 path: ast::Path,
457 use_tree_list: Option<ast::UseTreeList>,
458 alias: Option<ast::Rename>,
459 add_star: bool,
460) -> ast::UseTree {
461 let mut buf = "use ".to_owned();
462 buf += &path.syntax().to_string();
463 if let Some(use_tree_list) = use_tree_list {
464 format_to!(buf, "::{use_tree_list}");
465 }
466 if add_star {
467 buf += "::*";
468 }
469
470 if let Some(alias) = alias {
471 format_to!(buf, " {alias}");
472 }
473 ast_from_text(&buf)
474}
475
476pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList {
477 let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", ");
478 ast_from_text(&format!("use {{{use_trees}}};"))
479}
480
481pub fn use_(
482 attrs: impl IntoIterator<Item = ast::Attr>,
483 visibility: Option<ast::Visibility>,
484 use_tree: ast::UseTree,
485) -> ast::Use {
486 let attrs =
487 attrs.into_iter().fold(String::new(), |mut acc, attr| format_to_acc!(acc, "{}\n", attr));
488 let visibility = match visibility {
489 None => String::new(),
490 Some(it) => format!("{it} "),
491 };
492 ast_from_text(&format!("{attrs}{visibility}use {use_tree};"))
493}
494
495pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr {
496 ast_from_text(&format!("fn f() {{ {path} {fields} }}"))
497}
498
499pub fn record_expr_field_list(
500 fields: impl IntoIterator<Item = ast::RecordExprField>,
501) -> ast::RecordExprFieldList {
502 let fields = fields.into_iter().join(", ");
503 ast_from_text(&format!("fn f() {{ S {{ {fields} }} }}"))
504}
505
506pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
507 return match expr {
508 Some(expr) => from_text(&format!("{name}: {expr}")),
509 None => from_text(&name.to_string()),
510 };
511
512 fn from_text(text: &str) -> ast::RecordExprField {
513 ast_from_text(&format!("fn f() {{ S {{ {text}, }} }}"))
514 }
515}
516
517pub fn record_field(
518 visibility: Option<ast::Visibility>,
519 name: ast::Name,
520 ty: ast::Type,
521) -> ast::RecordField {
522 let visibility = match visibility {
523 None => String::new(),
524 Some(it) => format!("{it} "),
525 };
526 ast_from_text(&format!("struct S {{ {visibility}{name}: {ty}, }}"))
527}
528
529pub fn block_expr(
530 stmts: impl IntoIterator<Item = ast::Stmt>,
531 tail_expr: Option<ast::Expr>,
532) -> ast::BlockExpr {
533 quote! {
534 BlockExpr {
535 StmtList {
536 ['{'] "\n"
537 #(" " #stmts "\n")*
538 #(" " #tail_expr "\n")*
539 ['}']
540 }
541 }
542 }
543}
544
545pub fn async_move_block_expr(
546 stmts: impl IntoIterator<Item = ast::Stmt>,
547 tail_expr: Option<ast::Expr>,
548) -> ast::BlockExpr {
549 let mut buf = "async move {\n".to_owned();
550 for stmt in stmts.into_iter() {
551 format_to!(buf, " {stmt}\n");
552 }
553 if let Some(tail_expr) = tail_expr {
554 format_to!(buf, " {tail_expr}\n");
555 }
556 buf += "}";
557 ast_from_text(&format!("const _: () = {buf};"))
558}
559
560pub fn tail_only_block_expr(tail_expr: ast::Expr) -> ast::BlockExpr {
561 ast_from_text(&format!("fn f() {{ {tail_expr} }}"))
562}
563
564pub fn hacky_block_expr(
569 elements: impl IntoIterator<Item = crate::SyntaxElement>,
570 tail_expr: Option<ast::Expr>,
571) -> ast::BlockExpr {
572 let mut buf = "{\n".to_owned();
573 for node_or_token in elements.into_iter() {
574 match node_or_token {
575 rowan::NodeOrToken::Node(n) => format_to!(buf, " {n}\n"),
576 rowan::NodeOrToken::Token(t) => {
577 let kind = t.kind();
578 if kind == SyntaxKind::COMMENT {
579 format_to!(buf, " {t}\n")
580 } else if kind == SyntaxKind::WHITESPACE {
581 let content = t.text().trim_matches(|c| c != '\n');
582 if !content.is_empty() {
583 format_to!(buf, "{}", &content[1..])
584 }
585 }
586 }
587 }
588 }
589 if let Some(tail_expr) = tail_expr {
590 format_to!(buf, " {tail_expr}\n");
591 }
592 buf += "}";
593 ast_from_text(&format!("fn f() {buf}"))
594}
595
596pub fn expr_literal(text: &str) -> ast::Literal {
597 assert_eq!(text.trim(), text);
598 ast_from_text(&format!("fn f() {{ let _ = {text}; }}"))
599}
600
601pub fn expr_const_value(text: &str) -> ast::ConstArg {
602 ast_from_text(&format!("trait Foo<const N: usize = {text}> {{}}"))
603}
604
605pub fn expr_empty_block() -> ast::BlockExpr {
606 ast_from_text("const C: () = {};")
607}
608pub fn expr_path(path: ast::Path) -> ast::Expr {
609 expr_from_text(&path.to_string())
610}
611pub fn expr_continue(label: Option<ast::Lifetime>) -> ast::Expr {
612 match label {
613 Some(label) => expr_from_text(&format!("continue {label}")),
614 None => expr_from_text("continue"),
615 }
616}
617pub fn expr_bin_op(lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr {
619 expr_from_text(&format!("{lhs} {op} {rhs}"))
620}
621pub fn expr_break(label: Option<ast::Lifetime>, expr: Option<ast::Expr>) -> ast::Expr {
622 let mut s = String::from("break");
623
624 if let Some(label) = label {
625 format_to!(s, " {label}");
626 }
627
628 if let Some(expr) = expr {
629 format_to!(s, " {expr}");
630 }
631
632 expr_from_text(&s)
633}
634pub fn expr_return(expr: Option<ast::Expr>) -> ast::Expr {
635 match expr {
636 Some(expr) => expr_from_text(&format!("return {expr}")),
637 None => expr_from_text("return"),
638 }
639}
640pub fn expr_try(expr: ast::Expr) -> ast::Expr {
641 expr_from_text(&format!("{expr}?"))
642}
643pub fn expr_await(expr: ast::Expr) -> ast::Expr {
644 expr_from_text(&format!("{expr}.await"))
645}
646pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::MatchExpr {
647 let ws = block_whitespace(&expr);
648 expr_from_text(&format!("match {expr}{ws}{match_arm_list}"))
649}
650pub fn expr_if(
651 condition: ast::Expr,
652 then_branch: ast::BlockExpr,
653 else_branch: Option<ast::ElseBranch>,
654) -> ast::IfExpr {
655 let else_branch = match else_branch {
656 Some(ast::ElseBranch::Block(block)) => format!("else {block}"),
657 Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {if_expr}"),
658 None => String::new(),
659 };
660 let ws = block_whitespace(&condition);
661 expr_from_text(&format!("if {condition}{ws}{then_branch} {else_branch}"))
662}
663pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::ForExpr {
664 let ws = block_whitespace(&expr);
665 expr_from_text(&format!("for {pat} in {expr}{ws}{block}"))
666}
667
668pub fn expr_while_loop(condition: ast::Expr, block: ast::BlockExpr) -> ast::WhileExpr {
669 let ws = block_whitespace(&condition);
670 expr_from_text(&format!("while {condition}{ws}{block}"))
671}
672
673pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr {
674 expr_from_text(&format!("loop {block}"))
675}
676
677pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::PrefixExpr {
678 let token = token(op);
679 expr_from_text(&format!("{token}{expr}"))
680}
681pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::CallExpr {
682 expr_from_text(&format!("{f}{arg_list}"))
683}
684pub fn expr_method_call(
685 receiver: ast::Expr,
686 method: ast::NameRef,
687 arg_list: ast::ArgList,
688) -> ast::MethodCallExpr {
689 expr_from_text(&format!("{receiver}.{method}{arg_list}"))
690}
691pub fn expr_macro(path: ast::Path, tt: ast::TokenTree) -> ast::MacroExpr {
692 expr_from_text(&format!("{path}!{tt}"))
693}
694pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
695 expr_from_text(&if exclusive { format!("&mut {expr}") } else { format!("&{expr}") })
696}
697pub fn expr_raw_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
698 expr_from_text(&if exclusive {
699 format!("&raw mut {expr}")
700 } else {
701 format!("&raw const {expr}")
702 })
703}
704pub fn expr_reborrow(expr: ast::Expr) -> ast::Expr {
705 expr_from_text(&format!("&mut *{expr}"))
706}
707pub fn expr_closure(
708 pats: impl IntoIterator<Item = ast::Param>,
709 expr: ast::Expr,
710) -> ast::ClosureExpr {
711 let params = pats.into_iter().join(", ");
712 expr_from_text(&format!("|{params}| {expr}"))
713}
714pub fn expr_field(receiver: ast::Expr, field: &str) -> ast::Expr {
715 expr_from_text(&format!("{receiver}.{field}"))
716}
717pub fn expr_paren(expr: ast::Expr) -> ast::ParenExpr {
718 expr_from_text(&format!("({expr})"))
719}
720pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::TupleExpr {
721 let expr = elements.into_iter().format(", ");
722 expr_from_text(&format!("({expr})"))
723}
724pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::BinExpr {
725 expr_from_text(&format!("{lhs} = {rhs}"))
726}
727fn expr_from_text<E: Into<ast::Expr> + AstNode>(text: &str) -> E {
728 ast_from_text(&format!("const C: () = {text};"))
729}
730fn block_whitespace(after: &impl AstNode) -> &'static str {
731 if after.syntax().text().contains_char('\n') { "\n" } else { " " }
732}
733pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr {
734 ast_from_text(&format!("const _: () = while let {pattern} = {expr} {{}};"))
735}
736
737pub fn arg_list(args: impl IntoIterator<Item = ast::Expr>) -> ast::ArgList {
738 let args = args.into_iter().format(", ");
739 ast_from_text(&format!("fn main() {{ ()({args}) }}"))
740}
741
742pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat {
743 let mut s = String::from("fn f(");
744 if ref_ {
745 s.push_str("ref ");
746 }
747 if mut_ {
748 s.push_str("mut ");
749 }
750 format_to!(s, "{name}");
751 s.push_str(": ())");
752 ast_from_text(&s)
753}
754
755pub fn wildcard_pat() -> ast::WildcardPat {
756 return from_text("_");
757
758 fn from_text(text: &str) -> ast::WildcardPat {
759 ast_from_text(&format!("fn f({text}: ())"))
760 }
761}
762
763pub fn rest_pat() -> ast::RestPat {
764 ast_from_text("fn f() { let ..; }")
765}
766
767pub fn literal_pat(lit: &str) -> ast::LiteralPat {
768 return from_text(lit);
769
770 fn from_text(text: &str) -> ast::LiteralPat {
771 ast_from_text(&format!("fn f() {{ match x {{ {text} => {{}} }} }}"))
772 }
773}
774
775pub fn slice_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::SlicePat {
776 let pats_str = pats.into_iter().join(", ");
777 return from_text(&format!("[{pats_str}]"));
778
779 fn from_text(text: &str) -> ast::SlicePat {
780 ast_from_text(&format!("fn f() {{ match () {{{text} => ()}} }}"))
781 }
782}
783
784pub fn tuple_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::TuplePat {
788 let mut count: usize = 0;
789 let mut pats_str = pats.into_iter().inspect(|_| count += 1).join(", ");
790 if count == 1 {
791 pats_str.push(',');
792 }
793 return from_text(&format!("({pats_str})"));
794
795 fn from_text(text: &str) -> ast::TuplePat {
796 ast_from_text(&format!("fn f({text}: ())"))
797 }
798}
799
800pub fn tuple_struct_pat(
801 path: ast::Path,
802 pats: impl IntoIterator<Item = ast::Pat>,
803) -> ast::TupleStructPat {
804 let pats_str = pats.into_iter().join(", ");
805 return from_text(&format!("{path}({pats_str})"));
806
807 fn from_text(text: &str) -> ast::TupleStructPat {
808 ast_from_text(&format!("fn f({text}: ())"))
809 }
810}
811
812pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> ast::RecordPat {
813 let pats_str = pats.into_iter().join(", ");
814 return from_text(&format!("{path} {{ {pats_str} }}"));
815
816 fn from_text(text: &str) -> ast::RecordPat {
817 ast_from_text(&format!("fn f({text}: ())"))
818 }
819}
820
821pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat {
822 ast_from_text(&format!("fn f({path} {fields}: ()))"))
823}
824
825pub fn record_pat_field_list(
826 fields: impl IntoIterator<Item = ast::RecordPatField>,
827 rest_pat: Option<ast::RestPat>,
828) -> ast::RecordPatFieldList {
829 let mut fields = fields.into_iter().join(", ");
830 if let Some(rest_pat) = rest_pat {
831 if !fields.is_empty() {
832 fields.push_str(", ");
833 }
834 format_to!(fields, "{rest_pat}");
835 }
836 ast_from_text(&format!("fn f(S {{ {fields} }}: ()))"))
837}
838
839pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField {
840 ast_from_text(&format!("fn f(S {{ {name_ref}: {pat} }}: ()))"))
841}
842
843pub fn record_pat_field_shorthand(pat: ast::Pat) -> ast::RecordPatField {
844 ast_from_text(&format!("fn f(S {{ {pat} }}: ()))"))
845}
846
847pub fn path_pat(path: ast::Path) -> ast::Pat {
849 return from_text(&path.to_string());
850 fn from_text(text: &str) -> ast::Pat {
851 ast_from_text(&format!("fn f({text}: ())"))
852 }
853}
854
855pub fn or_pat(pats: impl IntoIterator<Item = ast::Pat>, leading_pipe: bool) -> ast::OrPat {
859 let leading_pipe = if leading_pipe { "| " } else { "" };
860 let pats = pats.into_iter().join(" | ");
861
862 return from_text(&format!("{leading_pipe}{pats}"));
863 fn from_text(text: &str) -> ast::OrPat {
864 ast_from_text(&format!("fn f({text}: ())"))
865 }
866}
867
868pub fn box_pat(pat: ast::Pat) -> ast::BoxPat {
869 ast_from_text(&format!("fn f(box {pat}: ())"))
870}
871
872pub fn paren_pat(pat: ast::Pat) -> ast::ParenPat {
873 ast_from_text(&format!("fn f(({pat}): ())"))
874}
875
876pub fn range_pat(start: Option<ast::Pat>, end: Option<ast::Pat>) -> ast::RangePat {
877 ast_from_text(&format!(
878 "fn f({}..{}: ())",
879 start.map(|e| e.to_string()).unwrap_or_default(),
880 end.map(|e| e.to_string()).unwrap_or_default()
881 ))
882}
883
884pub fn ref_pat(pat: ast::Pat) -> ast::RefPat {
885 ast_from_text(&format!("fn f(&{pat}: ())"))
886}
887
888pub fn match_arm(pat: ast::Pat, guard: Option<ast::MatchGuard>, expr: ast::Expr) -> ast::MatchArm {
889 let comma_str = if expr.is_block_like() { "" } else { "," };
890 let ws = guard.as_ref().filter(|_| expr.is_block_like()).map_or(" ", block_whitespace);
891 return match guard {
892 Some(guard) => from_text(&format!("{pat} {guard} =>{ws}{expr}{comma_str}")),
893 None => from_text(&format!("{pat} => {expr}{comma_str}")),
894 };
895
896 fn from_text(text: &str) -> ast::MatchArm {
897 ast_from_text(&format!("fn f() {{ match () {{{text}}} }}"))
898 }
899}
900
901pub fn match_guard(condition: ast::Expr) -> ast::MatchGuard {
902 return from_text(&format!("if {condition}"));
903
904 fn from_text(text: &str) -> ast::MatchGuard {
905 ast_from_text(&format!("fn f() {{ match () {{() {text} => () }}"))
906 }
907}
908
909pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
910 let arms_str = arms.into_iter().fold(String::new(), |mut acc, arm| {
911 let needs_comma =
912 arm.comma_token().is_none() && arm.expr().is_none_or(|it| !it.is_block_like());
913 let comma = if needs_comma && arm.comma_token().is_none() { "," } else { "" };
914 let arm = arm.syntax();
915 format_to_acc!(acc, " {arm}{comma}\n")
916 });
917 return from_text(&arms_str);
918
919 fn from_text(text: &str) -> ast::MatchArmList {
920 ast_from_text(&format!("fn f() {{ match () {{\n{text}}} }}"))
921 }
922}
923
924pub fn where_pred(
925 path: Either<ast::Lifetime, ast::Type>,
926 bounds: impl IntoIterator<Item = ast::TypeBound>,
927) -> ast::WherePred {
928 let bounds = bounds.into_iter().join(" + ");
929 return from_text(&format!("{path}: {bounds}"));
930
931 fn from_text(text: &str) -> ast::WherePred {
932 ast_from_text(&format!("fn f() where {text} {{ }}"))
933 }
934}
935
936pub fn where_clause(preds: impl IntoIterator<Item = ast::WherePred>) -> ast::WhereClause {
937 let preds = preds.into_iter().join(", ");
938 return from_text(preds.as_str());
939
940 fn from_text(text: &str) -> ast::WhereClause {
941 ast_from_text(&format!("fn f() where {text} {{ }}"))
942 }
943}
944
945pub fn let_stmt(
946 pattern: ast::Pat,
947 ty: Option<ast::Type>,
948 initializer: Option<ast::Expr>,
949) -> ast::LetStmt {
950 let mut text = String::new();
951 format_to!(text, "let {pattern}");
952 if let Some(ty) = ty {
953 format_to!(text, ": {ty}");
954 }
955 match initializer {
956 Some(it) => format_to!(text, " = {it};"),
957 None => format_to!(text, ";"),
958 };
959 ast_from_text(&format!("fn f() {{ {text} }}"))
960}
961
962pub fn let_else_stmt(
963 pattern: ast::Pat,
964 ty: Option<ast::Type>,
965 expr: ast::Expr,
966 diverging: ast::BlockExpr,
967) -> ast::LetStmt {
968 let mut text = String::new();
969 format_to!(text, "let {pattern}");
970 if let Some(ty) = ty {
971 format_to!(text, ": {ty}");
972 }
973 format_to!(text, " = {expr} else {diverging};");
974 ast_from_text(&format!("fn f() {{ {text} }}"))
975}
976
977pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
978 let semi = if expr.is_block_like() { "" } else { ";" };
979 ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}"))
980}
981
982pub fn item_const(
983 attrs: impl IntoIterator<Item = ast::Attr>,
984 visibility: Option<ast::Visibility>,
985 name: ast::Name,
986 ty: ast::Type,
987 expr: ast::Expr,
988) -> ast::Const {
989 let attrs =
990 attrs.into_iter().fold(String::new(), |mut acc, attr| format_to_acc!(acc, "{}\n", attr));
991 let visibility = match visibility {
992 None => String::new(),
993 Some(it) => format!("{it} "),
994 };
995 ast_from_text(&format!("{attrs}{visibility}const {name}: {ty} = {expr};"))
996}
997
998pub fn item_static(
999 visibility: Option<ast::Visibility>,
1000 is_unsafe: bool,
1001 is_mut: bool,
1002 name: ast::Name,
1003 ty: ast::Type,
1004 expr: Option<ast::Expr>,
1005) -> ast::Static {
1006 let visibility = match visibility {
1007 None => String::new(),
1008 Some(it) => format!("{it} "),
1009 };
1010 let is_unsafe = if is_unsafe { "unsafe " } else { "" };
1011 let is_mut = if is_mut { "mut " } else { "" };
1012 let expr = match expr {
1013 Some(it) => &format!(" = {it}"),
1014 None => "",
1015 };
1016
1017 ast_from_text(&format!("{visibility}{is_unsafe}static {is_mut}{name}: {ty}{expr};"))
1018}
1019
1020pub fn unnamed_param(ty: ast::Type) -> ast::Param {
1021 quote! {
1022 Param {
1023 #ty
1024 }
1025 }
1026}
1027
1028pub fn untyped_param(pat: ast::Pat) -> ast::Param {
1029 quote! {
1030 Param {
1031 #pat
1032 }
1033 }
1034}
1035
1036pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param {
1037 ast_from_text(&format!("fn f({pat}: {ty}) {{ }}"))
1038}
1039
1040pub fn self_param() -> ast::SelfParam {
1041 ast_from_text("fn f(&self) { }")
1042}
1043
1044pub fn mut_self_param() -> ast::SelfParam {
1045 ast_from_text("fn f(&mut self) { }")
1046}
1047
1048pub fn ret_type(ty: ast::Type) -> ast::RetType {
1049 ast_from_text(&format!("fn f() -> {ty} {{ }}"))
1050}
1051
1052pub fn param_list(
1053 self_param: Option<ast::SelfParam>,
1054 pats: impl IntoIterator<Item = ast::Param>,
1055) -> ast::ParamList {
1056 let args = pats.into_iter().join(", ");
1057 let list = match self_param {
1058 Some(self_param) if args.is_empty() => format!("fn f({self_param}) {{ }}"),
1059 Some(self_param) => format!("fn f({self_param}, {args}) {{ }}"),
1060 None => format!("fn f({args}) {{ }}"),
1061 };
1062 ast_from_text(&list)
1063}
1064
1065pub fn trait_(
1066 is_unsafe: bool,
1067 ident: &str,
1068 gen_params: Option<ast::GenericParamList>,
1069 where_clause: Option<ast::WhereClause>,
1070 assoc_items: ast::AssocItemList,
1071) -> ast::Trait {
1072 let mut text = String::new();
1073
1074 if is_unsafe {
1075 format_to!(text, "unsafe ");
1076 }
1077
1078 format_to!(text, "trait {ident}");
1079
1080 if let Some(gen_params) = gen_params {
1081 format_to!(text, "{} ", gen_params.to_string());
1082 } else {
1083 text.push(' ');
1084 }
1085
1086 if let Some(where_clause) = where_clause {
1087 format_to!(text, "{} ", where_clause.to_string());
1088 }
1089
1090 format_to!(text, "{}", assoc_items.to_string());
1091
1092 ast_from_text(&text)
1093}
1094
1095pub fn type_bound_text(bound: &str) -> ast::TypeBound {
1097 ast_from_text(&format!("fn f<T: {bound}>() {{ }}"))
1098}
1099
1100pub fn type_bound(bound: ast::Type) -> ast::TypeBound {
1101 ast_from_text(&format!("fn f<T: {bound}>() {{ }}"))
1102}
1103
1104pub fn type_bound_list(
1105 bounds: impl IntoIterator<Item = ast::TypeBound>,
1106) -> Option<ast::TypeBoundList> {
1107 let bounds = bounds.into_iter().map(|it| it.to_string()).unique().join(" + ");
1108 if bounds.is_empty() {
1109 return None;
1110 }
1111 Some(ast_from_text(&format!("fn f<T: {bounds}>() {{ }}")))
1112}
1113
1114pub fn type_param(name: ast::Name, bounds: Option<ast::TypeBoundList>) -> ast::TypeParam {
1115 let bounds = bounds.map_or_else(String::new, |it| format!(": {it}"));
1116 ast_from_text(&format!("fn f<{name}{bounds}>() {{ }}"))
1117}
1118
1119pub fn const_param(name: ast::Name, ty: ast::Type) -> ast::ConstParam {
1120 ast_from_text(&format!("fn f<const {name}: {ty}>() {{ }}"))
1121}
1122
1123pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam {
1124 ast_from_text(&format!("fn f<{lifetime}>() {{ }}"))
1125}
1126
1127pub fn generic_param_list(
1128 pats: impl IntoIterator<Item = ast::GenericParam>,
1129) -> ast::GenericParamList {
1130 let args = pats.into_iter().join(", ");
1131 ast_from_text(&format!("fn f<{args}>() {{ }}"))
1132}
1133
1134pub fn type_arg(ty: ast::Type) -> ast::TypeArg {
1135 ast_from_text(&format!("const S: T<{ty}> = ();"))
1136}
1137
1138pub fn lifetime_arg(lifetime: ast::Lifetime) -> ast::LifetimeArg {
1139 ast_from_text(&format!("const S: T<{lifetime}> = ();"))
1140}
1141
1142pub fn turbofish_generic_arg_list(
1143 args: impl IntoIterator<Item = ast::GenericArg>,
1144) -> ast::GenericArgList {
1145 let args = args.into_iter().join(", ");
1146 ast_from_text(&format!("const S: T::<{args}> = ();"))
1147}
1148
1149pub fn generic_arg_list(args: impl IntoIterator<Item = ast::GenericArg>) -> ast::GenericArgList {
1150 let args = args.into_iter().join(", ");
1151 ast_from_text(&format!("const S: T<{args}> = ();"))
1152}
1153
1154pub fn visibility_pub_crate() -> ast::Visibility {
1155 ast_from_text("pub(crate) struct S")
1156}
1157
1158pub fn visibility_pub() -> ast::Visibility {
1159 ast_from_text("pub struct S")
1160}
1161
1162pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> ast::TupleFieldList {
1163 let fields = fields.into_iter().join(", ");
1164 ast_from_text(&format!("struct f({fields});"))
1165}
1166
1167pub fn record_field_list(
1168 fields: impl IntoIterator<Item = ast::RecordField>,
1169) -> ast::RecordFieldList {
1170 let fields = fields.into_iter().join(", ");
1171 ast_from_text(&format!("struct f {{ {fields} }}"))
1172}
1173
1174pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField {
1175 let visibility = match visibility {
1176 None => String::new(),
1177 Some(it) => format!("{it} "),
1178 };
1179 ast_from_text(&format!("struct f({visibility}{ty});"))
1180}
1181
1182pub fn variant_list(variants: impl IntoIterator<Item = ast::Variant>) -> ast::VariantList {
1183 let variants = variants.into_iter().join(", ");
1184 ast_from_text(&format!("enum f {{ {variants} }}"))
1185}
1186
1187pub fn variant(
1188 visibility: Option<ast::Visibility>,
1189 name: ast::Name,
1190 field_list: Option<ast::FieldList>,
1191 discriminant: Option<ast::Expr>,
1192) -> ast::Variant {
1193 let visibility = match visibility {
1194 None => String::new(),
1195 Some(it) => format!("{it} "),
1196 };
1197
1198 let field_list = match field_list {
1199 None => String::new(),
1200 Some(it) => match it {
1201 ast::FieldList::RecordFieldList(record) => format!(" {record}"),
1202 ast::FieldList::TupleFieldList(tuple) => format!("{tuple}"),
1203 },
1204 };
1205
1206 let discriminant = match discriminant {
1207 Some(it) => format!(" = {it}"),
1208 None => String::new(),
1209 };
1210 ast_from_text(&format!("enum f {{ {visibility}{name}{field_list}{discriminant} }}"))
1211}
1212
1213pub fn fn_(
1214 attrs: impl IntoIterator<Item = ast::Attr>,
1215 visibility: Option<ast::Visibility>,
1216 fn_name: ast::Name,
1217 type_params: Option<ast::GenericParamList>,
1218 where_clause: Option<ast::WhereClause>,
1219 params: ast::ParamList,
1220 body: ast::BlockExpr,
1221 ret_type: Option<ast::RetType>,
1222 is_async: bool,
1223 is_const: bool,
1224 is_unsafe: bool,
1225 is_gen: bool,
1226) -> ast::Fn {
1227 let attrs =
1228 attrs.into_iter().fold(String::new(), |mut acc, attr| format_to_acc!(acc, "{}\n", attr));
1229 let type_params = match type_params {
1230 Some(type_params) => format!("{type_params}"),
1231 None => "".into(),
1232 };
1233 let where_clause = match where_clause {
1234 Some(it) => format!("{it} "),
1235 None => "".into(),
1236 };
1237 let ret_type = match ret_type {
1238 Some(ret_type) => format!("{ret_type} "),
1239 None => "".into(),
1240 };
1241 let visibility = match visibility {
1242 None => String::new(),
1243 Some(it) => format!("{it} "),
1244 };
1245
1246 let async_literal = if is_async { "async " } else { "" };
1247 let const_literal = if is_const { "const " } else { "" };
1248 let unsafe_literal = if is_unsafe { "unsafe " } else { "" };
1249 let gen_literal = if is_gen { "gen " } else { "" };
1250
1251 ast_from_text(&format!(
1252 "{attrs}{visibility}{const_literal}{async_literal}{gen_literal}{unsafe_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}",
1253 ))
1254}
1255pub fn struct_(
1256 visibility: Option<ast::Visibility>,
1257 strukt_name: ast::Name,
1258 generic_param_list: Option<ast::GenericParamList>,
1259 field_list: ast::FieldList,
1260) -> ast::Struct {
1261 let (semicolon, ws) =
1262 if matches!(field_list, ast::FieldList::TupleFieldList(_)) { (";", "") } else { ("", " ") };
1263 let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string());
1264 let visibility = match visibility {
1265 None => String::new(),
1266 Some(it) => format!("{it} "),
1267 };
1268
1269 ast_from_text(&format!(
1270 "{visibility}struct {strukt_name}{type_params}{ws}{field_list}{semicolon}"
1271 ))
1272}
1273
1274pub fn enum_(
1275 attrs: impl IntoIterator<Item = ast::Attr>,
1276 visibility: Option<ast::Visibility>,
1277 enum_name: ast::Name,
1278 generic_param_list: Option<ast::GenericParamList>,
1279 where_clause: Option<ast::WhereClause>,
1280 variant_list: ast::VariantList,
1281) -> ast::Enum {
1282 let attrs =
1283 attrs.into_iter().fold(String::new(), |mut acc, attr| format_to_acc!(acc, "{}\n", attr));
1284 let visibility = match visibility {
1285 None => String::new(),
1286 Some(it) => format!("{it} "),
1287 };
1288
1289 let generic_params = generic_param_list.map(|it| it.to_string()).unwrap_or_default();
1290 let where_clause = where_clause.map(|it| format!(" {it}")).unwrap_or_default();
1291
1292 ast_from_text(&format!(
1293 "{attrs}{visibility}enum {enum_name}{generic_params}{where_clause} {variant_list}"
1294 ))
1295}
1296
1297pub fn attr_outer(meta: ast::Meta) -> ast::Attr {
1298 ast_from_text(&format!("#[{meta}]"))
1299}
1300
1301pub fn attr_inner(meta: ast::Meta) -> ast::Attr {
1302 ast_from_text(&format!("#![{meta}]"))
1303}
1304
1305pub fn meta_expr(path: ast::Path, expr: ast::Expr) -> ast::Meta {
1306 ast_from_text(&format!("#[{path} = {expr}]"))
1307}
1308
1309pub fn meta_token_tree(path: ast::Path, tt: ast::TokenTree) -> ast::Meta {
1310 ast_from_text(&format!("#[{path}{tt}]"))
1311}
1312
1313pub fn meta_path(path: ast::Path) -> ast::Meta {
1314 ast_from_text(&format!("#[{path}]"))
1315}
1316
1317pub fn token_tree(
1318 delimiter: SyntaxKind,
1319 tt: impl IntoIterator<Item = NodeOrToken<ast::TokenTree, SyntaxToken>>,
1320) -> ast::TokenTree {
1321 let (l_delimiter, r_delimiter) = match delimiter {
1322 T!['('] => ('(', ')'),
1323 T!['['] => ('[', ']'),
1324 T!['{'] => ('{', '}'),
1325 _ => panic!("invalid delimiter `{delimiter:?}`"),
1326 };
1327 let tt = tt.into_iter().join("");
1328
1329 ast_from_text(&format!("tt!{l_delimiter}{tt}{r_delimiter}"))
1330}
1331
1332#[track_caller]
1333fn ast_from_text<N: AstNode>(text: &str) -> N {
1334 ast_from_text_with_edition(text, Edition::CURRENT)
1335}
1336
1337#[track_caller]
1338fn ast_from_text_with_edition<N: AstNode>(text: &str, edition: Edition) -> N {
1339 let parse = SourceFile::parse(text, edition);
1340 let node = match parse.tree().syntax().descendants().find_map(N::cast) {
1341 Some(it) => it,
1342 None => {
1343 let node = std::any::type_name::<N>();
1344 panic!("Failed to make ast node `{node}` from text {text}")
1345 }
1346 };
1347 let node = node.clone_subtree();
1348 assert_eq!(node.syntax().text_range().start(), 0.into());
1349 node
1350}
1351
1352pub fn token(kind: SyntaxKind) -> SyntaxToken {
1353 tokens::SOURCE_FILE
1354 .tree()
1355 .syntax()
1356 .clone_for_update()
1357 .descendants_with_tokens()
1358 .filter_map(|it| it.into_token())
1359 .find(|it| it.kind() == kind)
1360 .unwrap_or_else(|| panic!("unhandled token: {kind:?}"))
1361}
1362
1363pub mod tokens {
1364 use std::sync::LazyLock;
1365
1366 use parser::Edition;
1367
1368 use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, ast};
1369
1370 pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
1371 SourceFile::parse(
1372 "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] }, while loop {} {})\n;\n\nunsafe impl A for B where: {}",
1373 Edition::CURRENT,
1374 )
1375 });
1376
1377 pub fn semicolon() -> SyntaxToken {
1378 SOURCE_FILE
1379 .tree()
1380 .syntax()
1381 .clone_for_update()
1382 .descendants_with_tokens()
1383 .filter_map(|it| it.into_token())
1384 .find(|it| it.kind() == SEMICOLON)
1385 .unwrap()
1386 }
1387
1388 pub fn single_space() -> SyntaxToken {
1389 SOURCE_FILE
1390 .tree()
1391 .syntax()
1392 .clone_for_update()
1393 .descendants_with_tokens()
1394 .filter_map(|it| it.into_token())
1395 .find(|it| it.kind() == WHITESPACE && it.text() == " ")
1396 .unwrap()
1397 }
1398
1399 pub fn crate_kw() -> SyntaxToken {
1400 SOURCE_FILE
1401 .tree()
1402 .syntax()
1403 .clone_for_update()
1404 .descendants_with_tokens()
1405 .filter_map(|it| it.into_token())
1406 .find(|it| it.kind() == CRATE_KW)
1407 .unwrap()
1408 }
1409
1410 pub fn whitespace(text: &str) -> SyntaxToken {
1411 assert!(text.trim().is_empty());
1412 let sf = SourceFile::parse(text, Edition::CURRENT).ok().unwrap();
1413 sf.syntax().clone_for_update().first_child_or_token().unwrap().into_token().unwrap()
1414 }
1415
1416 pub fn doc_comment(text: &str) -> SyntaxToken {
1417 assert!(!text.trim().is_empty());
1418 let sf = SourceFile::parse(text, Edition::CURRENT).ok().unwrap();
1419 sf.syntax().first_child_or_token().unwrap().into_token().unwrap()
1420 }
1421
1422 pub fn literal(text: &str) -> SyntaxToken {
1423 assert_eq!(text.trim(), text);
1424 let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {text}; }}"));
1425 lit.syntax().first_child_or_token().unwrap().into_token().unwrap()
1426 }
1427
1428 pub fn ident(text: &str) -> SyntaxToken {
1429 assert_eq!(text.trim(), text);
1430 let path: ast::Path = super::ext::ident_path(text);
1431 path.syntax()
1432 .descendants_with_tokens()
1433 .filter_map(|it| it.into_token())
1434 .find(|it| it.kind() == IDENT)
1435 .unwrap()
1436 }
1437
1438 pub fn single_newline() -> SyntaxToken {
1439 let res = SOURCE_FILE
1440 .tree()
1441 .syntax()
1442 .clone_for_update()
1443 .descendants_with_tokens()
1444 .filter_map(|it| it.into_token())
1445 .find(|it| it.kind() == WHITESPACE && it.text() == "\n")
1446 .unwrap();
1447 res.detach();
1448 res
1449 }
1450
1451 pub fn blank_line() -> SyntaxToken {
1452 SOURCE_FILE
1453 .tree()
1454 .syntax()
1455 .clone_for_update()
1456 .descendants_with_tokens()
1457 .filter_map(|it| it.into_token())
1458 .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n")
1459 .unwrap()
1460 }
1461
1462 pub struct WsBuilder(SourceFile);
1463
1464 impl WsBuilder {
1465 pub fn new(text: &str) -> WsBuilder {
1466 WsBuilder(SourceFile::parse(text, Edition::CURRENT).ok().unwrap())
1467 }
1468 pub fn ws(&self) -> SyntaxToken {
1469 self.0.syntax().first_child_or_token().unwrap().into_token().unwrap()
1470 }
1471 }
1472}
1473
1474#[cfg(test)]
1475mod tests {
1476 use expect_test::expect;
1477
1478 use super::*;
1479
1480 #[track_caller]
1481 fn check(node: impl AstNode, expect: expect_test::Expect) {
1482 let node_debug = format!("{:#?}", node.syntax());
1483 expect.assert_eq(&node_debug);
1484 }
1485
1486 #[test]
1487 fn test_unnamed_param() {
1488 check(
1489 unnamed_param(ty("Vec")),
1490 expect![[r#"
1491 PARAM@0..3
1492 PATH_TYPE@0..3
1493 PATH@0..3
1494 PATH_SEGMENT@0..3
1495 NAME_REF@0..3
1496 IDENT@0..3 "Vec"
1497 "#]],
1498 );
1499
1500 check(
1501 unnamed_param(ty("Vec<T>")),
1502 expect![[r#"
1503 PARAM@0..6
1504 PATH_TYPE@0..6
1505 PATH@0..6
1506 PATH_SEGMENT@0..6
1507 NAME_REF@0..3
1508 IDENT@0..3 "Vec"
1509 GENERIC_ARG_LIST@3..6
1510 L_ANGLE@3..4 "<"
1511 TYPE_ARG@4..5
1512 PATH_TYPE@4..5
1513 PATH@4..5
1514 PATH_SEGMENT@4..5
1515 NAME_REF@4..5
1516 IDENT@4..5 "T"
1517 R_ANGLE@5..6 ">"
1518 "#]],
1519 );
1520 }
1521
1522 #[test]
1523 fn test_untyped_param() {
1524 check(
1525 untyped_param(path_pat(ext::ident_path("name"))),
1526 expect![[r#"
1527 PARAM@0..4
1528 IDENT_PAT@0..4
1529 NAME@0..4
1530 IDENT@0..4 "name"
1531 "#]],
1532 );
1533
1534 check(
1535 untyped_param(
1536 range_pat(
1537 Some(path_pat(ext::ident_path("start"))),
1538 Some(path_pat(ext::ident_path("end"))),
1539 )
1540 .into(),
1541 ),
1542 expect![[r#"
1543 PARAM@0..10
1544 RANGE_PAT@0..10
1545 IDENT_PAT@0..5
1546 NAME@0..5
1547 IDENT@0..5 "start"
1548 DOT2@5..7 ".."
1549 IDENT_PAT@7..10
1550 NAME@7..10
1551 IDENT@7..10 "end"
1552 "#]],
1553 );
1554 }
1555}