Skip to content

Commit

Permalink
Move some more AST makers to the quote macro
Browse files Browse the repository at this point in the history
And implement addons as necessary.

There are many more makers to be moved, and I'm not completely satisfied with this (due to the ease of making a mistake in the AST structure, and slightly less but also because of the need to remember whitespaces), but this is already enough to see how this will look like.
  • Loading branch information
ChayimFriedman2 committed Dec 30, 2024
1 parent a0c052f commit 33f1f1d
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 53 deletions.
2 changes: 2 additions & 0 deletions src/tools/rust-analyzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ new_ret_no_self = "allow"
useless_asref = "allow"
# Has false positives
assigning_clones = "allow"
# Does not work with macros
vec_init_then_push = "allow"

## Following lints should be tackled at some point
too_many_arguments = "allow"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ pub(crate) fn generate_fn_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>)

let is_unsafe = func_node.unsafe_token().is_some();
let ty = make::ty_fn_ptr(
None,
is_unsafe,
func_node.abi(),
fn_params_vec.into_iter(),
Expand Down
83 changes: 33 additions & 50 deletions src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
//! Keep in mind that `from_text` functions should be kept private. The public
//! API should require to assemble every node piecewise. The trick of
//! `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 `quote!`. Do not add more usages to `from_text` -
//! use `quote!` instead.
mod quote;

Expand Down Expand Up @@ -120,7 +121,11 @@ pub fn name(name: &str) -> ast::Name {
}
pub fn name_ref(name_ref: &str) -> ast::NameRef {
let raw_escape = raw_ident_esc(name_ref);
ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}"))
quote! {
NameRef {
[IDENT format!("{raw_escape}{name_ref}")]
}
}
}
fn raw_ident_esc(ident: &str) -> &'static str {
if is_raw_identifier(ident, Edition::CURRENT) {
Expand All @@ -137,7 +142,11 @@ pub fn lifetime(text: &str) -> ast::Lifetime {
tmp = format!("'{text}");
text = &tmp;
}
ast_from_text(&format!("fn f<{text}>() {{ }}"))
quote! {
Lifetime {
[LIFETIME_IDENT text]
}
}
}

// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la
Expand Down Expand Up @@ -177,63 +186,37 @@ pub fn ty_alias(
where_clause: Option<ast::WhereClause>,
assignment: Option<(ast::Type, Option<ast::WhereClause>)>,
) -> ast::TypeAlias {
let mut s = String::new();
s.push_str(&format!("type {ident}"));

if let Some(list) = generic_param_list {
s.push_str(&list.to_string());
}

if let Some(list) = type_param_bounds {
s.push_str(&format!(" : {list}"));
}

if let Some(cl) = where_clause {
s.push_str(&format!(" {cl}"));
}

if let Some(exp) = assignment {
if let Some(cl) = exp.1 {
s.push_str(&format!(" = {} {cl}", exp.0));
} else {
s.push_str(&format!(" = {}", exp.0));
let (assignment_ty, assignment_where) = assignment.unzip();
let assignment_where = assignment_where.flatten();
quote! {
TypeAlias {
[type] " "
Name { [IDENT ident] }
#generic_param_list
#(" " [:] " " #type_param_bounds)*
#(" " #where_clause)*
#(" " [=] " " #assignment_ty)*
#(" " #assignment_where)*
[;]
}
}

s.push(';');
ast_from_text(&s)
}

pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
for_lifetime_list: Option<ast::GenericParamList>,
is_unsafe: bool,
abi: Option<ast::Abi>,
params: I,
mut params: I,
ret_type: Option<ast::RetType>,
) -> ast::FnPtrType {
let mut s = String::from("type __ = ");

if let Some(list) = for_lifetime_list {
format_to!(s, "for{} ", list);
}

if is_unsafe {
s.push_str("unsafe ");
}

if let Some(abi) = abi {
format_to!(s, "{} ", abi)
}

s.push_str("fn");

format_to!(s, "({})", params.map(|p| p.to_string()).join(", "));

if let Some(ret_type) = ret_type {
format_to!(s, " {}", ret_type);
let is_unsafe = is_unsafe.then_some(());
let first_param = params.next();
quote! {
FnPtrType {
#(#is_unsafe [unsafe] " ")* #(#abi " ")* [fn]
['('] #first_param #([,] " " #params)* [')']
#(" " #ret_type)*
}
}

ast_from_text(&s)
}

pub fn assoc_item_list() -> ast::AssocItemList {
Expand Down
25 changes: 23 additions & 2 deletions src/tools/rust-analyzer/crates/syntax/src/ast/make/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ macro_rules! quote_impl_ {
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
};

( @append $children:ident
[ $token_kind:ident $token_text:expr ]
$($rest:tt)*
) => {
$children.push($crate::ast::make::quote::NodeOrToken::Token(
$crate::ast::make::quote::GreenToken::new(
$crate::ast::make::quote::RSyntaxKind($crate::SyntaxKind::$token_kind as u16),
&$token_text,
),
));
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
};

( @append $children:ident
[$($token:tt)+]
$($rest:tt)*
Expand Down Expand Up @@ -115,7 +128,9 @@ 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
/// Tokens are creates with their syntax enclosed by brackets, e.g. `[::]` or `['{']`. Alternatively, tokens can
/// be created with the syntax `[token_kind token_text]`, where `token_kind` is a variant of `SyntaxKind` (e.g.
/// `IDENT`) and `token_text` is an expression producing `String` or `&str`. 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`,
Expand All @@ -126,6 +141,7 @@ pub(crate) use quote_impl_ as quote_impl;
/// Be careful to closely match the Ungrammar AST, there is no validation for this!
macro_rules! quote_ {
( $root:ident { $($tree:tt)* } ) => {{
#[allow(unused_mut)]
let mut root = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
$crate::ast::make::quote::GreenNode,
$crate::ast::make::quote::GreenToken,
Expand All @@ -146,7 +162,7 @@ pub(crate) trait ToNodeChild {

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());
children.push((*self.syntax().clone_subtree().green()).to_owned().into());
}
}

Expand All @@ -158,6 +174,11 @@ impl<C: ToNodeChild> ToNodeChild for Option<C> {
}
}

// This is useful when you want conditionally, based on some `bool`, to emit some code.
impl ToNodeChild for () {
fn append_node_child(self, _children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {}
}

pub(crate) const fn verify_only_whitespaces(text: &str) {
let text = text.as_bytes();
let mut i = 0;
Expand Down

0 comments on commit 33f1f1d

Please sign in to comment.