Skip to content

Commit 05b48e4

Browse files
committed
internal: Add SyntaxFactory to ease generating nodes with syntax mappings
1 parent af9a658 commit 05b48e4

File tree

5 files changed

+186
-101
lines changed

5 files changed

+186
-101
lines changed

crates/syntax/src/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod make;
88
mod node_ext;
99
mod operators;
1010
pub mod prec;
11+
pub mod syntax_factory;
1112
mod token_ext;
1213
mod traits;
1314

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! Builds upon [`crate::ast::make`] constructors to create ast fragments with
2+
//! optional syntax mappings.
3+
//!
4+
//! Instead of forcing make constructors to perform syntax mapping, we instead
5+
//! let [`SyntaxFactory`] handle constructing the mappings. Care must be taken
6+
//! to remember to feed the syntax mappings into a [`SyntaxEditor`](crate::syntax_editor::SyntaxEditor),
7+
//! if applicable.
8+
9+
mod constructors;
10+
11+
use std::cell::{RefCell, RefMut};
12+
13+
use crate::syntax_editor::SyntaxMapping;
14+
15+
pub struct SyntaxFactory {
16+
// Stored in a refcell so that the factory methods can be &self
17+
mappings: Option<RefCell<SyntaxMapping>>,
18+
}
19+
20+
impl SyntaxFactory {
21+
/// Creates a new [`SyntaxFactory`], generating mappings between input nodes and generated nodes.
22+
pub fn new() -> Self {
23+
Self { mappings: Some(RefCell::new(SyntaxMapping::new())) }
24+
}
25+
26+
/// Creates a [`SyntaxFactory`] without generating mappings.
27+
pub fn without_mappings() -> Self {
28+
Self { mappings: None }
29+
}
30+
31+
/// Gets all of the tracked syntax mappings, if any.
32+
pub fn finish_with_mappings(self) -> SyntaxMapping {
33+
self.mappings.unwrap_or_default().into_inner()
34+
}
35+
36+
fn mappings(&self) -> Option<RefMut<'_, SyntaxMapping>> {
37+
self.mappings.as_ref().map(|it| it.borrow_mut())
38+
}
39+
}
40+
41+
impl Default for SyntaxFactory {
42+
fn default() -> Self {
43+
Self::without_mappings()
44+
}
45+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Wrappers over [`make`] constructors
2+
use itertools::Itertools;
3+
4+
use crate::{
5+
ast::{self, make, HasName},
6+
syntax_editor::SyntaxMappingBuilder,
7+
AstNode,
8+
};
9+
10+
use super::SyntaxFactory;
11+
12+
impl SyntaxFactory {
13+
pub fn name(&self, name: &str) -> ast::Name {
14+
make::name(name).clone_for_update()
15+
}
16+
17+
pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat {
18+
let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update();
19+
20+
if let Some(mut mapping) = self.mappings() {
21+
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
22+
builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
23+
builder.finish(&mut mapping);
24+
}
25+
26+
ast
27+
}
28+
29+
pub fn block_expr(
30+
&self,
31+
stmts: impl IntoIterator<Item = ast::Stmt>,
32+
tail_expr: Option<ast::Expr>,
33+
) -> ast::BlockExpr {
34+
let stmts = stmts.into_iter().collect_vec();
35+
let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec();
36+
37+
let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update();
38+
39+
if let Some((mut mapping, stmt_list)) = self.mappings().zip(ast.stmt_list()) {
40+
let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
41+
42+
builder.map_children(
43+
input.into_iter(),
44+
stmt_list.statements().map(|it| it.syntax().clone()),
45+
);
46+
47+
if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) {
48+
builder.map_node(input.syntax().clone(), output.syntax().clone());
49+
}
50+
51+
builder.finish(&mut mapping);
52+
}
53+
54+
ast
55+
}
56+
57+
pub fn expr_path(&self, path: ast::Path) -> ast::Expr {
58+
let ast::Expr::PathExpr(ast) = make::expr_path(path.clone()).clone_for_update() else {
59+
unreachable!()
60+
};
61+
62+
if let Some(mut mapping) = self.mappings() {
63+
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
64+
builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone());
65+
builder.finish(&mut mapping);
66+
}
67+
68+
ast.into()
69+
}
70+
71+
pub fn expr_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr {
72+
let ast::Expr::RefExpr(ast) = make::expr_ref(expr.clone(), exclusive).clone_for_update()
73+
else {
74+
unreachable!()
75+
};
76+
77+
if let Some(mut mapping) = self.mappings() {
78+
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
79+
builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone());
80+
builder.finish(&mut mapping);
81+
}
82+
83+
ast.into()
84+
}
85+
86+
pub fn let_stmt(
87+
&self,
88+
pattern: ast::Pat,
89+
ty: Option<ast::Type>,
90+
initializer: Option<ast::Expr>,
91+
) -> ast::LetStmt {
92+
let ast =
93+
make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update();
94+
95+
if let Some(mut mapping) = self.mappings() {
96+
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
97+
builder.map_node(pattern.syntax().clone(), ast.pat().unwrap().syntax().clone());
98+
if let Some(input) = ty {
99+
builder.map_node(input.syntax().clone(), ast.ty().unwrap().syntax().clone());
100+
}
101+
if let Some(input) = initializer {
102+
builder
103+
.map_node(input.syntax().clone(), ast.initializer().unwrap().syntax().clone());
104+
}
105+
builder.finish(&mut mapping);
106+
}
107+
108+
ast
109+
}
110+
}

