mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 03:54:42 +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
 | //! `parse(format!())` we use internally is an implementation detail -- long
 | ||||||
| //! term, it will be replaced with direct tree manipulation.
 | //! term, it will be replaced with direct tree manipulation.
 | ||||||
| 
 | 
 | ||||||
|  | mod quote; | ||||||
|  | 
 | ||||||
| use itertools::Itertools; | use itertools::Itertools; | ||||||
| use parser::{Edition, T}; | use parser::{Edition, T}; | ||||||
| use rowan::NodeOrToken; | use rowan::NodeOrToken; | ||||||
| use stdx::{format_to, format_to_acc, never}; | use stdx::{format_to, format_to_acc, never}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     ast::{self, Param}, |     ast::{self, make::quote::quote, Param}, | ||||||
|     utils::is_raw_identifier, |     utils::is_raw_identifier, | ||||||
|     AstNode, SourceFile, SyntaxKind, SyntaxToken, |     AstNode, SourceFile, SyntaxKind, SyntaxToken, | ||||||
| }; | }; | ||||||
|  | @ -480,15 +482,16 @@ pub fn block_expr( | ||||||
|     stmts: impl IntoIterator<Item = ast::Stmt>, |     stmts: impl IntoIterator<Item = ast::Stmt>, | ||||||
|     tail_expr: Option<ast::Expr>, |     tail_expr: Option<ast::Expr>, | ||||||
| ) -> ast::BlockExpr { | ) -> ast::BlockExpr { | ||||||
|     let mut buf = "{\n".to_owned(); |     quote! { | ||||||
|     for stmt in stmts.into_iter() { |         BlockExpr { | ||||||
|         format_to!(buf, "    {stmt}\n"); |             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( | 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