about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2023-09-05 12:13:24 +0200
committerLukas Wirth <lukastw97@gmail.com>2023-09-05 12:27:52 +0200
commit15048304e3a806714cb7d64fbb6b40b5d76f0201 (patch)
tree9a96328fa41a7a4f9203224a1087ac4192265b0e
parent9b8eb807a365cfa42b8514c9ade73e9805ff54f1 (diff)
downloadrust-15048304e3a806714cb7d64fbb6b40b5d76f0201.tar.gz
rust-15048304e3a806714cb7d64fbb6b40b5d76f0201.zip
Implement offset_of in hir-def and hir-ty
-rw-r--r--crates/hir-def/src/body/lower.rs8
-rw-r--r--crates/hir-def/src/body/pretty.rs10
-rw-r--r--crates/hir-def/src/hir.rs9
-rw-r--r--crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs18
-rw-r--r--crates/hir-expand/src/builtin_fn_macro.rs6
-rw-r--r--crates/hir-ty/src/infer/closure.rs1
-rw-r--r--crates/hir-ty/src/infer/expr.rs1
-rw-r--r--crates/hir-ty/src/infer/mutability.rs1
-rw-r--r--crates/hir-ty/src/mir/lower.rs3
-rw-r--r--crates/hir-ty/src/tests/simple.rs89
-rw-r--r--crates/hir-ty/src/tests/traits.rs28
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs19
-rw-r--r--crates/parser/test_data/parser/inline/ok/0132_box_expr.rast90
-rw-r--r--crates/parser/test_data/parser/inline/ok/0132_box_expr.rs5
-rw-r--r--crates/syntax/rust.ungram2
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs3
-rw-r--r--crates/syntax/src/tests/sourcegen_ast.rs20
17 files changed, 120 insertions, 193 deletions
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 43f58a1bdb0..b536da2e57c 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -31,7 +31,7 @@ use crate::{
     hir::{
         dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy,
         ClosureKind, Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
-        Pat, PatId, RecordFieldPat, RecordLitField, Statement,
+        OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
     },
     item_scope::BuiltinShadowMode,
     lang_item::LangItem,
@@ -649,7 +649,11 @@ impl ExprCollector<'_> {
             }
             ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
             ast::Expr::AsmExpr(_) => self.missing_expr(),
-            ast::Expr::OffsetOfExpr(_) => self.missing_expr(),
+            ast::Expr::OffsetOfExpr(e) => {
+                let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
+                let fields = e.fields().map(|it| it.as_name()).collect();
+                self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr)
+            }
             ast::Expr::FormatArgsExpr(_) => self.missing_expr(),
         })
     }
diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs
index 5d71abe37cc..6058fcbb652 100644
--- a/crates/hir-def/src/body/pretty.rs
+++ b/crates/hir-def/src/body/pretty.rs
@@ -3,6 +3,7 @@
 use std::fmt::{self, Write};
 
 use hir_expand::db::ExpandDatabase;
