tx3_lang/
analyzing.rs

1//! Semantic analysis of the Tx3 language.
2//!
3//! This module takes an AST and performs semantic analysis on it. It checks for
4//! duplicate definitions, unknown symbols, and other semantic errors.
5
6use std::{collections::HashMap, rc::Rc};
7use thiserror::Error;
8
9use crate::ast::*;
10
11#[derive(Debug, thiserror::Error, miette::Diagnostic)]
12#[error("not in scope: {name}")]
13#[diagnostic(code(tx3::not_in_scope))]
14pub struct NotInScopeError {
15    pub name: String,
16
17    #[source_code]
18    src: Option<String>,
19
20    #[label]
21    span: Span,
22}
23
24#[derive(Debug, thiserror::Error, miette::Diagnostic)]
25#[error("invalid symbol, expected {expected}, got {got}")]
26#[diagnostic(code(tx3::invalid_symbol))]
27pub struct InvalidSymbolError {
28    pub expected: &'static str,
29    pub got: String,
30
31    #[source_code]
32    src: Option<String>,
33
34    #[label]
35    span: Span,
36}
37
38#[derive(Error, Debug, miette::Diagnostic)]
39pub enum Error {
40    #[error("duplicate definition: {0}")]
41    #[diagnostic(code(tx3::duplicate_definition))]
42    DuplicateDefinition(String),
43
44    #[error(transparent)]
45    #[diagnostic(transparent)]
46    NotInScope(#[from] NotInScopeError),
47
48    #[error("needs parent scope")]
49    #[diagnostic(code(tx3::needs_parent_scope))]
50    NeedsParentScope,
51
52    #[error(transparent)]
53    #[diagnostic(transparent)]
54    InvalidSymbol(#[from] InvalidSymbolError),
55}
56
57impl Error {
58    pub fn span(&self) -> &Span {
59        match self {
60            Self::NotInScope(x) => &x.span,
61            Self::InvalidSymbol(x) => &x.span,
62            _ => &Span::DUMMY,
63        }
64    }
65
66    pub fn src(&self) -> Option<&str> {
67        match self {
68            Self::NotInScope(x) => x.src.as_deref(),
69            _ => None,
70        }
71    }
72
73    pub fn not_in_scope(name: String, ast: &impl crate::parsing::AstNode) -> Self {
74        Self::NotInScope(NotInScopeError {
75            name,
76            src: None,
77            span: ast.span().clone(),
78        })
79    }
80
81    pub fn invalid_symbol(
82        expected: &'static str,
83        got: &Symbol,
84        ast: &impl crate::parsing::AstNode,
85    ) -> Self {
86        Self::InvalidSymbol(InvalidSymbolError {
87            expected,
88            got: format!("{:?}", got),
89            src: None,
90            span: ast.span().clone(),
91        })
92    }
93}
94
95#[derive(Debug, Default)]
96pub struct AnalyzeReport {
97    pub errors: Vec<Error>,
98}
99
100impl AnalyzeReport {
101    pub fn is_empty(&self) -> bool {
102        self.errors.is_empty()
103    }
104
105    pub fn ok(self) -> Result<(), Self> {
106        if self.is_empty() {
107            Ok(())
108        } else {
109            Err(self)
110        }
111    }
112}
113
114impl std::error::Error for AnalyzeReport {}
115
116impl std::fmt::Display for AnalyzeReport {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "AnalyzeReport {{ errors: {:?} }}", self.errors)
119    }
120}
121
122impl std::ops::Add for Error {
123    type Output = AnalyzeReport;
124
125    fn add(self, other: Self) -> Self::Output {
126        Self::Output {
127            errors: vec![self, other],
128        }
129    }
130}
131
132impl From<Error> for AnalyzeReport {
133    fn from(error: Error) -> Self {
134        Self {
135            errors: vec![error],
136        }
137    }
138}
139
140impl From<Vec<Error>> for AnalyzeReport {
141    fn from(errors: Vec<Error>) -> Self {
142        Self { errors }
143    }
144}
145
146impl std::ops::Add for AnalyzeReport {
147    type Output = AnalyzeReport;
148
149    fn add(self, other: Self) -> Self::Output {
150        [self, other].into_iter().collect()
151    }
152}
153
154impl FromIterator<Error> for AnalyzeReport {
155    fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
156        Self {
157            errors: iter.into_iter().collect(),
158        }
159    }
160}
161
162impl FromIterator<AnalyzeReport> for AnalyzeReport {
163    fn from_iter<T: IntoIterator<Item = AnalyzeReport>>(iter: T) -> Self {
164        Self {
165            errors: iter.into_iter().flat_map(|r| r.errors).collect(),
166        }
167    }
168}
169
170macro_rules! bail_report {
171    ($($args:expr),*) => {
172        { return AnalyzeReport::from(vec![$($args),*]); }
173    };
174}
175
176impl Scope {
177    pub fn new(parent: Option<Rc<Scope>>) -> Self {
178        Self {
179            symbols: HashMap::new(),
180            parent,
181        }
182    }
183
184    pub fn track_type_def(&mut self, type_: &TypeDef) {
185        self.symbols
186            .insert(type_.name.clone(), Symbol::TypeDef(Box::new(type_.clone())));
187    }
188
189    pub fn track_variant_case(&mut self, case: &VariantCase) {
190        self.symbols.insert(
191            case.name.clone(),
192            Symbol::VariantCase(Box::new(case.clone())),
193        );
194    }
195
196    pub fn track_record_field(&mut self, field: &RecordField) {
197        self.symbols.insert(
198            field.name.clone(),
199            Symbol::RecordField(Box::new(field.clone())),
200        );
201    }
202
203    pub fn track_party_def(&mut self, party: &PartyDef) {
204        self.symbols.insert(
205            party.name.clone(),
206            Symbol::PartyDef(Box::new(party.clone())),
207        );
208    }
209
210    pub fn track_policy_def(&mut self, policy: &PolicyDef) {
211        self.symbols.insert(
212            policy.name.clone(),
213            Symbol::PolicyDef(Box::new(policy.clone())),
214        );
215    }
216
217    pub fn track_asset_def(&mut self, asset: &AssetDef) {
218        self.symbols.insert(
219            asset.name.clone(),
220            Symbol::AssetDef(Box::new(asset.clone())),
221        );
222    }
223
224    pub fn track_param_var(&mut self, param: &str, ty: Type) {
225        self.symbols.insert(
226            param.to_string(),
227            Symbol::ParamVar(param.to_string(), Box::new(ty)),
228        );
229    }
230
231    pub fn track_input(&mut self, name: &str, ty: Type) {
232        self.symbols.insert(
233            name.to_string(),
234            Symbol::Input(name.to_string(), Box::new(ty)),
235        );
236    }
237
238    pub fn track_record_fields_for_type(&mut self, r#type: &Type) {
239        let schema = resolve_type_schema(r#type);
240
241        for (name, r#type) in schema {
242            self.track_record_field(&RecordField {
243                name,
244                r#type,
245                span: Span::DUMMY,
246            });
247        }
248    }
249
250    pub fn resolve(&self, name: &str) -> Option<Symbol> {
251        if let Some(symbol) = self.symbols.get(name) {
252            Some(symbol.clone())
253        } else if let Some(parent) = &self.parent {
254            parent.resolve(name)
255        } else {
256            None
257        }
258    }
259}
260
261fn resolve_type_schema(ty: &Type) -> Vec<(String, Type)> {
262    match ty {
263        Type::AnyAsset => {
264            vec![
265                ("amount".to_string(), Type::Int),
266                ("policy".to_string(), Type::Bytes),
267                ("asset_name".to_string(), Type::Bytes),
268            ]
269        }
270        Type::UtxoRef => {
271            vec![
272                ("tx_hash".to_string(), Type::Bytes),
273                ("output_index".to_string(), Type::Int),
274            ]
275        }
276        Type::Custom(identifier) => {
277            let def = identifier.symbol.as_ref().and_then(|s| s.as_type_def());
278
279            match def {
280                Some(ty) if ty.cases.len() == 1 => ty.cases[0]
281                    .fields
282                    .iter()
283                    .map(|f| (f.name.clone(), f.r#type.clone()))
284                    .collect(),
285                _ => vec![],
286            }
287        }
288        _ => vec![],
289    }
290}
291
292/// A trait for types that can be semantically analyzed.
293///
294/// Types implementing this trait can validate their semantic correctness and
295/// resolve symbol references within a given scope.
296pub trait Analyzable {
297    /// Performs semantic analysis on the type.
298    ///
299    /// # Arguments
300    /// * `parent` - Optional parent scope containing symbol definitions
301    ///
302    /// # Returns
303    /// * `AnalyzeReport` of the analysis. Empty if no errors are found.
304    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport;
305
306    /// Returns true if all of the symbols have been resolved .
307    fn is_resolved(&self) -> bool;
308}
309
310impl<T: Analyzable> Analyzable for Option<T> {
311    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
312        if let Some(item) = self {
313            item.analyze(parent)
314        } else {
315            AnalyzeReport::default()
316        }
317    }
318
319    fn is_resolved(&self) -> bool {
320        self.as_ref().map_or(true, |x| x.is_resolved())
321    }
322}
323
324impl<T: Analyzable> Analyzable for Box<T> {
325    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
326        self.as_mut().analyze(parent)
327    }
328
329    fn is_resolved(&self) -> bool {
330        self.as_ref().is_resolved()
331    }
332}
333
334impl<T: Analyzable> Analyzable for Vec<T> {
335    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
336        self.iter_mut()
337            .map(|item| item.analyze(parent.clone()))
338            .collect()
339    }
340
341    fn is_resolved(&self) -> bool {
342        self.iter().all(|x| x.is_resolved())
343    }
344}
345
346impl Analyzable for PolicyField {
347    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
348        match self {
349            PolicyField::Hash(x) => x.analyze(parent),
350            PolicyField::Script(x) => x.analyze(parent),
351            PolicyField::Ref(x) => x.analyze(parent),
352        }
353    }
354
355    fn is_resolved(&self) -> bool {
356        match self {
357            PolicyField::Hash(x) => x.is_resolved(),
358            PolicyField::Script(x) => x.is_resolved(),
359            PolicyField::Ref(x) => x.is_resolved(),
360        }
361    }
362}
363impl Analyzable for PolicyConstructor {
364    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
365        self.fields.analyze(parent)
366    }
367
368    fn is_resolved(&self) -> bool {
369        self.fields.is_resolved()
370    }
371}
372
373impl Analyzable for PolicyDef {
374    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
375        match &mut self.value {
376            PolicyValue::Constructor(x) => x.analyze(parent),
377            PolicyValue::Assign(_) => AnalyzeReport::default(),
378        }
379    }
380
381    fn is_resolved(&self) -> bool {
382        match &self.value {
383            PolicyValue::Constructor(x) => x.is_resolved(),
384            PolicyValue::Assign(_) => true,
385        }
386    }
387}
388
389impl Analyzable for DataBinaryOp {
390    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
391        let left = self.left.analyze(parent.clone());
392        let right = self.right.analyze(parent.clone());
393
394        left + right
395    }
396
397    fn is_resolved(&self) -> bool {
398        self.left.is_resolved() && self.right.is_resolved()
399    }
400}
401
402impl Analyzable for RecordConstructorField {
403    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
404        let name = self.name.analyze(parent.clone());
405        let value = self.value.analyze(parent.clone());
406
407        name + value
408    }
409
410    fn is_resolved(&self) -> bool {
411        self.name.is_resolved() && self.value.is_resolved()
412    }
413}
414
415impl Analyzable for VariantCaseConstructor {
416    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
417        let name = self.name.analyze(parent.clone());
418
419        let mut scope = Scope::new(parent);
420
421        let case = match &self.name.symbol {
422            Some(Symbol::VariantCase(x)) => x,
423            Some(x) => bail_report!(Error::invalid_symbol("VariantCase", x, &self.name)),
424            None => bail_report!(Error::not_in_scope(self.name.value.clone(), &self.name)),
425        };
426
427        for field in case.fields.iter() {
428            scope.track_record_field(field);
429        }
430
431        self.scope = Some(Rc::new(scope));
432
433        let fields = self.fields.analyze(self.scope.clone());
434
435        let spread = self.spread.analyze(self.scope.clone());
436
437        name + fields + spread
438    }
439
440    fn is_resolved(&self) -> bool {
441        self.name.is_resolved() && self.fields.is_resolved() && self.spread.is_resolved()
442    }
443}
444
445impl Analyzable for StructConstructor {
446    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
447        let r#type = self.r#type.analyze(parent.clone());
448
449        let mut scope = Scope::new(parent);
450
451        let type_def = match &self.r#type.symbol {
452            Some(Symbol::TypeDef(x)) => x,
453            Some(x) => bail_report!(Error::invalid_symbol("TypeDef", x, &self.r#type)),
454            _ => unreachable!(),
455        };
456
457        for case in type_def.cases.iter() {
458            scope.track_variant_case(case);
459        }
460
461        self.scope = Some(Rc::new(scope));
462
463        let case = self.case.analyze(self.scope.clone());
464
465        r#type + case
466    }
467
468    fn is_resolved(&self) -> bool {
469        self.r#type.is_resolved() && self.case.is_resolved()
470    }
471}
472
473impl Analyzable for ListConstructor {
474    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
475        self.elements.analyze(parent)
476    }
477
478    fn is_resolved(&self) -> bool {
479        self.elements.is_resolved()
480    }
481}
482
483impl Analyzable for DataExpr {
484    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
485        match self {
486            DataExpr::StructConstructor(x) => x.analyze(parent),
487            DataExpr::ListConstructor(x) => x.analyze(parent),
488            DataExpr::Identifier(x) => x.analyze(parent),
489            DataExpr::PropertyAccess(x) => x.analyze(parent),
490            DataExpr::BinaryOp(x) => x.analyze(parent),
491            _ => AnalyzeReport::default(),
492        }
493    }
494
495    fn is_resolved(&self) -> bool {
496        match self {
497            DataExpr::StructConstructor(x) => x.is_resolved(),
498            DataExpr::ListConstructor(x) => x.is_resolved(),
499            DataExpr::Identifier(x) => x.is_resolved(),
500            DataExpr::PropertyAccess(x) => x.is_resolved(),
501            DataExpr::BinaryOp(x) => x.is_resolved(),
502            _ => true,
503        }
504    }
505}
506
507impl Analyzable for AssetBinaryOp {
508    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
509        let left = self.left.analyze(parent.clone());
510        let right = self.right.analyze(parent.clone());
511
512        left + right
513    }
514
515    fn is_resolved(&self) -> bool {
516        self.left.is_resolved() && self.right.is_resolved()
517    }
518}
519
520impl Analyzable for StaticAssetConstructor {
521    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
522        let amount = self.amount.analyze(parent.clone());
523        let r#type = self.r#type.analyze(parent.clone());
524
525        amount + r#type
526    }
527
528    fn is_resolved(&self) -> bool {
529        self.amount.is_resolved() && self.r#type.is_resolved()
530    }
531}
532
533impl Analyzable for AnyAssetConstructor {
534    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
535        let policy = self.policy.analyze(parent.clone());
536        let asset_name = self.asset_name.analyze(parent.clone());
537        let amount = self.amount.analyze(parent.clone());
538
539        policy + asset_name + amount
540    }
541
542    fn is_resolved(&self) -> bool {
543        self.policy.is_resolved() && self.asset_name.is_resolved() && self.amount.is_resolved()
544    }
545}
546
547impl Analyzable for PropertyAccess {
548    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
549        let object = self.object.analyze(parent.clone());
550
551        let mut scope = Scope::new(parent);
552
553        if let Some(ty) = self.object.symbol.as_ref().and_then(|s| s.target_type()) {
554            scope.track_record_fields_for_type(&ty);
555        }
556
557        self.scope = Some(Rc::new(scope));
558
559        let path = self.path.analyze(self.scope.clone());
560
561        object + path
562    }
563
564    fn is_resolved(&self) -> bool {
565        self.object.is_resolved() && self.path.is_resolved()
566    }
567}
568
569impl Analyzable for AssetExpr {
570    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
571        match self {
572            AssetExpr::Identifier(x) => x.analyze(parent),
573            AssetExpr::StaticConstructor(x) => x.analyze(parent),
574            AssetExpr::AnyConstructor(x) => x.analyze(parent),
575            AssetExpr::BinaryOp(x) => x.analyze(parent),
576            AssetExpr::PropertyAccess(x) => x.analyze(parent),
577        }
578    }
579
580    fn is_resolved(&self) -> bool {
581        match self {
582            AssetExpr::Identifier(x) => x.is_resolved(),
583            AssetExpr::StaticConstructor(x) => x.is_resolved(),
584            AssetExpr::AnyConstructor(x) => x.is_resolved(),
585            AssetExpr::BinaryOp(x) => x.is_resolved(),
586            AssetExpr::PropertyAccess(x) => x.is_resolved(),
587        }
588    }
589}
590
591impl Analyzable for AddressExpr {
592    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
593        match self {
594            AddressExpr::Identifier(x) => x.analyze(parent),
595            _ => AnalyzeReport::default(),
596        }
597    }
598
599    fn is_resolved(&self) -> bool {
600        match self {
601            AddressExpr::Identifier(x) => x.is_resolved(),
602            _ => true,
603        }
604    }
605}
606impl Analyzable for Identifier {
607    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
608        let symbol = parent.and_then(|p| p.resolve(&self.value));
609
610        if symbol.is_none() {
611            bail_report!(Error::not_in_scope(self.value.clone(), self));
612        }
613
614        self.symbol = symbol;
615
616        AnalyzeReport::default()
617    }
618
619    fn is_resolved(&self) -> bool {
620        self.symbol.is_some()
621    }
622}
623
624impl Analyzable for Type {
625    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
626        match self {
627            Type::Custom(x) => x.analyze(parent),
628            Type::List(x) => x.analyze(parent),
629            _ => AnalyzeReport::default(),
630        }
631    }
632
633    fn is_resolved(&self) -> bool {
634        match self {
635            Type::Custom(x) => x.is_resolved(),
636            Type::List(x) => x.is_resolved(),
637            _ => true,
638        }
639    }
640}
641
642impl Analyzable for InputBlockField {
643    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
644        match self {
645            InputBlockField::From(x) => x.analyze(parent),
646            InputBlockField::DatumIs(x) => x.analyze(parent),
647            InputBlockField::MinAmount(x) => x.analyze(parent),
648            InputBlockField::Redeemer(x) => x.analyze(parent),
649            InputBlockField::Ref(x) => x.analyze(parent),
650        }
651    }
652
653    fn is_resolved(&self) -> bool {
654        match self {
655            InputBlockField::From(x) => x.is_resolved(),
656            InputBlockField::DatumIs(x) => x.is_resolved(),
657            InputBlockField::MinAmount(x) => x.is_resolved(),
658            InputBlockField::Redeemer(x) => x.is_resolved(),
659            InputBlockField::Ref(x) => x.is_resolved(),
660        }
661    }
662}
663
664impl Analyzable for InputBlock {
665    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
666        self.fields.analyze(parent)
667    }
668
669    fn is_resolved(&self) -> bool {
670        self.fields.is_resolved()
671    }
672}
673
674impl Analyzable for OutputBlockField {
675    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
676        match self {
677            OutputBlockField::To(x) => x.analyze(parent),
678            OutputBlockField::Amount(x) => x.analyze(parent),
679            OutputBlockField::Datum(x) => x.analyze(parent),
680        }
681    }
682
683    fn is_resolved(&self) -> bool {
684        match self {
685            OutputBlockField::To(x) => x.is_resolved(),
686            OutputBlockField::Amount(x) => x.is_resolved(),
687            OutputBlockField::Datum(x) => x.is_resolved(),
688        }
689    }
690}
691
692impl Analyzable for OutputBlock {
693    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
694        self.fields.analyze(parent)
695    }
696
697    fn is_resolved(&self) -> bool {
698        self.fields.is_resolved()
699    }
700}
701
702impl Analyzable for RecordField {
703    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
704        self.r#type.analyze(parent)
705    }
706
707    fn is_resolved(&self) -> bool {
708        self.r#type.is_resolved()
709    }
710}
711
712impl Analyzable for VariantCase {
713    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
714        self.fields.analyze(parent)
715    }
716
717    fn is_resolved(&self) -> bool {
718        self.fields.is_resolved()
719    }
720}
721
722impl Analyzable for TypeDef {
723    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
724        self.cases.analyze(parent)
725    }
726
727    fn is_resolved(&self) -> bool {
728        self.cases.is_resolved()
729    }
730}
731
732impl Analyzable for MintBlockField {
733    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
734        match self {
735            MintBlockField::Amount(x) => x.analyze(parent),
736            MintBlockField::Redeemer(x) => x.analyze(parent),
737        }
738    }
739
740    fn is_resolved(&self) -> bool {
741        match self {
742            MintBlockField::Amount(x) => x.is_resolved(),
743            MintBlockField::Redeemer(x) => x.is_resolved(),
744        }
745    }
746}
747
748impl Analyzable for MintBlock {
749    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
750        self.fields.analyze(parent)
751    }
752
753    fn is_resolved(&self) -> bool {
754        self.fields.is_resolved()
755    }
756}
757
758impl Analyzable for ChainSpecificBlock {
759    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
760        match self {
761            ChainSpecificBlock::Cardano(x) => x.analyze(parent),
762        }
763    }
764
765    fn is_resolved(&self) -> bool {
766        match self {
767            ChainSpecificBlock::Cardano(x) => x.is_resolved(),
768        }
769    }
770}
771
772impl Analyzable for TxDef {
773    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
774        // analyze static types before anything else
775
776        let params = self
777            .parameters
778            .parameters
779            .iter_mut()
780            .map(|param| param.r#type.analyze(parent.clone()))
781            .collect::<AnalyzeReport>();
782
783        let input_types = self
784            .inputs
785            .iter_mut()
786            .flat_map(|input| input.fields.iter_mut())
787            .map(|field| match field {
788                InputBlockField::DatumIs(x) => x.analyze(parent.clone()),
789                _ => AnalyzeReport::default(),
790            })
791            .collect::<AnalyzeReport>();
792
793        // create the new scope and populate its symbols
794
795        let mut scope = Scope::new(parent.clone());
796
797        scope.symbols.insert("fees".to_string(), Symbol::Fees);
798
799        for param in self.parameters.parameters.iter() {
800            scope.track_param_var(&param.name, param.r#type.clone());
801        }
802
803        for input in self.inputs.iter() {
804            scope.track_input(
805                &input.name,
806                input.datum_is().cloned().unwrap_or(Type::Undefined),
807            );
808        }
809
810        // enter the new scope and analyze the rest of the program
811
812        self.scope = Some(Rc::new(scope));
813
814        let inputs = self.inputs.analyze(self.scope.clone());
815
816        let outputs = self.outputs.analyze(self.scope.clone());
817
818        let mint = self.mint.analyze(self.scope.clone());
819
820        let adhoc = self.adhoc.analyze(self.scope.clone());
821
822        params + input_types + inputs + outputs + mint + adhoc
823    }
824
825    fn is_resolved(&self) -> bool {
826        self.inputs.is_resolved()
827            && self.outputs.is_resolved()
828            && self.mint.is_resolved()
829            && self.adhoc.is_resolved()
830    }
831}
832
833static ADA: std::sync::LazyLock<AssetDef> = std::sync::LazyLock::new(|| AssetDef {
834    name: "Ada".to_string(),
835    policy: None,
836    asset_name: None,
837    span: Span::DUMMY,
838});
839
840impl Analyzable for Program {
841    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
842        let mut scope = Scope::new(parent);
843
844        for party in self.parties.iter() {
845            scope.track_party_def(party);
846        }
847
848        for policy in self.policies.iter() {
849            scope.track_policy_def(policy);
850        }
851
852        scope.track_asset_def(&ADA);
853
854        for asset in self.assets.iter() {
855            scope.track_asset_def(asset);
856        }
857
858        for type_def in self.types.iter() {
859            scope.track_type_def(type_def);
860        }
861
862        self.scope = Some(Rc::new(scope));
863
864        // TODO: Add parties
865        // let parties = self.parties.analyze(self.scope.clone());
866
867        let policies = self.policies.analyze(self.scope.clone());
868
869        // TODO: Add assets
870        // let assets = self.assets.analyze(self.scope.clone());
871
872        let types = self.types.analyze(self.scope.clone());
873
874        let txs = self.txs.analyze(self.scope.clone());
875
876        policies + types + txs
877    }
878
879    fn is_resolved(&self) -> bool {
880        self.policies.is_resolved() && self.types.is_resolved() && self.txs.is_resolved()
881    }
882}
883
884/// Performs semantic analysis on a Tx3 program AST.
885///
886/// This function validates the entire program structure, checking for:
887/// - Duplicate definitions
888/// - Unknown symbol references
889/// - Type correctness
890/// - Other semantic constraints
891///
892/// # Arguments
893/// * `ast` - Mutable reference to the program AST to analyze
894///
895/// # Returns
896/// * `AnalyzeReport` of the analysis. Empty if no errors are found.
897pub fn analyze(ast: &mut Program) -> AnalyzeReport {
898    ast.analyze(None)
899}