about summary refs log tree commit diff
path: root/src/tools/rust-analyzer/crates
diff options
context:
space:
mode:
authorChayim Refael Friedman <chayimfr@gmail.com>2024-12-30 04:54:39 +0200
committerChayim Refael Friedman <chayimfr@gmail.com>2024-12-30 04:54:39 +0200
commita0c052fa57083031234f35462fbee5026413fd8d (patch)
treeb48c07569e45630b02cf02495e9d2845c4c35ebc /src/tools/rust-analyzer/crates
parentbe12c80d23404c59cfbe80529f6f466637619140 (diff)
downloadrust-a0c052fa57083031234f35462fbee5026413fd8d.tar.gz
rust-a0c052fa57083031234f35462fbee5026413fd8d.zip
Create a `quote!`-like API for crafting AST nodes
Instead of messing with textual `make`.

And port one `make` helper to it, for the sake of testing.
Diffstat (limited to 'src/tools/rust-analyzer/crates')
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make.rs21
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make/quote.rs170
2 files changed, 182 insertions, 9 deletions
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
index eb96ab6ef59..059028bea82 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
@@ -10,13 +10,15 @@
 //! `parse(format!())` we use internally is an implementation detail -- long
 //! term, it will be replaced with direct tree manipulation.
 
+mod quote;
+
 use itertools::Itertools;
 use parser::{Edition, T};
 use rowan::NodeOrToken;
 use stdx::{format_to, format_to_acc, never};
 
 use crate::{
-    ast::{self, Param},
+    ast::{self, make::quote::quote, Param},
     utils::is_raw_identifier,
     AstNode, SourceFile, SyntaxKind, SyntaxToken,
 };
