use std::fs;
use std::path::PathBuf;
use genco::prelude::*;
use xshell::Shell;
use crate::cairo_spec::get_spec;
use crate::spec::{Member, Node, NodeKind, Variant, Variants};
pub fn project_root() -> PathBuf {
let dir = env!("CARGO_MANIFEST_DIR");
let res = PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned();
assert!(res.join("Cargo.toml").exists(), "Could not find project root directory.");
res
}
pub fn ensure_file_content(filename: PathBuf, content: String) {
if let Ok(old_contents) = fs::read_to_string(&filename)
&& old_contents == content
{
return;
}
fs::write(&filename, content).unwrap();
}
pub fn get_codes() -> Vec<(String, String)> {
vec![
(
"crates/cairo-lang-syntax/src/node/ast.rs".into(),
reformat_rust_code(generate_ast_code().to_string().unwrap()),
),
(
"crates/cairo-lang-syntax/src/node/kind.rs".into(),
reformat_rust_code(generate_kinds_code().to_string().unwrap()),
),
(
"crates/cairo-lang-syntax/src/node/key_fields.rs".into(),
reformat_rust_code(generate_key_fields_code().to_string().unwrap()),
),
]
}
pub fn reformat_rust_code(text: String) -> String {
reformat_rust_code_inner(reformat_rust_code_inner(text))
}
pub fn reformat_rust_code_inner(text: String) -> String {
let sh = Shell::new().unwrap();
let cmd = sh.cmd("rustfmt").env("RUSTUP_TOOLCHAIN", "nightly-2025-12-05");
let cmd_with_args = cmd.arg("--config-path").arg(project_root().join("rustfmt.toml"));
let mut stdout = cmd_with_args.stdin(text).read().unwrap();
if !stdout.ends_with('\n') {
stdout.push('\n');
}
stdout
}
fn generate_kinds_code() -> rust::Tokens {
let spec = get_spec();
let mut tokens = quote! {
$("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
use core::fmt;
use serde::{Deserialize, Serialize};
};
let kinds = name_tokens(&spec, |k| !matches!(k, NodeKind::Enum { .. }));
let token_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Token { .. }));
let keyword_token_kinds =
name_tokens(&spec, |k| matches!(k, NodeKind::Token { is_keyword } if *is_keyword));
let terminal_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { .. }));
let keyword_terminal_kinds =
name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { is_keyword, .. } if *is_keyword));
let missing_kinds = spec.iter().filter_map(|n| {
if let NodeKind::Enum { missing_variant, .. } = &n.kind {
missing_variant.as_ref().map(|v| v.kind.as_str())
} else {
None
}
});
tokens.extend(quote! {
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, salsa::Update, cairo_lang_proc_macros::HeapSize)]
pub enum SyntaxKind {
$(for t in kinds => $t,)
}
impl SyntaxKind {
pub fn is_token(&self) -> bool {
matches!(
*self,
$(for t in token_kinds join ( | ) => SyntaxKind::$t)
)
}
pub fn is_terminal(&self) -> bool {
matches!(
*self,
$(for t in terminal_kinds join ( | ) => SyntaxKind::$t)
)
}
pub fn is_keyword_token(&self) -> bool {
matches!(
*self,
$(for t in keyword_token_kinds join ( | ) => SyntaxKind::$t)
)
}
pub fn is_keyword_terminal(&self) -> bool {
matches!(
*self,
$(for t in keyword_terminal_kinds join ( | ) => SyntaxKind::$t)
)
}
pub fn is_missing(&self) -> bool {
matches!(
*self,
$(for t in missing_kinds join ( | ) => SyntaxKind::$t)
)
}
}
impl fmt::Display for SyntaxKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
});
tokens
}
fn name_tokens(spec: &[Node], predicate: impl Fn(&NodeKind) -> bool) -> impl Iterator<Item = &str> {
spec.iter().filter(move |n| predicate(&n.kind)).map(|n| n.name.as_str())
}
fn generate_key_fields_code() -> rust::Tokens {
let spec = get_spec();
let mut arms = rust::Tokens::new();
for Node { name, kind } in spec {
match kind {
NodeKind::Struct { members } | NodeKind::Terminal { members, .. } => {
let mut fields = rust::Tokens::new();
let mut key_fields_range = 0..0;
for (i, member) in members.into_iter().enumerate() {
let field_name = member.name;
if member.key {
if key_fields_range.is_empty() {
key_fields_range = i..(i + 1);
} else {
assert_eq!(key_fields_range.end, i, "Key fields must be contiguous.");
key_fields_range.end = i + 1;
}
if !fields.is_empty() {
fields.extend(quote! { $(", ") });
}
fields.extend(quote!($field_name));
}
}
if !fields.is_empty() {
arms.extend(quote! {
$("\n// Key fields:") $fields.$("\n")
});
}
let key_fields_range =
format!("{}..{}", key_fields_range.start, key_fields_range.end);
arms.extend(quote! {
SyntaxKind::$name => $key_fields_range,
});
}
NodeKind::List { .. } | NodeKind::SeparatedList { .. } | NodeKind::Token { .. } => {
arms.extend(quote! {
SyntaxKind::$name => 0..0,
});
}
NodeKind::Enum { .. } => {}
}
}
let tokens = quote! {
$("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
use super::kind::SyntaxKind;
$("/// Gets the vector of children ids that are the indexing key for this SyntaxKind.")
$("///")
$("/// Each SyntaxKind has some children that are defined in the spec to be its indexing key")
$("/// for its stable pointer. See [super::stable_ptr].")
pub fn key_fields_range(kind: SyntaxKind) -> core::ops::Range<usize> {
match kind {
$arms
}
}
};
tokens
}
fn generate_ast_code() -> rust::Tokens {
let spec = get_spec();
let mut tokens = quote! {
$("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
#![allow(clippy::match_single_binding)]
#![allow(clippy::too_many_arguments)]
#![allow(dead_code)]
#![allow(unused_variables)]
use std::ops::Deref;
use cairo_lang_filesystem::span::TextWidth;
use cairo_lang_filesystem::ids::SmolStrId;
use cairo_lang_utils::{extract_matches, Intern};
use cairo_lang_proc_macros::HeapSize;
use salsa::Database;
use super::element_list::ElementList;
use super::green::GreenNodeDetails;
use super::kind::SyntaxKind;
use super::{
GreenId, GreenNode, SyntaxNode, SyntaxStablePtrId, Terminal, Token, TypedStablePtr,
TypedSyntaxNode,
};
#[path = "ast_ext.rs"]
mod ast_ext;
};
let spec_clone = spec.clone();
let all_tokens: Vec<_> =
spec_clone.iter().filter(|node| matches!(node.kind, NodeKind::Terminal { .. })).collect();
for Node { name, kind } in spec {
tokens.extend(match kind {
NodeKind::Enum { variants, missing_variant } => {
let variants_list = match variants {
Variants::List(variants) => variants,
Variants::AllTokens => all_tokens
.iter()
.map(|node| Variant { name: node.name.clone(), kind: node.name.clone() })
.collect(),
};
gen_enum_code(name, variants_list, missing_variant)
}
NodeKind::Struct { members } => gen_struct_code(name, members, false),
NodeKind::Terminal { members, .. } => gen_struct_code(name, members, true),
NodeKind::Token { .. } => gen_token_code(name),
NodeKind::List { element_type } => gen_list_code(name, element_type),
NodeKind::SeparatedList { element_type, separator_type } => {
gen_separated_list_code(name, element_type, separator_type)
}
});
}
tokens
}
fn gen_list_code(name: String, element_type: String) -> rust::Tokens {
let ptr_name = format!("{name}Ptr");
let green_name = format!("{name}Green");
let element_green_name = format!("{element_type}Green");
let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
quote! {
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 1>);
impl<'db> Deref for $(&name)<'db>{
type Target = ElementList<'db, $(&element_type)<'db>, 1>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'db> $(&name)<'db>{
pub fn new_green(
db: &'db dyn Database, children: &[$(&element_green_name)<'db>]
) -> $(&green_name)<'db> {
let width = children.iter().map(|id|
id.0.long(db).width(db)).sum();
$(&green_name)(GreenNode {
kind: SyntaxKind::$(&name),
details: GreenNodeDetails::Node {
children: children.iter().map(|x| x.0).collect(),
width,
},
}.intern(db))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
type SyntaxNode = $(&name)<'db>;
fn untyped(self) -> SyntaxStablePtrId<'db> {
self.0
}
fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
$(&name)::from_syntax_node(db, self.0.lookup(db))
}
}
impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
fn from(ptr: $(&ptr_name)<'db>) -> Self {
ptr.untyped()
}
}
$common_code
}
}
fn gen_separated_list_code(
name: String,
element_type: String,
separator_type: String,
) -> rust::Tokens {
let ptr_name = format!("{name}Ptr");
let green_name = format!("{name}Green");
let element_or_separator_green_name = format!("{name}ElementOrSeparatorGreen");
let element_green_name = format!("{element_type}Green");
let separator_green_name = format!("{separator_type}Green");
let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
quote! {
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 2>);
impl<'db> Deref for $(&name)<'db>{
type Target = ElementList<'db, $(&element_type)<'db>, 2>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'db> $(&name)<'db>{
pub fn new_green(
db: &'db dyn Database, children: &[$(&element_or_separator_green_name)<'db>]
) -> $(&green_name)<'db> {
let width = children.iter().map(|id|
id.id().long(db).width(db)).sum();
$(&green_name)(GreenNode {
kind: SyntaxKind::$(&name),
details: GreenNodeDetails::Node {
children: children.iter().map(|x| x.id()).collect(),
width,
},
}.intern(db))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
type SyntaxNode = $(&name)<'db>;
fn untyped(self) -> SyntaxStablePtrId<'db> {
self.0
}
fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
$(&name)::from_syntax_node(db, self.0.lookup(db))
}
}
impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
fn from(ptr: $(&ptr_name)<'db>) -> Self {
ptr.untyped()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
pub enum $(&element_or_separator_green_name)<'db> {
Separator($(&separator_green_name)<'db>),
Element($(&element_green_name)<'db>),
}
impl<'db> From<$(&separator_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
fn from(value: $(&separator_green_name)<'db>) -> Self {
$(&element_or_separator_green_name)::Separator(value)
}
}
impl<'db> From<$(&element_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
fn from(value: $(&element_green_name)<'db>) -> Self {
$(&element_or_separator_green_name)::Element(value)
}
}
impl<'db> $(&element_or_separator_green_name)<'db> {
fn id(&self) -> GreenId<'db> {
match self {
$(&element_or_separator_green_name)::Separator(green) => green.0,
$(&element_or_separator_green_name)::Element(green) => green.0,
}
}
}
$common_code
}
}
fn gen_common_list_code(name: &str, green_name: &str, ptr_name: &str) -> rust::Tokens {
quote! {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
pub struct $green_name<'db>(pub GreenId<'db>);
impl<'db> TypedSyntaxNode<'db> for $name<'db> {
const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$name);
type StablePtr = $ptr_name<'db>;
type Green = $green_name<'db>;
fn missing(db: &'db dyn Database) -> Self::Green {
$green_name(
GreenNode {
kind: SyntaxKind::$name,
details: GreenNodeDetails::Node { children: [].into(), width: TextWidth::default() },
}.intern(db)
)
}
fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
Self(ElementList::new(node))
}
fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
if node.kind(db) == SyntaxKind::$name {
Some(Self(ElementList::new(node)))
} else {
None
}
}
fn as_syntax_node(&self) -> SyntaxNode<'db> {
self.node
}
fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
$ptr_name(self.node.stable_ptr(db))
}
}
}
}
fn gen_enum_code(
name: String,
variants: Vec<Variant>,
missing_variant: Option<Variant>,
) -> rust::Tokens {
let ptr_name = format!("{name}Ptr");
let green_name = format!("{name}Green");
let mut enum_body = quote! {};
let mut from_node_body = quote! {};
let mut cast_body = quote! {};
let mut ptr_conversions = quote! {};
let mut green_conversions = quote! {};
for variant in &variants {
let n = &variant.name;
let k = &variant.kind;
enum_body.extend(quote! {
$n($k<'db>),
});
from_node_body.extend(quote! {
SyntaxKind::$k => $(&name)::$n($k::from_syntax_node(db, node)),
});
cast_body.extend(quote! {
SyntaxKind::$k => Some($(&name)::$n($k::from_syntax_node(db, node))),
});
let variant_ptr = format!("{k}Ptr");
ptr_conversions.extend(quote! {
impl<'db> From<$(&variant_ptr)<'db>> for $(&ptr_name)<'db> {
fn from(value: $(&variant_ptr)<'db>) -> Self {
Self(value.0)
}
}
});
let variant_green = format!("{k}Green");
green_conversions.extend(quote! {
impl<'db> From<$(&variant_green)<'db>> for $(&green_name)<'db> {
fn from(value: $(&variant_green)<'db>) -> Self {
Self(value.0)
}
}
});
}
let missing_body = match missing_variant {
Some(missing) => quote! {
$(&green_name)($(missing.kind)::missing(db).0)
},
None => quote! {
panic!("No missing variant.");
},
};
quote! {
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub enum $(&name)<'db>{
$enum_body
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
type SyntaxNode = $(&name)<'db>;
fn untyped(self) -> SyntaxStablePtrId<'db> {
self.0
}
fn lookup(&self, db: &'db dyn Database) -> Self::SyntaxNode {
$(&name)::from_syntax_node(db, self.0.lookup(db))
}
}
impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
fn from(ptr: $(&ptr_name)<'db>) -> Self {
ptr.untyped()
}
}
$ptr_conversions
$green_conversions
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
pub struct $(&green_name)<'db>(pub GreenId<'db>);
impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
const OPTIONAL_KIND: Option<SyntaxKind> = None;
type StablePtr = $(&ptr_name)<'db>;
type Green = $(&green_name)<'db>;
fn missing(db: &'db dyn Database) -> Self::Green {
$missing_body
}
fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
let kind = node.kind(db);
match kind{
$from_node_body
_ => panic!(
"Unexpected syntax kind {:?} when constructing {}.",
kind,
$[str]($[const](&name))),
}
}
fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
let kind = node.kind(db);
match kind {
$cast_body
_ => None,
}
}
fn as_syntax_node(&self) -> SyntaxNode<'db> {
match self {
$(for v in &variants => $(&name)::$(&v.name)(x) => x.as_syntax_node(),)
}
}
fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
$(&ptr_name)(self.as_syntax_node().stable_ptr(db))
}
}
impl<'db> $(&name)<'db> {
$("/// Checks if a kind of a variant of [")$(&name)$("].")
pub fn is_variant(kind: SyntaxKind) -> bool {
matches!(kind, $(for v in &variants join (|) => SyntaxKind::$(&v.kind)))
}
}
}
}
fn gen_token_code(name: String) -> rust::Tokens {
let green_name = format!("{name}Green");
let ptr_name = format!("{name}Ptr");
quote! {
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub struct $(&name)<'db> {
node: SyntaxNode<'db>,
}
impl<'db> Token<'db> for $(&name)<'db> {
fn new_green(db: &'db dyn Database, text: SmolStrId<'db>) -> Self::Green {
$(&green_name)(GreenNode {
kind: SyntaxKind::$(&name),
details: GreenNodeDetails::Token(text),
}.intern(db))
}
fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
*extract_matches!(&self.node.green_node(db).details,
GreenNodeDetails::Token)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
type SyntaxNode = $(&name)<'db>;
fn untyped(self) -> SyntaxStablePtrId<'db> {
self.0
}
fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
$(&name)::from_syntax_node(db, self.0.lookup(db))
}
}
impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
fn from(ptr: $(&ptr_name)<'db>) -> Self {
ptr.untyped()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
pub struct $(&green_name)<'db>(pub GreenId<'db>);
impl<'db> $(&green_name)<'db> {
pub fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
*extract_matches!(&self.0.long(db).details, GreenNodeDetails::Token)
}
}
impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
type StablePtr = $(&ptr_name)<'db>;
type Green = $(&green_name)<'db>;
fn missing(db: &'db dyn Database) -> Self::Green {
$(&green_name)(GreenNode {
kind: SyntaxKind::TokenMissing,
details: GreenNodeDetails::Token(SmolStrId::from(db, "")),
}.intern(db))
}
fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
match node.green_node(db).details {
GreenNodeDetails::Token(_) => Self { node },
GreenNodeDetails::Node { .. } => panic!(
"Expected a token {:?}, not an internal node",
SyntaxKind::$(&name)
),
}
}
fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
match node.green_node(db).details {
GreenNodeDetails::Token(_) => Some(Self { node }),
GreenNodeDetails::Node { .. } => None,
}
}
fn as_syntax_node(&self) -> SyntaxNode<'db> {
self.node
}
fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
$(&ptr_name)(self.node.stable_ptr(db))
}
}
}
}
fn gen_struct_code(name: String, members: Vec<Member>, is_terminal: bool) -> rust::Tokens {
let green_name = format!("{name}Green");
let mut body = rust::Tokens::new();
let mut field_indices = quote! {};
let mut args = quote! {};
let mut params = quote! {};
let mut args_for_missing = quote! {};
let mut ptr_getters = quote! {};
let mut key_field_index: usize = 0;
for (i, Member { name, kind, key }) in members.iter().enumerate() {
let index_name = format!("INDEX_{}", name.to_uppercase());
field_indices.extend(quote! {
pub const $index_name : usize = $i;
});
let key_name_green = format!("{name}_green");
args.extend(quote! {$name.0,});
let child_green = format!("{kind}Green");
params.extend(quote! {$name: $(&child_green)<'db>,});
body.extend(quote! {
pub fn $name(&self, db: &'db dyn Database) -> $kind<'db> {
$kind::from_syntax_node(db, self.node.get_children(db)[$i])
}
});
args_for_missing.extend(quote! {$kind::missing(db).0,});
if *key {
ptr_getters.extend(quote! {
pub fn $(&key_name_green)(self, db: &'db dyn Database) -> $(&child_green)<'db> {
$(&child_green)(self.0.0.key_fields(db)[$key_field_index])
}
});
key_field_index += 1;
}
}
let ptr_name = format!("{name}Ptr");
let new_green_impl = if is_terminal {
let token_name = name.replace("Terminal", "Token");
quote! {
impl<'db> Terminal<'db> for $(&name)<'db> {
const KIND: SyntaxKind = SyntaxKind::$(&name);
type TokenType = $(&token_name)<'db>;
fn new_green(
db: &'db dyn Database,
leading_trivia: TriviaGreen<'db>,
token: <<$(&name)<'db> as Terminal<'db>>::TokenType as TypedSyntaxNode<'db>>::Green,
trailing_trivia: TriviaGreen<'db>
) -> Self::Green {
let children = [$args];
let width =
children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
$(&green_name)(GreenNode {
kind: SyntaxKind::$(&name),
details: GreenNodeDetails::Node { children: children.into(), width },
}.intern(db))
}
fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
let GreenNodeDetails::Node{children,..} = &self.node.green_node(db).details else {
unreachable!("Expected a node, not a token");
};
*extract_matches!(&children[1].long(db).details, GreenNodeDetails::Token)
}
}
}
} else {
quote! {
impl<'db> $(&name)<'db> {
$field_indices
pub fn new_green(db: &'db dyn Database, $params) -> $(&green_name)<'db> {
let children = [$args];
let width =
children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
$(&green_name)(GreenNode {
kind: SyntaxKind::$(&name),
details: GreenNodeDetails::Node { children: children.into(), width },
}.intern(db))
}
}
}
};
quote! {
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
pub struct $(&name)<'db> {
node: SyntaxNode<'db>,
}
$new_green_impl
impl<'db> $(&name)<'db> {
$body
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
impl<'db> $(&ptr_name)<'db> {
$ptr_getters
}
impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
type SyntaxNode = $(&name)<'db>;
fn untyped(self) -> SyntaxStablePtrId<'db> {
self.0
}
fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
$(&name)::from_syntax_node(db, self.0.lookup(db))
}
}
impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
fn from(ptr: $(&ptr_name)<'db>) -> Self {
ptr.untyped()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
pub struct $(&green_name)<'db>(pub GreenId<'db>);
impl<'db> TypedSyntaxNode<'db> for $(&name)<'db> {
const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
type StablePtr = $(&ptr_name)<'db>;
type Green = $(&green_name)<'db>;
fn missing(db: &'db dyn Database) -> Self::Green {
$(&green_name)(GreenNode {
kind: SyntaxKind::$(&name),
details: GreenNodeDetails::Node {
children: [$args_for_missing].into(),
width: TextWidth::default(),
},
}.intern(db))
}
fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
let kind = node.kind(db);
assert_eq!(kind, SyntaxKind::$(&name), "Unexpected SyntaxKind {:?}. Expected {:?}.", kind, SyntaxKind::$(&name));
Self { node }
}
fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
let kind = node.kind(db);
if kind == SyntaxKind::$(&name) {
Some(Self::from_syntax_node(db, node))
} else {
None
}
}
fn as_syntax_node(&self) -> SyntaxNode<'db> {
self.node
}
fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
$(&ptr_name)(self.node.stable_ptr(db))
}
}
}
}