diff options
Diffstat (limited to 'crates/syntax/src/ast/make.rs')
-rw-r--r-- | crates/syntax/src/ast/make.rs | 392 |
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. | ||
7 | use itertools::Itertools; | ||
8 | use stdx::format_to; | ||
9 | |||
10 | use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken}; | ||
11 | |||
12 | pub fn name(text: &str) -> ast::Name { | ||
13 | ast_from_text(&format!("mod {};", text)) | ||
14 | } | ||
15 | |||
16 | pub fn name_ref(text: &str) -> ast::NameRef { | ||
17 | ast_from_text(&format!("fn f() {{ {}; }}", text)) | ||
18 | } | ||
19 | |||
20 | pub fn ty(text: &str) -> ast::Type { | ||
21 | ast_from_text(&format!("impl {} for D {{}};", text)) | ||
22 | } | ||
23 | |||
24 | pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { | ||
25 | ast_from_text(&format!("use {};", name_ref)) | ||
26 | } | ||
27 | pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { | ||
28 | path_from_text(&format!("use {}", segment)) | ||
29 | } | ||
30 | pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { | ||
31 | path_from_text(&format!("{}::{}", qual, segment)) | ||
32 | } | ||
33 | pub fn path_from_text(text: &str) -> ast::Path { | ||
34 | ast_from_text(text) | ||
35 | } | ||
36 | |||
37 | pub 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 | |||
58 | pub 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 | |||
63 | pub fn use_(use_tree: ast::UseTree) -> ast::Use { | ||
64 | ast_from_text(&format!("use {};", use_tree)) | ||
65 | } | ||
66 | |||
67 | pub 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 | |||
78 | pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { | ||
79 | ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) | ||
80 | } | ||
81 | |||
82 | pub 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 | |||
97 | pub fn expr_unit() -> ast::Expr { | ||
98 | expr_from_text("()") | ||
99 | } | ||
100 | pub fn expr_empty_block() -> ast::Expr { | ||
101 | expr_from_text("{}") | ||
102 | } | ||
103 | pub fn expr_unimplemented() -> ast::Expr { | ||
104 | expr_from_text("unimplemented!()") | ||
105 | } | ||
106 | pub fn expr_unreachable() -> ast::Expr { | ||
107 | expr_from_text("unreachable!()") | ||
108 | } | ||
109 | pub fn expr_todo() -> ast::Expr { | ||
110 | expr_from_text("todo!()") | ||
111 | } | ||
112 | pub fn expr_path(path: ast::Path) -> ast::Expr { | ||
113 | expr_from_text(&path.to_string()) | ||
114 | } | ||
115 | pub fn expr_continue() -> ast::Expr { | ||
116 | expr_from_text("continue") | ||
117 | } | ||
118 | pub fn expr_break() -> ast::Expr { | ||
119 | expr_from_text("break") | ||
120 | } | ||
121 | pub fn expr_return() -> ast::Expr { | ||
122 | expr_from_text("return") | ||
123 | } | ||
124 | pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { | ||
125 | expr_from_text(&format!("match {} {}", expr, match_arm_list)) | ||
126 | } | ||
127 | pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr { | ||
128 | expr_from_text(&format!("if {} {}", condition, then_branch)) | ||
129 | } | ||
130 | pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { | ||
131 | let token = token(op); | ||
132 | expr_from_text(&format!("{}{}", token, expr)) | ||
133 | } | ||
134 | fn expr_from_text(text: &str) -> ast::Expr { | ||
135 | ast_from_text(&format!("const C: () = {};", text)) | ||
136 | } | ||
137 | |||
138 | pub fn try_expr_from_text(text: &str) -> Option<ast::Expr> { | ||
139 | try_ast_from_text(&format!("const C: () = {};", text)) | ||
140 | } | ||
141 | |||
142 | pub 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 | |||
151 | pub 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 | |||
159 | pub 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 | ||
172 | pub 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 | |||
181 | pub 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 | |||
193 | pub 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. | ||
203 | pub 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 | |||
210 | pub 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 | |||
219 | pub 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 | |||
235 | pub 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 | |||
247 | pub 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 | |||
256 | pub 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 | } | ||
263 | pub 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 | |||
268 | pub 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 | |||
278 | pub fn param(name: String, ty: String) -> ast::Param { | ||
279 | ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) | ||
280 | } | ||
281 | |||
282 | pub 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 | |||
287 | pub fn visibility_pub_crate() -> ast::Visibility { | ||
288 | ast_from_text("pub(crate) struct S") | ||
289 | } | ||
290 | |||
291 | pub 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 | |||
307 | fn 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 | |||
322 | fn 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 | |||
332 | fn unroot(n: SyntaxNode) -> SyntaxNode { | ||
333 | SyntaxNode::new_root(n.green().clone()) | ||
334 | } | ||
335 | |||
336 | pub 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 | } | ||