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