cairo_lang_syntax_codegen/
generator.rs

1use std::fs;
2use std::path::PathBuf;
3
4use genco::prelude::*;
5use xshell::Shell;
6
7use crate::cairo_spec::get_spec;
8use crate::spec::{Member, Node, NodeKind, Variant, Variants};
9
10pub fn project_root() -> PathBuf {
11    // This is the directory of Cargo.toml of the syntax_codegen crate.
12    let dir = env!("CARGO_MANIFEST_DIR");
13    // Pop the "/crates/cairo-lang-syntax-codegen" suffix.
14    let res = PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned();
15    assert!(res.join("Cargo.toml").exists(), "Could not find project root directory.");
16    res
17}
18
19pub fn ensure_file_content(filename: PathBuf, content: String) {
20    if let Ok(old_contents) = fs::read_to_string(&filename) {
21        if old_contents == content {
22            return;
23        }
24    }
25
26    fs::write(&filename, content).unwrap();
27}
28
29pub fn get_codes() -> Vec<(String, String)> {
30    vec![
31        (
32            "crates/cairo-lang-syntax/src/node/ast.rs".into(),
33            reformat_rust_code(generate_ast_code().to_string().unwrap()),
34        ),
35        (
36            "crates/cairo-lang-syntax/src/node/kind.rs".into(),
37            reformat_rust_code(generate_kinds_code().to_string().unwrap()),
38        ),
39        (
40            "crates/cairo-lang-syntax/src/node/key_fields.rs".into(),
41            reformat_rust_code(generate_key_fields_code().to_string().unwrap()),
42        ),
43    ]
44}
45
46pub fn reformat_rust_code(text: String) -> String {
47    // Since rustfmt is used with nightly features, it takes 2 runs to reach a fixed point.
48    reformat_rust_code_inner(reformat_rust_code_inner(text))
49}
50pub fn reformat_rust_code_inner(text: String) -> String {
51    let sh = Shell::new().unwrap();
52    let cmd = sh.cmd("rustfmt").env("RUSTUP_TOOLCHAIN", "nightly-2025-02-16");
53    let cmd_with_args = cmd.arg("--config-path").arg(project_root().join("rustfmt.toml"));
54    let mut stdout = cmd_with_args.stdin(text).read().unwrap();
55    if !stdout.ends_with('\n') {
56        stdout.push('\n');
57    }
58    stdout
59}
60
61fn generate_kinds_code() -> rust::Tokens {
62    let spec = get_spec();
63    let mut tokens = quote! {
64        $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.\n")
65        use core::fmt;
66        use serde::{Deserialize, Serialize};
67    };
68
69    // SyntaxKind.
70    let kinds = name_tokens(&spec, |k| !matches!(k, NodeKind::Enum { .. }));
71    let token_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Token { .. }));
72    let keyword_token_kinds =
73        name_tokens(&spec, |k| matches!(k, NodeKind::Token { is_keyword } if *is_keyword));
74    let terminal_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { .. }));
75    let keyword_terminal_kinds =
76        name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { is_keyword, .. } if *is_keyword));
77
78    tokens.extend(quote! {
79        #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
80        pub enum SyntaxKind {
81            $(for t in kinds => $t,)
82        }
83        impl SyntaxKind {
84            pub fn is_token(&self) -> bool {
85                matches!(
86                    *self,
87                    $(for t in token_kinds join ( | ) => SyntaxKind::$t)
88                )
89            }
90            pub fn is_terminal(&self) -> bool {
91                matches!(
92                    *self,
93                    $(for t in terminal_kinds join ( | ) => SyntaxKind::$t)
94                )
95            }
96            pub fn is_keyword_token(&self) -> bool {
97                matches!(
98                    *self,
99                    $(for t in keyword_token_kinds join ( | ) => SyntaxKind::$t)
100                )
101            }
102            pub fn is_keyword_terminal(&self) -> bool {
103                matches!(
104                    *self,
105                    $(for t in keyword_terminal_kinds join ( | ) => SyntaxKind::$t)
106                )
107            }
108        }
109        impl fmt::Display for SyntaxKind {
110            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111                write!(f, "{self:?}")
112            }
113        }
114    });
115    tokens
116}
117
118/// Returns an iterator to the names of the tokens matching `predicate`.
119fn name_tokens(spec: &[Node], predicate: impl Fn(&NodeKind) -> bool) -> impl Iterator<Item = &str> {
120    spec.iter().filter(move |n| predicate(&n.kind)).map(|n| n.name.as_str())
121}
122
123fn generate_key_fields_code() -> rust::Tokens {
124    let spec = get_spec();
125    let mut arms = rust::Tokens::new();
126
127    for Node { name, kind } in spec.into_iter() {
128        match kind {
129            NodeKind::Struct { members } | NodeKind::Terminal { members, .. } => {
130                let mut fields = rust::Tokens::new();
131                for (i, member) in members.into_iter().enumerate() {
132                    let field_name = member.name;
133                    if member.key {
134                        fields.extend(quote! { $("/*") $field_name $("*/") children[$i], });
135                    }
136                }
137                arms.extend(quote! {
138                    SyntaxKind::$name => {vec![$fields]},
139                });
140            }
141            NodeKind::List { .. } | NodeKind::SeparatedList { .. } | NodeKind::Token { .. } => {
142                arms.extend(quote! {
143                    SyntaxKind::$name => vec![],
144                });
145            }
146            NodeKind::Enum { .. } => {}
147        }
148    }
149    let tokens = quote! {
150        $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.\n")
151        use super::ids::GreenId;
152        use super::kind::SyntaxKind;
153
154        $("/// Gets the vector of children ids that are the indexing key for this SyntaxKind.\n")
155        $("///\n")
156        $("/// Each SyntaxKind has some children that are defined in the spec to be its indexing key\n")
157        $("/// for its stable pointer. See [super::stable_ptr].\n")
158        pub fn get_key_fields(kind: SyntaxKind, children: &[GreenId]) -> Vec<GreenId> {
159            match kind {
160                $arms
161            }
162        }
163    };
164    tokens
165}
166
167fn generate_ast_code() -> rust::Tokens {
168    let spec = get_spec();
169    let mut tokens = quote! {
170        $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.\n")
171        #![allow(clippy::match_single_binding)]
172        #![allow(clippy::too_many_arguments)]
173        #![allow(dead_code)]
174        #![allow(unused_variables)]
175        use std::ops::Deref;
176        use std::sync::Arc;
177
178        use cairo_lang_filesystem::span::TextWidth;
179        use cairo_lang_utils::{extract_matches, Intern, LookupIntern};
180        use smol_str::SmolStr;
181
182        use super::element_list::ElementList;
183        use super::green::GreenNodeDetails;
184        use super::kind::SyntaxKind;
185        use super::{
186            GreenId, GreenNode, SyntaxGroup, SyntaxNode, SyntaxStablePtr, SyntaxStablePtrId,
187            Terminal, Token, TypedStablePtr, TypedSyntaxNode,
188        };
189
190        #[path = "ast_ext.rs"]
191        mod ast_ext;
192    };
193    let spec_clone = spec.clone();
194    let all_tokens: Vec<_> =
195        spec_clone.iter().filter(|node| matches!(node.kind, NodeKind::Terminal { .. })).collect();
196    for Node { name, kind } in spec.into_iter() {
197        tokens.extend(match kind {
198            NodeKind::Enum { variants, missing_variant } => {
199                let variants_list = match variants {
200                    Variants::List(variants) => variants,
201                    Variants::AllTokens => all_tokens
202                        .iter()
203                        .map(|node| Variant { name: node.name.clone(), kind: node.name.clone() })
204                        .collect(),
205                };
206                gen_enum_code(name, variants_list, missing_variant)
207            }
208            NodeKind::Struct { members } => gen_struct_code(name, members, false),
209            NodeKind::Terminal { members, .. } => gen_struct_code(name, members, true),
210            NodeKind::Token { .. } => gen_token_code(name),
211            NodeKind::List { element_type } => gen_list_code(name, element_type),
212            NodeKind::SeparatedList { element_type, separator_type } => {
213                gen_separated_list_code(name, element_type, separator_type)
214            }
215        });
216    }
217    tokens
218}
219
220fn gen_list_code(name: String, element_type: String) -> rust::Tokens {
221    // TODO(spapini): Change Deref to Borrow.
222    let ptr_name = format!("{name}Ptr");
223    let green_name = format!("{name}Green");
224    let element_green_name = format!("{element_type}Green");
225    let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
226    quote! {
227        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
228        pub struct $(&name)(ElementList<$(&element_type),1>);
229        impl Deref for $(&name){
230            type Target = ElementList<$(&element_type),1>;
231
232            fn deref(&self) -> &Self::Target {
233                &self.0
234            }
235        }
236        impl $(&name){
237            pub fn new_green(
238                db: &dyn SyntaxGroup, children: Vec<$(&element_green_name)>
239            ) -> $(&green_name) {
240                let width = children.iter().map(|id|
241                    id.0.lookup_intern(db).width()).sum();
242                $(&green_name)(Arc::new(GreenNode {
243                    kind: SyntaxKind::$(&name),
244                    details: GreenNodeDetails::Node {
245                        children: children.iter().map(|x| x.0).collect(),
246                        width,
247                    },
248                }).intern(db))
249            }
250        }
251        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
252        pub struct $(&ptr_name)(pub SyntaxStablePtrId);
253        impl TypedStablePtr for $(&ptr_name) {
254            type SyntaxNode = $(&name);
255            fn untyped(&self) -> SyntaxStablePtrId {
256                self.0
257            }
258            fn lookup(&self, db: &dyn SyntaxGroup) -> $(&name) {
259                $(&name)::from_syntax_node(db, self.0.lookup(db))
260            }
261        }
262        impl From<$(&ptr_name)> for SyntaxStablePtrId {
263            fn from(ptr: $(&ptr_name)) -> Self {
264                ptr.untyped()
265            }
266        }
267        $common_code
268    }
269}
270
271fn gen_separated_list_code(
272    name: String,
273    element_type: String,
274    separator_type: String,
275) -> rust::Tokens {
276    // TODO(spapini): Change Deref to Borrow.
277    let ptr_name = format!("{name}Ptr");
278    let green_name = format!("{name}Green");
279    let element_or_separator_green_name = format!("{name}ElementOrSeparatorGreen");
280    let element_green_name = format!("{element_type}Green");
281    let separator_green_name = format!("{separator_type}Green");
282    let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
283    quote! {
284        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
285        pub struct $(&name)(ElementList<$(&element_type),2>);
286        impl Deref for $(&name){
287            type Target = ElementList<$(&element_type),2>;
288
289            fn deref(&self) -> &Self::Target {
290                &self.0
291            }
292        }
293        impl $(&name){
294            pub fn new_green(
295                db: &dyn SyntaxGroup, children: Vec<$(&element_or_separator_green_name)>
296            ) -> $(&green_name) {
297                let width = children.iter().map(|id|
298                    id.id().lookup_intern(db).width()).sum();
299                $(&green_name)(Arc::new(GreenNode {
300                    kind: SyntaxKind::$(&name),
301                    details: GreenNodeDetails::Node {
302                        children: children.iter().map(|x| x.id()).collect(),
303                        width,
304                    },
305                }).intern(db))
306            }
307        }
308        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
309        pub struct $(&ptr_name)(pub SyntaxStablePtrId);
310        impl TypedStablePtr for $(&ptr_name) {
311            type SyntaxNode = $(&name);
312            fn untyped(&self) -> SyntaxStablePtrId {
313                self.0
314            }
315            fn lookup(&self, db: &dyn SyntaxGroup) -> $(&name) {
316                $(&name)::from_syntax_node(db, self.0.lookup(db))
317            }
318        }
319        impl From<$(&ptr_name)> for SyntaxStablePtrId {
320            fn from(ptr: $(&ptr_name)) -> Self {
321                ptr.untyped()
322            }
323        }
324        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
325        pub enum $(&element_or_separator_green_name) {
326            Separator($(&separator_green_name)),
327            Element($(&element_green_name)),
328        }
329        impl From<$(&separator_green_name)> for $(&element_or_separator_green_name) {
330            fn from(value: $(&separator_green_name)) -> Self {
331                $(&element_or_separator_green_name)::Separator(value)
332            }
333        }
334        impl From<$(&element_green_name)> for $(&element_or_separator_green_name) {
335            fn from(value: $(&element_green_name)) -> Self {
336                $(&element_or_separator_green_name)::Element(value)
337            }
338        }
339        impl $(&element_or_separator_green_name) {
340            fn id(&self) -> GreenId {
341                match self {
342                    $(&element_or_separator_green_name)::Separator(green) => green.0,
343                    $(&element_or_separator_green_name)::Element(green) => green.0,
344                }
345            }
346        }
347        $common_code
348    }
349}
350
351fn gen_common_list_code(name: &str, green_name: &str, ptr_name: &str) -> rust::Tokens {
352    quote! {
353        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
354        pub struct $green_name(pub GreenId);
355        impl TypedSyntaxNode for $name {
356            const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$name);
357            type StablePtr = $ptr_name;
358            type Green = $green_name;
359            fn missing(db: &dyn SyntaxGroup) -> Self::Green {
360                $green_name(Arc::new(
361                    GreenNode {
362                        kind: SyntaxKind::$name,
363                        details: GreenNodeDetails::Node { children: vec![], width: TextWidth::default() },
364                    }).intern(db)
365                )
366            }
367            fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self {
368                Self(ElementList::new(node))
369            }
370            fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
371                if node.kind(db) == SyntaxKind::$name {
372                    Some(Self(ElementList::new(node)))
373                } else {
374                    None
375                }
376            }
377            fn as_syntax_node(&self) -> SyntaxNode {
378                self.node.clone()
379            }
380            fn stable_ptr(&self) -> Self::StablePtr {
381                $ptr_name(self.node.0.stable_ptr)
382            }
383        }
384        impl From<&$name> for SyntaxStablePtrId {
385            fn from(node: &$name) -> Self {
386                node.stable_ptr().untyped()
387            }
388        }
389    }
390}
391
392#[expect(clippy::literal_string_with_formatting_args)]
393fn gen_enum_code(
394    name: String,
395    variants: Vec<Variant>,
396    missing_variant: Option<Variant>,
397) -> rust::Tokens {
398    let ptr_name = format!("{name}Ptr");
399    let green_name = format!("{name}Green");
400    let mut enum_body = quote! {};
401    let mut from_node_body = quote! {};
402    let mut cast_body = quote! {};
403    let mut ptr_conversions = quote! {};
404    let mut green_conversions = quote! {};
405    for variant in &variants {
406        let n = &variant.name;
407        let k = &variant.kind;
408
409        enum_body.extend(quote! {
410            $n($k),
411        });
412        from_node_body.extend(quote! {
413            SyntaxKind::$k => $(&name)::$n($k::from_syntax_node(db, node)),
414        });
415        cast_body.extend(quote! {
416            SyntaxKind::$k => Some($(&name)::$n($k::from_syntax_node(db, node))),
417        });
418        let variant_ptr = format!("{k}Ptr");
419        ptr_conversions.extend(quote! {
420            impl From<$(&variant_ptr)> for $(&ptr_name) {
421                fn from(value: $(&variant_ptr)) -> Self {
422                    Self(value.0)
423                }
424            }
425        });
426        let variant_green = format!("{k}Green");
427        green_conversions.extend(quote! {
428            impl From<$(&variant_green)> for $(&green_name) {
429                fn from(value: $(&variant_green)) -> Self {
430                    Self(value.0)
431                }
432            }
433        });
434    }
435    let missing_body = match missing_variant {
436        Some(missing) => quote! {
437            $(&green_name)($(missing.kind)::missing(db).0)
438        },
439        None => quote! {
440            panic!("No missing variant.");
441        },
442    };
443    quote! {
444        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
445        pub enum $(&name){
446            $enum_body
447        }
448        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
449        pub struct $(&ptr_name)(pub SyntaxStablePtrId);
450        impl TypedStablePtr for $(&ptr_name) {
451            type SyntaxNode = $(&name);
452            fn untyped(&self) -> SyntaxStablePtrId {
453                self.0
454            }
455            fn lookup(&self, db: &dyn SyntaxGroup) -> $(&name) {
456                $(&name)::from_syntax_node(db, self.0.lookup(db))
457            }
458        }
459        impl From<$(&ptr_name)> for SyntaxStablePtrId {
460            fn from(ptr: $(&ptr_name)) -> Self {
461                ptr.untyped()
462            }
463        }
464        $ptr_conversions
465        $green_conversions
466        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
467        pub struct $(&green_name)(pub GreenId);
468        impl TypedSyntaxNode for $(&name){
469            const OPTIONAL_KIND: Option<SyntaxKind> = None;
470            type StablePtr = $(&ptr_name);
471            type Green = $(&green_name);
472            fn missing(db: &dyn SyntaxGroup) -> Self::Green {
473                $missing_body
474            }
475            fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self {
476                let kind = node.kind(db);
477                match kind{
478                    $from_node_body
479                    _ => panic!(
480                        "Unexpected syntax kind {:?} when constructing {}.",
481                        kind,
482                        $[str]($[const](&name))),
483                }
484            }
485            fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
486                let kind = node.kind(db);
487                match kind {
488                    $cast_body
489                    _ => None,
490                }
491            }
492            fn as_syntax_node(&self) -> SyntaxNode {
493                match self {
494                    $(for v in &variants => $(&name)::$(&v.name)(x) => x.as_syntax_node(),)
495                }
496            }
497            fn stable_ptr(&self) -> Self::StablePtr {
498                $(&ptr_name)(self.as_syntax_node().0.stable_ptr)
499            }
500        }
501        impl From<&$(&name)> for SyntaxStablePtrId {
502            fn from(node: &$(&name)) -> Self {
503                node.stable_ptr().untyped()
504            }
505        }
506        impl $(&name) {
507            $("/// Checks if a kind of a variant of [")$(&name)$("].\n")
508            pub fn is_variant(kind: SyntaxKind) -> bool {
509                matches!(kind, $(for v in &variants join (|) => SyntaxKind::$(&v.kind)))
510            }
511        }
512    }
513}
514
515#[expect(clippy::literal_string_with_formatting_args)]
516fn gen_token_code(name: String) -> rust::Tokens {
517    let green_name = format!("{name}Green");
518    let ptr_name = format!("{name}Ptr");
519
520    quote! {
521        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
522        pub struct $(&name) {
523            node: SyntaxNode,
524        }
525        impl Token for $(&name) {
526            fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green {
527                $(&green_name)(Arc::new(GreenNode {
528                    kind: SyntaxKind::$(&name),
529                    details: GreenNodeDetails::Token(text),
530                }).intern(db))
531            }
532            fn text(&self, db: &dyn SyntaxGroup) -> SmolStr {
533                extract_matches!(&self.node.0.green.lookup_intern(db).details,
534                    GreenNodeDetails::Token).clone()
535            }
536        }
537        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
538        pub struct $(&ptr_name)(pub SyntaxStablePtrId);
539        impl TypedStablePtr for $(&ptr_name) {
540            type SyntaxNode = $(&name);
541            fn untyped(&self) -> SyntaxStablePtrId {
542                self.0
543            }
544            fn lookup(&self, db: &dyn SyntaxGroup) -> $(&name) {
545                $(&name)::from_syntax_node(db, self.0.lookup(db))
546            }
547        }
548        impl From<$(&ptr_name)> for SyntaxStablePtrId {
549            fn from(ptr: $(&ptr_name)) -> Self {
550                ptr.untyped()
551            }
552        }
553        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
554        pub struct $(&green_name)(pub GreenId);
555        impl $(&green_name) {
556            pub fn text(&self, db: &dyn SyntaxGroup) -> SmolStr {
557                extract_matches!(
558                    &self.0.lookup_intern(db).details, GreenNodeDetails::Token).clone()
559            }
560        }
561        impl TypedSyntaxNode for $(&name){
562            const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
563            type StablePtr = $(&ptr_name);
564            type Green = $(&green_name);
565            fn missing(db: &dyn SyntaxGroup) -> Self::Green {
566                $(&green_name)(Arc::new(GreenNode {
567                    kind: SyntaxKind::TokenMissing,
568                    details: GreenNodeDetails::Token("".into()),
569                }).intern(db))
570            }
571            fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self {
572                match node.0.green.lookup_intern(db).details {
573                    GreenNodeDetails::Token(_) => Self { node },
574                    GreenNodeDetails::Node { .. } => panic!(
575                        "Expected a token {:?}, not an internal node",
576                        SyntaxKind::$(&name)
577                    ),
578                }
579            }
580            fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
581                match node.0.green.lookup_intern(db).details {
582                    GreenNodeDetails::Token(_) => Some(Self { node }),
583                    GreenNodeDetails::Node { .. } => None,
584                }
585            }
586            fn as_syntax_node(&self) -> SyntaxNode {
587                self.node.clone()
588            }
589            fn stable_ptr(&self) -> Self::StablePtr {
590                $(&ptr_name)(self.node.0.stable_ptr)
591            }
592        }
593        impl From<&$(&name)> for SyntaxStablePtrId {
594            fn from(node: &$(&name)) -> Self {
595                node.stable_ptr().untyped()
596            }
597        }
598    }
599}
600
601#[expect(clippy::literal_string_with_formatting_args)]
602fn gen_struct_code(name: String, members: Vec<Member>, is_terminal: bool) -> rust::Tokens {
603    let green_name = format!("{name}Green");
604    let mut body = rust::Tokens::new();
605    let mut field_indices = quote! {};
606    let mut args = quote! {};
607    let mut params = quote! {};
608    let mut args_for_missing = quote! {};
609    let mut ptr_getters = quote! {};
610    let mut key_field_index: usize = 0;
611    for (i, Member { name, kind, key }) in members.iter().enumerate() {
612        let index_name = format!("INDEX_{}", name.to_uppercase());
613        field_indices.extend(quote! {
614            pub const $index_name : usize = $i;
615        });
616        let key_name_green = format!("{name}_green");
617        args.extend(quote! {$name.0,});
618        // TODO(spapini): Validate that children SyntaxKinds are as expected.
619
620        let child_green = format!("{kind}Green");
621        params.extend(quote! {$name: $(&child_green),});
622        body.extend(quote! {
623            pub fn $name(&self, db: &dyn SyntaxGroup) -> $kind {
624                $kind::from_syntax_node(db, self.children[$i].clone())
625            }
626        });
627        args_for_missing.extend(quote! {$kind::missing(db).0,});
628
629        if *key {
630            ptr_getters.extend(quote! {
631                pub fn $(&key_name_green)(self, db: &dyn SyntaxGroup) -> $(&child_green) {
632                    let ptr = self.0.lookup_intern(db);
633                    if let SyntaxStablePtr::Child { key_fields, .. } = ptr {
634                        $(&child_green)(key_fields[$key_field_index])
635                    } else {
636                        panic!("Unexpected key field query on root.");
637                    }
638                }
639            });
640            key_field_index += 1;
641        }
642    }
643    let ptr_name = format!("{name}Ptr");
644    let new_green_impl = if is_terminal {
645        let token_name = name.replace("Terminal", "Token");
646        quote! {
647            impl Terminal for $(&name) {
648                const KIND: SyntaxKind = SyntaxKind::$(&name);
649                type TokenType = $(&token_name);
650                fn new_green(
651                    db: &dyn SyntaxGroup,
652                    leading_trivia: TriviaGreen,
653                    token: <<$(&name) as Terminal>::TokenType as TypedSyntaxNode>::Green,
654                    trailing_trivia: TriviaGreen
655                ) -> Self::Green {
656                    let children: Vec<GreenId> = vec![$args];
657                    let width = children.iter().copied().map(|id|
658                        id.lookup_intern(db).width()).sum();
659                    $(&green_name)(Arc::new(GreenNode {
660                        kind: SyntaxKind::$(&name),
661                        details: GreenNodeDetails::Node { children, width },
662                    }).intern(db))
663                }
664                fn text(&self, db: &dyn SyntaxGroup) -> SmolStr {
665                    self.token(db).text(db)
666                }
667            }
668        }
669    } else {
670        quote! {
671            impl $(&name) {
672                $field_indices
673                pub fn new_green(db: &dyn SyntaxGroup, $params) -> $(&green_name) {
674                    let children: Vec<GreenId> = vec![$args];
675                    let width = children.iter().copied().map(|id|
676                        id.lookup_intern(db).width()).sum();
677                    $(&green_name)(Arc::new(GreenNode {
678                        kind: SyntaxKind::$(&name),
679                        details: GreenNodeDetails::Node { children, width },
680                    }).intern(db))
681                }
682            }
683        }
684    };
685    quote! {
686        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
687        pub struct $(&name) {
688            node: SyntaxNode,
689            children: Arc<[SyntaxNode]>,
690        }
691        $new_green_impl
692        impl $(&name) {
693            $body
694        }
695        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
696        pub struct $(&ptr_name)(pub SyntaxStablePtrId);
697        impl $(&ptr_name) {
698            $ptr_getters
699        }
700        impl TypedStablePtr for $(&ptr_name) {
701            type SyntaxNode = $(&name);
702            fn untyped(&self) -> SyntaxStablePtrId {
703                self.0
704            }
705            fn lookup(&self, db: &dyn SyntaxGroup) -> $(&name) {
706                $(&name)::from_syntax_node(db, self.0.lookup(db))
707            }
708        }
709        impl From<$(&ptr_name)> for SyntaxStablePtrId {
710            fn from(ptr: $(&ptr_name)) -> Self {
711                ptr.untyped()
712            }
713        }
714        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
715        pub struct $(&green_name)(pub GreenId);
716        impl TypedSyntaxNode for $(&name) {
717            const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
718            type StablePtr = $(&ptr_name);
719            type Green = $(&green_name);
720            fn missing(db: &dyn SyntaxGroup) -> Self::Green {
721                // Note: A missing syntax element should result in an internal green node
722                // of width 0, with as much structure as possible.
723                $(&green_name)(Arc::new(GreenNode {
724                    kind: SyntaxKind::$(&name),
725                    details: GreenNodeDetails::Node {
726                        children: vec![$args_for_missing],
727                        width: TextWidth::default(),
728                    },
729                }).intern(db))
730            }
731            fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self {
732                let kind = node.kind(db);
733                assert_eq!(kind, SyntaxKind::$(&name), "Unexpected SyntaxKind {:?}. Expected {:?}.", kind, SyntaxKind::$(&name));
734                let children = db.get_children(node.clone());
735                Self { node, children }
736            }
737            fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
738                let kind = node.kind(db);
739                if kind == SyntaxKind::$(&name) {
740                    Some(Self::from_syntax_node(db, node))
741                } else {
742                    None
743                }
744            }
745            fn as_syntax_node(&self) -> SyntaxNode {
746                self.node.clone()
747            }
748            fn stable_ptr(&self) -> Self::StablePtr {
749                $(&ptr_name)(self.node.0.stable_ptr)
750            }
751        }
752        impl From<&$(&name)> for SyntaxStablePtrId {
753            fn from(node: &$(&name)) -> Self {
754                node.stable_ptr().untyped()
755            }
756        }
757    }
758}