Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 2311e96

Browse files
committed
wip: new syntax tree editor
1 parent c7f4874 commit 2311e96

File tree

4 files changed

+723
-0
lines changed

4 files changed

+723
-0
lines changed

src/tools/rust-analyzer/crates/syntax/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod ast;
4040
#[doc(hidden)]
4141
pub mod fuzz;
4242
pub mod hacks;
43+
pub mod syntax_editor;
4344
pub mod ted;
4445
pub mod utils;
4546

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
//! Syntax Tree editor
2+
//!
3+
//! Inspired by Roslyn's [`SyntaxEditor`], but is temporarily built upon mutable syntax tree editing.
4+
//!
5+
//! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs
6+
7+
use std::{
8+
num::NonZeroU32,
9+
sync::atomic::{AtomicU32, Ordering},
10+
};
11+
12+
use rowan::TextRange;
13+
use rustc_hash::FxHashMap;
14+
15+
use crate::{SyntaxElement, SyntaxNode, SyntaxToken};
16+
17+
mod edit_algo;
18+
mod mapping;
19+
20+
pub use mapping::{SyntaxMapping, SyntaxMappingBuilder};
21+
22+
#[derive(Debug)]
23+
pub struct SyntaxEditor {
24+
root: SyntaxNode,
25+
changes: Vec<Change>,
26+
mappings: SyntaxMapping,
27+
annotations: Vec<(SyntaxElement, SyntaxAnnotation)>,
28+
}
29+
30+
impl SyntaxEditor {
31+
/// Creates a syntax editor to start editing from `root`
32+
pub fn new(root: SyntaxNode) -> Self {
33+
Self { root, changes: vec![], mappings: SyntaxMapping::new(), annotations: vec![] }
34+
}
35+
36+
pub fn add_annotation(&mut self, element: impl Element, annotation: SyntaxAnnotation) {
37+
self.annotations.push((element.syntax_element(), annotation))
38+
}
39+
40+
pub fn combine(&mut self, other: SyntaxEditor) {
41+
todo!()
42+
}
43+
44+
pub fn delete(&mut self, element: impl Element) {
45+
self.changes.push(Change::Replace(element.syntax_element(), None));
46+
}
47+
48+
pub fn replace(&mut self, old: impl Element, new: impl Element) {
49+
self.changes.push(Change::Replace(old.syntax_element(), Some(new.syntax_element())));
50+
}
51+
52+
pub fn finish(self) -> SyntaxEdit {
53+
edit_algo::apply_edits(self)
54+
}
55+
}
56+
57+
pub struct SyntaxEdit {
58+
root: SyntaxNode,
59+
changed_elements: Vec<SyntaxElement>,
60+
annotations: FxHashMap<SyntaxAnnotation, Vec<SyntaxElement>>,
61+
}
62+
63+
impl SyntaxEdit {
64+
pub fn root(&self) -> &SyntaxNode {
65+
&self.root
66+
}
67+
68+
pub fn changed_elements(&self) -> &[SyntaxElement] {
69+
self.changed_elements.as_slice()
70+
}
71+
72+
pub fn find_annotation(&self, annotation: SyntaxAnnotation) -> Option<&[SyntaxElement]> {
73+
self.annotations.get(&annotation).as_ref().map(|it| it.as_slice())
74+
}
75+
}
76+
77+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78+
#[repr(transparent)]
79+
pub struct SyntaxAnnotation(NonZeroU32);
80+
81+
impl SyntaxAnnotation {
82+
/// Creates a unique syntax annotation to attach data to.
83+
pub fn new() -> Self {
84+
static COUNTER: AtomicU32 = AtomicU32::new(1);
85+
86+
// We want the id to be unique across threads, but we don't want to
87+
// tie it to other `SeqCst` operations.
88+
let id = COUNTER.fetch_add(1, Ordering::AcqRel);
89+
90+
Self(NonZeroU32::new(id).expect("syntax annotation id overflow"))
91+
}
92+
}
93+
94+
/// Position describing where to insert elements
95+
#[derive(Debug)]
96+
pub struct Position {
97+
repr: PositionRepr,
98+
}
99+
100+
#[derive(Debug)]
101+
enum PositionRepr {
102+
FirstChild(SyntaxNode),
103+
After(SyntaxElement),
104+
}
105+
106+
impl Position {
107+
pub fn after(elem: impl Element) -> Position {
108+
let repr = PositionRepr::After(elem.syntax_element());
109+
Position { repr }
110+
}
111+
112+
pub fn before(elem: impl Element) -> Position {
113+
let elem = elem.syntax_element();
114+
let repr = match elem.prev_sibling_or_token() {
115+
Some(it) => PositionRepr::After(it),
116+
None => PositionRepr::FirstChild(elem.parent().unwrap()),
117+
};
118+
Position { repr }
119+
}
120+
121+
pub fn first_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position {
122+
let repr = PositionRepr::FirstChild(node.clone().into());
123+
Position { repr }
124+
}
125+
126+
pub fn last_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position {
127+
let node = node.clone().into();
128+
let repr = match node.last_child_or_token() {
129+
Some(it) => PositionRepr::After(it),
130+
None => PositionRepr::FirstChild(node),
131+
};
132+
Position { repr }
133+
}
134+
}
135+
136+
#[derive(Debug)]
137+
enum Change {
138+
/// Represents both a replace single element and a delete element operation.
139+
Replace(SyntaxElement, Option<SyntaxElement>),
140+
}
141+
142+
impl Change {
143+
fn target_range(&self) -> TextRange {
144+
match self {
145+
Change::Replace(target, _) => target.text_range(),
146+
}
147+
}
148+
149+
fn target_parent(&self) -> SyntaxNode {
150+
match self {
151+
Change::Replace(target, _) => target.parent().unwrap(),
152+
}
153+
}
154+
155+
fn change_kind(&self) -> ChangeKind {
156+
match self {
157+
Change::Replace(_, _) => ChangeKind::Replace,
158+
}
159+
}
160+
}
161+
162+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
163+
enum ChangeKind {
164+
Insert,
165+
// TODO: deal with replace spans
166+
Replace,
167+
}
168+
169+
/// Utility trait to allow calling syntax editor functions with references or owned
170+
/// nodes. Do not use outside of this module.
171+
pub trait Element {
172+
fn syntax_element(self) -> SyntaxElement;
173+
}
174+
175+
impl<E: Element + Clone> Element for &'_ E {
176+
fn syntax_element(self) -> SyntaxElement {
177+
self.clone().syntax_element()
178+
}
179+
}
180+
181+
impl Element for SyntaxElement {
182+
fn syntax_element(self) -> SyntaxElement {
183+
self
184+
}
185+
}
186+
187+
impl Element for SyntaxNode {
188+
fn syntax_element(self) -> SyntaxElement {
189+
self.into()
190+
}
191+
}
192+
193+
impl Element for SyntaxToken {
194+
fn syntax_element(self) -> SyntaxElement {
195+
self.into()
196+
}
197+
}
198+
199+
#[cfg(test)]
200+
mod tests {
201+
use expect_test::expect;
202+
use itertools::Itertools;
203+
204+
use crate::{
205+
ast::{self, make, HasName},
206+
AstNode,
207+
};
208+
209+
use super::*;
210+
211+
fn make_ident_pat(
212+
editor: Option<&mut SyntaxEditor>,
213+
ref_: bool,
214+
mut_: bool,
215+
name: ast::Name,
216+
) -> ast::IdentPat {
217+
let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update();
218+
219+
if let Some(editor) = editor {
220+
let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone());
221+
mapping.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
222+
mapping.finish(editor);
223+
}
224+
225+
ast
226+
}
227+
228+
fn make_let_stmt(
229+
editor: Option<&mut SyntaxEditor>,
230+
pattern: ast::Pat,
231+
ty: Option<ast::Type>,
232+
initializer: Option<ast::Expr>,
233+
) -> ast::LetStmt {
234+
let ast =
235+
make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update();
236+
237+
if let Some(editor) = editor {
238+
let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone());
239+
mapping.map_node(pattern.syntax().clone(), ast.pat().unwrap().syntax().clone());
240+
if let Some(input) = ty {
241+
mapping.map_node(input.syntax().clone(), ast.ty().unwrap().syntax().clone());
242+
}
243+
if let Some(input) = initializer {
244+
mapping
245+
.map_node(input.syntax().clone(), ast.initializer().unwrap().syntax().clone());
246+
}
247+
mapping.finish(editor);
248+
}
249+
250+
ast
251+
}
252+
253+
fn make_block_expr(
254+
editor: Option<&mut SyntaxEditor>,
255+
stmts: impl IntoIterator<Item = ast::Stmt>,
256+
tail_expr: Option<ast::Expr>,
257+
) -> ast::BlockExpr {
258+
let stmts = stmts.into_iter().collect_vec();
259+
let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec();
260+
261+
let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update();
262+
263+
if let Some((editor, stmt_list)) = editor.zip(ast.stmt_list()) {
264+
let mut mapping = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
265+
266+
mapping.map_children(
267+
input.into_iter(),
268+
stmt_list.statements().map(|it| it.syntax().clone()),
269+
);
270+
271+
if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) {
272+
mapping.map_node(input.syntax().clone(), output.syntax().clone());
273+
}
274+
275+
mapping.finish(editor);
276+
}
277+
278+
ast
279+
}
280+
281+
#[test]
282+
fn it() {
283+
let root = make::match_arm(
284+
[make::wildcard_pat().into()],
285+
None,
286+
make::expr_tuple([
287+
make::expr_bin_op(
288+
make::expr_literal("2").into(),
289+
ast::BinaryOp::ArithOp(ast::ArithOp::Add),
290+
make::expr_literal("2").into(),
291+
),
292+
make::expr_literal("true").into(),
293+
]),
294+
);
295+
296+
let to_wrap = root.syntax().descendants().find_map(ast::TupleExpr::cast).unwrap();
297+
let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap();
298+
299+
let mut editor = SyntaxEditor::new(root.syntax().clone());
300+
301+
let name = make::name("var_name");
302+
let name_ref = make::name_ref("var_name").clone_for_update();
303+
304+
let placeholder_snippet = SyntaxAnnotation::new();
305+
editor.add_annotation(name.syntax(), placeholder_snippet);
306+
editor.add_annotation(name_ref.syntax(), placeholder_snippet);
307+
308+
let make_ident_pat = make_ident_pat(Some(&mut editor), false, false, name);
309+
let make_let_stmt = make_let_stmt(
310+
Some(&mut editor),
311+
make_ident_pat.into(),
312+
None,
313+
Some(to_replace.clone().into()),
314+
);
315+
let new_block = make_block_expr(
316+
Some(&mut editor),
317+
[make_let_stmt.into()],
318+
Some(to_wrap.clone().into()),
319+
);
320+
321+
// should die:
322+
editor.replace(to_replace.syntax(), name_ref.syntax());
323+
editor.replace(to_wrap.syntax(), new_block.syntax());
324+
// editor.replace(to_replace.syntax(), name_ref.syntax());
325+
326+
// dbg!(&editor.mappings);
327+
let edit = editor.finish();
328+
329+
let expect = expect![];
330+
expect.assert_eq(&edit.root.to_string());
331+
assert_eq!(edit.find_annotation(placeholder_snippet).map(|it| it.len()), Some(2));
332+
}
333+
}

0 commit comments

Comments
 (0)