//! This module contains free-standing functions for creating AST fragments out //! of smaller pieces. //! //! Note that all functions here intended to be stupid constructors, which just //! assemble a finish node from immediate children. If you want to do something //! smarter than that, it belongs to the `ext` submodule. //! //! Keep in mind that `from_text` functions should be kept private. The public //! API should require to assemble every node piecewise. The trick of //! `parse(format!())` we use internally is an implementation detail -- long //! term, it will be replaced with direct tree manipulation. use itertools::Itertools; use parser::{Edition, T}; use rowan::NodeOrToken; use stdx::{format_to, format_to_acc, never}; use crate::{ast, utils::is_raw_identifier, AstNode, SourceFile, SyntaxKind, SyntaxToken}; /// While the parent module defines basic atomic "constructors", the `ext` /// module defines shortcuts for common things. /// /// It's named `ext` rather than `shortcuts` just to keep it short. pub mod ext { use super::*; pub fn simple_ident_pat(name: ast::Name) -> ast::IdentPat { return from_text(&name.text()); fn from_text(text: &str) -> ast::IdentPat { ast_from_text(&format!("fn f({text}: ())")) } } pub fn ident_path(ident: &str) -> ast::Path { path_unqualified(path_segment(name_ref(ident))) } pub fn path_from_idents<'a>( parts: impl std::iter::IntoIterator, ) -> Option { let mut iter = parts.into_iter(); let base = ext::ident_path(iter.next()?); let path = iter.fold(base, |base, s| { let path = ext::ident_path(s); path_concat(base, path) }); Some(path) } pub fn field_from_idents<'a>( parts: impl std::iter::IntoIterator, ) -> Option { let mut iter = parts.into_iter(); let base = expr_path(ext::ident_path(iter.next()?)); let expr = iter.fold(base, expr_field); Some(expr) } pub fn expr_unreachable() -> ast::Expr { expr_from_text("unreachable!()") } pub fn expr_todo() -> ast::Expr { expr_from_text("todo!()") } pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr { expr_from_text(&format!("{ty}::default()")) } pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr { expr_from_text(&format!("{ty}::new()")) } pub fn expr_self() -> ast::Expr { expr_from_text("self") } pub fn zero_number() -> ast::Expr { expr_from_text("0") } pub fn zero_float() -> ast::Expr { expr_from_text("0.0") } pub fn empty_str() -> ast::Expr { expr_from_text(r#""""#) } pub fn empty_char() -> ast::Expr { expr_from_text("'\x00'") } pub fn default_bool() -> ast::Expr { expr_from_text("false") } pub fn option_none() -> ast::Expr { expr_from_text("None") } pub fn empty_block_expr() -> ast::BlockExpr { block_expr(None, None) } pub fn ty_name(name: ast::Name) -> ast::Type { ty_path(ident_path(&name.to_string())) } pub fn ty_bool() -> ast::Type { ty_path(ident_path("bool")) } pub fn ty_option(t: ast::Type) -> ast::Type { ty_from_text(&format!("Option<{t}>")) } pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type { ty_from_text(&format!("Result<{t}, {e}>")) } } pub fn name(name: &str) -> ast::Name { let raw_escape = raw_ident_esc(name); ast_from_text(&format!("mod {raw_escape}{name};")) } pub fn name_ref(name_ref: &str) -> ast::NameRef { let raw_escape = raw_ident_esc(name_ref); ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}")) } fn raw_ident_esc(ident: &str) -> &'static str { if is_raw_identifier(ident) { "r#" } else { "" } } pub fn lifetime(text: &str) -> ast::Lifetime { let mut text = text; let tmp; if never!(!text.starts_with('\'')) { tmp = format!("'{text}"); text = &tmp; } ast_from_text(&format!("fn f<{text}>() {{ }}")) } // FIXME: replace stringly-typed constructor with a family of typed ctors, a-la // `expr_xxx`. pub fn ty(text: &str) -> ast::Type { ty_from_text(text) } pub fn ty_placeholder() -> ast::Type { ty_from_text("_") } pub fn ty_unit() -> ast::Type { ty_from_text("()") } pub fn ty_tuple(types: impl IntoIterator) -> ast::Type { let mut count: usize = 0; let mut contents = types.into_iter().inspect(|_| count += 1).join(", "); if count == 1 { contents.push(','); } ty_from_text(&format!("({contents})")) } pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type { ty_from_text(&if exclusive { format!("&mut {target}") } else { format!("&{target}") }) } pub fn ty_path(path: ast::Path) -> ast::Type { ty_from_text(&path.to_string()) } fn ty_from_text(text: &str) -> ast::Type { ast_from_text(&format!("type _T = {text};")) } pub fn ty_alias( ident: &str, generic_param_list: Option, type_param_bounds: Option, where_clause: Option, assignment: Option<(ast::Type, Option)>, ) -> ast::TypeAlias { let mut s = String::new(); s.push_str(&format!("type {}", ident)); if let Some(list) = generic_param_list { s.push_str(&list.to_string()); } if let Some(list) = type_param_bounds { s.push_str(&format!(" : {}", &list)); } if let Some(cl) = where_clause { s.push_str(&format!(" {}", &cl.to_string())); } if let Some(exp) = assignment { if let Some(cl) = exp.1 { s.push_str(&format!(" = {} {}", &exp.0.to_string(), &cl.to_string())); } else { s.push_str(&format!(" = {}", &exp.0.to_string())); } } s.push(';'); ast_from_text(&s) } pub fn assoc_item_list() -> ast::AssocItemList { ast_from_text("impl C for D {}") } fn merge_gen_params( ps: Option, bs: Option, ) -> Option { match (ps, bs) { (None, None) => None, (None, Some(bs)) => Some(bs), (Some(ps), None) => Some(ps), (Some(ps), Some(bs)) => { // make sure lifetime is placed before other generic params let generic_params = ps.generic_params().merge_by(bs.generic_params(), |_, b| { !matches!(b, ast::GenericParam::LifetimeParam(_)) }); Some(generic_param_list(generic_params)) } } } fn merge_where_clause( ps: Option, bs: Option, ) -> Option { match (ps, bs) { (None, None) => None, (None, Some(bs)) => Some(bs), (Some(ps), None) => Some(ps), (Some(ps), Some(bs)) => { let preds = where_clause(std::iter::empty()).clone_for_update(); ps.predicates().for_each(|p| preds.add_predicate(p)); bs.predicates().for_each(|p| preds.add_predicate(p)); Some(preds) } } } pub fn impl_( generic_params: Option, generic_args: Option, path_type: ast::Type, where_clause: Option, body: Option>>, ) -> ast::Impl { let gen_args = generic_args.map_or_else(String::new, |it| it.to_string()); let gen_params = generic_params.map_or_else(String::new, |it| it.to_string()); let body_newline = if where_clause.is_some() && body.is_none() { "\n".to_owned() } else { String::new() }; let where_clause = match where_clause { Some(pr) => format!("\n{pr}\n"), None => " ".to_owned(), }; let body = match body { Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""), None => String::new(), }; ast_from_text(&format!( "impl{gen_params} {path_type}{gen_args}{where_clause}{{{body_newline}{body}}}" )) } pub fn impl_trait( is_unsafe: bool, trait_gen_params: Option, trait_gen_args: Option, type_gen_params: Option, type_gen_args: Option, is_negative: bool, path_type: ast::Type, ty: ast::Type, trait_where_clause: Option, ty_where_clause: Option, body: Option>>, ) -> ast::Impl { let is_unsafe = if is_unsafe { "unsafe " } else { "" }; let trait_gen_args = trait_gen_args.map(|args| args.to_string()).unwrap_or_default(); let type_gen_args = type_gen_args.map(|args| args.to_string()).unwrap_or_default(); let gen_params = merge_gen_params(trait_gen_params, type_gen_params) .map_or_else(String::new, |it| it.to_string()); let is_negative = if is_negative { "! " } else { "" }; let body_newline = if (ty_where_clause.is_some() || trait_where_clause.is_some()) && body.is_none() { "\n".to_owned() } else { String::new() }; let where_clause = merge_where_clause(ty_where_clause, trait_where_clause) .map_or_else(|| " ".to_owned(), |wc| format!("\n{}\n", wc)); let body = match body { Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""), None => String::new(), }; ast_from_text(&format!("{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{{{body_newline}{body}}}")) } pub fn impl_trait_type(bounds: ast::TypeBoundList) -> ast::ImplTraitType { ast_from_text(&format!("fn f(x: impl {bounds}) {{}}")) } pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { ast_from_text(&format!("type __ = {name_ref};")) } pub fn path_segment_ty(type_ref: ast::Type, trait_ref: Option) -> ast::PathSegment { let text = match trait_ref { Some(trait_ref) => format!("fn f(x: <{type_ref} as {trait_ref}>) {{}}"), None => format!("fn f(x: <{type_ref}>) {{}}"), }; ast_from_text(&text) } pub fn path_segment_self() -> ast::PathSegment { ast_from_text("use self;") } pub fn path_segment_super() -> ast::PathSegment { ast_from_text("use super;") } pub fn path_segment_crate() -> ast::PathSegment { ast_from_text("use crate;") } pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { ast_from_text(&format!("type __ = {segment};")) } pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { ast_from_text(&format!("{qual}::{segment}")) } // FIXME: path concatenation operation doesn't make sense as AST op. pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path { ast_from_text(&format!("type __ = {first}::{second};")) } pub fn path_from_segments( segments: impl IntoIterator, is_abs: bool, ) -> ast::Path { let segments = segments.into_iter().map(|it| it.syntax().clone()).join("::"); ast_from_text(&if is_abs { format!("fn f(x: ::{segments}) {{}}") } else { format!("fn f(x: {segments}) {{}}") }) } pub fn join_paths(paths: impl IntoIterator) -> ast::Path { let paths = paths.into_iter().map(|it| it.syntax().clone()).join("::"); ast_from_text(&format!("type __ = {paths};")) } // FIXME: should not be pub pub fn path_from_text(text: &str) -> ast::Path { ast_from_text(&format!("fn main() {{ let test = {text}; }}")) } pub fn use_tree_glob() -> ast::UseTree { ast_from_text("use *;") } pub fn use_tree( path: ast::Path, use_tree_list: Option, alias: Option, add_star: bool, ) -> ast::UseTree { let mut buf = "use ".to_owned(); buf += &path.syntax().to_string(); if let Some(use_tree_list) = use_tree_list { format_to!(buf, "::{use_tree_list}"); } if add_star { buf += "::*"; } if let Some(alias) = alias { format_to!(buf, " {alias}"); } ast_from_text(&buf) } pub fn use_tree_list(use_trees: impl IntoIterator) -> ast::UseTreeList { let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", "); ast_from_text(&format!("use {{{use_trees}}};")) } pub fn use_(visibility: Option, use_tree: ast::UseTree) -> ast::Use { let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; ast_from_text(&format!("{visibility}use {use_tree};")) } pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr { ast_from_text(&format!("fn f() {{ {path} {fields} }}")) } pub fn record_expr_field_list( fields: impl IntoIterator, ) -> ast::RecordExprFieldList { let fields = fields.into_iter().join(", "); ast_from_text(&format!("fn f() {{ S {{ {fields} }} }}")) } pub fn record_expr_field(name: ast::NameRef, expr: Option) -> ast::RecordExprField { return match expr { Some(expr) => from_text(&format!("{name}: {expr}")), None => from_text(&name.to_string()), }; fn from_text(text: &str) -> ast::RecordExprField { ast_from_text(&format!("fn f() {{ S {{ {text}, }} }}")) } } pub fn record_field( visibility: Option, name: ast::Name, ty: ast::Type, ) -> ast::RecordField { let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; ast_from_text(&format!("struct S {{ {visibility}{name}: {ty}, }}")) } pub fn block_expr( stmts: impl IntoIterator, tail_expr: Option, ) -> ast::BlockExpr { let mut buf = "{\n".to_owned(); for stmt in stmts.into_iter() { format_to!(buf, " {stmt}\n"); } if let Some(tail_expr) = tail_expr { format_to!(buf, " {tail_expr}\n"); } buf += "}"; ast_from_text(&format!("fn f() {buf}")) } pub fn async_move_block_expr( stmts: impl IntoIterator, tail_expr: Option, ) -> ast::BlockExpr { let mut buf = "async move {\n".to_owned(); for stmt in stmts.into_iter() { format_to!(buf, " {stmt}\n"); } if let Some(tail_expr) = tail_expr { format_to!(buf, " {tail_expr}\n"); } buf += "}"; ast_from_text(&format!("const _: () = {buf};")) } pub fn tail_only_block_expr(tail_expr: ast::Expr) -> ast::BlockExpr { ast_from_text(&format!("fn f() {{ {tail_expr} }}")) } /// Ideally this function wouldn't exist since it involves manual indenting. /// It differs from `make::block_expr` by also supporting comments and whitespace. /// /// FIXME: replace usages of this with the mutable syntax tree API pub fn hacky_block_expr( elements: impl IntoIterator, tail_expr: Option, ) -> ast::BlockExpr { let mut buf = "{\n".to_owned(); for node_or_token in elements.into_iter() { match node_or_token { rowan::NodeOrToken::Node(n) => format_to!(buf, " {n}\n"), rowan::NodeOrToken::Token(t) => { let kind = t.kind(); if kind == SyntaxKind::COMMENT { format_to!(buf, " {t}\n") } else if kind == SyntaxKind::WHITESPACE { let content = t.text().trim_matches(|c| c != '\n'); if !content.is_empty() { format_to!(buf, "{}", &content[1..]) } } } } } if let Some(tail_expr) = tail_expr { format_to!(buf, " {tail_expr}\n"); } buf += "}"; ast_from_text(&format!("fn f() {buf}")) } pub fn expr_unit() -> ast::Expr { expr_from_text("()") } pub fn expr_literal(text: &str) -> ast::Literal { assert_eq!(text.trim(), text); ast_from_text(&format!("fn f() {{ let _ = {text}; }}")) } pub fn expr_const_value(text: &str) -> ast::ConstArg { ast_from_text(&format!("trait Foo {{}}")) } pub fn expr_empty_block() -> ast::Expr { expr_from_text("{}") } pub fn expr_path(path: ast::Path) -> ast::Expr { expr_from_text(&path.to_string()) } pub fn expr_continue(label: Option) -> ast::Expr { match label { Some(label) => expr_from_text(&format!("continue {label}")), None => expr_from_text("continue"), } } // Consider `op: SyntaxKind` instead for nicer syntax at the call-site? pub fn expr_bin_op(lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { expr_from_text(&format!("{lhs} {op} {rhs}")) } pub fn expr_break(label: Option, expr: Option) -> ast::Expr { let mut s = String::from("break"); if let Some(label) = label { format_to!(s, " {label}"); } if let Some(expr) = expr { format_to!(s, " {expr}"); } expr_from_text(&s) } pub fn expr_return(expr: Option) -> ast::Expr { match expr { Some(expr) => expr_from_text(&format!("return {expr}")), None => expr_from_text("return"), } } pub fn expr_try(expr: ast::Expr) -> ast::Expr { expr_from_text(&format!("{expr}?")) } pub fn expr_await(expr: ast::Expr) -> ast::Expr { expr_from_text(&format!("{expr}.await")) } pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { expr_from_text(&format!("match {expr} {match_arm_list}")) } pub fn expr_if( condition: ast::Expr, then_branch: ast::BlockExpr, else_branch: Option, ) -> ast::Expr { let else_branch = match else_branch { Some(ast::ElseBranch::Block(block)) => format!("else {block}"), Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {if_expr}"), None => String::new(), }; expr_from_text(&format!("if {condition} {then_branch} {else_branch}")) } pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr { expr_from_text(&format!("for {pat} in {expr} {block}")) } pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr { expr_from_text(&format!("loop {block}")) } pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { let token = token(op); expr_from_text(&format!("{token}{expr}")) } pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { expr_from_text(&format!("{f}{arg_list}")) } pub fn expr_method_call( receiver: ast::Expr, method: ast::NameRef, arg_list: ast::ArgList, ) -> ast::Expr { expr_from_text(&format!("{receiver}.{method}{arg_list}")) } pub fn expr_macro_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { expr_from_text(&format!("{f}!{arg_list}")) } pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { expr_from_text(&if exclusive { format!("&mut {expr}") } else { format!("&{expr}") }) } pub fn expr_reborrow(expr: ast::Expr) -> ast::Expr { expr_from_text(&format!("&mut *{expr}")) } pub fn expr_closure(pats: impl IntoIterator, expr: ast::Expr) -> ast::Expr { let params = pats.into_iter().join(", "); expr_from_text(&format!("|{params}| {expr}")) } pub fn expr_field(receiver: ast::Expr, field: &str) -> ast::Expr { expr_from_text(&format!("{receiver}.{field}")) } pub fn expr_paren(expr: ast::Expr) -> ast::Expr { expr_from_text(&format!("({expr})")) } pub fn expr_tuple(elements: impl IntoIterator) -> ast::Expr { let expr = elements.into_iter().format(", "); expr_from_text(&format!("({expr})")) } pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { expr_from_text(&format!("{lhs} = {rhs}")) } fn expr_from_text(text: &str) -> ast::Expr { ast_from_text(&format!("const C: () = {text};")) } pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { ast_from_text(&format!("const _: () = while let {pattern} = {expr} {{}};")) } pub fn arg_list(args: impl IntoIterator) -> ast::ArgList { let args = args.into_iter().format(", "); ast_from_text(&format!("fn main() {{ ()({args}) }}")) } pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { let mut s = String::from("fn f("); if ref_ { s.push_str("ref "); } if mut_ { s.push_str("mut "); } format_to!(s, "{name}"); s.push_str(": ())"); ast_from_text(&s) } pub fn wildcard_pat() -> ast::WildcardPat { return from_text("_"); fn from_text(text: &str) -> ast::WildcardPat { ast_from_text(&format!("fn f({text}: ())")) } } pub fn rest_pat() -> ast::RestPat { ast_from_text("fn f(..)") } pub fn literal_pat(lit: &str) -> ast::LiteralPat { return from_text(lit); fn from_text(text: &str) -> ast::LiteralPat { ast_from_text(&format!("fn f() {{ match x {{ {text} => {{}} }} }}")) } } pub fn slice_pat(pats: impl IntoIterator) -> ast::SlicePat { let pats_str = pats.into_iter().join(", "); return from_text(&format!("[{pats_str}]")); fn from_text(text: &str) -> ast::SlicePat { ast_from_text(&format!("fn f() {{ match () {{{text} => ()}} }}")) } } /// Creates a tuple of patterns from an iterator of patterns. /// /// Invariant: `pats` must be length > 0 pub fn tuple_pat(pats: impl IntoIterator) -> ast::TuplePat { let mut count: usize = 0; let mut pats_str = pats.into_iter().inspect(|_| count += 1).join(", "); if count == 1 { pats_str.push(','); } return from_text(&format!("({pats_str})")); fn from_text(text: &str) -> ast::TuplePat { ast_from_text(&format!("fn f({text}: ())")) } } pub fn tuple_struct_pat( path: ast::Path, pats: impl IntoIterator, ) -> ast::TupleStructPat { let pats_str = pats.into_iter().join(", "); return from_text(&format!("{path}({pats_str})")); fn from_text(text: &str) -> ast::TupleStructPat { ast_from_text(&format!("fn f({text}: ())")) } } pub fn record_pat(path: ast::Path, pats: impl IntoIterator) -> ast::RecordPat { let pats_str = pats.into_iter().join(", "); return from_text(&format!("{path} {{ {pats_str} }}")); fn from_text(text: &str) -> ast::RecordPat { ast_from_text(&format!("fn f({text}: ())")) } } pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat { ast_from_text(&format!("fn f({path} {fields}: ()))")) } pub fn record_pat_field_list( fields: impl IntoIterator, rest_pat: Option, ) -> ast::RecordPatFieldList { let mut fields = fields.into_iter().join(", "); if let Some(rest_pat) = rest_pat { if !fields.is_empty() { fields.push_str(", "); } format_to!(fields, "{rest_pat}"); } ast_from_text(&format!("fn f(S {{ {fields} }}: ()))")) } pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField { ast_from_text(&format!("fn f(S {{ {name_ref}: {pat} }}: ()))")) } pub fn record_pat_field_shorthand(name_ref: ast::NameRef) -> ast::RecordPatField { ast_from_text(&format!("fn f(S {{ {name_ref} }}: ()))")) } /// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. pub fn path_pat(path: ast::Path) -> ast::Pat { return from_text(&path.to_string()); fn from_text(text: &str) -> ast::Pat { ast_from_text(&format!("fn f({text}: ())")) } } pub fn match_arm( pats: impl IntoIterator, guard: Option, expr: ast::Expr, ) -> ast::MatchArm { let pats_str = pats.into_iter().join(" | "); return match guard { Some(guard) => from_text(&format!("{pats_str} if {guard} => {expr}")), None => from_text(&format!("{pats_str} => {expr}")), }; fn from_text(text: &str) -> ast::MatchArm { ast_from_text(&format!("fn f() {{ match () {{{text}}} }}")) } } pub fn match_arm_with_guard( pats: impl IntoIterator, guard: ast::Expr, expr: ast::Expr, ) -> ast::MatchArm { let pats_str = pats.into_iter().join(" | "); return from_text(&format!("{pats_str} if {guard} => {expr}")); fn from_text(text: &str) -> ast::MatchArm { ast_from_text(&format!("fn f() {{ match () {{{text}}} }}")) } } pub fn match_arm_list(arms: impl IntoIterator) -> ast::MatchArmList { let arms_str = arms.into_iter().fold(String::new(), |mut acc, arm| { let needs_comma = arm.expr().map_or(true, |it| !it.is_block_like()); let comma = if needs_comma { "," } else { "" }; let arm = arm.syntax(); format_to_acc!(acc, " {arm}{comma}\n") }); return from_text(&arms_str); fn from_text(text: &str) -> ast::MatchArmList { ast_from_text(&format!("fn f() {{ match () {{\n{text}}} }}")) } } pub fn where_pred( path: ast::Path, bounds: impl IntoIterator, ) -> ast::WherePred { let bounds = bounds.into_iter().join(" + "); return from_text(&format!("{path}: {bounds}")); fn from_text(text: &str) -> ast::WherePred { ast_from_text(&format!("fn f() where {text} {{ }}")) } } pub fn where_clause(preds: impl IntoIterator) -> ast::WhereClause { let preds = preds.into_iter().join(", "); return from_text(preds.as_str()); fn from_text(text: &str) -> ast::WhereClause { ast_from_text(&format!("fn f() where {text} {{ }}")) } } pub fn let_stmt( pattern: ast::Pat, ty: Option, initializer: Option, ) -> ast::LetStmt { let mut text = String::new(); format_to!(text, "let {pattern}"); if let Some(ty) = ty { format_to!(text, ": {ty}"); } match initializer { Some(it) => format_to!(text, " = {it};"), None => format_to!(text, ";"), }; ast_from_text(&format!("fn f() {{ {text} }}")) } pub fn let_else_stmt( pattern: ast::Pat, ty: Option, expr: ast::Expr, diverging: ast::BlockExpr, ) -> ast::LetStmt { let mut text = String::new(); format_to!(text, "let {pattern}"); if let Some(ty) = ty { format_to!(text, ": {ty}"); } format_to!(text, " = {expr} else {diverging};"); ast_from_text(&format!("fn f() {{ {text} }}")) } pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt { let semi = if expr.is_block_like() { "" } else { ";" }; ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}")) } pub fn item_const( visibility: Option, name: ast::Name, ty: ast::Type, expr: ast::Expr, ) -> ast::Const { let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; ast_from_text(&format!("{visibility} const {name}: {ty} = {expr};")) } pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param { ast_from_text(&format!("fn f({pat}: {ty}) {{ }}")) } pub fn self_param() -> ast::SelfParam { ast_from_text("fn f(&self) { }") } pub fn mut_self_param() -> ast::SelfParam { ast_from_text("fn f(&mut self) { }") } pub fn ret_type(ty: ast::Type) -> ast::RetType { ast_from_text(&format!("fn f() -> {ty} {{ }}")) } pub fn param_list( self_param: Option, pats: impl IntoIterator, ) -> ast::ParamList { let args = pats.into_iter().join(", "); let list = match self_param { Some(self_param) if args.is_empty() => format!("fn f({self_param}) {{ }}"), Some(self_param) => format!("fn f({self_param}, {args}) {{ }}"), None => format!("fn f({args}) {{ }}"), }; ast_from_text(&list) } pub fn trait_( is_unsafe: bool, ident: &str, gen_params: Option, where_clause: Option, assoc_items: ast::AssocItemList, ) -> ast::Trait { let mut text = String::new(); if is_unsafe { format_to!(text, "unsafe "); } format_to!(text, "trait {ident}"); if let Some(gen_params) = gen_params { format_to!(text, "{} ", gen_params.to_string()); } else { text.push(' '); } if let Some(where_clause) = where_clause { format_to!(text, "{} ", where_clause.to_string()); } format_to!(text, "{}", assoc_items.to_string()); ast_from_text(&text) } // FIXME: remove when no one depends on `generate_impl_text_inner` pub fn type_bound_text(bound: &str) -> ast::TypeBound { ast_from_text(&format!("fn f() {{ }}")) } pub fn type_bound(bound: ast::Type) -> ast::TypeBound { ast_from_text(&format!("fn f() {{ }}")) } pub fn type_bound_list( bounds: impl IntoIterator, ) -> Option { let bounds = bounds.into_iter().map(|it| it.to_string()).unique().join(" + "); if bounds.is_empty() { return None; } Some(ast_from_text(&format!("fn f() {{ }}"))) } pub fn type_param(name: ast::Name, bounds: Option) -> ast::TypeParam { let bounds = bounds.map_or_else(String::new, |it| format!(": {it}")); ast_from_text(&format!("fn f<{name}{bounds}>() {{ }}")) } pub fn const_param(name: ast::Name, ty: ast::Type) -> ast::ConstParam { ast_from_text(&format!("fn f() {{ }}")) } pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam { ast_from_text(&format!("fn f<{lifetime}>() {{ }}")) } pub fn generic_param_list( pats: impl IntoIterator, ) -> ast::GenericParamList { let args = pats.into_iter().join(", "); ast_from_text(&format!("fn f<{args}>() {{ }}")) } pub fn type_arg(ty: ast::Type) -> ast::TypeArg { ast_from_text(&format!("const S: T<{ty}> = ();")) } pub fn lifetime_arg(lifetime: ast::Lifetime) -> ast::LifetimeArg { ast_from_text(&format!("const S: T<{lifetime}> = ();")) } pub fn turbofish_generic_arg_list( args: impl IntoIterator, ) -> ast::GenericArgList { let args = args.into_iter().join(", "); ast_from_text(&format!("const S: T::<{args}> = ();")) } pub fn generic_arg_list(args: impl IntoIterator) -> ast::GenericArgList { let args = args.into_iter().join(", "); ast_from_text(&format!("const S: T<{args}> = ();")) } pub fn visibility_pub_crate() -> ast::Visibility { ast_from_text("pub(crate) struct S") } pub fn visibility_pub() -> ast::Visibility { ast_from_text("pub struct S") } pub fn tuple_field_list(fields: impl IntoIterator) -> ast::TupleFieldList { let fields = fields.into_iter().join(", "); ast_from_text(&format!("struct f({fields});")) } pub fn record_field_list( fields: impl IntoIterator, ) -> ast::RecordFieldList { let fields = fields.into_iter().join(", "); ast_from_text(&format!("struct f {{ {fields} }}")) } pub fn tuple_field(visibility: Option, ty: ast::Type) -> ast::TupleField { let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; ast_from_text(&format!("struct f({visibility}{ty});")) } pub fn variant_list(variants: impl IntoIterator) -> ast::VariantList { let variants = variants.into_iter().join(", "); ast_from_text(&format!("enum f {{ {variants} }}")) } pub fn variant(name: ast::Name, field_list: Option) -> ast::Variant { let field_list = match field_list { None => String::new(), Some(it) => match it { ast::FieldList::RecordFieldList(record) => format!(" {record}"), ast::FieldList::TupleFieldList(tuple) => format!("{tuple}"), }, }; ast_from_text(&format!("enum f {{ {name}{field_list} }}")) } pub fn fn_( visibility: Option, fn_name: ast::Name, type_params: Option, where_clause: Option, params: ast::ParamList, body: ast::BlockExpr, ret_type: Option, is_async: bool, is_const: bool, is_unsafe: bool, ) -> ast::Fn { let type_params = match type_params { Some(type_params) => format!("{type_params}"), None => "".into(), }; let where_clause = match where_clause { Some(it) => format!("{it} "), None => "".into(), }; let ret_type = match ret_type { Some(ret_type) => format!("{ret_type} "), None => "".into(), }; let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; let async_literal = if is_async { "async " } else { "" }; let const_literal = if is_const { "const " } else { "" }; let unsafe_literal = if is_unsafe { "unsafe " } else { "" }; ast_from_text(&format!( "{visibility}{async_literal}{const_literal}{unsafe_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}", )) } pub fn struct_( visibility: Option, strukt_name: ast::Name, generic_param_list: Option, field_list: ast::FieldList, ) -> ast::Struct { let semicolon = if matches!(field_list, ast::FieldList::TupleFieldList(_)) { ";" } else { "" }; let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string()); let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; ast_from_text(&format!("{visibility}struct {strukt_name}{type_params}{field_list}{semicolon}",)) } pub fn enum_( visibility: Option, enum_name: ast::Name, variant_list: ast::VariantList, ) -> ast::Enum { let visibility = match visibility { None => String::new(), Some(it) => format!("{it} "), }; ast_from_text(&format!("{visibility}enum {enum_name} {variant_list}")) } pub fn attr_outer(meta: ast::Meta) -> ast::Attr { ast_from_text(&format!("#[{meta}]")) } pub fn attr_inner(meta: ast::Meta) -> ast::Attr { ast_from_text(&format!("#![{meta}]")) } pub fn meta_expr(path: ast::Path, expr: ast::Expr) -> ast::Meta { ast_from_text(&format!("#[{path} = {expr}]")) } pub fn meta_token_tree(path: ast::Path, tt: ast::TokenTree) -> ast::Meta { ast_from_text(&format!("#[{path}{tt}]")) } pub fn meta_path(path: ast::Path) -> ast::Meta { ast_from_text(&format!("#[{path}]")) } pub fn token_tree( delimiter: SyntaxKind, tt: Vec>, ) -> ast::TokenTree { let (l_delimiter, r_delimiter) = match delimiter { T!['('] => ('(', ')'), T!['['] => ('[', ']'), T!['{'] => ('{', '}'), _ => panic!("invalid delimiter `{delimiter:?}`"), }; let tt = tt.into_iter().join(""); ast_from_text(&format!("tt!{l_delimiter}{tt}{r_delimiter}")) } #[track_caller] fn ast_from_text(text: &str) -> N { let parse = SourceFile::parse(text, Edition::CURRENT); let node = match parse.tree().syntax().descendants().find_map(N::cast) { Some(it) => it, None => { let node = std::any::type_name::(); panic!("Failed to make ast node `{node}` from text {text}") } }; let node = node.clone_subtree(); assert_eq!(node.syntax().text_range().start(), 0.into()); node } pub fn token(kind: SyntaxKind) -> SyntaxToken { tokens::SOURCE_FILE .tree() .syntax() .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == kind) .unwrap_or_else(|| panic!("unhandled token: {kind:?}")) } pub mod tokens { use once_cell::sync::Lazy; use parser::Edition; use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; pub(super) static SOURCE_FILE: Lazy> = Lazy::new(|| { SourceFile::parse( "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let a @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT, ) }); pub fn semicolon() -> SyntaxToken { SOURCE_FILE .tree() .syntax() .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == SEMICOLON) .unwrap() } pub fn single_space() -> SyntaxToken { SOURCE_FILE .tree() .syntax() .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == WHITESPACE && it.text() == " ") .unwrap() } pub fn whitespace(text: &str) -> SyntaxToken { assert!(text.trim().is_empty()); let sf = SourceFile::parse(text, Edition::CURRENT).ok().unwrap(); sf.syntax().clone_for_update().first_child_or_token().unwrap().into_token().unwrap() } pub fn doc_comment(text: &str) -> SyntaxToken { assert!(!text.trim().is_empty()); let sf = SourceFile::parse(text, Edition::CURRENT).ok().unwrap(); sf.syntax().first_child_or_token().unwrap().into_token().unwrap() } pub fn literal(text: &str) -> SyntaxToken { assert_eq!(text.trim(), text); let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {text}; }}")); lit.syntax().first_child_or_token().unwrap().into_token().unwrap() } pub fn ident(text: &str) -> SyntaxToken { assert_eq!(text.trim(), text); let path: ast::Path = super::ext::ident_path(text); path.syntax() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == IDENT) .unwrap() } pub fn single_newline() -> SyntaxToken { let res = SOURCE_FILE .tree() .syntax() .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == WHITESPACE && it.text() == "\n") .unwrap(); res.detach(); res } pub fn blank_line() -> SyntaxToken { SOURCE_FILE .tree() .syntax() .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n") .unwrap() } pub struct WsBuilder(SourceFile); impl WsBuilder { pub fn new(text: &str) -> WsBuilder { WsBuilder(SourceFile::parse(text, Edition::CURRENT).ok().unwrap()) } pub fn ws(&self) -> SyntaxToken { self.0.syntax().first_child_or_token().unwrap().into_token().unwrap() } } }