Skip to main content

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