Skip to main content

ra_ap_syntax/ast/
make.rs

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