11904: internal: Wrap macros in expr position in `MacroExpr` node r=jonas-schievink a=jonas-schievink

This lets us distinguish them from macros in item position just by looking at the syntax tree.

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11854

bors r+

Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
This commit is contained in:
bors[bot] 2022-04-05 15:48:09 +00:00 committed by GitHub
commit b5eaf56666
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 642 additions and 548 deletions

View File

@ -110,8 +110,8 @@ fn body(&self) -> Option<&Body> {
fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprId> {
let src = match expr {
ast::Expr::MacroCall(call) => {
self.expand_expr(db, InFile::new(self.file_id, call.clone()))?
ast::Expr::MacroExpr(expr) => {
self.expand_expr(db, InFile::new(self.file_id, expr.macro_call()?.clone()))?
}
_ => InFile::new(self.file_id, expr.clone()),
};

View File

@ -506,7 +506,8 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
None => self.alloc_expr(Expr::Missing, syntax_ptr),
}
}
ast::Expr::MacroCall(e) => {
ast::Expr::MacroExpr(e) => {
let e = e.macro_call()?;
let macro_ptr = AstPtr::new(&e);
let id = self.collect_macro_call(e, macro_ptr.clone(), true, |this, expansion| {
expansion.map(|it| this.collect_expr(it))
@ -629,7 +630,11 @@ fn collect_stmt(&mut self, s: ast::Stmt) {
}
let has_semi = stmt.semicolon_token().is_some();
// Note that macro could be expended to multiple statements
if let Some(ast::Expr::MacroCall(m)) = stmt.expr() {
if let Some(ast::Expr::MacroExpr(e)) = stmt.expr() {
let m = match e.macro_call() {
Some(it) => it,
None => return,
};
let macro_ptr = AstPtr::new(&m);
let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());

View File

@ -79,6 +79,34 @@ macro_rules! n_nuple {
);
}
#[test]
fn issue_3642_bad_macro_stackover() {
lower(
r#"
#[macro_export]
macro_rules! match_ast {
(match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
(match ($node:expr) {
$( ast::$ast:ident($it:ident) => $res:expr, )*
_ => $catch_all:expr $(,)?
}) => {{
$( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
{ $catch_all }
}};
}
fn main() {
let anchor = match_ast! {
match parent {
as => {},
_ => return None
}
};
}"#,
);
}
#[test]
fn macro_resolve() {
// Regression test for a path resolution bug introduced with inner item handling.

View File

@ -371,3 +371,27 @@ mod tests {
"#]],
);
}
#[test]
fn stmt_macro_expansion_with_trailing_expr() {
cov_mark::check!(macro_stmt_with_trailing_macro_expr);
check_at(
r#"
macro_rules! mac {
() => { mac!($) };
($x:tt) => { fn inner() {} };
}
fn foo() {
mac!();
$0
}
"#,
expect![[r#"
block scope
inner: v
crate
foo: v
"#]],
)
}

View File

@ -48,22 +48,33 @@ pub(super) fn lower_module_items(mut self, item_owner: &dyn HasModuleItem) -> It
pub(super) fn lower_macro_stmts(mut self, stmts: ast::MacroStmts) -> ItemTree {
self.tree.top_level = stmts
.statements()
.filter_map(|stmt| match stmt {
.filter_map(|stmt| {
match stmt {
ast::Stmt::Item(item) => Some(item),
// Macro calls can be both items and expressions. The syntax library always treats
// them as expressions here, so we undo that.
ast::Stmt::ExprStmt(es) => match es.expr()? {
ast::Expr::MacroCall(call) => {
ast::Expr::MacroExpr(expr) => {
cov_mark::hit!(macro_call_in_macro_stmts_is_added_to_item_tree);
Some(call.into())
Some(expr.macro_call()?.into())
}
_ => None,
},
_ => None,
}
})
.flat_map(|item| self.lower_mod_item(&item))
.collect();
if let Some(ast::Expr::MacroExpr(tail_macro)) = stmts.expr() {
if let Some(call) = tail_macro.macro_call() {
cov_mark::hit!(macro_stmt_with_trailing_macro_expr);
if let Some(mod_item) = self.lower_mod_item(&call.into()) {
self.tree.top_level.push(mod_item);
}
}
}
self.tree
}
@ -75,7 +86,7 @@ pub(super) fn lower_block(mut self, block: &ast::BlockExpr) -> ItemTree {
// Macro calls can be both items and expressions. The syntax library always treats
// them as expressions here, so we undo that.
ast::Stmt::ExprStmt(es) => match es.expr()? {
ast::Expr::MacroCall(call) => self.lower_mod_item(&call.into()),
ast::Expr::MacroExpr(expr) => self.lower_mod_item(&expr.macro_call()?.into()),
_ => None,
},
_ => None,

View File

@ -249,8 +249,7 @@ macro_rules! format_args {
fn main() {
let _ =
// +errors
format_args!("{} {:?}", a.);
format_args!/*+errors*/("{} {:?}", a.);
}
"#,
expect![[r##"

View File

@ -530,8 +530,7 @@ macro_rules! m {
}
fn f() -> i32 {
// +tree
m!{}
m!/*+tree*/{}
}
"#,
expect![[r#"

View File

@ -885,6 +885,16 @@ pub fn from_call_site(call: &ast::MacroCall) -> ExpandTo {
None => return ExpandTo::Statements,
};
// FIXME: macros in statement position are treated as expression statements, they should
// probably be their own statement kind. The *grand*parent indicates what's valid.
if parent.kind() == MACRO_EXPR
&& parent
.parent()
.map_or(true, |p| matches!(p.kind(), EXPR_STMT | STMT_LIST | MACRO_STMTS))
{
return ExpandTo::Statements;
}
match parent.kind() {
MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => ExpandTo::Items,
MACRO_STMTS | EXPR_STMT | STMT_LIST => ExpandTo::Statements,
@ -895,26 +905,13 @@ pub fn from_call_site(call: &ast::MacroCall) -> ExpandTo {
| CLOSURE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR | INDEX_EXPR | LET_EXPR
| MATCH_ARM | MATCH_EXPR | MATCH_GUARD | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR
| PREFIX_EXPR | RANGE_EXPR | RECORD_EXPR_FIELD | REF_EXPR | RETURN_EXPR | TRY_EXPR
| TUPLE_EXPR | WHILE_EXPR => ExpandTo::Expr,
| TUPLE_EXPR | WHILE_EXPR | MACRO_EXPR => ExpandTo::Expr,
_ => {
match ast::LetStmt::cast(parent) {
Some(let_stmt) => {
if let Some(true) = let_stmt.initializer().map(|it| it.syntax() == syn) {
ExpandTo::Expr
} else if let Some(true) = let_stmt.ty().map(|it| it.syntax() == syn) {
ExpandTo::Type
} else {
ExpandTo::Pattern
}
}
None => {
// Unknown , Just guess it is `Items`
ExpandTo::Items
}
}
}
}
}
}
#[derive(Debug)]

View File

@ -444,34 +444,6 @@ fn test() {
);
}
#[test]
fn issue_3642_bad_macro_stackover() {
check_no_mismatches(
r#"
#[macro_export]
macro_rules! match_ast {
(match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
(match ($node:expr) {
$( ast::$ast:ident($it:ident) => $res:expr, )*
_ => $catch_all:expr $(,)?
}) => {{
$( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
{ $catch_all }
}};
}
fn main() {
let anchor = match_ast! {
match parent {
as => {},
_ => return None
}
};
}"#,
);
}
#[test]
fn issue_3999_slice() {
check_infer(

View File

@ -156,7 +156,7 @@ fn hl(
highlights.push(HighlightedRange { category: None, range: token.text_range() });
}
}
ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => {
if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
highlights.push(HighlightedRange {
category: None,

View File

@ -163,6 +163,7 @@ fn foo() {
L_CURLY@10..11 "{"
WHITESPACE@11..16 "\n "
EXPR_STMT@16..58
MACRO_EXPR@16..57
MACRO_CALL@16..57
PATH@16..22
PATH_SEGMENT@16..22
@ -214,6 +215,7 @@ fn foo() {
}"#,
expect![[r#"
EXPR_STMT@16..58
MACRO_EXPR@16..57
MACRO_CALL@16..57
PATH@16..22
PATH_SEGMENT@16..22

View File

@ -601,6 +601,17 @@ mod m {}
);
}
#[test]
fn noop_in_item_position_with_macro() {
type_char_noop('{', r#"$0println!();"#);
type_char_noop(
'{',
r#"
fn main() $0println!("hello");
}"#,
);
}
#[test]
fn adds_closing_brace_for_use_tree() {
type_char(

View File

@ -111,7 +111,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) ->
| ast::Expr::ForExpr(_)
| ast::Expr::IfExpr(_)
| ast::Expr::LoopExpr(_)
| ast::Expr::MacroCall(_)
| ast::Expr::MacroExpr(_)
| ast::Expr::MatchExpr(_)
| ast::Expr::PrefixExpr(_)
| ast::Expr::RangeExpr(_)

View File

@ -649,8 +649,8 @@ fn analyze(
ast::Expr::PathExpr(path_expr) => {
cb(path_expr.path().and_then(|it| it.as_single_name_ref()))
}
ast::Expr::MacroCall(call) => {
if let Some(tt) = call.token_tree() {
ast::Expr::MacroExpr(expr) => {
if let Some(tt) = expr.macro_call().and_then(|call| call.token_tree()) {
tt.syntax()
.children_with_tokens()
.flat_map(SyntaxElement::into_token)
@ -923,7 +923,7 @@ fn reference_is_exclusive(
/// checks if this expr requires `&mut` access, recurses on field access
fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
if let ast::Expr::MacroCall(_) = expr {
if let ast::Expr::MacroExpr(_) = expr {
// FIXME: expand macro and check output for mutable usages of the variable?
return None;
}
@ -1015,7 +1015,7 @@ fn path_element_of_reference(
None
})?;
stdx::always!(
matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)),
matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroExpr(_)),
"unexpected expression type for variable usage: {:?}",
path
);

View File

@ -39,15 +39,16 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
.map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
.collect::<Option<Vec<ast::Expr>>>()?;
let parent = macro_call.syntax().parent()?;
let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?;
let parent = macro_expr.syntax().parent()?;
let (range, text) = match &*input_expressions {
// dbg!()
[] => {
match_ast! {
match parent {
ast::StmtList(__) => {
let range = macro_call.syntax().text_range();
let range = match whitespace_start(macro_call.syntax().prev_sibling_or_token()) {
let range = macro_expr.syntax().text_range();
let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) {
Some(start) => range.cover_offset(start),
None => range,
};

View File

@ -62,11 +62,6 @@ pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent<ast::Expr>)
match ast::Stmt::cast(node.clone()) {
// Don't skip subtree since we want to process the expression child next
Some(ast::Stmt::ExprStmt(_)) | Some(ast::Stmt::LetStmt(_)) => (),
// This might be an expression
Some(ast::Stmt::Item(ast::Item::MacroCall(mcall))) => {
cb(WalkEvent::Enter(ast::Expr::MacroCall(mcall)));
preorder.skip_subtree();
}
// skip inner items which might have their own expressions
Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
None => {
@ -319,7 +314,7 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
| ast::Expr::ForExpr(_)
| ast::Expr::IndexExpr(_)
| ast::Expr::Literal(_)
| ast::Expr::MacroCall(_)
| ast::Expr::MacroExpr(_)
| ast::Expr::MacroStmts(_)
| ast::Expr::MethodCallExpr(_)
| ast::Expr::ParenExpr(_)

View File

@ -552,7 +552,7 @@ fn path_expr(p: &mut Parser, r: Restrictions) -> (CompletedMarker, BlockLike) {
}
T![!] if !p.at(T![!=]) => {
let block_like = items::macro_call_after_excl(p);
(m.complete(p, MACRO_CALL), block_like)
(m.complete(p, MACRO_CALL).precede(p).complete(p, MACRO_EXPR), block_like)
}
_ => (m.complete(p, PATH_EXPR), BlockLike::NotBlock),
}

View File

@ -605,6 +605,7 @@ fn try_block_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
if p.nth_at(1, T![!]) {
// test try_macro_fallback
// fn foo() { try!(Ok(())); }
let macro_call = p.start();
let path = p.start();
let path_segment = p.start();
let name_ref = p.start();
@ -613,7 +614,8 @@ fn try_block_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
path_segment.complete(p, PATH_SEGMENT);
path.complete(p, PATH);
let _block_like = items::macro_call_after_excl(p);
return m.complete(p, MACRO_CALL);
macro_call.complete(p, MACRO_CALL);
return m.complete(p, MACRO_EXPR);
}
p.bump(T![try]);

View File

@ -190,6 +190,7 @@ pub enum SyntaxKind {
YIELD_EXPR,
LET_EXPR,
UNDERSCORE_EXPR,
MACRO_EXPR,
MATCH_EXPR,
MATCH_ARM_LIST,
MATCH_ARM,

View File

@ -104,6 +104,7 @@ SOURCE_FILE
IDENT "entries"
COLON ":"
WHITESPACE " "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -12,6 +12,7 @@ SOURCE_FILE
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -81,6 +81,7 @@ SOURCE_FILE
WHITESPACE " "
EQ "="
WHITESPACE " "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -110,6 +110,7 @@ SOURCE_FILE
WHITESPACE "\n "
R_CURLY "}"
WHITESPACE "\n "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -35,7 +35,7 @@ SOURCE_FILE
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
MACRO_CALL
MACRO_EXPR
ATTR
POUND "#"
L_BRACK "["
@ -46,6 +46,7 @@ SOURCE_FILE
IDENT "B"
R_BRACK "]"
WHITESPACE " "
MACRO_CALL
PATH
PATH_SEGMENT
NAME_REF

View File

@ -13,6 +13,7 @@ SOURCE_FILE
L_CURLY "{"
WHITESPACE " "
EXPR_STMT
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -396,6 +396,7 @@ SOURCE_FILE
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -887,6 +888,7 @@ SOURCE_FILE
PAREN_EXPR
L_PAREN "("
BIN_EXPR
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -905,6 +907,7 @@ SOURCE_FILE
WHITESPACE " "
PAREN_EXPR
L_PAREN "("
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -934,6 +937,7 @@ SOURCE_FILE
PAREN_EXPR
L_PAREN "("
BIN_EXPR
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -978,6 +982,7 @@ SOURCE_FILE
PAREN_EXPR
L_PAREN "("
BIN_EXPR
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -1130,6 +1135,7 @@ SOURCE_FILE
WHITESPACE " "
FAT_ARROW "=>"
WHITESPACE " "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -1176,6 +1182,7 @@ SOURCE_FILE
WHITESPACE " "
EQ "="
WHITESPACE " "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -1204,6 +1211,7 @@ SOURCE_FILE
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -1353,6 +1361,7 @@ SOURCE_FILE
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -1508,6 +1517,7 @@ SOURCE_FILE
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT
@ -1741,6 +1751,7 @@ SOURCE_FILE
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -42,6 +42,7 @@ SOURCE_FILE
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -38,6 +38,7 @@ SOURCE_FILE
IDENT "B"
R_BRACK "]"
WHITESPACE " "
MACRO_EXPR
MACRO_CALL
PATH
PATH_SEGMENT

View File

@ -342,7 +342,7 @@ Expr =
| IndexExpr
| Literal
| LoopExpr
| MacroCall
| MacroExpr
| MacroStmts
| MatchExpr
| MethodCallExpr
@ -360,6 +360,9 @@ Expr =
| LetExpr
| UnderscoreExpr
MacroExpr =
MacroCall
Literal =
Attr* value:(
'int_number' | 'float_number'

View File

@ -918,6 +918,14 @@ impl LoopExpr {
pub fn loop_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![loop]) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MacroExpr {
pub(crate) syntax: SyntaxNode,
}
impl MacroExpr {
pub fn macro_call(&self) -> Option<MacroCall> { support::child(&self.syntax) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MatchExpr {
pub(crate) syntax: SyntaxNode,
@ -1518,7 +1526,7 @@ pub enum Expr {
IndexExpr(IndexExpr),
Literal(Literal),
LoopExpr(LoopExpr),
MacroCall(MacroCall),
MacroExpr(MacroExpr),
MacroStmts(MacroStmts),
MatchExpr(MatchExpr),
MethodCallExpr(MethodCallExpr),
@ -2532,6 +2540,17 @@ fn cast(syntax: SyntaxNode) -> Option<Self> {
}
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for MacroExpr {
fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_EXPR }
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl AstNode for MatchExpr {
fn can_cast(kind: SyntaxKind) -> bool { kind == MATCH_EXPR }
fn cast(syntax: SyntaxNode) -> Option<Self> {
@ -3313,8 +3332,8 @@ fn from(node: Literal) -> Expr { Expr::Literal(node) }
impl From<LoopExpr> for Expr {
fn from(node: LoopExpr) -> Expr { Expr::LoopExpr(node) }
}
impl From<MacroCall> for Expr {
fn from(node: MacroCall) -> Expr { Expr::MacroCall(node) }
impl From<MacroExpr> for Expr {
fn from(node: MacroExpr) -> Expr { Expr::MacroExpr(node) }
}
impl From<MacroStmts> for Expr {
fn from(node: MacroStmts) -> Expr { Expr::MacroStmts(node) }
@ -3369,7 +3388,7 @@ fn can_cast(kind: SyntaxKind) -> bool {
match kind {
ARRAY_EXPR | AWAIT_EXPR | BIN_EXPR | BLOCK_EXPR | BOX_EXPR | BREAK_EXPR | CALL_EXPR
| CAST_EXPR | CLOSURE_EXPR | CONTINUE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR
| INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_CALL | MACRO_STMTS | MATCH_EXPR
| INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_EXPR | MACRO_STMTS | MATCH_EXPR
| METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR | RANGE_EXPR
| RECORD_EXPR | REF_EXPR | RETURN_EXPR | TRY_EXPR | TUPLE_EXPR | WHILE_EXPR
| YIELD_EXPR | LET_EXPR | UNDERSCORE_EXPR => true,
@ -3394,7 +3413,7 @@ fn cast(syntax: SyntaxNode) -> Option<Self> {
INDEX_EXPR => Expr::IndexExpr(IndexExpr { syntax }),
LITERAL => Expr::Literal(Literal { syntax }),
LOOP_EXPR => Expr::LoopExpr(LoopExpr { syntax }),
MACRO_CALL => Expr::MacroCall(MacroCall { syntax }),
MACRO_EXPR => Expr::MacroExpr(MacroExpr { syntax }),
MACRO_STMTS => Expr::MacroStmts(MacroStmts { syntax }),
MATCH_EXPR => Expr::MatchExpr(MatchExpr { syntax }),
METHOD_CALL_EXPR => Expr::MethodCallExpr(MethodCallExpr { syntax }),
@ -3433,7 +3452,7 @@ fn syntax(&self) -> &SyntaxNode {
Expr::IndexExpr(it) => &it.syntax,
Expr::Literal(it) => &it.syntax,
Expr::LoopExpr(it) => &it.syntax,
Expr::MacroCall(it) => &it.syntax,
Expr::MacroExpr(it) => &it.syntax,
Expr::MacroStmts(it) => &it.syntax,
Expr::MatchExpr(it) => &it.syntax,
Expr::MethodCallExpr(it) => &it.syntax,
@ -4506,6 +4525,11 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for MacroExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for MatchExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)

View File

@ -144,6 +144,7 @@ pub(crate) struct KindsSrc<'a> {
"YIELD_EXPR",
"LET_EXPR",
"UNDERSCORE_EXPR",
"MACRO_EXPR",
"MATCH_EXPR",
"MATCH_ARM_LIST",
"MATCH_ARM",