Skip to content

Commit eeb462e

Browse files
authored
Add event node support (#87)
1 parent 1b5ef07 commit eeb462e

File tree

27 files changed

+1475
-10
lines changed

27 files changed

+1475
-10
lines changed

codama-attributes/src/attributes.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ impl<'a> Attributes<'a> {
7272
self.has_codama_derive("CodamaAccount")
7373
|| self.has_codama_derive("CodamaAccounts")
7474
|| self.has_codama_derive("CodamaErrors")
75+
|| self.has_codama_derive("CodamaEvent")
76+
|| self.has_codama_derive("CodamaEvents")
7577
|| self.has_codama_derive("CodamaInstruction")
7678
|| self.has_codama_derive("CodamaInstructions")
7779
|| self.has_codama_derive("CodamaPda")

codama-attributes/src/codama_directives/program_directive.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ impl ProgramDirective {
7575
..ProgramNode::default()
7676
}
7777
.into(),
78+
Node::Event(event) => ProgramNode {
79+
name: self.name.clone(),
80+
public_key: self.address.clone(),
81+
events: vec![event],
82+
..ProgramNode::default()
83+
}
84+
.into(),
7885
other => other,
7986
}
8087
}

codama-korok-visitors/src/combine_modules_visitor.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ fn get_scraps_root_node(nodes: Vec<Node>) -> Option<RootNode> {
141141
add_or_replace_node_with_name(&mut root.program.pdas, node);
142142
has_scraps = true
143143
}
144+
Node::Event(node) => {
145+
add_or_replace_node_with_name(&mut root.program.events, node);
146+
has_scraps = true
147+
}
144148
_ => (),
145149
}
146150
}
@@ -210,8 +214,9 @@ fn merge_program_nodes(this: &mut ProgramNode, that: ProgramNode) {
210214
merge_nodes_with_name(&mut this.accounts, that.accounts);
211215
merge_nodes_with_name(&mut this.instructions, that.instructions);
212216
merge_nodes_with_name(&mut this.defined_types, that.defined_types);
213-
merge_nodes_with_name(&mut this.errors, that.errors);
214217
merge_nodes_with_name(&mut this.pdas, that.pdas);
218+
merge_nodes_with_name(&mut this.events, that.events);
219+
merge_nodes_with_name(&mut this.errors, that.errors);
215220
}
216221

217222
fn merge_nodes_with_name<T>(nodes: &mut Vec<T>, new_nodes: Vec<T>)

