1pub mod contracts;
16pub mod enums;
17pub mod events;
18pub mod expressions;
19pub mod functions;
20pub mod source_units;
21pub mod statements;
22pub mod types;
23pub mod variables;
24#[cfg(test)]
25pub mod visitor;
26pub mod yul;
27
28pub use contracts::*;
30pub use enums::*;
31pub use events::*;
32pub use expressions::*;
33pub use functions::*;
34pub use source_units::*;
35pub use statements::*;
36pub use types::*;
37pub use variables::*;
38#[cfg(test)]
39pub use visitor::*;
40pub use yul::*;
41
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44
45#[derive(Clone, Debug)]
56pub enum DeclNode {
57 FunctionDefinition(FunctionDefinition),
58 VariableDeclaration(VariableDeclaration),
59 ContractDefinition(ContractDefinition),
60 EventDefinition(EventDefinition),
61 ErrorDefinition(ErrorDefinition),
62 StructDefinition(StructDefinition),
63 EnumDefinition(EnumDefinition),
64 ModifierDefinition(ModifierDefinition),
65 UserDefinedValueTypeDefinition(UserDefinedValueTypeDefinition),
66}
67
68impl DeclNode {
69 pub fn id(&self) -> NodeID {
71 match self {
72 Self::FunctionDefinition(n) => n.id,
73 Self::VariableDeclaration(n) => n.id,
74 Self::ContractDefinition(n) => n.id,
75 Self::EventDefinition(n) => n.id,
76 Self::ErrorDefinition(n) => n.id,
77 Self::StructDefinition(n) => n.id,
78 Self::EnumDefinition(n) => n.id,
79 Self::ModifierDefinition(n) => n.id,
80 Self::UserDefinedValueTypeDefinition(n) => n.id,
81 }
82 }
83
84 pub fn name(&self) -> &str {
86 match self {
87 Self::FunctionDefinition(n) => &n.name,
88 Self::VariableDeclaration(n) => &n.name,
89 Self::ContractDefinition(n) => &n.name,
90 Self::EventDefinition(n) => &n.name,
91 Self::ErrorDefinition(n) => &n.name,
92 Self::StructDefinition(n) => &n.name,
93 Self::EnumDefinition(n) => &n.name,
94 Self::ModifierDefinition(n) => &n.name,
95 Self::UserDefinedValueTypeDefinition(n) => &n.name,
96 }
97 }
98
99 pub fn src(&self) -> &str {
101 match self {
102 Self::FunctionDefinition(n) => &n.src,
103 Self::VariableDeclaration(n) => &n.src,
104 Self::ContractDefinition(n) => &n.src,
105 Self::EventDefinition(n) => &n.src,
106 Self::ErrorDefinition(n) => &n.src,
107 Self::StructDefinition(n) => &n.src,
108 Self::EnumDefinition(n) => &n.src,
109 Self::ModifierDefinition(n) => &n.src,
110 Self::UserDefinedValueTypeDefinition(n) => &n.src,
111 }
112 }
113
114 pub fn scope(&self) -> Option<NodeID> {
116 match self {
117 Self::FunctionDefinition(n) => n.scope,
118 Self::VariableDeclaration(n) => n.scope,
119 Self::ContractDefinition(n) => n.scope,
120 Self::EventDefinition(_) => None,
121 Self::ErrorDefinition(_) => None,
122 Self::StructDefinition(n) => n.scope,
123 Self::EnumDefinition(_) => None,
124 Self::ModifierDefinition(_) => None,
125 Self::UserDefinedValueTypeDefinition(_) => None,
126 }
127 }
128
129 pub fn documentation(&self) -> Option<&Documentation> {
131 match self {
132 Self::FunctionDefinition(n) => n.documentation.as_ref(),
133 Self::VariableDeclaration(n) => n.documentation.as_ref(),
134 Self::ContractDefinition(n) => n.documentation.as_ref(),
135 Self::EventDefinition(n) => n.documentation.as_ref(),
136 Self::ErrorDefinition(n) => n.documentation.as_ref(),
137 Self::StructDefinition(n) => n.documentation.as_ref(),
138 Self::EnumDefinition(n) => n.documentation.as_ref(),
139 Self::ModifierDefinition(n) => n.documentation.as_ref(),
140 Self::UserDefinedValueTypeDefinition(_) => None,
141 }
142 }
143
144 pub fn selector(&self) -> Option<&str> {
146 match self {
147 Self::FunctionDefinition(n) => n.function_selector.as_deref(),
148 Self::VariableDeclaration(n) => n.function_selector.as_deref(),
149 Self::EventDefinition(n) => n.event_selector.as_deref(),
150 Self::ErrorDefinition(n) => n.error_selector.as_deref(),
151 _ => None,
152 }
153 }
154
155 pub fn extract_typed_selector(&self) -> Option<crate::types::Selector> {
160 match self {
161 Self::FunctionDefinition(n) => n
162 .function_selector
163 .as_deref()
164 .map(|s| crate::types::Selector::Func(crate::types::FuncSelector::new(s))),
165 Self::VariableDeclaration(n) => n
166 .function_selector
167 .as_deref()
168 .map(|s| crate::types::Selector::Func(crate::types::FuncSelector::new(s))),
169 Self::ErrorDefinition(n) => n
170 .error_selector
171 .as_deref()
172 .map(|s| crate::types::Selector::Func(crate::types::FuncSelector::new(s))),
173 Self::EventDefinition(n) => n
174 .event_selector
175 .as_deref()
176 .map(|s| crate::types::Selector::Event(crate::types::EventSelector::new(s))),
177 _ => None,
178 }
179 }
180
181 pub fn extract_doc_text(&self) -> Option<String> {
185 self.documentation().map(|doc| match doc {
186 Documentation::String(s) => s.clone(),
187 Documentation::Structured(s) => s.text.clone(),
188 })
189 }
190
191 pub fn build_signature(&self) -> Option<String> {
196 match self {
197 Self::FunctionDefinition(n) => {
198 let params = format_params_typed(&n.parameters);
199 let returns = format_params_typed(&n.return_parameters);
200
201 let mut sig = match n.kind {
202 FunctionKind::Constructor => format!("constructor({params})"),
203 FunctionKind::Receive => "receive() external payable".to_string(),
204 FunctionKind::Fallback => format!("fallback({params})"),
205 _ => format!("function {}({params})", n.name),
206 };
207
208 if !matches!(n.kind, FunctionKind::Constructor | FunctionKind::Receive)
210 && let Some(vis) = &n.visibility
211 {
212 let vis_str = vis.to_string();
213 if !vis_str.is_empty() {
214 sig.push_str(&format!(" {vis_str}"));
215 }
216 }
217
218 if !matches!(n.state_mutability, StateMutability::Nonpayable) {
220 sig.push_str(&format!(" {}", n.state_mutability));
221 }
222
223 if !returns.is_empty() {
224 sig.push_str(&format!(" returns ({returns})"));
225 }
226 Some(sig)
227 }
228 Self::ModifierDefinition(n) => {
229 let params = format_params_typed(&n.parameters);
230 Some(format!("modifier {}({params})", n.name))
231 }
232 Self::EventDefinition(n) => {
233 let params = format_params_typed(&n.parameters);
234 Some(format!("event {}({params})", n.name))
235 }
236 Self::ErrorDefinition(n) => {
237 let params = format_params_typed(&n.parameters);
238 Some(format!("error {}({params})", n.name))
239 }
240 Self::VariableDeclaration(n) => {
241 let type_str = n
242 .type_descriptions
243 .type_string
244 .as_deref()
245 .unwrap_or("unknown");
246 let vis = n
247 .visibility
248 .as_ref()
249 .map(|v| v.to_string())
250 .unwrap_or_default();
251
252 let mut sig = type_str.to_string();
253 if !vis.is_empty() {
254 sig.push_str(&format!(" {vis}"));
255 }
256 match &n.mutability {
257 Some(Mutability::Constant) => sig.push_str(" constant"),
258 Some(Mutability::Immutable) => sig.push_str(" immutable"),
259 _ => {}
260 }
261 sig.push_str(&format!(" {}", n.name));
262 Some(sig)
263 }
264 Self::ContractDefinition(n) => {
265 let mut sig = format!("{} {}", n.contract_kind, n.name);
266
267 if !n.base_contracts.is_empty() {
268 let base_names: Vec<&str> = n
269 .base_contracts
270 .iter()
271 .map(|b| b.base_name.name.as_str())
272 .collect();
273 if !base_names.is_empty() {
274 sig.push_str(&format!(" is {}", base_names.join(", ")));
275 }
276 }
277 Some(sig)
278 }
279 Self::StructDefinition(n) => {
280 let mut sig = format!("struct {} {{\n", n.name);
281 for member in &n.members {
282 let mtype = member
283 .type_descriptions
284 .type_string
285 .as_deref()
286 .unwrap_or("?");
287 sig.push_str(&format!(" {mtype} {};\n", member.name));
288 }
289 sig.push('}');
290 Some(sig)
291 }
292 Self::EnumDefinition(n) => {
293 let mut sig = format!("enum {} {{\n", n.name);
294 for member in &n.members {
295 sig.push_str(&format!(" {},\n", member.name));
296 }
297 sig.push('}');
298 Some(sig)
299 }
300 Self::UserDefinedValueTypeDefinition(n) => {
301 let underlying = type_name_to_str(&n.underlying_type);
302 Some(format!("type {} is {underlying}", n.name))
303 }
304 }
305 }
306
307 pub fn param_strings(&self) -> Vec<String> {
312 match self {
313 Self::FunctionDefinition(n) => build_param_strings_typed(&n.parameters),
314 Self::ModifierDefinition(n) => build_param_strings_typed(&n.parameters),
315 Self::EventDefinition(n) => build_param_strings_typed(&n.parameters),
316 Self::ErrorDefinition(n) => build_param_strings_typed(&n.parameters),
317 _ => Vec::new(),
318 }
319 }
320
321 pub fn parameters(&self) -> Option<&ParameterList> {
323 match self {
324 Self::FunctionDefinition(n) => Some(&n.parameters),
325 Self::ModifierDefinition(n) => Some(&n.parameters),
326 Self::EventDefinition(n) => Some(&n.parameters),
327 Self::ErrorDefinition(n) => Some(&n.parameters),
328 _ => None,
329 }
330 }
331
332 pub fn return_parameters(&self) -> Option<&ParameterList> {
334 match self {
335 Self::FunctionDefinition(n) => Some(&n.return_parameters),
336 _ => None,
337 }
338 }
339
340 pub fn node_type(&self) -> &'static str {
342 match self {
343 Self::FunctionDefinition(_) => "FunctionDefinition",
344 Self::VariableDeclaration(_) => "VariableDeclaration",
345 Self::ContractDefinition(_) => "ContractDefinition",
346 Self::EventDefinition(_) => "EventDefinition",
347 Self::ErrorDefinition(_) => "ErrorDefinition",
348 Self::StructDefinition(_) => "StructDefinition",
349 Self::EnumDefinition(_) => "EnumDefinition",
350 Self::ModifierDefinition(_) => "ModifierDefinition",
351 Self::UserDefinedValueTypeDefinition(_) => "UserDefinedValueTypeDefinition",
352 }
353 }
354
355 pub fn type_string(&self) -> Option<&str> {
357 match self {
358 Self::VariableDeclaration(n) => n.type_descriptions.type_string.as_deref(),
359 _ => None,
360 }
361 }
362
363 pub fn param_names(&self) -> Option<Vec<String>> {
369 match self {
370 Self::FunctionDefinition(n) => Some(
371 n.parameters
372 .parameters
373 .iter()
374 .map(|p| p.name.clone())
375 .collect(),
376 ),
377 Self::ModifierDefinition(n) => Some(
378 n.parameters
379 .parameters
380 .iter()
381 .map(|p| p.name.clone())
382 .collect(),
383 ),
384 Self::EventDefinition(n) => Some(
385 n.parameters
386 .parameters
387 .iter()
388 .map(|p| p.name.clone())
389 .collect(),
390 ),
391 Self::ErrorDefinition(n) => Some(
392 n.parameters
393 .parameters
394 .iter()
395 .map(|p| p.name.clone())
396 .collect(),
397 ),
398 Self::StructDefinition(n) => Some(n.members.iter().map(|m| m.name.clone()).collect()),
399 _ => None,
400 }
401 }
402
403 pub fn is_constructor(&self) -> bool {
405 matches!(self, Self::FunctionDefinition(n) if matches!(n.kind, crate::solc_ast::enums::FunctionKind::Constructor))
406 }
407}
408
409pub fn format_params_typed(params: &ParameterList) -> String {
415 let parts: Vec<String> = params
416 .parameters
417 .iter()
418 .map(|p| {
419 let type_str = p.type_descriptions.type_string.as_deref().unwrap_or("?");
420 let name = &p.name;
421 let storage = p
422 .storage_location
423 .as_ref()
424 .map(|s| s.to_string())
425 .unwrap_or_else(|| "default".to_string());
426
427 if name.is_empty() {
428 type_str.to_string()
429 } else if storage != "default" {
430 format!("{type_str} {storage} {name}")
431 } else {
432 format!("{type_str} {name}")
433 }
434 })
435 .collect();
436
437 parts.join(", ")
438}
439
440fn build_param_strings_typed(params: &ParameterList) -> Vec<String> {
444 params
445 .parameters
446 .iter()
447 .map(|p| {
448 let type_str = p.type_descriptions.type_string.as_deref().unwrap_or("?");
449 let name = &p.name;
450 let storage = p
451 .storage_location
452 .as_ref()
453 .map(|s| s.to_string())
454 .unwrap_or_else(|| "default".to_string());
455
456 if name.is_empty() {
457 type_str.to_string()
458 } else if storage != "default" {
459 format!("{type_str} {storage} {name}")
460 } else {
461 format!("{type_str} {name}")
462 }
463 })
464 .collect()
465}
466
467pub fn type_name_to_str(tn: &TypeName) -> &str {
469 match tn {
470 TypeName::ElementaryTypeName(e) => e
471 .type_descriptions
472 .type_string
473 .as_deref()
474 .unwrap_or(&e.name),
475 TypeName::UserDefinedTypeName(u) => u
476 .type_descriptions
477 .type_string
478 .as_deref()
479 .unwrap_or("unknown"),
480 TypeName::FunctionTypeName(f) => f
481 .type_descriptions
482 .type_string
483 .as_deref()
484 .unwrap_or("function"),
485 TypeName::Mapping(m) => m
486 .type_descriptions
487 .type_string
488 .as_deref()
489 .unwrap_or("mapping"),
490 TypeName::ArrayTypeName(a) => a
491 .type_descriptions
492 .type_string
493 .as_deref()
494 .unwrap_or("array"),
495 }
496}
497
498const DECL_NODE_TYPES: &[&str] = &[
502 "FunctionDefinition",
503 "VariableDeclaration",
504 "ContractDefinition",
505 "EventDefinition",
506 "ErrorDefinition",
507 "StructDefinition",
508 "EnumDefinition",
509 "ModifierDefinition",
510 "UserDefinedValueTypeDefinition",
511];
512
513const STRIP_FIELDS: &[&str] = &[
517 "body",
518 "modifiers",
519 "value",
520 "overrides",
521 "baseFunctions",
522 "baseModifiers",
523 "nameLocation",
524 "implemented",
525 "isVirtual",
526 "abstract",
527 "contractDependencies",
528 "usedErrors",
529 "usedEvents",
530 "fullyImplemented",
531 "linearizedBaseContracts",
532 "canonicalName",
533 "constant",
534 "indexed",
535];
536
537const STRIP_CHILD_FIELDS: &[&str] = &[
540 "body",
541 "modifiers",
542 "value",
543 "overrides",
544 "baseFunctions",
545 "baseModifiers",
546 "nameLocation",
547 "implemented",
548 "isVirtual",
549 "constant",
550 "indexed",
551 "canonicalName",
552];
553
554pub struct ExtractedDecls {
556 pub decl_index: HashMap<NodeID, DeclNode>,
558 pub node_id_to_source_path: HashMap<NodeID, String>,
560}
561
562pub fn extract_decl_nodes(sources: &serde_json::Value) -> Option<ExtractedDecls> {
580 let sources_obj = sources.as_object()?;
581 let source_count = sources_obj.len();
584 let mut decl_index = HashMap::with_capacity(source_count * 32);
585 let mut id_to_path = HashMap::with_capacity(source_count * 32);
586
587 for (path, source_data) in sources_obj {
588 let ast_node = source_data.get("ast")?;
589
590 if let Some(su_id) = ast_node.get("id").and_then(|v| v.as_i64()) {
592 id_to_path.insert(su_id, path.clone());
593 }
594
595 if let Some(nodes) = ast_node.get("nodes").and_then(|v| v.as_array()) {
597 for node in nodes {
598 walk_and_extract(node, path, &mut decl_index, &mut id_to_path);
599 }
600 }
601 }
602
603 Some(ExtractedDecls {
604 decl_index,
605 node_id_to_source_path: id_to_path,
606 })
607}
608
609fn walk_and_extract(
611 node: &serde_json::Value,
612 source_path: &str,
613 decl_index: &mut HashMap<NodeID, DeclNode>,
614 id_to_path: &mut HashMap<NodeID, String>,
615) {
616 let obj = match node.as_object() {
617 Some(o) => o,
618 None => return,
619 };
620
621 let node_type = match obj.get("nodeType").and_then(|v| v.as_str()) {
622 Some(nt) => nt,
623 None => return,
624 };
625
626 let node_id = obj.get("id").and_then(|v| v.as_i64());
627
628 if let Some(id) = node_id {
630 id_to_path.insert(id, source_path.to_string());
631 }
632
633 if DECL_NODE_TYPES.contains(&node_type)
635 && let Some(id) = node_id
636 {
637 let node_value = if node_type == "ContractDefinition" {
641 build_filtered_contract(obj)
642 } else {
643 build_filtered_decl(obj)
644 };
645
646 if let Some(decl) = deserialize_decl_node(node_type, node_value) {
648 decl_index.insert(id, decl);
649 }
650 }
651
652 if let Some(children) = obj.get("nodes").and_then(|v| v.as_array()) {
657 for child in children {
658 walk_and_extract(child, source_path, decl_index, id_to_path);
659 }
660 }
661
662 for param_key in &["parameters", "returnParameters"] {
665 if let Some(param_list) = obj.get(*param_key).and_then(|v| v.as_object()) {
666 if let Some(pl_id) = param_list.get("id").and_then(|v| v.as_i64()) {
668 id_to_path.insert(pl_id, source_path.to_string());
669 }
670 if let Some(params) = param_list.get("parameters").and_then(|v| v.as_array()) {
671 for param in params {
672 walk_and_extract(param, source_path, decl_index, id_to_path);
673 }
674 }
675 }
676 }
677
678 if let Some(members) = obj.get("members").and_then(|v| v.as_array()) {
680 for member in members {
681 walk_and_extract(member, source_path, decl_index, id_to_path);
682 }
683 }
684}
685
686fn build_filtered_decl(obj: &serde_json::Map<String, serde_json::Value>) -> serde_json::Value {
691 let mut filtered = serde_json::Map::with_capacity(obj.len());
692 for (key, value) in obj {
693 if !STRIP_FIELDS.contains(&key.as_str()) {
694 filtered.insert(key.clone(), value.clone());
695 }
696 }
697 serde_json::Value::Object(filtered)
698}
699
700fn build_filtered_contract(obj: &serde_json::Map<String, serde_json::Value>) -> serde_json::Value {
706 let mut filtered = serde_json::Map::with_capacity(obj.len());
707 for (key, value) in obj {
708 if STRIP_FIELDS.contains(&key.as_str()) {
709 continue;
710 }
711 if key == "nodes" {
712 if let Some(arr) = value.as_array() {
714 let filtered_nodes: Vec<serde_json::Value> = arr
715 .iter()
716 .map(|child| {
717 if let Some(child_obj) = child.as_object() {
718 let mut filtered_child =
719 serde_json::Map::with_capacity(child_obj.len());
720 for (ck, cv) in child_obj {
721 if !STRIP_CHILD_FIELDS.contains(&ck.as_str()) {
722 filtered_child.insert(ck.clone(), cv.clone());
723 }
724 }
725 serde_json::Value::Object(filtered_child)
726 } else {
727 child.clone()
728 }
729 })
730 .collect();
731 filtered.insert(key.clone(), serde_json::Value::Array(filtered_nodes));
732 } else {
733 filtered.insert(key.clone(), value.clone());
734 }
735 } else {
736 filtered.insert(key.clone(), value.clone());
737 }
738 }
739 serde_json::Value::Object(filtered)
740}
741
742fn deserialize_decl_node(node_type: &str, value: serde_json::Value) -> Option<DeclNode> {
744 match node_type {
745 "FunctionDefinition" => serde_json::from_value::<FunctionDefinition>(value)
746 .ok()
747 .map(DeclNode::FunctionDefinition),
748 "VariableDeclaration" => serde_json::from_value::<VariableDeclaration>(value)
749 .ok()
750 .map(DeclNode::VariableDeclaration),
751 "ContractDefinition" => serde_json::from_value::<ContractDefinition>(value)
752 .ok()
753 .map(DeclNode::ContractDefinition),
754 "EventDefinition" => serde_json::from_value::<EventDefinition>(value)
755 .ok()
756 .map(DeclNode::EventDefinition),
757 "ErrorDefinition" => serde_json::from_value::<ErrorDefinition>(value)
758 .ok()
759 .map(DeclNode::ErrorDefinition),
760 "StructDefinition" => serde_json::from_value::<StructDefinition>(value)
761 .ok()
762 .map(DeclNode::StructDefinition),
763 "EnumDefinition" => serde_json::from_value::<EnumDefinition>(value)
764 .ok()
765 .map(DeclNode::EnumDefinition),
766 "ModifierDefinition" => serde_json::from_value::<ModifierDefinition>(value)
767 .ok()
768 .map(DeclNode::ModifierDefinition),
769 "UserDefinedValueTypeDefinition" => {
770 serde_json::from_value::<UserDefinedValueTypeDefinition>(value)
771 .ok()
772 .map(DeclNode::UserDefinedValueTypeDefinition)
773 }
774 _ => None,
775 }
776}
777
778pub type NodeID = i64;
782
783#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
787#[serde(rename_all = "camelCase")]
788pub struct TypeDescriptions {
789 pub type_string: Option<String>,
791 pub type_identifier: Option<String>,
793}
794
795#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
799#[serde(untagged)]
800pub enum Documentation {
801 String(String),
803 Structured(StructuredDocumentation),
805}
806
807#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
809#[serde(rename_all = "camelCase")]
810pub struct StructuredDocumentation {
811 pub id: NodeID,
812 pub src: String,
813 pub text: String,
814}
815
816#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
820#[serde(rename_all = "camelCase")]
821pub struct ExternalReference {
822 pub declaration: NodeID,
823 #[serde(default)]
824 pub is_offset: bool,
825 #[serde(default)]
826 pub is_slot: bool,
827 pub src: String,
828 #[serde(default)]
829 pub suffix: Option<String>,
830 pub value_size: Option<i64>,
831}
832
833#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
838#[serde(rename_all = "camelCase")]
839pub struct UsingForFunction {
840 pub function: Option<IdentifierPath>,
842 pub definition: Option<IdentifierPath>,
844 pub operator: Option<String>,
846}
847
848#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
850#[serde(rename_all = "camelCase")]
851pub struct IdentifierPath {
852 pub id: NodeID,
853 pub name: String,
854 #[serde(default)]
855 pub name_locations: Vec<String>,
856 pub referenced_declaration: Option<NodeID>,
857 pub src: String,
858}
859
860#[cfg(test)]
863mod tests {
864 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
868 #[serde(rename_all = "camelCase")]
869 pub struct SolcOutput {
870 #[serde(default)]
871 pub sources: std::collections::HashMap<String, SourceEntry>,
872 #[serde(default)]
873 pub source_id_to_path: std::collections::HashMap<String, String>,
874 }
875
876 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
878 pub struct SourceEntry {
879 pub id: i64,
880 pub ast: super::SourceUnit,
881 }
882 use super::*;
883 use std::path::Path;
884
885 fn load_fixture() -> SolcOutput {
887 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
888 let json = std::fs::read_to_string(&path)
889 .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
890 serde_json::from_str(&json)
891 .unwrap_or_else(|e| panic!("failed to deserialize poolmanager.json: {e}"))
892 }
893
894 #[test]
895 fn deserialize_poolmanager() {
896 let output = load_fixture();
897
898 assert_eq!(
900 output.sources.len(),
901 45,
902 "expected 45 source files in poolmanager.json"
903 );
904
905 for (path, entry) in &output.sources {
907 assert!(entry.ast.id > 0, "bad AST id for {path}");
908 assert!(!entry.ast.src.is_empty(), "empty src for {path}");
909 }
910 }
911
912 #[test]
913 fn deserialize_all_source_units() {
914 let output = load_fixture();
915
916 let mut contract_count = 0;
919 for (_path, entry) in output.sources {
920 for node in &entry.ast.nodes {
921 if matches!(node, SourceUnitNode::ContractDefinition(_)) {
922 contract_count += 1;
923 }
924 }
925 }
926 assert_eq!(
927 contract_count, 43,
928 "expected 43 top-level ContractDefinitions"
929 );
930 }
931
932 struct NodeCounter {
936 functions: usize,
937 contracts: usize,
938 events: usize,
939 errors: usize,
940 variables: usize,
941 total_nodes: usize,
942 }
943
944 impl NodeCounter {
945 fn new() -> Self {
946 Self {
947 functions: 0,
948 contracts: 0,
949 events: 0,
950 errors: 0,
951 variables: 0,
952 total_nodes: 0,
953 }
954 }
955 }
956
957 impl AstVisitor for NodeCounter {
958 fn visit_node(&mut self, _id: NodeID, _src: &str) -> bool {
959 self.total_nodes += 1;
960 true
961 }
962
963 fn visit_function_definition(&mut self, node: &FunctionDefinition) -> bool {
964 self.functions += 1;
965 self.visit_node(node.id, &node.src)
966 }
967
968 fn visit_contract_definition(&mut self, node: &ContractDefinition) -> bool {
969 self.contracts += 1;
970 self.visit_node(node.id, &node.src)
971 }
972
973 fn visit_event_definition(&mut self, node: &EventDefinition) -> bool {
974 self.events += 1;
975 self.visit_node(node.id, &node.src)
976 }
977
978 fn visit_error_definition(&mut self, node: &ErrorDefinition) -> bool {
979 self.errors += 1;
980 self.visit_node(node.id, &node.src)
981 }
982
983 fn visit_variable_declaration(&mut self, node: &VariableDeclaration) -> bool {
984 self.variables += 1;
985 self.visit_node(node.id, &node.src)
986 }
987 }
988
989 #[test]
990 fn visitor_counts_nodes() {
991 let output = load_fixture();
992 let mut counter = NodeCounter::new();
993
994 for (_path, entry) in output.sources {
995 entry.ast.accept(&mut counter);
996 }
997
998 assert_eq!(counter.contracts, 43, "expected 43 ContractDefinitions");
999 assert_eq!(counter.functions, 215, "expected 215 FunctionDefinitions");
1000 assert_eq!(counter.events, 12, "expected 12 EventDefinitions");
1001 assert_eq!(counter.errors, 42, "expected 42 ErrorDefinitions");
1002 assert!(
1003 counter.variables > 0,
1004 "expected at least some VariableDeclarations"
1005 );
1006 assert!(
1007 counter.total_nodes > 0,
1008 "expected total_nodes to be non-zero"
1009 );
1010
1011 let specific_sum = counter.contracts + counter.functions + counter.events + counter.errors;
1013 assert!(
1014 counter.total_nodes > specific_sum,
1015 "total_nodes ({}) should be > sum of specific types ({specific_sum})",
1016 counter.total_nodes
1017 );
1018 }
1019
1020 #[test]
1021 fn cached_build_populates_decl_index() {
1022 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1023 let json = std::fs::read_to_string(&path).unwrap();
1024 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1025
1026 let build = crate::goto::CachedBuild::new(raw, 0);
1027
1028 assert!(
1029 !build.decl_index.is_empty(),
1030 "decl_index should be populated"
1031 );
1032
1033 let mut funcs = 0;
1035 let mut vars = 0;
1036 let mut contracts = 0;
1037 let mut events = 0;
1038 let mut errors = 0;
1039 for decl in build.decl_index.values() {
1040 match decl {
1041 DeclNode::FunctionDefinition(_) => funcs += 1,
1042 DeclNode::VariableDeclaration(_) => vars += 1,
1043 DeclNode::ContractDefinition(_) => contracts += 1,
1044 DeclNode::EventDefinition(_) => events += 1,
1045 DeclNode::ErrorDefinition(_) => errors += 1,
1046 _ => {}
1047 }
1048 }
1049
1050 assert_eq!(
1051 contracts, 43,
1052 "expected 43 ContractDefinitions in decl_index"
1053 );
1054 assert_eq!(funcs, 215, "expected 215 FunctionDefinitions in decl_index");
1055 assert_eq!(events, 12, "expected 12 EventDefinitions in decl_index");
1056 assert_eq!(errors, 42, "expected 42 ErrorDefinitions in decl_index");
1057 assert!(vars > 0, "expected VariableDeclarations in decl_index");
1058 }
1059
1060 #[test]
1062 fn node_id_to_source_path_covers_decl_index() {
1063 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1064 let json = std::fs::read_to_string(&path).unwrap();
1065 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1066
1067 let build = crate::goto::CachedBuild::new(raw, 0);
1068
1069 assert!(
1070 !build.node_id_to_source_path.is_empty(),
1071 "node_id_to_source_path should be populated"
1072 );
1073
1074 let mut missing = Vec::new();
1076 for (id, decl) in &build.decl_index {
1077 if matches!(decl, DeclNode::VariableDeclaration(v) if v.state_variable != Some(true)) {
1080 continue;
1081 }
1082 if !build.node_id_to_source_path.contains_key(id) {
1083 missing.push(format!(
1084 "id={id} name={:?} type={}",
1085 decl.name(),
1086 decl.node_type()
1087 ));
1088 }
1089 }
1090
1091 assert!(
1092 missing.is_empty(),
1093 "Declarations without source path ({}):\n{}",
1094 missing.len(),
1095 missing.join("\n"),
1096 );
1097 }
1098}