diff options
Diffstat (limited to 'crates/syntax/src/ast/make.rs')
-rw-r--r-- | crates/syntax/src/ast/make.rs | 402 |
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. | ||
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_segment_self() -> ast::PathSegment { | ||
28 | ast_from_text("use self;") | ||
29 | } | ||
30 | pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { | ||
31 | path_from_text(&format!("use {}", segment)) | ||
32 | } | ||
33 | pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { | ||
34 | path_from_text(&format!("{}::{}", qual, segment)) | ||
35 | } | ||
36 | pub fn path_from_text(text: &str) -> ast::Path { | ||
37 | ast_from_text(text) | ||
38 | } | ||
39 | |||
40 | pub 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 | |||
61 | pub 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 | |||
66 | pub fn use_(use_tree: ast::UseTree) -> ast::Use { | ||
67 | ast_from_text(&format!("use {};", use_tree)) | ||
68 | } | ||
69 | |||
70 | pub 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 | |||
81 | pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { | ||
82 | ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) | ||
83 | } | ||
84 | |||
85 | pub 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 | |||
100 | pub fn expr_unit() -> ast::Expr { | ||
101 | expr_from_text("()") | ||
102 | } | ||
103 | pub fn expr_empty_block() -> ast::Expr { | ||
104 | expr_from_text("{}") | ||
105 | } | ||
106 | pub fn expr_unimplemented() -> ast::Expr { | ||
107 | expr_from_text("unimplemented!()") | ||
108 | } | ||
109 | pub fn expr_unreachable() -> ast::Expr { | ||
110 | expr_from_text("unreachable!()") | ||
111 | } | ||
112 | pub fn expr_todo() -> ast::Expr { | ||
113 | expr_from_text("todo!()") | ||
114 | } | ||
115 | pub fn expr_path(path: ast::Path) -> ast::Expr { | ||
116 | expr_from_text(&path.to_string()) | ||
117 | } | ||
118 | pub fn expr_continue() -> ast::Expr { | ||
119 | expr_from_text("continue") | ||
120 | } | ||
121 | pub fn expr_break() -> ast::Expr { | ||
122 | expr_from_text("break") | ||
123 | } | ||
124 | pub fn expr_return() -> ast::Expr { | ||
125 | expr_from_text("return") | ||
126 | } | ||
127 | pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { | ||
128 | expr_from_text(&format!("match {} {}", expr, match_arm_list)) | ||
129 | } | ||
130 | pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr { | ||
131 | expr_from_text(&format!("if {} {}", condition, then_branch)) | ||
132 | } | ||
133 | pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { | ||
134 | let token = token(op); | ||
135 | expr_from_text(&format!("{}{}", token, expr)) | ||
136 | } | ||
137 | pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { | ||
138 | expr_from_text(&format!("{}{}", f, arg_list)) | ||
139 | } | ||
140 | fn expr_from_text(text: &str) -> ast::Expr { | ||
141 | ast_from_text(&format!("const C: () = {};", text)) | ||
142 | } | ||
143 | |||
144 | pub fn try_expr_from_text(text: &str) -> Option<ast::Expr> { | ||
145 | try_ast_from_text(&format!("const C: () = {};", text)) | ||
146 | } | ||
147 | |||
148 | pub 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 | |||
157 | pub fn arg_list(args: impl IntoIterator<Item = ast::Expr>) -> ast::ArgList { | ||
158 | ast_from_text(&format!("fn main() {{ ()({}) }}", args.into_iter().format(", "))) | ||
159 | } | ||
160 | |||
161 | pub 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 | |||
169 | pub 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 | ||
182 | pub 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 | |||
191 | pub 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 | |||
203 | pub 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. | ||
213 | pub 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 | |||
220 | pub 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 | |||
229 | pub 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 | |||
245 | pub 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 | |||
257 | pub 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 | |||
266 | pub 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 | } | ||
273 | pub 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 | |||
278 | pub 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 | |||
288 | pub fn param(name: String, ty: String) -> ast::Param { | ||
289 | ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) | ||
290 | } | ||
291 | |||
292 | pub 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 | |||
297 | pub fn visibility_pub_crate() -> ast::Visibility { | ||
298 | ast_from_text("pub(crate) struct S") | ||
299 | } | ||
300 | |||
301 | pub 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 | |||
317 | fn 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 | |||
332 | fn 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 | |||
342 | fn unroot(n: SyntaxNode) -> SyntaxNode { | ||
343 | SyntaxNode::new_root(n.green().clone()) | ||
344 | } | ||
345 | |||
346 | pub 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 | } | ||