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