mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 20:09:01 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									505e82c19c
								
							
						
					
					
						commit
						abd7263179
					
				
					 2 changed files with 182 additions and 9 deletions
				
			
		|  | @ -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"); | ||||
|     quote! { | ||||
|         BlockExpr { | ||||
|             StmtList { | ||||
|                 ['{'] "\n" | ||||
|                 #("    " #stmts "\n")* | ||||
|                 #("    " #tail_expr "\n")* | ||||
|                 ['}'] | ||||
|             } | ||||
|         } | ||||
|     if let Some(tail_expr) = tail_expr { | ||||
|         format_to!(buf, "    {tail_expr}\n"); | ||||
|     } | ||||
|     buf += "}"; | ||||
|     ast_from_text(&format!("fn f() {buf}")) | ||||
| } | ||||
| 
 | ||||
| pub fn async_move_block_expr( | ||||
|  |  | |||
							
								
								
									
										170
									
								
								crates/syntax/src/ast/make/quote.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								crates/syntax/src/ast/make/quote.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Chayim Refael Friedman
						Chayim Refael Friedman