@@ -480,15 +482,16 @@ pub fn block_expr(
     stmts: impl IntoIterator<Item = ast::Stmt>,
     tail_expr: Option<ast::Expr>,
 ) -> ast::BlockExpr {
-    let mut buf = "{\n".to_owned();
-    for stmt in stmts.into_iter() {
-        format_to!(buf, "    {stmt}\n");
-    }
-    if let Some(tail_expr) = tail_expr {
-        format_to!(buf, "    {tail_expr}\n");
+    quote! {
+        BlockExpr {
+            StmtList {
+                ['{'] "\n"
+                #("    " #stmts "\n")*
+                #("    " #tail_expr "\n")*
+                ['}']
+            }
+        }
     }
-    buf += "}";
-    ast_from_text(&format!("fn f() {buf}"))
 }
 
 pub fn async_move_block_expr(
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make/quote.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make/quote.rs
new file mode 100644
index 00000000000..1c66a413f87
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make/quote.rs
@@ -0,0 +1,170 @@
+//! A `quote!`-like API for crafting AST nodes.
+
+pub(crate) use rowan::{GreenNode, GreenToken, NodeOrToken, SyntaxKind as RSyntaxKind};
+
+macro_rules! quote_impl_ {
+    ( @append $children:ident ) => {}; // Base case.
+
+    ( @append $children:ident
+        $node:ident {
+            $($tree:tt)*
+        }
+        $($rest:tt)*
+    ) => {
+        {
+            #[allow(unused_mut)]
+            let mut inner_children = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
+                $crate::ast::make::quote::GreenNode,
+                $crate::ast::make::quote::GreenToken,
+            >>::new();
+            $crate::ast::make::quote::quote_impl!( @append inner_children
+                $($tree)*
+            );
+            let kind = <$crate::ast::$node as $crate::ast::AstNode>::kind();
+            let node = $crate::ast::make::quote::GreenNode::new($crate::ast::make::quote::RSyntaxKind(kind as u16), inner_children);
+            $children.push($crate::ast::make::quote::NodeOrToken::Node(node));
+        }
+        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
+    };
+
+    ( @append $children:ident
+        [$($token:tt)+]
+        $($rest:tt)*
+    ) => {
+        $children.push($crate::ast::make::quote::NodeOrToken::Token(
+            $crate::ast::make::quote::GreenToken::new(
+                $crate::ast::make::quote::RSyntaxKind($crate::T![ $($token)+ ] as u16),
+                const { $crate::T![ $($token)+ ].text() },
+            ),
+        ));
+        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
+    };
+
+    ( @append $children:ident
+        $whitespace:literal
+        $($rest:tt)*
+    ) => {
+        const { $crate::ast::make::quote::verify_only_whitespaces($whitespace) };
+        $children.push($crate::ast::make::quote::NodeOrToken::Token(
+            $crate::ast::make::quote::GreenToken::new(
+                $crate::ast::make::quote::RSyntaxKind($crate::SyntaxKind::WHITESPACE as u16),
+                $whitespace,
+            ),
+        ));
+        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
+    };
+
+    ( @append $children:ident
+        # $var:ident
+        $($rest:tt)*
+    ) => {
+        $crate::ast::make::quote::ToNodeChild::append_node_child($var, &mut $children);
+        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
+    };
+
+    ( @append $children:ident
+        #( $($repetition:tt)+ )*
+        $($rest:tt)*
+    ) => {
+        $crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
+            [] [] $($repetition)*
+        );
+        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
+    };
+
+    // Base case - no repetition var.
+    ( @extract_pounded_in_repetition $children:ident
+        [ $($repetition:tt)* ] [ ]
+    ) => {
+        ::std::compile_error!("repetition in `ast::make::quote!()` without variable");
+    };
+
+    // Base case - repetition var found.
+    ( @extract_pounded_in_repetition $children:ident
+        [ $($repetition:tt)* ] [ $repetition_var:ident ]
+    ) => {
+        ::std::iter::IntoIterator::into_iter($repetition_var).for_each(|$repetition_var| {
+            $crate::ast::make::quote::quote_impl!( @append $children $($repetition)* );
+        });
+    };
+
+    ( @extract_pounded_in_repetition $children:ident
+        [ $($repetition:tt)* ] [ $repetition_var1:ident ] # $repetition_var2:ident $($rest:tt)*
+    ) => {
+        ::std::compile_error!("repetition in `ast::make::quote!()` with more than one variable");
+    };
+
+    ( @extract_pounded_in_repetition $children:ident
+        [ $($repetition:tt)* ] [ ] # $repetition_var:ident $($rest:tt)*
+    ) => {
+        $crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
+            [ $($repetition)* # $repetition_var ] [ $repetition_var ] $($rest)*
+        );
+    };
+
+    ( @extract_pounded_in_repetition $children:ident
+        [ $($repetition:tt)* ] [ $($repetition_var:tt)* ] $non_repetition_var:tt $($rest:tt)*
+    ) => {
+        $crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
+            [ $($repetition)* $non_repetition_var ] [ $($repetition_var)* ] $($rest)*
+        );
+    };
+}
+pub(crate) use quote_impl_ as quote_impl;
+
+/// A `quote!`-like API for crafting AST nodes.
+///
+/// Syntax: AST nodes are created with `Node { children }`, where `Node` is the node name in `ast` (`ast::Node`).
+/// Tokens are creates with their syntax enclosed by brackets, e.g. `[::]` or `['{']`. Whitespaces can be added
+/// as string literals (i.e. `"\n    "` is a whitespace token). Interpolation is allowed with `#` (`#variable`),
+/// from `AstNode`s and `Option`s of them. Repetition is also supported, with only one repeating variable
+/// and no separator (`#("\n" #variable [>])*`), for any `IntoIterator`. Note that `Option`s are also `IntoIterator`,
+/// which can help when you want to conditionally include something along with an optional node.
+///
+/// There needs to be one root node, and its type is returned.
+///
+/// Be careful to closely match the Ungrammar AST, there is no validation for this!
+macro_rules! quote_ {
+    ( $root:ident { $($tree:tt)* } ) => {{
+        let mut root = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
+            $crate::ast::make::quote::GreenNode,
+            $crate::ast::make::quote::GreenToken,
+        >>::with_capacity(1);
+        $crate::ast::make::quote::quote_impl!( @append root $root { $($tree)* } );
+        let root = root.into_iter().next().unwrap();
+        let root = $crate::SyntaxNode::new_root(root.into_node().unwrap());
+        <$crate::ast::$root as $crate::ast::AstNode>::cast(root).unwrap()
+    }};
+}
+pub(crate) use quote_ as quote;
+
+use crate::AstNode;
+
+pub(crate) trait ToNodeChild {
+    fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>);
+}
+
+impl<N: AstNode> ToNodeChild for N {
+    fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
+        children.push(self.syntax().clone_subtree().green().to_owned().into());
+    }
+}
+
+impl<C: ToNodeChild> ToNodeChild for Option<C> {
+    fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
+        if let Some(child) = self {
+            child.append_node_child(children);
+        }
+    }
+}
+
+pub(crate) const fn verify_only_whitespaces(text: &str) {
+    let text = text.as_bytes();
+    let mut i = 0;
+    while i < text.len() {
+        if !text[i].is_ascii_whitespace() {
+            panic!("non-whitespace found in whitespace token");
+        }
+        i += 1;
+    }
+}