codama-korok-visitors/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod set_accounts_visitor;
1010
mod set_default_values_visitor;
1111
mod set_defined_types_visitor;
1212
mod set_errors_visitor;
13+
mod set_events_visitor;
1314
mod set_instructions_visitors;
1415
mod set_pdas_visitor;
1516
mod set_program_metadata_visitor;
@@ -29,6 +30,7 @@ pub use set_accounts_visitor::*;
2930
pub use set_default_values_visitor::*;
3031
pub use set_defined_types_visitor::*;
3132
pub use set_errors_visitor::*;
33+
pub use set_events_visitor::*;
3234
pub use set_instructions_visitors::*;
3335
pub use set_pdas_visitor::*;
3436
pub use set_program_metadata_visitor::*;
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
use crate::{CombineTypesVisitor, KorokVisitor};
2+
use codama_attributes::{
3+
DiscriminatorDirective, EnumDiscriminatorDirective, ProgramDirective, TryFromFilter,
4+
};
5+
use codama_errors::CodamaResult;
6+
use codama_nodes::{
7+
CamelCaseString, DefaultValueStrategy, EnumVariantTypeNode, EventNode, FieldDiscriminatorNode,
8+
NestedTypeNode, NestedTypeNodeTrait, Node, NumberValueNode, ProgramNode, StructFieldTypeNode,
9+
StructTypeNode,
10+
};
11+
use codama_syn_helpers::extensions::*;
12+
13+
pub struct SetEventsVisitor {
14+
combine_types: CombineTypesVisitor,
15+
enum_name: String,
16+
enum_discriminator: EnumDiscriminatorDirective,
17+
enum_current_discriminator: usize,
18+
}
19+
20+
impl Default for SetEventsVisitor {
21+
fn default() -> Self {
22+
Self {
23+
combine_types: CombineTypesVisitor::strict(),
24+
enum_name: "".to_string(),
25+
enum_discriminator: EnumDiscriminatorDirective::default(),
26+
enum_current_discriminator: 0,
27+
}
28+
}
29+
}
30+
31+
impl SetEventsVisitor {
32+
pub fn new() -> Self {
33+
Self::default()
34+
}
35+
}
36+
37+
impl KorokVisitor for SetEventsVisitor {
38+
fn visit_struct(&mut self, korok: &mut codama_koroks::StructKorok) -> CodamaResult<()> {
39+
// No overrides.
40+
if korok.node.is_some() {
41+
return Ok(());
42+
};
43+
44+
// Ensure the struct has the `CodamaEvent` attribute.
45+
if !korok.attributes.has_codama_derive("CodamaEvent") {
46+
return Ok(());
47+
};
48+
49+
// Create a `DefinedTypeNode` from the struct, if it doesn't already exist.
50+
self.combine_types.visit_struct(korok)?;
51+
52+
// Transform the defined type into an event node.
53+
let (name, data) = parse_struct(korok)?;
54+
55+
let event = EventNode {
56+
discriminators: DiscriminatorDirective::nodes(&korok.attributes),
57+
..EventNode::new(name, data)
58+
};
59+
60+
korok.node = Some(ProgramDirective::apply(&korok.attributes, event.into()));
61+
62+
Ok(())
63+
}
64+
65+
fn visit_enum(&mut self, korok: &mut codama_koroks::EnumKorok) -> CodamaResult<()> {
66+
// No overrides.
67+
if korok.node.is_some() {
68+
return Ok(());
69+
};
70+
71+
// Ensure the enum has the `CodamaEvents` attribute.
72+
if !korok.attributes.has_codama_derive("CodamaEvents") {
73+
return Ok(());
74+
};
75+
76+
// Create a `DefinedTypeNode` from the enum.
77+
self.combine_types.visit_enum(korok)?;
78+
79+
// Get enum discriminator info.
80+
let enum_discriminator = korok
81+
.attributes
82+
.get_last(EnumDiscriminatorDirective::filter)
83+
.cloned()
84+
.unwrap_or_else(|| EnumDiscriminatorDirective::from(&korok.node));
85+
86+
// Transform each variant into an `EventNode`.
87+
self.enum_name = korok.ast.ident.to_string();
88+
self.enum_discriminator = enum_discriminator;
89+
90+
self.enum_current_discriminator = 0;
91+
self.visit_children(korok)?;
92+
self.enum_current_discriminator = 0;
93+
94+
// Gather all events in the variants.
95+
let events = korok
96+
.variants
97+
.iter()
98+
.filter_map(|variant| match &variant.node {
99+
Some(Node::Event(event)) => Some(event.clone()),
100+
_ => None,
101+
})
102+
.collect::<Vec<_>>();
103+
104+
let node: Node = ProgramNode {
105+
events,
106+
..ProgramNode::default()
107+
}
108+
.into();
109+
110+
korok.node = Some(ProgramDirective::apply(&korok.attributes, node));
111+
112+
Ok(())
113+
}
114+
115+
fn visit_enum_variant(
116+
&mut self,
117+
korok: &mut codama_koroks::EnumVariantKorok,
118+
) -> CodamaResult<()> {
119+
// Update current discriminator.
120+
let current_discriminator = match &korok.ast.discriminant {
121+
Some((_, expr)) => expr.as_unsigned_integer()?,
122+
_ => self.enum_current_discriminator,
123+
};
124+
125+
self.enum_current_discriminator = current_discriminator + 1;
126+
127+
// Skip variants with #[codama(skip)] directive.
128+
if korok.attributes.has_codama_attribute("skip") {
129+
return Ok(());
130+
};
131+
132+
let discriminator = StructFieldTypeNode {
133+
default_value_strategy: Some(DefaultValueStrategy::Omitted),
134+
default_value: Some(NumberValueNode::new(current_discriminator as u64).into()),
135+
..StructFieldTypeNode::from(&self.enum_discriminator)
136+
};
137+
138+
let discriminator_name = discriminator.name.clone();
139+
140+
let (name, data) = parse_enum_variant(korok, &self.enum_name)?;
141+
142+
let data = data.map_nested_type_node(|node| {
143+
let mut fields = node.fields;
144+
145+
fields.insert(0, discriminator);
146+
147+
StructTypeNode { fields }
148+
});
149+
150+
let mut discriminators = DiscriminatorDirective::nodes(&korok.attributes);
151+
152+
discriminators.insert(0, FieldDiscriminatorNode::new(discriminator_name, 0).into());
153+
154+
let event = EventNode {
155+
discriminators,
156+
..EventNode::new(name, data)
157+
};
158+
159+
korok.node = Some(event.into());
160+
161+
Ok(())
162+
}
163+
}
164+
165+
fn parse_struct(
166+
korok: &codama_koroks::StructKorok,
167+
) -> CodamaResult<(CamelCaseString, NestedTypeNode<StructTypeNode>)> {
168+
// Ensure we have a `DefinedTypeNode` to work with.
169+
if let Some(Node::DefinedType(node)) = &korok.node {
170+
// Ensure the data type is a struct.
171+
if let Ok(data) = NestedTypeNode::<StructTypeNode>::try_from(node.r#type.clone()) {
172+
return Ok((node.name.clone(), data));
173+
};
174+
};
175+
176+
// Handle error.
177+
let message = format!(
178+
"The \"{}\" struct could not be used as an Event because its type is not a `NestedTypeNode<StructTypeNode>`.",
179+
korok.ast.ident,
180+
);
181+
182+
Err(korok.ast.error(message).into())
183+
}
184+
185+
fn parse_enum_variant(
186+
korok: &codama_koroks::EnumVariantKorok,
187+
enum_name: &str,
188+
) -> CodamaResult<(CamelCaseString, NestedTypeNode<StructTypeNode>)> {
189+
// Ensure we have a `Node`.
190+
if let Some(node) = &korok.node {
191+
// Ensure we have a `EnumVariantTypeNode`.
192+
if let Ok(node) = EnumVariantTypeNode::try_from(node.clone()) {
193+
match node {
194+
// Ensure we have a non-nested `StructTypeNode`.
195+
EnumVariantTypeNode::Struct(node) => {
196+
return Ok((node.name, node.r#struct));
197+
}
198+
// Or an empty variant.
199+
EnumVariantTypeNode::Empty(node) => {
200+
return Ok((node.name, StructTypeNode::new(vec![]).into()))
201+
}
202+
// Or a tuple variant — convert items to struct fields.
203+
// Use field.name() which returns #[codama(name = "...")] if provided,
204+
// otherwise fall back to synthetic names like "arg0", "arg1".
205+
EnumVariantTypeNode::Tuple(node) => {
206+
if let NestedTypeNode::Value(tuple) = node.tuple {
207+
let fields = tuple
208+
.items
209+
.into_iter()
210+
.enumerate()
211+
.map(|(i, item)| {
212+
let name = korok
213+
.fields
214+
.get(i)
215+
.and_then(|f| f.name())
216+
.unwrap_or_else(|| format!("arg{}", i).into());
217+
218+
StructFieldTypeNode::new(name, item)
219+
})
220+
.collect();
221+
222+
return Ok((node.name, StructTypeNode::new(fields).into()));
223+
};
224+
}
225+
}
226+
};
227+
};
228+
229+
// Handle error.
230+
let message = format!(
231+
"The \"{}\" variant of the \"{enum_name}\" enum could not be used as an Event because we cannot get a `StructTypeNode` for it. This is likely because it is not using named fields.",
232+
korok.ast.ident
233+
);
234+
235+
Err(korok.ast.error(message).into())
236+
}

0 commit comments

Comments
 (0)