aboutsummaryrefslogtreecommitdiff
path: root/crates/syntax/src/ast/make.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/syntax/src/ast/make.rs')
-rw-r--r--crates/syntax/src/ast/make.rs392
1 files changed, 392 insertions, 0 deletions
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
new file mode 100644
index 000000000..254a37fe3
--- /dev/null
+++ b/crates/syntax/src/ast/make.rs
@@ -0,0 +1,392 @@
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 probably doesn't belong in this module.
7use itertools::Itertools;
8use stdx::format_to;
9
10use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken};
11
12pub fn name(text: &str) -> ast::Name {
13 ast_from_text(&format!("mod {};", text))
14}
15
16pub fn name_ref(text: &str) -> ast::NameRef {
17 ast_from_text(&format!("fn f() {{ {}; }}", text))
18}
19
20pub fn ty(text: &str) -> ast::Type {
21 ast_from_text(&format!("impl {} for D {{}};", text))
22}
23
24pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
25 ast_from_text(&format!("use {};", name_ref))
26}
27pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
28 path_from_text(&format!("use {}", segment))
29}
30pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
31 path_from_text(&format!("{}::{}", qual, segment))
32}
33pub fn path_from_text(text: &str) -> ast::Path {
34 ast_from_text(text)
35}
36
37pub fn use_tree(
38 path: ast::Path,
39 use_tree_list: Option<ast::UseTreeList>,
40 alias: Option<ast::Rename>,
41 add_star: bool,
42) -> ast::UseTree {
43 let mut buf = "use ".to_string();
44 buf += &path.syntax().to_string();
45 if let Some(use_tree_list) = use_tree_list {
46 format_to!(buf, "::{}", use_tree_list);
47 }
48 if add_star {
49 buf += "::*";
50 }
51
52 if let Some(alias) = alias {
53 format_to!(buf, " {}", alias);
54 }
55 ast_from_text(&buf)
56}
57
58pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList {
59 let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", ");
60 ast_from_text(&format!("use {{{}}};", use_trees))
61}
62
63pub fn use_(use_tree: ast::UseTree) -> ast::Use {
64 ast_from_text(&format!("use {};", use_tree))
65}
66
67pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
68 return match expr {
69 Some(expr) => from_text(&format!("{}: {}", name, expr)),
70 None => from_text(&name.to_string()),
71 };
72
73 fn from_text(text: &str) -> ast::RecordExprField {
74 ast_from_text(&format!("fn f() {{ S {{ {}, }} }}", text))
75 }
76}
77
78pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField {
79 ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty))
80}
81
82pub fn block_expr(
83 stmts: impl IntoIterator<Item = ast::Stmt>,
84 tail_expr: Option<ast::Expr>,
85) -> ast::BlockExpr {
86 let mut buf = "{\n".to_string();
87 for stmt in stmts.into_iter() {
88 format_to!(buf, " {}\n", stmt);
89 }
90 if let Some(tail_expr) = tail_expr {
91 format_to!(buf, " {}\n", tail_expr)
92 }
93 buf += "}";
94 ast_from_text(&format!("fn f() {}", buf))
95}
96
97pub fn expr_unit() -> ast::Expr {
98 expr_from_text("()")
99}
100pub fn expr_empty_block() -> ast::Expr {
101 expr_from_text("{}")
102}
103pub fn expr_unimplemented() -> ast::Expr {
104 expr_from_text("unimplemented!()")
105}
106pub fn expr_unreachable() -> ast::Expr {
107 expr_from_text("unreachable!()")
108}
109pub fn expr_todo() -> ast::Expr {
110 expr_from_text("todo!()")
111}
112pub fn expr_path(path: ast::Path) -> ast::Expr {
113 expr_from_text(&path.to_string())
114}
115pub fn expr_continue() -> ast::Expr {
116 expr_from_text("continue")
117}
118pub fn expr_break() -> ast::Expr {
119 expr_from_text("break")
120}
121pub fn expr_return() -> ast::Expr {
122 expr_from_text("return")
123}
124pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
125 expr_from_text(&format!("match {} {}", expr, match_arm_list))
126}
127pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr {
128 expr_from_text(&format!("if {} {}", condition, then_branch))
129}
130pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
131 let token = token(op);
132 expr_from_text(&format!("{}{}", token, expr))
133}
134fn expr_from_text(text: &str) -> ast::Expr {
135 ast_from_text(&format!("const C: () = {};", text))
136}
137
138pub fn try_expr_from_text(text: &str) -> Option<ast::Expr> {
139 try_ast_from_text(&format!("const C: () = {};", text))
140}
141
142pub fn condition(expr: ast::Expr, pattern: Option<ast::Pat>) -> ast::Condition {
143 match pattern {
144 None => ast_from_text(&format!("const _: () = while {} {{}};", expr)),
145 Some(pattern) => {
146 ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr))
147 }
148 }
149}
150
151pub fn ident_pat(name: ast::Name) -> ast::IdentPat {
152 return from_text(name.text());
153
154 fn from_text(text: &str) -> ast::IdentPat {
155 ast_from_text(&format!("fn f({}: ())", text))
156 }
157}
158
159pub fn wildcard_pat() -> ast::WildcardPat {
160 return from_text("_");
161
162 fn from_text(text: &str) -> ast::WildcardPat {
163 ast_from_text(&format!("fn f({}: ())", text))
164 }
165}
166
167/// Creates a tuple of patterns from an interator of patterns.
168///
169/// Invariant: `pats` must be length > 1
170///
171/// FIXME handle `pats` length == 1
172pub fn tuple_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::TuplePat {
173 let pats_str = pats.into_iter().map(|p| p.to_string()).join(", ");
174 return from_text(&format!("({})", pats_str));
175
176 fn from_text(text: &str) -> ast::TuplePat {
177 ast_from_text(&format!("fn f({}: ())", text))
178 }
179}
180
181pub fn tuple_struct_pat(
182 path: ast::Path,
183 pats: impl IntoIterator<Item = ast::Pat>,
184) -> ast::TupleStructPat {
185 let pats_str = pats.into_iter().join(", ");
186 return from_text(&format!("{}({})", path, pats_str));
187
188 fn from_text(text: &str) -> ast::TupleStructPat {
189 ast_from_text(&format!("fn f({}: ())", text))
190 }
191}
192
193pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> ast::RecordPat {
194 let pats_str = pats.into_iter().join(", ");
195 return from_text(&format!("{} {{ {} }}", path, pats_str));
196
197 fn from_text(text: &str) -> ast::RecordPat {
198 ast_from_text(&format!("fn f({}: ())", text))
199 }
200}
201
202/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
203pub fn path_pat(path: ast::Path) -> ast::Pat {
204 return from_text(&path.to_string());
205 fn from_text(text: &str) -> ast::Pat {
206 ast_from_text(&format!("fn f({}: ())", text))
207 }
208}
209
210pub fn match_arm(pats: impl IntoIterator<Item = ast::Pat>, expr: ast::Expr) -> ast::MatchArm {
211 let pats_str = pats.into_iter().join(" | ");
212 return from_text(&format!("{} => {}", pats_str, expr));
213
214 fn from_text(text: &str) -> ast::MatchArm {
215 ast_from_text(&format!("fn f() {{ match () {{{}}} }}", text))
216 }
217}
218
219pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
220 let arms_str = arms
221 .into_iter()
222 .map(|arm| {
223 let needs_comma = arm.expr().map_or(true, |it| !it.is_block_like());
224 let comma = if needs_comma { "," } else { "" };
225 format!(" {}{}\n", arm.syntax(), comma)
226 })
227 .collect::<String>();
228 return from_text(&arms_str);
229
230 fn from_text(text: &str) -> ast::MatchArmList {
231 ast_from_text(&format!("fn f() {{ match () {{\n{}}} }}", text))
232 }
233}
234
235pub fn where_pred(
236 path: ast::Path,
237 bounds: impl IntoIterator<Item = ast::TypeBound>,
238) -> ast::WherePred {
239 let bounds = bounds.into_iter().join(" + ");
240 return from_text(&format!("{}: {}", path, bounds));
241
242 fn from_text(text: &str) -> ast::WherePred {
243 ast_from_text(&format!("fn f() where {} {{ }}", text))
244 }
245}
246
247pub fn where_clause(preds: impl IntoIterator<Item = ast::WherePred>) -> ast::WhereClause {
248 let preds = preds.into_iter().join(", ");
249 return from_text(preds.as_str());
250
251 fn from_text(text: &str) -> ast::WhereClause {
252 ast_from_text(&format!("fn f() where {} {{ }}", text))
253 }
254}
255
256pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetStmt {
257 let text = match initializer {
258 Some(it) => format!("let {} = {};", pattern, it),
259 None => format!("let {};", pattern),
260 };
261 ast_from_text(&format!("fn f() {{ {} }}", text))
262}
263pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
264 let semi = if expr.is_block_like() { "" } else { ";" };
265 ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi))
266}
267
268pub fn token(kind: SyntaxKind) -> SyntaxToken {
269 tokens::SOURCE_FILE
270 .tree()
271 .syntax()
272 .descendants_with_tokens()
273 .filter_map(|it| it.into_token())
274 .find(|it| it.kind() == kind)
275 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
276}
277
278pub fn param(name: String, ty: String) -> ast::Param {
279 ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty))
280}
281
282pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList {
283 let args = pats.into_iter().join(", ");
284 ast_from_text(&format!("fn f({}) {{ }}", args))
285}
286
287pub fn visibility_pub_crate() -> ast::Visibility {
288 ast_from_text("pub(crate) struct S")
289}
290
291pub fn fn_(
292 visibility: Option<ast::Visibility>,
293 fn_name: ast::Name,
294 type_params: Option<ast::GenericParamList>,
295 params: ast::ParamList,
296 body: ast::BlockExpr,
297) -> ast::Fn {
298 let type_params =
299 if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
300 let visibility = match visibility {
301 None => String::new(),
302 Some(it) => format!("{} ", it),
303 };
304 ast_from_text(&format!("{}fn {}{}{} {}", visibility, fn_name, type_params, params, body))
305}
306
307fn ast_from_text<N: AstNode>(text: &str) -> N {
308 let parse = SourceFile::parse(text);
309 let node = match parse.tree().syntax().descendants().find_map(N::cast) {
310 Some(it) => it,
311 None => {
312 panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text)
313 }
314 };
315 let node = node.syntax().clone();
316 let node = unroot(node);
317 let node = N::cast(node).unwrap();
318 assert_eq!(node.syntax().text_range().start(), 0.into());
319 node
320}
321
322fn try_ast_from_text<N: AstNode>(text: &str) -> Option<N> {
323 let parse = SourceFile::parse(text);
324 let node = parse.tree().syntax().descendants().find_map(N::cast)?;
325 let node = node.syntax().clone();
326 let node = unroot(node);
327 let node = N::cast(node).unwrap();
328 assert_eq!(node.syntax().text_range().start(), 0.into());
329 Some(node)
330}
331
332fn unroot(n: SyntaxNode) -> SyntaxNode {
333 SyntaxNode::new_root(n.green().clone())
334}
335
336pub mod tokens {
337 use once_cell::sync::Lazy;
338
339 use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken};
340
341 pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> =
342 Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true)\n;"));
343
344 pub fn single_space() -> SyntaxToken {
345 SOURCE_FILE
346 .tree()
347 .syntax()
348 .descendants_with_tokens()
349 .filter_map(|it| it.into_token())
350 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ")
351 .unwrap()
352 }
353
354 pub fn whitespace(text: &str) -> SyntaxToken {
355 assert!(text.trim().is_empty());
356 let sf = SourceFile::parse(text).ok().unwrap();
357 sf.syntax().first_child_or_token().unwrap().into_token().unwrap()
358 }
359
360 pub fn doc_comment(text: &str) -> SyntaxToken {
361 assert!(!text.trim().is_empty());
362 let sf = SourceFile::parse(text).ok().unwrap();
363 sf.syntax().first_child_or_token().unwrap().into_token().unwrap()
364 }
365
366 pub fn literal(text: &str) -> SyntaxToken {
367 assert_eq!(text.trim(), text);
368 let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {}; }}", text));
369 lit.syntax().first_child_or_token().unwrap().into_token().unwrap()
370 }
371
372 pub fn single_newline() -> SyntaxToken {
373 SOURCE_FILE
374 .tree()
375 .syntax()
376 .descendants_with_tokens()
377 .filter_map(|it| it.into_token())
378 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n")
379 .unwrap()
380 }
381
382 pub struct WsBuilder(SourceFile);
383
384 impl WsBuilder {
385 pub fn new(text: &str) -> WsBuilder {
386 WsBuilder(SourceFile::parse(text).ok().unwrap())
387 }
388 pub fn ws(&self) -> SyntaxToken {
389 self.0.syntax().first_child_or_token().unwrap().into_token().unwrap()
390 }
391 }
392}