crates/syntax/src/syntax_editor.rs

Lines changed: 27 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ impl SyntaxEditor {
100100
pub fn finish(self) -> SyntaxEdit {
101101
edit_algo::apply_edits(self)
102102
}
103+
104+
pub fn add_mappings(&mut self, other: SyntaxMapping) {
105+
self.mappings.merge(other);
106+
}
103107
}
104108

105109
/// Represents a completed [`SyntaxEditor`] operation.
@@ -319,85 +323,14 @@ fn is_ancestor_or_self_of_element(node: &SyntaxElement, ancestor: &SyntaxNode) -
319323
#[cfg(test)]
320324
mod tests {
321325
use expect_test::expect;
322-
use itertools::Itertools;
323326

324327
use crate::{
325-
ast::{self, make, HasName},
328+
ast::{self, make, syntax_factory::SyntaxFactory},
326329
AstNode,
327330
};
328331

329332
use super::*;
330333

331-
fn make_ident_pat(
332-
editor: Option<&mut SyntaxEditor>,
333-
ref_: bool,
334-
mut_: bool,
335-
name: ast::Name,
336-
) -> ast::IdentPat {
337-
let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update();
338-
339-
if let Some(editor) = editor {
340-
let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone());
341-
mapping.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
342-
mapping.finish(editor);
343-
}
344-
345-
ast
346-
}
347-
348-
fn make_let_stmt(
349-
editor: Option<&mut SyntaxEditor>,
350-
pattern: ast::Pat,
351-
ty: Option<ast::Type>,
352-
initializer: Option<ast::Expr>,
353-
) -> ast::LetStmt {
354-
let ast =
355-
make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update();
356-
357-
if let Some(editor) = editor {
358-
let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone());
359-
mapping.map_node(pattern.syntax().clone(), ast.pat().unwrap().syntax().clone());
360-
if let Some(input) = ty {
361-
mapping.map_node(input.syntax().clone(), ast.ty().unwrap().syntax().clone());
362-
}
363-
if let Some(input) = initializer {
364-
mapping
365-
.map_node(input.syntax().clone(), ast.initializer().unwrap().syntax().clone());
366-
}
367-
mapping.finish(editor);
368-
}
369-
370-
ast
371-
}
372-
373-
fn make_block_expr(
374-
editor: Option<&mut SyntaxEditor>,
375-
stmts: impl IntoIterator<Item = ast::Stmt>,
376-
tail_expr: Option<ast::Expr>,
377-
) -> ast::BlockExpr {
378-
let stmts = stmts.into_iter().collect_vec();
379-
let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec();
380-
381-
let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update();
382-
383-
if let Some((editor, stmt_list)) = editor.zip(ast.stmt_list()) {
384-
let mut mapping = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
385-
386-
mapping.map_children(
387-
input.into_iter(),
388-
stmt_list.statements().map(|it| it.syntax().clone()),
389-
);
390-
391-
if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) {
392-
mapping.map_node(input.syntax().clone(), output.syntax().clone());
393-
}
394-
395-
mapping.finish(editor);
396-
}
397-
398-
ast
399-
}
400-
401334
#[test]
402335
fn basic_usage() {
403336
let root = make::match_arm(
@@ -417,6 +350,7 @@ mod tests {
417350
let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap();
418351

419352
let mut editor = SyntaxEditor::new(root.syntax().clone());
353+
let make = SyntaxFactory::new();
420354

421355
let name = make::name("var_name");
422356
let name_ref = make::name_ref("var_name").clone_for_update();
@@ -425,21 +359,20 @@ mod tests {
425359
editor.add_annotation(name.syntax(), placeholder_snippet);
426360
editor.add_annotation(name_ref.syntax(), placeholder_snippet);
427361

428-
let make_ident_pat = make_ident_pat(Some(&mut editor), false, false, name);
429-
let make_let_stmt = make_let_stmt(
430-
Some(&mut editor),
431-
make_ident_pat.into(),
432-
None,
433-
Some(to_replace.clone().into()),
434-
);
435-
let new_block = make_block_expr(
436-
Some(&mut editor),
437-
[make_let_stmt.into()],
362+
let new_block = make.block_expr(
363+
[make
364+
.let_stmt(
365+
make.ident_pat(false, false, name.clone()).into(),
366+
None,
367+
Some(to_replace.clone().into()),
368+
)
369+
.into()],
438370
Some(to_wrap.clone().into()),
439371
);
440372

441373
editor.replace(to_replace.syntax(), name_ref.syntax());
442374
editor.replace(to_wrap.syntax(), new_block.syntax());
375+
editor.add_mappings(make.finish_with_mappings());
443376

444377
let edit = editor.finish();
445378

@@ -473,11 +406,11 @@ mod tests {
473406
let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap();
474407

475408
let mut editor = SyntaxEditor::new(root.syntax().clone());
409+
let make = SyntaxFactory::without_mappings();
476410

477411
editor.insert(
478412
Position::first_child_of(root.stmt_list().unwrap().syntax()),
479-
make_let_stmt(
480-
None,
413+
make.let_stmt(
481414
make::ext::simple_ident_pat(make::name("first")).into(),
482415
None,
483416
Some(make::expr_literal("1").into()),
@@ -487,8 +420,7 @@ mod tests {
487420

488421
editor.insert(
489422
Position::after(second_let.syntax()),
490-
make_let_stmt(
491-
None,
423+
make.let_stmt(
492424
make::ext::simple_ident_pat(make::name("third")).into(),
493425
None,
494426
Some(make::expr_literal("3").into()),
@@ -528,19 +460,17 @@ mod tests {
528460
let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap();
529461

530462
let mut editor = SyntaxEditor::new(root.syntax().clone());
463+
let make = SyntaxFactory::new();
531464

532-
let new_block_expr =
533-
make_block_expr(Some(&mut editor), [], Some(ast::Expr::BlockExpr(inner_block.clone())));
465+
let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone())));
534466

535-
let first_let = make_let_stmt(
536-
Some(&mut editor),
467+
let first_let = make.let_stmt(
537468
make::ext::simple_ident_pat(make::name("first")).into(),
538469
None,
539470
Some(make::expr_literal("1").into()),
540471
);
541472

542-
let third_let = make_let_stmt(
543-
Some(&mut editor),
473+
let third_let = make.let_stmt(
544474
make::ext::simple_ident_pat(make::name("third")).into(),
545475
None,
546476
Some(make::expr_literal("3").into()),
@@ -552,6 +482,7 @@ mod tests {
552482
);
553483
editor.insert(Position::after(second_let.syntax()), third_let.syntax());
554484
editor.replace(inner_block.syntax(), new_block_expr.syntax());
485+
editor.add_mappings(make.finish_with_mappings());
555486

556487
let edit = editor.finish();
557488

@@ -581,12 +512,11 @@ mod tests {
581512
let inner_block = root.clone();
582513

583514
let mut editor = SyntaxEditor::new(root.syntax().clone());
515+
let make = SyntaxFactory::new();
584516

585-
let new_block_expr =
586-
make_block_expr(Some(&mut editor), [], Some(ast::Expr::BlockExpr(inner_block.clone())));
517+
let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone())));
587518

588-
let first_let = make_let_stmt(
589-
Some(&mut editor),
519+
let first_let = make.let_stmt(
590520
make::ext::simple_ident_pat(make::name("first")).into(),
591521
None,
592522
Some(make::expr_literal("1").into()),
@@ -597,6 +527,7 @@ mod tests {
597527
first_let.syntax(),
598528
);
599529
editor.replace(inner_block.syntax(), new_block_expr.syntax());
530+
editor.add_mappings(make.finish_with_mappings());
600531

601532
let edit = editor.finish();
602533

0 commit comments

Comments
 (0)