+use itertools::Itertools;
 use syntax::ast::HasName;
 
 use crate::{
@@ -154,6 +155,15 @@ impl Printer<'_> {
         match expr {
             Expr::Missing => w!(self, "�"),
             Expr::Underscore => w!(self, "_"),
+            Expr::OffsetOf(offset_of) => {
+                w!(self, "builtin#offset_of!(");
+                self.print_type_ref(&offset_of.container);
+                w!(
+                    self,
+                    ", {})",
+                    offset_of.fields.iter().format_with(".", |field, f| f(&field.display(self.db)))
+                );
+            }
             Expr::Path(path) => self.print_path(path),
             Expr::If { condition, then_branch, else_branch } => {
                 w!(self, "if ");
diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs
index 6591c92ac62..f03df800a05 100644
--- a/crates/hir-def/src/hir.rs
+++ b/crates/hir-def/src/hir.rs
@@ -281,6 +281,13 @@ pub enum Expr {
     Array(Array),
     Literal(Literal),
     Underscore,
+    OffsetOf(OffsetOf),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct OffsetOf {
+    pub container: Interned<TypeRef>,
+    pub fields: Box<[Name]>,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -341,7 +348,7 @@ impl Expr {
     pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
         match self {
             Expr::Missing => {}
-            Expr::Path(_) => {}
+            Expr::Path(_) | Expr::OffsetOf(_) => {}
             Expr::If { condition, then_branch, else_branch } => {
                 f(*condition);
                 f(*then_branch);
diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index ea02072ff5c..a350511c7a1 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -42,12 +42,22 @@ fn main() {
     }
 }
 "#,
-        expect![[r#"
+        expect![[r##"
 #[rustc_builtin_macro]
-macro_rules! column {() => {}}
+macro_rules! asm {() => {}}
 
-fn main() { 0 as u32; }
-"#]],
+fn main() {
+    let i: u64 = 3;
+    let o: u64;
+    unsafe {
+        builtin #asm ( {
+            $crate::format_args!("mov {0}, {1}");
+            $crate::format_args!("add {0}, 5");
+        }
+        );
+    }
+}
+"##]],
     );
 }
 
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index 7eec6361760..00ee5e8b9d0 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -419,9 +419,9 @@ fn asm_expand(
 
     let pound = quote! {@PUNCT '#'};
     let expanded = quote! {
-        builtin #pound asm {
-            ##literals
-        }
+        builtin #pound asm (
+            {##literals}
+        )
     };
     ExpandResult::ok(expanded)
 }
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index cb442de843a..ceb96a71653 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -452,6 +452,7 @@ impl InferenceContext<'_> {
 
     fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
         match &self.body[tgt_expr] {
+            Expr::OffsetOf(_) => (),
             Expr::If { condition, then_branch, else_branch } => {
                 self.consume_expr(*condition);
                 self.consume_expr(*then_branch);
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 8b352141084..d139d929fc3 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -843,6 +843,7 @@ impl InferenceContext<'_> {
                 });
                 expected
             }
+            Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
         };
         // use a new type variable if we got unknown here
         let ty = self.insert_type_vars_shallow(ty);
diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs
index 396ca0044ff..853c171a06a 100644
--- a/crates/hir-ty/src/infer/mutability.rs
+++ b/crates/hir-ty/src/infer/mutability.rs
@@ -35,6 +35,7 @@ impl InferenceContext<'_> {
     fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) {
         match &self.body[tgt_expr] {
             Expr::Missing => (),
+            Expr::OffsetOf(_) => (),
             &Expr::If { condition, then_branch, else_branch } => {
                 self.infer_mut_expr(condition, Mutability::Not);
                 self.infer_mut_expr(then_branch, Mutability::Not);
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 7c15aa42fd2..3410c87d391 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -370,6 +370,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
         mut current: BasicBlockId,
     ) -> Result<Option<BasicBlockId>> {
         match &self.body.exprs[expr_id] {
+            Expr::OffsetOf(_) => {
+                not_supported!("builtin#offset_of")
+            }
             Expr::Missing => {
                 if let DefWithBodyId::FunctionId(f) = self.owner {
                     let assoc = f.lookup(self.db.upcast());
diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs
index 2ad7946c8ac..e7cb7cd4174 100644
--- a/crates/hir-ty/src/tests/simple.rs
+++ b/crates/hir-ty/src/tests/simple.rs
@@ -3,55 +3,6 @@ use expect_test::expect;
 use super::{check, check_infer, check_no_mismatches, check_types};
 
 #[test]
-fn infer_box() {
-    check_types(
-        r#"
-//- /main.rs crate:main deps:std
-fn test() {
-    let x = box 1;
-    let t = (x, box x, box &1, box [1]);
-    t;
-} //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; 1]>)
-
-//- /std.rs crate:std
-#[prelude_import] use prelude::*;
-mod prelude {}
-
-mod boxed {
-    #[lang = "owned_box"]
-    pub struct Box<T: ?Sized> {
-        inner: *mut T,
-    }
-}
-"#,
-    );
-}
-
-#[test]
-fn infer_box_with_allocator() {
-    check_types(
-        r#"
-//- /main.rs crate:main deps:std
-fn test() {
-    let x = box 1;
-    let t = (x, box x, box &1, box [1]);
-    t;
-} //^ (Box<i32, {unknown}>, Box<Box<i32, {unknown}>, {unknown}>, Box<&i32, {unknown}>, Box<[i32; 1], {unknown}>)
-
-//- /std.rs crate:std
-#[prelude_import] use prelude::*;
-mod boxed {
-    #[lang = "owned_box"]
-    pub struct Box<T: ?Sized, A: Allocator> {
-        inner: *mut T,
-        allocator: A,
-    }
-}
-"#,
-    );
-}
-
-#[test]
 fn infer_adt_self() {
     check_types(
         r#"
@@ -2763,8 +2714,8 @@ impl<T> [T] {
 }
 
 fn test() {
-    let vec = <[_]>::into_vec(box [1i32]);
-    let v: Vec<Box<dyn B>> = <[_]> :: into_vec(box [box Astruct]);
+    let vec = <[_]>::into_vec(#[rustc_box] Box::new([1i32]));
+    let v: Vec<Box<dyn B>> = <[_]> :: into_vec(#[rustc_box] Box::new([#[rustc_box] Box::new(Astruct)]));
 }
 
 trait B{}
@@ -2774,20 +2725,20 @@ impl B for Astruct {}
         expect![[r#"
             604..608 'self': Box<[T], A>
             637..669 '{     ...     }': Vec<T, A>
-            683..796 '{     ...t]); }': ()
+            683..853 '{     ...])); }': ()
             693..696 'vec': Vec<i32, Global>
             699..714 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global>
-            699..726 '<[_]>:...1i32])': Vec<i32, Global>
-            715..725 'box [1i32]': Box<[i32; 1], Global>
-            719..725 '[1i32]': [i32; 1]
-            720..724 '1i32': i32
-            736..737 'v': Vec<Box<dyn B, Global>, Global>
-            757..774 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global>
-            757..793 '<[_]> ...ruct])': Vec<Box<dyn B, Global>, Global>
-            775..792 'box [b...truct]': Box<[Box<dyn B, Global>; 1], Global>
-            779..792 '[box Astruct]': [Box<dyn B, Global>; 1]
-            780..791 'box Astruct': Box<Astruct, Global>
-            784..791 'Astruct': Astruct
+            699..745 '<[_]>:...i32]))': Vec<i32, Global>
+            715..744 '#[rust...1i32])': Box<[i32; 1], Global>
+            737..743 '[1i32]': [i32; 1]
+            738..742 '1i32': i32
+            755..756 'v': Vec<Box<dyn B, Global>, Global>
+            776..793 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global>
+            776..850 '<[_]> ...ct)]))': Vec<Box<dyn B, Global>, Global>
+            794..849 '#[rust...uct)])': Box<[Box<dyn B, Global>; 1], Global>
+            816..848 '[#[rus...ruct)]': [Box<dyn B, Global>; 1]
+            817..847 '#[rust...truct)': Box<Astruct, Global>
+            839..846 'Astruct': Astruct
         "#]],
     )
 }
@@ -3649,3 +3600,15 @@ fn main() {
 "#,
     );
 }
+
+#[test]
+fn offset_of() {
+    check_types(
+        r#"
+fn main() {
+    builtin#offset_of((,), 0);
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^ usize
+}
+"#,
+    );
+}
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index 542df8b3468..d36b885ec14 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -162,16 +162,16 @@ unsafe impl Allocator for Global {}
 
 #[lang = "owned_box"]
 #[fundamental]
-pub struct Box<T: ?Sized, A: Allocator = Global>;
+pub struct Box<T: ?Sized, A: Allocator = Global>(T);
 
 impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator> CoerceUnsized<Box<U, A>> for Box<T, A> {}
 
 fn send() ->  Box<dyn Future<Output = ()> + Send + 'static>{
-    box async move {}
+    Box(async move {})
 }
 
 fn not_send() -> Box<dyn Future<Output = ()> + 'static> {
-    box async move {}
+    Box(async move {})
 }
     "#,
     );
@@ -3057,7 +3057,7 @@ impl<T: ?Sized> core::ops::Deref for Box<T> {
 
 fn foo() {
     let s = None;
-    let f: Box<dyn FnOnce(&Option<i32>)> = box (|ps| {});
+    let f: Box<dyn FnOnce(&Option<i32>)> = Box { inner: &mut (|ps| {}) };
     f(&s);
 }"#,
         expect![[r#"
@@ -3068,19 +3068,19 @@ fn foo() {
             186..197 '*self.inner': T
             187..191 'self': &Box<T>
             187..197 'self.inner': *mut T
-            218..308 '{     ...&s); }': ()
+            218..324 '{     ...&s); }': ()
             228..229 's': Option<i32>
             232..236 'None': Option<i32>
             246..247 'f': Box<dyn FnOnce(&Option<i32>)>
-            281..294 'box (|ps| {})': Box<impl Fn(&Option<i32>)>
-            286..293 '|ps| {}': impl Fn(&Option<i32>)
-            287..289 'ps': &Option<i32>
-            291..293 '{}': ()
-            300..301 'f': Box<dyn FnOnce(&Option<i32>)>
-            300..305 'f(&s)': ()
-            302..304 '&s': &Option<i32>
-            303..304 's': Option<i32>
-            281..294: expected Box<dyn FnOnce(&Option<i32>)>, got Box<impl Fn(&Option<i32>)>
+            281..310 'Box { ... {}) }': Box<dyn FnOnce(&Option<i32>)>
+            294..308 '&mut (|ps| {})': &mut impl Fn(&Option<i32>)
+            300..307 '|ps| {}': impl Fn(&Option<i32>)
+            301..303 'ps': &Option<i32>
+            305..307 '{}': ()
+            316..317 'f': Box<dyn FnOnce(&Option<i32>)>
+            316..321 'f(&s)': ()
+            318..320 '&s': &Option<i32>
+            319..320 's': Option<i32>
         "#]],
     );
 }
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 7a4f4e4777d..1dd68f556c4 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -220,24 +220,39 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker {
 // fn foo() {
 //     builtin#asm(0);
 //     builtin#format_args(0);
-//     builtin#builtin(0);
+//     builtin#offset_of(Foo, bar.baz.0);
 // }
 fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
     let m = p.start();
     p.bump_remap(T![builtin]);
     p.bump(T![#]);
     if p.at_contextual_kw(T![offset_of]) {
+        p.bump_remap(T![offset_of]);
         p.expect(T!['(']);
         type_(p);
-        p.bump(T![,]);
+        p.expect(T![,]);
+        while !p.at(EOF) && !p.at(T![')']) {
+            if p.at(IDENT) || p.at(INT_NUMBER) {
+                name_ref_or_index(p);
+            // } else if p.at(FLOAT_NUMBER) {
+            // FIXME: needs float hack
+            } else {
+                p.err_and_bump("expected field name or number");
+            }
+            if !p.at(T![')']) {
+                p.expect(T![.]);
+            }
+        }
         p.expect(T![')']);
         Some(m.complete(p, OFFSET_OF_EXPR))
     } else if p.at_contextual_kw(T![format_args]) {
+        p.bump_remap(T![format_args]);
         p.expect(T!['(']);
         expr(p);
         p.expect(T![')']);
         Some(m.complete(p, FORMAT_ARGS_EXPR))
     } else if p.at_contextual_kw(T![asm]) {
+        p.bump_remap(T![asm]);
         p.expect(T!['(']);
         expr(p);
         p.expect(T![')']);
diff --git a/crates/parser/test_data/parser/inline/ok/0132_box_expr.rast b/crates/parser/test_data/parser/inline/ok/0132_box_expr.rast
deleted file mode 100644
index b21f37cd85a..00000000000
--- a/crates/parser/test_data/parser/inline/ok/0132_box_expr.rast
+++ /dev/null
@@ -1,90 +0,0 @@
-SOURCE_FILE
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "foo"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          WHITESPACE " "
-          IDENT_PAT
-            NAME
-              IDENT "x"
-          WHITESPACE " "
-          EQ "="
-          WHITESPACE " "
-          BOX_EXPR
-            BOX_KW "box"
-            WHITESPACE " "
-            LITERAL
-              INT_NUMBER "1i32"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          WHITESPACE " "
-          IDENT_PAT
-            NAME
-              IDENT "y"
-          WHITESPACE " "
-          EQ "="
-          WHITESPACE " "
-          TUPLE_EXPR
-            L_PAREN "("
-            BOX_EXPR
-              BOX_KW "box"
-              WHITESPACE " "
-              LITERAL
-                INT_NUMBER "1i32"
-            COMMA ","
-            WHITESPACE " "
-            BOX_EXPR
-              BOX_KW "box"
-              WHITESPACE " "
-              LITERAL
-                INT_NUMBER "2i32"
-            R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          WHITESPACE " "
-          IDENT_PAT
-            NAME
-              IDENT "z"
-          WHITESPACE " "
-          EQ "="
-          WHITESPACE " "
-          CALL_EXPR
-            PATH_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "Foo"
-            ARG_LIST
-              L_PAREN "("
-              BOX_EXPR
-                BOX_KW "box"
-                WHITESPACE " "
-                LITERAL
-                  INT_NUMBER "1i32"
-              COMMA ","
-              WHITESPACE " "
-              BOX_EXPR
-                BOX_KW "box"
-                WHITESPACE " "
-                LITERAL
-                  INT_NUMBER "2i32"
-              R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n"
-        R_CURLY "}"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs b/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs
deleted file mode 100644
index fc9923b7137..00000000000
--- a/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-fn foo() {
-    let x = box 1i32;
-    let y = (box 1i32, box 2i32);
-    let z = Foo(box 1i32, box 2i32);
-}
diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram
index bd9d2ad1784..d7b1c91216b 100644
--- a/crates/syntax/rust.ungram
+++ b/crates/syntax/rust.ungram
@@ -376,7 +376,7 @@ Expr =
 | UnderscoreExpr
 
 OffsetOfExpr =
-  Attr* 'builtin' '#' 'offset_of' '(' ')'
+  Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')'
 
 AsmExpr =
   Attr* 'builtin' '#' 'asm' '(' ')'
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 82b9c28a566..d817589223e 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -1014,6 +1014,9 @@ impl OffsetOfExpr {
         support::token(&self.syntax, T![offset_of])
     }
     pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+    pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
+    pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+    pub fn fields(&self) -> AstChildren<NameRef> { support::children(&self.syntax) }
     pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
 }
 
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index c49c5fa108b..56227fce9b5 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -623,7 +623,7 @@ fn lower_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {
 }
 
 fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, rule: &Rule) {
-    if lower_comma_list(acc, grammar, label, rule) {
+    if lower_seperated_list(acc, grammar, label, rule) {
         return;
     }
 
@@ -689,7 +689,7 @@ fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, r
 }
 
 // (T (',' T)* ','?)
-fn lower_comma_list(
+fn lower_seperated_list(
     acc: &mut Vec<Field>,
     grammar: &Grammar,
     label: Option<&String>,
@@ -699,19 +699,23 @@ fn lower_comma_list(
         Rule::Seq(it) => it,
         _ => return false,
     };
-    let (node, repeat, trailing_comma) = match rule.as_slice() {
-        [Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_comma)] => {
-            (node, repeat, trailing_comma)
+    let (node, repeat, trailing_sep) = match rule.as_slice() {
+        [Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => {
+            (node, repeat, Some(trailing_sep))
         }
+        [Rule::Node(node), Rule::Rep(repeat)] => (node, repeat, None),
         _ => return false,
     };
     let repeat = match &**repeat {
         Rule::Seq(it) => it,
         _ => return false,
     };
-    match repeat.as_slice() {
-        [comma, Rule::Node(n)] if comma == &**trailing_comma && n == node => (),
-        _ => return false,
+    if !matches!(
+        repeat.as_slice(),
+        [comma, Rule::Node(n)]
+            if trailing_sep.map_or(true, |it| comma == &**it) && n == node
+    ) {
+        return false;
     }
     let ty = grammar[*node].name.clone();
     let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));