Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/syntax/src/ast/make.rs')
-rw-r--r--crates/syntax/src/ast/make.rs126
1 files changed, 65 insertions, 61 deletions
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index eb96ab6ef5..282cbc4b3a 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -8,7 +8,10 @@
//! 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.
+//! term, it will be replaced with `quote!`. Do not add more usages to `from_text` -
+//! use `quote!` instead.
+
+mod quote;
use itertools::Itertools;
use parser::{Edition, T};
@@ -16,7 +19,7 @@ use rowan::NodeOrToken;
use stdx::{format_to, format_to_acc, never};
use crate::{
- ast::{self, Param},
+ ast::{self, make::quote::quote, Param},
utils::is_raw_identifier,
AstNode, SourceFile, SyntaxKind, SyntaxToken,
};
@@ -118,7 +121,11 @@ pub fn name(name: &str) -> ast::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}; }}"))
+ quote! {
+ NameRef {
+ [IDENT format!("{raw_escape}{name_ref}")]
+ }
+ }
}
fn raw_ident_esc(ident: &str) -> &'static str {
if is_raw_identifier(ident, Edition::CURRENT) {
@@ -135,7 +142,11 @@ pub fn lifetime(text: &str) -> ast::Lifetime {
tmp = format!("'{text}");
text = &tmp;
}
- ast_from_text(&format!("fn f<{text}>() {{ }}"))
+ quote! {
+ Lifetime {
+ [LIFETIME_IDENT text]
+ }
+ }
}
// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la
@@ -175,63 +186,37 @@ pub fn ty_alias(
where_clause: Option<ast::WhereClause>,
assignment: Option<(ast::Type, Option<ast::WhereClause>)>,
) -> 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}"));
- }
-
- if let Some(exp) = assignment {
- if let Some(cl) = exp.1 {
- s.push_str(&format!(" = {} {cl}", exp.0));
- } else {
- s.push_str(&format!(" = {}", exp.0));
+ let (assignment_ty, assignment_where) = assignment.unzip();
+ let assignment_where = assignment_where.flatten();
+ quote! {
+ TypeAlias {
+ [type] " "
+ Name { [IDENT ident] }
+ #generic_param_list
+ #(" " [:] " " #type_param_bounds)*
+ #(" " #where_clause)*
+ #(" " [=] " " #assignment_ty)*
+ #(" " #assignment_where)*
+ [;]
}
}
-
- s.push(';');
- ast_from_text(&s)
}
pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
- for_lifetime_list: Option<ast::GenericParamList>,
is_unsafe: bool,
abi: Option<ast::Abi>,
- params: I,
+ mut params: I,
ret_type: Option<ast::RetType>,
) -> ast::FnPtrType {
- let mut s = String::from("type __ = ");
-
- if let Some(list) = for_lifetime_list {
- format_to!(s, "for{} ", list);
- }
-
- if is_unsafe {
- s.push_str("unsafe ");
- }
-
- if let Some(abi) = abi {
- format_to!(s, "{} ", abi)
- }
-
- s.push_str("fn");
-
- format_to!(s, "({})", params.map(|p| p.to_string()).join(", "));
-
- if let Some(ret_type) = ret_type {
- format_to!(s, " {}", ret_type);
+ let is_unsafe = is_unsafe.then_some(());
+ let first_param = params.next();
+ quote! {
+ FnPtrType {
+ #(#is_unsafe [unsafe] " ")* #(#abi " ")* [fn]
+ ['('] #first_param #([,] " " #params)* [')']
+ #(" " #ret_type)*
+ }
}
-
- ast_from_text(&s)
}
pub fn assoc_item_list() -> ast::AssocItemList {
@@ -351,6 +336,24 @@ pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
ast_from_text(&format!("type __ = {name_ref};"))
}
+/// Type and expressions/patterns path differ in whether they require `::` before generic arguments.
+/// Type paths allow them but they are often omitted, while expression/pattern paths require them.
+pub fn generic_ty_path_segment(
+ name_ref: ast::NameRef,
+ generic_args: impl IntoIterator<Item = ast::GenericArg>,
+) -> ast::PathSegment {
+ let mut generic_args = generic_args.into_iter();
+ let first_generic_arg = generic_args.next();
+ quote! {
+ PathSegment {
+ #name_ref
+ GenericArgList {
+ [<] #first_generic_arg #([,] " " #generic_args)* [>]
+ }
+ }
+ }
+}
+
pub fn path_segment_ty(type_ref: ast::Type, trait_ref: Option<ast::PathType>) -> ast::PathSegment {
let text = match trait_ref {
Some(trait_ref) => format!("fn f(x: <{type_ref} as {trait_ref}>) {{}}"),
@@ -480,15 +483,16 @@ pub fn block_expr(
stmts: impl IntoIterator<Item = ast::Stmt>,
tail_expr: Option<ast::Expr>,
) -> 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");
+ quote! {
+ BlockExpr {
+ StmtList {
+ ['{'] "\n"
+ #(" " #stmts "\n")*
+ #(" " #tail_expr "\n")*
+ ['}']
+ }
+ }
}
- buf += "}";
- ast_from_text(&format!("fn f() {buf}"))
}
pub fn async_move_block_expr(
@@ -815,7 +819,7 @@ pub fn match_arm_with_guard(
pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> 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 needs_comma = arm.expr().is_none_or(|it| !it.is_block_like());
let comma = if needs_comma { "," } else { "" };
let arm = arm.syntax();
format_to_acc!(acc, " {arm}{comma}\n")
@@ -828,7 +832,7 @@ pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::Mat
}
pub fn where_pred(
- path: ast::Path,
+ path: ast::Type,
bounds: impl IntoIterator<Item = ast::TypeBound>,
) -> ast::WherePred {
let bounds = bounds.into_iter().join(" + ");