Skip to main content

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};
7
8use miette::Diagnostic;
9
10use crate::ast::*;
11use crate::parsing::AstNode;
12
13const METADATA_MAX_SIZE_BYTES: usize = 64;
14
15#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
16#[error("not in scope: {name}")]
17#[diagnostic(code(tx3::not_in_scope))]
18pub struct NotInScopeError {
19    pub name: String,
20
21    #[source_code]
22    src: Option<String>,
23
24    #[label]
25    span: Span,
26}
27
28#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
29#[error("invalid symbol, expected {expected}, got {got}")]
30#[diagnostic(code(tx3::invalid_symbol))]
31pub struct InvalidSymbolError {
32    pub expected: &'static str,
33    pub got: String,
34
35    #[source_code]
36    src: Option<String>,
37
38    #[label]
39    span: Span,
40}
41
42#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
43#[error("invalid type ({got}), expected: {expected}")]
44#[diagnostic(code(tx3::invalid_type))]
45pub struct InvalidTargetTypeError {
46    pub expected: String,
47    pub got: String,
48
49    #[source_code]
50    src: Option<String>,
51
52    #[label]
53    span: Span,
54}
55
56#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
57#[error("function '{name}' expects {expected} argument(s), but got {got}")]
58#[diagnostic(code(tx3::arity_mismatch))]
59pub struct ArityError {
60    pub name: String,
61    pub expected: usize,
62    pub got: usize,
63
64    #[source_code]
65    src: Option<String>,
66
67    #[label]
68    span: Span,
69}
70
71#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
72#[error("optional output ({name}) cannot have a datum")]
73#[diagnostic(code(tx3::optional_output_datum))]
74pub struct OptionalOutputError {
75    pub name: String,
76
77    #[source_code]
78    src: Option<String>,
79
80    #[label]
81    span: Span,
82}
83
84#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
85#[error("metadata value exceeds 64 bytes: {size} bytes found")]
86#[diagnostic(code(tx3::metadata_size_limit_exceeded))]
87pub struct MetadataSizeLimitError {
88    pub size: usize,
89
90    #[source_code]
91    src: Option<String>,
92
93    #[label("value too large")]
94    span: Span,
95}
96
97#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq, Clone)]
98#[error("metadata key must be an integer, got: {key_type}")]
99#[diagnostic(code(tx3::metadata_invalid_key_type))]
100pub struct MetadataInvalidKeyTypeError {
101    pub key_type: String,
102
103    #[source_code]
104    src: Option<String>,
105
106    #[label("expected integer key")]
107    span: Span,
108}
109
110#[derive(thiserror::Error, Debug, miette::Diagnostic, PartialEq, Eq, Clone)]
111pub enum Error {
112    #[error("duplicate definition: {0}")]
113    #[diagnostic(code(tx3::duplicate_definition))]
114    DuplicateDefinition(String),
115
116    #[error(transparent)]
117    #[diagnostic(transparent)]
118    NotInScope(#[from] NotInScopeError),
119
120    #[error("needs parent scope")]
121    #[diagnostic(code(tx3::needs_parent_scope))]
122    NeedsParentScope,
123
124    #[error(transparent)]
125    #[diagnostic(transparent)]
126    InvalidSymbol(#[from] InvalidSymbolError),
127
128    // Invalid type for extension
129    #[error(transparent)]
130    #[diagnostic(transparent)]
131    InvalidTargetType(#[from] InvalidTargetTypeError),
132
133    #[error(transparent)]
134    #[diagnostic(transparent)]
135    MetadataSizeLimitExceeded(#[from] MetadataSizeLimitError),
136
137    #[error(transparent)]
138    #[diagnostic(transparent)]
139    MetadataInvalidKeyType(#[from] MetadataInvalidKeyTypeError),
140
141    #[error(transparent)]
142    #[diagnostic(transparent)]
143    InvalidOptionalOutput(#[from] OptionalOutputError),
144
145    #[error(transparent)]
146    #[diagnostic(transparent)]
147    Arity(#[from] ArityError),
148}
149
150impl Error {
151    pub fn span(&self) -> &Span {
152        match self {
153            Self::NotInScope(x) => &x.span,
154            Self::InvalidSymbol(x) => &x.span,
155            Self::InvalidTargetType(x) => &x.span,
156            Self::MetadataSizeLimitExceeded(x) => &x.span,
157            Self::MetadataInvalidKeyType(x) => &x.span,
158            Self::InvalidOptionalOutput(x) => &x.span,
159            Self::Arity(x) => &x.span,
160            _ => &Span::DUMMY,
161        }
162    }
163
164    pub fn src(&self) -> Option<&str> {
165        match self {
166            Self::NotInScope(x) => x.src.as_deref(),
167            Self::MetadataSizeLimitExceeded(x) => x.src.as_deref(),
168            Self::MetadataInvalidKeyType(x) => x.src.as_deref(),
169            _ => None,
170        }
171    }
172
173    pub fn arity(
174        name: String,
175        expected: usize,
176        got: usize,
177        ast: &impl crate::parsing::AstNode,
178    ) -> Self {
179        Self::Arity(ArityError {
180            name,
181            expected,
182            got,
183            src: None,
184            span: ast.span().clone(),
185        })
186    }
187
188    pub fn not_in_scope(name: String, ast: &impl crate::parsing::AstNode) -> Self {
189        Self::NotInScope(NotInScopeError {
190            name,
191            src: None,
192            span: ast.span().clone(),
193        })
194    }
195
196    fn symbol_type_name(symbol: &Symbol) -> String {
197        match symbol {
198            Symbol::TypeDef(type_def) => format!("TypeDef({})", type_def.name.value),
199            Symbol::AliasDef(alias_def) => format!("AliasDef({})", alias_def.name.value),
200            Symbol::VariantCase(case) => format!("VariantCase({})", case.name.value),
201            Symbol::RecordField(field) => format!("RecordField({})", field.name.value),
202            Symbol::PartyDef(party) => format!("PartyDef({})", party.name.value),
203            Symbol::PolicyDef(policy) => format!("PolicyDef({})", policy.name.value),
204            Symbol::AssetDef(asset) => format!("AssetDef({})", asset.name.value),
205            Symbol::EnvVar(name, _) => format!("EnvVar({})", name),
206            Symbol::ParamVar(name, _) => format!("ParamVar({})", name),
207            Symbol::FunctionDef(fn_def) => format!("FunctionDef({})", fn_def.name.value),
208            Symbol::Input(block) => format!("Input({})", block.name),
209            Symbol::Reference(block) => format!("Reference({})", block.name),
210            Symbol::Output(idx) => format!("Output({})", idx),
211            Symbol::LocalExpr(_) => "LocalExpr".to_string(),
212            Symbol::Fees => "Fees".to_string(),
213        }
214    }
215
216    pub fn invalid_symbol(
217        expected: &'static str,
218        got: &Symbol,
219        ast: &impl crate::parsing::AstNode,
220    ) -> Self {
221        Self::InvalidSymbol(InvalidSymbolError {
222            expected,
223            got: Self::symbol_type_name(got),
224            src: None,
225            span: ast.span().clone(),
226        })
227    }
228
229    pub fn invalid_target_type(
230        expected: &Type,
231        got: &Type,
232        ast: &impl crate::parsing::AstNode,
233    ) -> Self {
234        Self::InvalidTargetType(InvalidTargetTypeError {
235            expected: expected.to_string(),
236            got: got.to_string(),
237            src: None,
238            span: ast.span().clone(),
239        })
240    }
241}
242
243#[derive(Debug, Default, thiserror::Error, Diagnostic, Clone)]
244pub struct AnalyzeReport {
245    #[related]
246    pub errors: Vec<Error>,
247}
248
249impl AnalyzeReport {
250    pub fn is_empty(&self) -> bool {
251        self.errors.is_empty()
252    }
253
254    pub fn ok(self) -> Result<(), Self> {
255        if self.is_empty() {
256            Ok(())
257        } else {
258            Err(self)
259        }
260    }
261
262    pub fn expect_data_expr_type(expr: &DataExpr, expected: &Type) -> Self {
263        if expr.target_type().as_ref() != Some(expected) {
264            Self::from(Error::invalid_target_type(
265                expected,
266                expr.target_type().as_ref().unwrap_or(&Type::Undefined),
267                expr,
268            ))
269        } else {
270            Self::default()
271        }
272    }
273}
274
275impl std::fmt::Display for AnalyzeReport {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        if self.errors.is_empty() {
278            write!(f, "")
279        } else {
280            write!(f, "Failed with {} errors:", self.errors.len())?;
281            for error in &self.errors {
282                write!(f, "\n{} ({:?})", error, error)?;
283            }
284            Ok(())
285        }
286    }
287}
288
289impl std::ops::Add for Error {
290    type Output = AnalyzeReport;
291
292    fn add(self, other: Self) -> Self::Output {
293        Self::Output {
294            errors: vec![self, other],
295        }
296    }
297}
298
299impl From<Error> for AnalyzeReport {
300    fn from(error: Error) -> Self {
301        Self {
302            errors: vec![error],
303        }
304    }
305}
306
307impl From<Vec<Error>> for AnalyzeReport {
308    fn from(errors: Vec<Error>) -> Self {
309        Self { errors }
310    }
311}
312
313impl std::ops::Add for AnalyzeReport {
314    type Output = AnalyzeReport;
315
316    fn add(self, other: Self) -> Self::Output {
317        [self, other].into_iter().collect()
318    }
319}
320
321impl FromIterator<Error> for AnalyzeReport {
322    fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
323        Self {
324            errors: iter.into_iter().collect(),
325        }
326    }
327}
328
329impl FromIterator<AnalyzeReport> for AnalyzeReport {
330    fn from_iter<T: IntoIterator<Item = AnalyzeReport>>(iter: T) -> Self {
331        Self {
332            errors: iter.into_iter().flat_map(|r| r.errors).collect(),
333        }
334    }
335}
336
337macro_rules! bail_report {
338    ($($args:expr),*) => {
339        { return AnalyzeReport::from(vec![$($args),*]); }
340    };
341}
342
343impl Scope {
344    pub fn new(parent: Option<Rc<Scope>>) -> Self {
345        Self {
346            symbols: HashMap::new(),
347            parent,
348        }
349    }
350
351    pub fn track_env_var(&mut self, name: &str, ty: Type) {
352        self.symbols.insert(
353            name.to_string(),
354            Symbol::EnvVar(name.to_string(), Box::new(ty)),
355        );
356    }
357
358    pub fn track_type_def(&mut self, type_: &TypeDef) {
359        self.symbols.insert(
360            type_.name.value.clone(),
361            Symbol::TypeDef(Box::new(type_.clone())),
362        );
363    }
364
365    pub fn track_alias_def(&mut self, alias: &AliasDef) {
366        self.symbols.insert(
367            alias.name.value.clone(),
368            Symbol::AliasDef(Box::new(alias.clone())),
369        );
370    }
371
372    pub fn track_variant_case(&mut self, case: &VariantCase) {
373        self.symbols.insert(
374            case.name.value.clone(),
375            Symbol::VariantCase(Box::new(case.clone())),
376        );
377    }
378
379    pub fn track_record_field(&mut self, field: &RecordField) {
380        self.symbols.insert(
381            field.name.value.clone(),
382            Symbol::RecordField(Box::new(field.clone())),
383        );
384    }
385
386    pub fn track_party_def(&mut self, party: &PartyDef) {
387        self.symbols.insert(
388            party.name.value.clone(),
389            Symbol::PartyDef(Box::new(party.clone())),
390        );
391    }
392
393    pub fn track_policy_def(&mut self, policy: &PolicyDef) {
394        self.symbols.insert(
395            policy.name.value.clone(),
396            Symbol::PolicyDef(Box::new(policy.clone())),
397        );
398    }
399
400    pub fn track_asset_def(&mut self, asset: &AssetDef) {
401        self.symbols.insert(
402            asset.name.value.clone(),
403            Symbol::AssetDef(Box::new(asset.clone())),
404        );
405    }
406
407    pub fn track_param_var(&mut self, param: &str, ty: Type) {
408        self.symbols.insert(
409            param.to_string(),
410            Symbol::ParamVar(param.to_string(), Box::new(ty)),
411        );
412    }
413
414    pub fn track_fn_def(&mut self, fn_def: &FnDef) {
415        self.symbols.insert(
416            fn_def.name.value.clone(),
417            Symbol::FunctionDef(Box::new(fn_def.clone())),
418        );
419    }
420
421    pub fn track_local_expr(&mut self, name: &str, expr: DataExpr) {
422        self.symbols
423            .insert(name.to_string(), Symbol::LocalExpr(Box::new(expr)));
424    }
425
426    pub fn track_input(&mut self, name: &str, input: InputBlock) {
427        self.symbols
428            .insert(name.to_string(), Symbol::Input(Box::new(input)));
429    }
430
431    pub fn track_reference(&mut self, name: &str, reference: ReferenceBlock) {
432        self.symbols
433            .insert(name.to_string(), Symbol::Reference(Box::new(reference)));
434    }
435
436    pub fn track_output(&mut self, index: usize, output: OutputBlock) {
437        if let Some(n) = output.name {
438            self.symbols.insert(n.value, Symbol::Output(index));
439        }
440    }
441
442    pub fn track_record_fields_for_type(&mut self, ty: &Type) {
443        let schema = ty.properties();
444
445        for (name, subty) in schema {
446            self.track_record_field(&RecordField {
447                name: Identifier::new(name),
448                r#type: subty,
449                span: Span::DUMMY,
450            });
451        }
452    }
453
454    pub fn resolve(&self, name: &str) -> Option<Symbol> {
455        if let Some(symbol) = self.symbols.get(name) {
456            Some(symbol.clone())
457        } else if let Some(parent) = &self.parent {
458            parent.resolve(name)
459        } else {
460            None
461        }
462    }
463}
464
465/// A trait for types that can be semantically analyzed.
466///
467/// Types implementing this trait can validate their semantic correctness and
468/// resolve symbol references within a given scope.
469pub trait Analyzable {
470    /// Performs semantic analysis on the type.
471    ///
472    /// # Arguments
473    /// * `parent` - Optional parent scope containing symbol definitions
474    ///
475    /// # Returns
476    /// * `AnalyzeReport` of the analysis. Empty if no errors are found.
477    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport;
478
479    /// Returns true if all of the symbols have been resolved .
480    fn is_resolved(&self) -> bool;
481}
482
483impl<T: Analyzable> Analyzable for Option<T> {
484    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
485        if let Some(item) = self {
486            item.analyze(parent)
487        } else {
488            AnalyzeReport::default()
489        }
490    }
491
492    fn is_resolved(&self) -> bool {
493        self.as_ref().is_none_or(|x| x.is_resolved())
494    }
495}
496
497impl<T: Analyzable> Analyzable for Box<T> {
498    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
499        self.as_mut().analyze(parent)
500    }
501
502    fn is_resolved(&self) -> bool {
503        self.as_ref().is_resolved()
504    }
505}
506
507impl<T: Analyzable> Analyzable for Vec<T> {
508    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
509        self.iter_mut()
510            .map(|item| item.analyze(parent.clone()))
511            .collect()
512    }
513
514    fn is_resolved(&self) -> bool {
515        self.iter().all(|x| x.is_resolved())
516    }
517}
518
519impl Analyzable for PartyDef {
520    fn analyze(&mut self, _parent: Option<Rc<Scope>>) -> AnalyzeReport {
521        AnalyzeReport::default()
522    }
523
524    fn is_resolved(&self) -> bool {
525        true
526    }
527}
528
529impl Analyzable for PolicyField {
530    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
531        match self {
532            PolicyField::Hash(x) => x.analyze(parent),
533            PolicyField::Script(x) => x.analyze(parent),
534            PolicyField::Ref(x) => x.analyze(parent),
535        }
536    }
537
538    fn is_resolved(&self) -> bool {
539        match self {
540            PolicyField::Hash(x) => x.is_resolved(),
541            PolicyField::Script(x) => x.is_resolved(),
542            PolicyField::Ref(x) => x.is_resolved(),
543        }
544    }
545}
546impl Analyzable for PolicyConstructor {
547    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
548        self.fields.analyze(parent)
549    }
550
551    fn is_resolved(&self) -> bool {
552        self.fields.is_resolved()
553    }
554}
555
556impl Analyzable for PolicyDef {
557    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
558        match &mut self.value {
559            PolicyValue::Constructor(x) => x.analyze(parent),
560            PolicyValue::Assign(_) => AnalyzeReport::default(),
561        }
562    }
563
564    fn is_resolved(&self) -> bool {
565        match &self.value {
566            PolicyValue::Constructor(x) => x.is_resolved(),
567            PolicyValue::Assign(_) => true,
568        }
569    }
570}
571
572impl Analyzable for AddOp {
573    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
574        let left = self.lhs.analyze(parent.clone());
575        let right = self.rhs.analyze(parent.clone());
576
577        left + right
578    }
579
580    fn is_resolved(&self) -> bool {
581        self.lhs.is_resolved() && self.rhs.is_resolved()
582    }
583}
584
585impl Analyzable for ConcatOp {
586    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
587        let left = self.lhs.analyze(parent.clone());
588        let right = self.rhs.analyze(parent.clone());
589
590        left + right
591    }
592
593    fn is_resolved(&self) -> bool {
594        self.lhs.is_resolved() && self.rhs.is_resolved()
595    }
596}
597
598impl Analyzable for SubOp {
599    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
600        let left = self.lhs.analyze(parent.clone());
601        let right = self.rhs.analyze(parent.clone());
602
603        left + right
604    }
605
606    fn is_resolved(&self) -> bool {
607        self.lhs.is_resolved() && self.rhs.is_resolved()
608    }
609}
610
611impl Analyzable for MulOp {
612    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
613        let left = self.lhs.analyze(parent.clone());
614        let right = self.rhs.analyze(parent.clone());
615
616        left + right
617    }
618
619    fn is_resolved(&self) -> bool {
620        self.lhs.is_resolved() && self.rhs.is_resolved()
621    }
622}
623
624impl Analyzable for DivOp {
625    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
626        let left = self.lhs.analyze(parent.clone());
627        let right = self.rhs.analyze(parent.clone());
628
629        left + right
630    }
631
632    fn is_resolved(&self) -> bool {
633        self.lhs.is_resolved() && self.rhs.is_resolved()
634    }
635}
636
637impl Analyzable for NegateOp {
638    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
639        self.operand.analyze(parent)
640    }
641
642    fn is_resolved(&self) -> bool {
643        self.operand.is_resolved()
644    }
645}
646
647impl Analyzable for RecordConstructorField {
648    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
649        let name = self.name.analyze(parent.clone());
650
651        // skip the record-field scope so that param names aren't shadowed
652        // by same-named type fields (e.g. `MyType { counter: counter }`)
653        let outer = parent.as_ref().and_then(|p| p.parent.clone());
654        let value = self.value.analyze(outer);
655
656        name + value
657    }
658
659    fn is_resolved(&self) -> bool {
660        self.name.is_resolved() && self.value.is_resolved()
661    }
662}
663
664impl Analyzable for VariantCaseConstructor {
665    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
666        let name = if self.name.symbol.is_some() {
667            AnalyzeReport::default()
668        } else {
669            self.name.analyze(parent.clone())
670        };
671
672        let mut scope = Scope::new(parent);
673
674        let case = match &self.name.symbol {
675            Some(Symbol::VariantCase(x)) => x,
676            Some(x) => bail_report!(Error::invalid_symbol("VariantCase", x, &self.name)),
677            None => bail_report!(Error::not_in_scope(self.name.value.clone(), &self.name)),
678        };
679
680        for field in case.fields.iter() {
681            scope.track_record_field(field);
682        }
683
684        self.scope = Some(Rc::new(scope));
685
686        let fields = self.fields.analyze(self.scope.clone());
687
688        let spread = self.spread.analyze(self.scope.clone());
689
690        name + fields + spread
691    }
692
693    fn is_resolved(&self) -> bool {
694        self.name.is_resolved() && self.fields.is_resolved() && self.spread.is_resolved()
695    }
696}
697
698impl Analyzable for StructConstructor {
699    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
700        let r#type = self.r#type.analyze(parent.clone());
701
702        let mut scope = Scope::new(parent);
703
704        let type_def = match &self.r#type.symbol {
705            Some(Symbol::TypeDef(type_def)) => type_def.as_ref(),
706            Some(Symbol::AliasDef(alias_def)) => match alias_def.resolve_alias_chain() {
707                Some(resolved_type_def) => resolved_type_def,
708                None => {
709                    bail_report!(Error::invalid_symbol(
710                        "struct type",
711                        &Symbol::AliasDef(alias_def.clone()),
712                        &self.r#type
713                    ));
714                }
715            },
716            Some(symbol) => {
717                bail_report!(Error::invalid_symbol("struct type", symbol, &self.r#type));
718            }
719            None => {
720                bail_report!(Error::not_in_scope(
721                    self.r#type.value.clone(),
722                    &self.r#type
723                ));
724            }
725        };
726
727        for case in type_def.cases.iter() {
728            scope.track_variant_case(case);
729        }
730
731        self.scope = Some(Rc::new(scope));
732
733        let case = self.case.analyze(self.scope.clone());
734
735        r#type + case
736    }
737
738    fn is_resolved(&self) -> bool {
739        self.r#type.is_resolved() && self.case.is_resolved()
740    }
741}
742
743impl Analyzable for ListConstructor {
744    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
745        self.elements.analyze(parent)
746    }
747
748    fn is_resolved(&self) -> bool {
749        self.elements.is_resolved()
750    }
751}
752
753impl Analyzable for TupleConstructor {
754    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
755        self.elements.analyze(parent)
756    }
757
758    fn is_resolved(&self) -> bool {
759        self.elements.is_resolved()
760    }
761}
762
763impl Analyzable for MapField {
764    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
765        self.key.analyze(parent.clone()) + self.value.analyze(parent.clone())
766    }
767
768    fn is_resolved(&self) -> bool {
769        self.key.is_resolved() && self.value.is_resolved()
770    }
771}
772
773impl Analyzable for MapConstructor {
774    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
775        self.fields.analyze(parent)
776    }
777
778    fn is_resolved(&self) -> bool {
779        self.fields.is_resolved()
780    }
781}
782
783impl Analyzable for DataExpr {
784    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
785        match self {
786            DataExpr::StructConstructor(x) => x.analyze(parent),
787            DataExpr::ListConstructor(x) => x.analyze(parent),
788            DataExpr::MapConstructor(x) => x.analyze(parent),
789            DataExpr::TupleConstructor(x) => x.analyze(parent),
790            DataExpr::Identifier(x) => x.analyze(parent),
791            DataExpr::AddOp(x) => x.analyze(parent),
792            DataExpr::SubOp(x) => x.analyze(parent),
793            DataExpr::MulOp(x) => x.analyze(parent),
794            DataExpr::DivOp(x) => x.analyze(parent),
795            DataExpr::NegateOp(x) => x.analyze(parent),
796            DataExpr::PropertyOp(x) => x.analyze(parent),
797            DataExpr::AnyAssetConstructor(x) => x.analyze(parent),
798            DataExpr::FnCall(x) => x.analyze(parent),
799            DataExpr::ConcatOp(x) => x.analyze(parent),
800            _ => AnalyzeReport::default(),
801        }
802    }
803
804    fn is_resolved(&self) -> bool {
805        match self {
806            DataExpr::StructConstructor(x) => x.is_resolved(),
807            DataExpr::ListConstructor(x) => x.is_resolved(),
808            DataExpr::MapConstructor(x) => x.is_resolved(),
809            DataExpr::TupleConstructor(x) => x.is_resolved(),
810            DataExpr::Identifier(x) => x.is_resolved(),
811            DataExpr::AddOp(x) => x.is_resolved(),
812            DataExpr::SubOp(x) => x.is_resolved(),
813            DataExpr::MulOp(x) => x.is_resolved(),
814            DataExpr::DivOp(x) => x.is_resolved(),
815            DataExpr::NegateOp(x) => x.is_resolved(),
816            DataExpr::PropertyOp(x) => x.is_resolved(),
817            DataExpr::AnyAssetConstructor(x) => x.is_resolved(),
818            DataExpr::FnCall(x) => x.is_resolved(),
819            DataExpr::ConcatOp(x) => x.is_resolved(),
820            _ => true,
821        }
822    }
823}
824
825impl Analyzable for crate::ast::FnCall {
826    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
827        let callee = self.callee.analyze(parent.clone());
828
829        let mut args_report = AnalyzeReport::default();
830
831        for arg in &mut self.args {
832            args_report = args_report + arg.analyze(parent.clone());
833        }
834
835        let mut report = callee + args_report;
836
837        // Arity check: a call whose callee resolves to a function (user-defined
838        // or built-in) must pass exactly as many arguments as it declares.
839        // Skipped when the callee does not resolve to a function (e.g. an asset
840        // constructor, or an unresolved name already reported above).
841        let signature = self
842            .callee
843            .symbol
844            .as_ref()
845            .and_then(|s| s.as_fn_def())
846            .map(|fn_def| (fn_def.name.value.clone(), fn_def.parameters.parameters.len()));
847
848        if let Some((name, expected)) = signature {
849            let got = self.args.len();
850            if expected != got {
851                report = report + Error::arity(name, expected, got, self).into();
852            }
853        }
854
855        report
856    }
857
858    fn is_resolved(&self) -> bool {
859        self.callee.is_resolved() && self.args.iter().all(|arg| arg.is_resolved())
860    }
861}
862
863impl Analyzable for AnyAssetConstructor {
864    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
865        let policy = self.policy.analyze(parent.clone());
866        let asset_name = self.asset_name.analyze(parent.clone());
867        let amount = self.amount.analyze(parent.clone());
868
869        policy + asset_name + amount
870    }
871
872    fn is_resolved(&self) -> bool {
873        self.policy.is_resolved() && self.asset_name.is_resolved() && self.amount.is_resolved()
874    }
875}
876
877impl Analyzable for PropertyOp {
878    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
879        let object = self.operand.analyze(parent.clone());
880
881        let mut scope = Scope::new(parent);
882
883        if let Some(ty) = self.operand.target_type() {
884            scope.track_record_fields_for_type(&ty);
885        }
886
887        self.scope = Some(Rc::new(scope));
888
889        let path = self.property.analyze(self.scope.clone());
890
891        object + path
892    }
893
894    fn is_resolved(&self) -> bool {
895        self.operand.is_resolved() && self.property.is_resolved()
896    }
897}
898
899impl Analyzable for AddressExpr {
900    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
901        match self {
902            AddressExpr::Identifier(x) => x.analyze(parent),
903            _ => AnalyzeReport::default(),
904        }
905    }
906
907    fn is_resolved(&self) -> bool {
908        match self {
909            AddressExpr::Identifier(x) => x.is_resolved(),
910            _ => true,
911        }
912    }
913}
914
915impl Analyzable for AssetDef {
916    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
917        let policy = self.policy.analyze(parent.clone());
918        let asset_name = self.asset_name.analyze(parent.clone());
919
920        let policy_type = AnalyzeReport::expect_data_expr_type(&self.policy, &Type::Bytes);
921        let asset_name_type = AnalyzeReport::expect_data_expr_type(&self.asset_name, &Type::Bytes);
922
923        policy + asset_name + policy_type + asset_name_type
924    }
925
926    fn is_resolved(&self) -> bool {
927        self.policy.is_resolved() && self.asset_name.is_resolved()
928    }
929}
930
931impl Analyzable for Identifier {
932    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
933        let symbol = parent.and_then(|p| p.resolve(&self.value));
934
935        if symbol.is_none() {
936            bail_report!(Error::not_in_scope(self.value.clone(), self));
937        }
938
939        self.symbol = symbol;
940
941        AnalyzeReport::default()
942    }
943
944    fn is_resolved(&self) -> bool {
945        self.symbol.is_some()
946    }
947}
948
949impl Analyzable for Type {
950    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
951        match self {
952            Type::Custom(x) => x.analyze(parent),
953            Type::List(x) => x.analyze(parent),
954            Type::Map(key_type, value_type) => {
955                key_type.analyze(parent.clone()) + value_type.analyze(parent)
956            }
957            Type::Tuple(elements) => elements.analyze(parent),
958            _ => AnalyzeReport::default(),
959        }
960    }
961
962    fn is_resolved(&self) -> bool {
963        match self {
964            Type::Custom(x) => x.is_resolved(),
965            Type::List(x) => x.is_resolved(),
966            Type::Map(key_type, value_type) => key_type.is_resolved() && value_type.is_resolved(),
967            Type::Tuple(elements) => elements.iter().all(|t| t.is_resolved()),
968            _ => true,
969        }
970    }
971}
972
973impl Analyzable for InputBlockField {
974    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
975        match self {
976            InputBlockField::From(x) => x.analyze(parent),
977            InputBlockField::DatumIs(x) => x.analyze(parent),
978            InputBlockField::MinAmount(x) => x.analyze(parent),
979            InputBlockField::Redeemer(x) => x.analyze(parent),
980            InputBlockField::Ref(x) => x.analyze(parent),
981        }
982    }
983
984    fn is_resolved(&self) -> bool {
985        match self {
986            InputBlockField::From(x) => x.is_resolved(),
987            InputBlockField::DatumIs(x) => x.is_resolved(),
988            InputBlockField::MinAmount(x) => x.is_resolved(),
989            InputBlockField::Redeemer(x) => x.is_resolved(),
990            InputBlockField::Ref(x) => x.is_resolved(),
991        }
992    }
993}
994
995impl Analyzable for InputBlock {
996    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
997        self.fields.analyze(parent)
998    }
999
1000    fn is_resolved(&self) -> bool {
1001        self.fields.is_resolved()
1002    }
1003}
1004
1005fn validate_metadata_value_size(expr: &DataExpr) -> Result<(), MetadataSizeLimitError> {
1006    match expr {
1007        DataExpr::String(string_literal) => {
1008            let utf8_bytes = string_literal.value.as_bytes();
1009            if utf8_bytes.len() > METADATA_MAX_SIZE_BYTES {
1010                return Err(MetadataSizeLimitError {
1011                    size: utf8_bytes.len(),
1012                    src: None,
1013                    span: string_literal.span.clone(),
1014                });
1015            }
1016        }
1017        DataExpr::HexString(hex_literal) => {
1018            let hex_str = &hex_literal.value;
1019            let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
1020            let byte_length = hex_str.len() / 2;
1021
1022            if byte_length > METADATA_MAX_SIZE_BYTES {
1023                return Err(MetadataSizeLimitError {
1024                    size: byte_length,
1025                    src: None,
1026                    span: hex_literal.span.clone(),
1027                });
1028            }
1029        }
1030        _ => {}
1031    }
1032    Ok(())
1033}
1034
1035fn validate_metadata_key_type(expr: &DataExpr) -> Result<(), MetadataInvalidKeyTypeError> {
1036    match expr {
1037        DataExpr::Number(_) => Ok(()),
1038        DataExpr::Identifier(id) => match id.target_type() {
1039            Some(Type::Int) => Ok(()),
1040            Some(other_type) => Err(MetadataInvalidKeyTypeError {
1041                key_type: format!("identifier of type {}", other_type),
1042                src: None,
1043                span: id.span().clone(),
1044            }),
1045            None => Err(MetadataInvalidKeyTypeError {
1046                key_type: "unresolved identifier".to_string(),
1047                src: None,
1048                span: id.span().clone(),
1049            }),
1050        },
1051        _ => {
1052            let key_type = match expr {
1053                DataExpr::String(_) => "string",
1054                DataExpr::HexString(_) => "hex string",
1055                DataExpr::ListConstructor(_) => "list",
1056                DataExpr::MapConstructor(_) => "map",
1057                DataExpr::StructConstructor(_) => "struct",
1058                _ => "unknown",
1059            };
1060
1061            Err(MetadataInvalidKeyTypeError {
1062                key_type: key_type.to_string(),
1063                src: None,
1064                span: expr.span().clone(),
1065            })
1066        }
1067    }
1068}
1069
1070impl Analyzable for MetadataBlockField {
1071    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1072        let mut report = self.key.analyze(parent.clone()) + self.value.analyze(parent.clone());
1073
1074        validate_metadata_key_type(&self.key)
1075            .map_err(Error::MetadataInvalidKeyType)
1076            .err()
1077            .map(|e| report.errors.push(e));
1078
1079        validate_metadata_value_size(&self.value)
1080            .map_err(Error::MetadataSizeLimitExceeded)
1081            .err()
1082            .map(|e| report.errors.push(e));
1083
1084        report
1085    }
1086
1087    fn is_resolved(&self) -> bool {
1088        self.key.is_resolved() && self.value.is_resolved()
1089    }
1090}
1091
1092impl Analyzable for MetadataBlock {
1093    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1094        self.fields.analyze(parent)
1095    }
1096
1097    fn is_resolved(&self) -> bool {
1098        self.fields.is_resolved()
1099    }
1100}
1101
1102impl Analyzable for ValidityBlockField {
1103    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1104        match self {
1105            ValidityBlockField::SinceSlot(x) => x.analyze(parent),
1106            ValidityBlockField::UntilSlot(x) => x.analyze(parent),
1107        }
1108    }
1109    fn is_resolved(&self) -> bool {
1110        match self {
1111            ValidityBlockField::SinceSlot(x) => x.is_resolved(),
1112            ValidityBlockField::UntilSlot(x) => x.is_resolved(),
1113        }
1114    }
1115}
1116
1117impl Analyzable for ValidityBlock {
1118    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1119        self.fields.analyze(parent)
1120    }
1121
1122    fn is_resolved(&self) -> bool {
1123        self.fields.is_resolved()
1124    }
1125}
1126
1127impl Analyzable for OutputBlockField {
1128    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1129        match self {
1130            OutputBlockField::To(x) => x.analyze(parent),
1131            OutputBlockField::Amount(x) => x.analyze(parent),
1132            OutputBlockField::Datum(x) => x.analyze(parent),
1133        }
1134    }
1135
1136    fn is_resolved(&self) -> bool {
1137        match self {
1138            OutputBlockField::To(x) => x.is_resolved(),
1139            OutputBlockField::Amount(x) => x.is_resolved(),
1140            OutputBlockField::Datum(x) => x.is_resolved(),
1141        }
1142    }
1143}
1144
1145impl Analyzable for OutputBlock {
1146    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1147        validate_optional_output(self)
1148            .map(AnalyzeReport::from)
1149            .unwrap_or_else(|| AnalyzeReport::default())
1150            + self.fields.analyze(parent)
1151    }
1152
1153    fn is_resolved(&self) -> bool {
1154        self.fields.is_resolved()
1155    }
1156}
1157
1158fn validate_optional_output(output: &OutputBlock) -> Option<Error> {
1159    if output.optional {
1160        if let Some(_field) = output.find("datum") {
1161            return Some(Error::InvalidOptionalOutput(OptionalOutputError {
1162                name: output
1163                    .name
1164                    .as_ref()
1165                    .map(|i| i.value.clone())
1166                    .unwrap_or_else(|| "<anonymous>".to_string()),
1167                src: None,
1168                span: output.span.clone(),
1169            }));
1170        }
1171    }
1172
1173    None
1174}
1175
1176impl Analyzable for RecordField {
1177    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1178        self.r#type.analyze(parent)
1179    }
1180
1181    fn is_resolved(&self) -> bool {
1182        self.r#type.is_resolved()
1183    }
1184}
1185
1186impl Analyzable for VariantCase {
1187    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1188        self.fields.analyze(parent)
1189    }
1190
1191    fn is_resolved(&self) -> bool {
1192        self.fields.is_resolved()
1193    }
1194}
1195
1196impl Analyzable for AliasDef {
1197    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1198        self.alias_type.analyze(parent)
1199    }
1200
1201    fn is_resolved(&self) -> bool {
1202        self.alias_type.is_resolved() && self.is_alias_chain_resolved()
1203    }
1204}
1205
1206impl Analyzable for TypeDef {
1207    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1208        self.cases.analyze(parent)
1209    }
1210
1211    fn is_resolved(&self) -> bool {
1212        self.cases.is_resolved()
1213    }
1214}
1215
1216impl Analyzable for MintBlockField {
1217    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1218        match self {
1219            MintBlockField::Amount(x) => x.analyze(parent),
1220            MintBlockField::Redeemer(x) => x.analyze(parent),
1221        }
1222    }
1223
1224    fn is_resolved(&self) -> bool {
1225        match self {
1226            MintBlockField::Amount(x) => x.is_resolved(),
1227            MintBlockField::Redeemer(x) => x.is_resolved(),
1228        }
1229    }
1230}
1231
1232impl Analyzable for MintBlock {
1233    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1234        self.fields.analyze(parent)
1235    }
1236
1237    fn is_resolved(&self) -> bool {
1238        self.fields.is_resolved()
1239    }
1240}
1241
1242impl Analyzable for SignersBlock {
1243    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1244        self.signers.analyze(parent)
1245    }
1246
1247    fn is_resolved(&self) -> bool {
1248        self.signers.is_resolved()
1249    }
1250}
1251
1252impl Analyzable for ReferenceBlock {
1253    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1254        self.r#ref.analyze(parent.clone()) + self.datum_is.analyze(parent)
1255    }
1256
1257    fn is_resolved(&self) -> bool {
1258        self.r#ref.is_resolved() && self.datum_is.is_resolved()
1259    }
1260}
1261
1262impl Analyzable for CollateralBlockField {
1263    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1264        match self {
1265            CollateralBlockField::From(x) => x.analyze(parent),
1266            CollateralBlockField::MinAmount(x) => x.analyze(parent),
1267            CollateralBlockField::Ref(x) => x.analyze(parent),
1268        }
1269    }
1270
1271    fn is_resolved(&self) -> bool {
1272        match self {
1273            CollateralBlockField::From(x) => x.is_resolved(),
1274            CollateralBlockField::MinAmount(x) => x.is_resolved(),
1275            CollateralBlockField::Ref(x) => x.is_resolved(),
1276        }
1277    }
1278}
1279
1280impl Analyzable for CollateralBlock {
1281    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1282        self.fields.analyze(parent)
1283    }
1284
1285    fn is_resolved(&self) -> bool {
1286        self.fields.is_resolved()
1287    }
1288}
1289
1290impl Analyzable for ChainSpecificBlock {
1291    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1292        match self {
1293            ChainSpecificBlock::Cardano(x) => x.analyze(parent),
1294        }
1295    }
1296
1297    fn is_resolved(&self) -> bool {
1298        match self {
1299            ChainSpecificBlock::Cardano(x) => x.is_resolved(),
1300        }
1301    }
1302}
1303
1304impl Analyzable for LocalsAssign {
1305    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1306        self.value.analyze(parent)
1307    }
1308
1309    fn is_resolved(&self) -> bool {
1310        self.value.is_resolved()
1311    }
1312}
1313
1314impl Analyzable for LocalsBlock {
1315    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1316        self.assigns.analyze(parent)
1317    }
1318
1319    fn is_resolved(&self) -> bool {
1320        self.assigns.is_resolved()
1321    }
1322}
1323
1324impl Analyzable for LetBinding {
1325    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1326        self.value.analyze(parent)
1327    }
1328
1329    fn is_resolved(&self) -> bool {
1330        self.value.is_resolved()
1331    }
1332}
1333
1334impl Analyzable for FnBody {
1335    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1336        let mut report = AnalyzeReport::default();
1337
1338        for binding in &mut self.let_bindings {
1339            report = report + binding.analyze(parent.clone());
1340        }
1341
1342        report = report + self.result.analyze(parent);
1343
1344        report
1345    }
1346
1347    fn is_resolved(&self) -> bool {
1348        self.let_bindings.is_resolved() && self.result.is_resolved()
1349    }
1350}
1351
1352impl Analyzable for FnDef {
1353    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1354        let params_report = self.parameters.analyze(parent.clone());
1355        let return_type_report = self.return_type.analyze(parent.clone());
1356
1357        // Built-in functions have no body — nothing more to analyze
1358        let body = match &mut self.body {
1359            Some(body) => body,
1360            None => return params_report + return_type_report,
1361        };
1362
1363        let mut scope = Scope::new(parent);
1364
1365        for param in self.parameters.parameters.iter() {
1366            scope.track_param_var(&param.name.value, param.r#type.clone());
1367        }
1368
1369        // Add let-bindings sequentially - each binding creates a new scope layer
1370        let mut current_scope = Rc::new(scope);
1371
1372        let mut bindings_report = AnalyzeReport::default();
1373
1374        for binding in &mut body.let_bindings {
1375            bindings_report = bindings_report + binding.analyze(Some(current_scope.clone()));
1376            // Create a new scope layer with this binding available
1377            let mut next_scope = Scope::new(Some(current_scope));
1378            next_scope.track_local_expr(&binding.name.value, binding.value.clone());
1379            current_scope = Rc::new(next_scope);
1380        }
1381
1382        let result_report = body.result.analyze(Some(current_scope.clone()));
1383
1384        self.scope = Some(current_scope);
1385
1386        params_report + return_type_report + bindings_report + result_report
1387    }
1388
1389    fn is_resolved(&self) -> bool {
1390        self.parameters.is_resolved()
1391            && self.body.as_ref().map_or(true, |b| b.is_resolved())
1392    }
1393}
1394
1395impl Analyzable for ParamDef {
1396    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1397        self.r#type.analyze(parent)
1398    }
1399
1400    fn is_resolved(&self) -> bool {
1401        self.r#type.is_resolved()
1402    }
1403}
1404
1405impl Analyzable for ParameterList {
1406    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1407        self.parameters.analyze(parent)
1408    }
1409
1410    fn is_resolved(&self) -> bool {
1411        self.parameters.is_resolved()
1412    }
1413}
1414
1415impl TxDef {
1416    // best effort to analyze artifacts that might be circularly dependent on each other
1417    fn best_effort_analyze_circular_dependencies(&mut self, mut scope: Scope) -> Scope {
1418        if let Some(locals) = &self.locals {
1419            for assign in locals.assigns.iter() {
1420                scope.track_local_expr(&assign.name.value, assign.value.clone());
1421            }
1422        }
1423
1424        for (_, input) in self.inputs.iter().enumerate() {
1425            scope.track_input(&input.name, input.clone())
1426        }
1427
1428        for reference in self.references.iter() {
1429            scope.track_reference(&reference.name, reference.clone());
1430        }
1431
1432        for (index, output) in self.outputs.iter().enumerate() {
1433            scope.track_output(index, output.clone())
1434        }
1435
1436        let scope_snapshot = Rc::new(scope);
1437        let _ = self.locals.analyze(Some(scope_snapshot.clone()));
1438        let _ = self.references.analyze(Some(scope_snapshot.clone()));
1439        let _ = self.inputs.analyze(Some(scope_snapshot.clone()));
1440        let _ = self.outputs.analyze(Some(scope_snapshot.clone()));
1441
1442        Scope::new(Some(scope_snapshot))
1443    }
1444}
1445
1446impl Analyzable for TxDef {
1447    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1448        // analyze static types before anything else
1449        let params = self.parameters.analyze(parent.clone());
1450
1451        // create the new scope and populate its symbols
1452
1453        let mut scope = Scope::new(parent.clone());
1454
1455        scope.symbols.insert("fees".to_string(), Symbol::Fees);
1456
1457        for param in self.parameters.parameters.iter() {
1458            scope.track_param_var(&param.name.value, param.r#type.clone());
1459        }
1460
1461        for _ in 0..9 {
1462            scope = self.best_effort_analyze_circular_dependencies(scope);
1463        }
1464
1465        let final_scope = Rc::new(scope);
1466
1467        let locals = self.locals.analyze(Some(final_scope.clone()));
1468        let inputs = self.inputs.analyze(Some(final_scope.clone()));
1469        let outputs = self.outputs.analyze(Some(final_scope.clone()));
1470        let mints = self.mints.analyze(Some(final_scope.clone()));
1471        let burns = self.burns.analyze(Some(final_scope.clone()));
1472        let adhoc = self.adhoc.analyze(Some(final_scope.clone()));
1473        let validity = self.validity.analyze(Some(final_scope.clone()));
1474        let metadata = self.metadata.analyze(Some(final_scope.clone()));
1475        let signers = self.signers.analyze(Some(final_scope.clone()));
1476        let references = self.references.analyze(Some(final_scope.clone()));
1477        let collateral = self.collateral.analyze(Some(final_scope.clone()));
1478
1479        self.scope = Some(final_scope);
1480
1481        params
1482            + locals
1483            + inputs
1484            + outputs
1485            + mints
1486            + burns
1487            + adhoc
1488            + validity
1489            + metadata
1490            + signers
1491            + references
1492            + collateral
1493    }
1494
1495    fn is_resolved(&self) -> bool {
1496        self.inputs.is_resolved()
1497            && self.outputs.is_resolved()
1498            && self.mints.is_resolved()
1499            && self.locals.is_resolved()
1500            && self.adhoc.is_resolved()
1501            && self.validity.is_resolved()
1502            && self.metadata.is_resolved()
1503            && self.signers.is_resolved()
1504            && self.references.is_resolved()
1505            && self.collateral.is_resolved()
1506    }
1507}
1508
1509fn ada_asset_def() -> AssetDef {
1510    AssetDef {
1511        name: Identifier {
1512            value: "Ada".to_string(),
1513            symbol: None,
1514            span: Span::DUMMY,
1515        },
1516        policy: DataExpr::None,
1517        asset_name: DataExpr::None,
1518        span: Span::DUMMY,
1519    }
1520}
1521
1522fn resolve_types_and_aliases(
1523    scope_rc: &mut Rc<Scope>,
1524    types: &mut Vec<TypeDef>,
1525    aliases: &mut Vec<AliasDef>,
1526) -> (AnalyzeReport, AnalyzeReport) {
1527    let mut types_report = AnalyzeReport::default();
1528    let mut aliases_report = AnalyzeReport::default();
1529
1530    let mut pass_count = 0usize;
1531    let max_passes = 100usize; // prevent infinite loops
1532
1533    while pass_count < max_passes && !(types.is_resolved() && aliases.is_resolved()) {
1534        pass_count += 1;
1535
1536        let scope = Rc::get_mut(scope_rc).expect("scope should be unique during resolution");
1537
1538        for type_def in types.iter() {
1539            scope.track_type_def(type_def);
1540        }
1541        for alias_def in aliases.iter() {
1542            scope.track_alias_def(alias_def);
1543        }
1544
1545        types_report = types.analyze(Some(scope_rc.clone()));
1546        aliases_report = aliases.analyze(Some(scope_rc.clone()));
1547    }
1548
1549    (types_report, aliases_report)
1550}
1551
1552impl Analyzable for Program {
1553    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1554        let mut scope = Scope::new(parent);
1555
1556        if let Some(env) = self.env.as_ref() {
1557            for field in env.fields.iter() {
1558                scope.track_env_var(&field.name, field.r#type.clone());
1559            }
1560        }
1561
1562        for party in self.parties.iter() {
1563            scope.track_party_def(party);
1564        }
1565
1566        for policy in self.policies.iter() {
1567            scope.track_policy_def(policy);
1568        }
1569
1570        scope.track_asset_def(&ada_asset_def());
1571
1572        for asset in self.assets.iter() {
1573            scope.track_asset_def(asset);
1574        }
1575
1576        for type_def in self.types.iter() {
1577            scope.track_type_def(type_def);
1578        }
1579
1580        for alias_def in self.aliases.iter() {
1581            scope.track_alias_def(alias_def);
1582        }
1583
1584        for builtin in crate::builtins::all() {
1585            scope.track_fn_def(&builtin.definition());
1586        }
1587
1588        for fn_def in self.functions.iter() {
1589            scope.track_fn_def(fn_def);
1590        }
1591
1592        self.scope = Some(Rc::new(scope));
1593
1594        let parties = self.parties.analyze(self.scope.clone());
1595
1596        let policies = self.policies.analyze(self.scope.clone());
1597
1598        let assets = self.assets.analyze(self.scope.clone());
1599
1600        let mut types = self.types.clone();
1601        let mut aliases = self.aliases.clone();
1602
1603        let scope_rc = self.scope.as_mut().unwrap();
1604
1605        // Policies were tracked before analysis, so the symbol table holds
1606        // pre-analysis clones whose field expressions (e.g. `hash`/`ref`
1607        // referencing an env var) carry no resolved symbols. Lowering resolves a
1608        // policy through its symbol-table entry, so re-track the analyzed
1609        // definitions here.
1610        {
1611            let scope =
1612                Rc::get_mut(scope_rc).expect("scope should be unique during resolution");
1613            for policy in self.policies.iter() {
1614                scope.track_policy_def(policy);
1615            }
1616        }
1617
1618        let (types, aliases) = resolve_types_and_aliases(scope_rc, &mut types, &mut aliases);
1619
1620        // Functions may call other functions, and lowering inlines a callee's
1621        // *analyzed* body. A single analysis pass leaves each call site holding
1622        // a pre-analysis clone of its callee (whose own body is unresolved), so
1623        // we analyze to a fixed point: each pass re-registers the
1624        // progressively-analyzed definitions and re-resolves call sites against
1625        // them. Functions are non-recursive (the call graph is acyclic), so the
1626        // number of definitions is a sufficient upper bound on the longest call
1627        // chain and the iteration terminates.
1628        let program_scope = self.scope.clone();
1629        let mut functions = AnalyzeReport::default();
1630        for _ in 0..self.functions.len() {
1631            let mut fn_scope = Scope::new(program_scope.clone());
1632            for fn_def in self.functions.iter() {
1633                fn_scope.track_fn_def(fn_def);
1634            }
1635            functions = self.functions.analyze(Some(Rc::new(fn_scope)));
1636        }
1637
1638        // Final scope: txs resolve calls to the fully-analyzed definitions.
1639        let mut fn_scope = Scope::new(program_scope);
1640        for fn_def in self.functions.iter() {
1641            fn_scope.track_fn_def(fn_def);
1642        }
1643        self.scope = Some(Rc::new(fn_scope));
1644
1645        let txs = self.txs.analyze(self.scope.clone());
1646
1647        parties + policies + types + aliases + functions + txs + assets
1648    }
1649
1650    fn is_resolved(&self) -> bool {
1651        self.policies.is_resolved()
1652            && self.types.is_resolved()
1653            && self.aliases.is_resolved()
1654            && self.functions.is_resolved()
1655            && self.txs.is_resolved()
1656            && self.assets.is_resolved()
1657    }
1658}
1659
1660/// Performs semantic analysis on a Tx3 program AST.
1661///
1662/// This function validates the entire program structure, checking for:
1663/// - Duplicate definitions
1664/// - Unknown symbol references
1665/// - Type correctness
1666/// - Other semantic constraints
1667///
1668/// # Arguments
1669/// * `ast` - Mutable reference to the program AST to analyze
1670///
1671/// # Returns
1672/// * `AnalyzeReport` of the analysis. Empty if no errors are found.
1673pub fn analyze(ast: &mut Program) -> AnalyzeReport {
1674    ast.analyze(None)
1675}
1676
1677#[cfg(test)]
1678mod tests {
1679    use crate::parsing::{parse_string, parse_well_known_example};
1680
1681    use super::*;
1682
1683    // A policy is tracked in the symbol table before the policies are analyzed.
1684    // Re-tracking the analyzed definitions must leave the stored policy with
1685    // resolved field expressions, so a `hash`/`ref` referencing an env var is
1686    // usable downstream (lowering resolves a policy through this entry).
1687    #[test]
1688    fn policy_def_fields_resolved_in_symbol_table() {
1689        let mut program = parse_string(
1690            r#"
1691            env {
1692                policy_hash: Bytes,
1693                script_ref: UtxoRef,
1694            }
1695
1696            policy P {
1697                hash: policy_hash,
1698                ref: script_ref,
1699            }
1700            "#,
1701        )
1702        .unwrap();
1703
1704        analyze(&mut program).ok().unwrap();
1705
1706        let symbol = program
1707            .scope
1708            .as_ref()
1709            .unwrap()
1710            .resolve("P")
1711            .expect("policy P should be in scope");
1712
1713        match symbol {
1714            Symbol::PolicyDef(policy) => assert!(
1715                policy.is_resolved(),
1716                "policy stored in the symbol table should have resolved fields"
1717            ),
1718            other => panic!("expected PolicyDef, got {other:?}"),
1719        }
1720    }
1721
1722    #[test]
1723    fn test_program_with_semantic_errors() {
1724        let mut ast = parse_well_known_example("semantic_errors");
1725
1726        let report = analyze(&mut ast);
1727
1728        assert_eq!(report.errors.len(), 3);
1729
1730        assert_eq!(
1731            report.errors[0],
1732            Error::NotInScope(NotInScopeError {
1733                name: "missing_symbol".to_string(),
1734                src: None,
1735                span: Span::DUMMY,
1736            })
1737        );
1738
1739        assert_eq!(
1740            report.errors[1],
1741            Error::InvalidTargetType(InvalidTargetTypeError {
1742                expected: "Bytes".to_string(),
1743                got: "Int".to_string(),
1744                src: None,
1745                span: Span::DUMMY,
1746            })
1747        );
1748
1749        assert_eq!(
1750            report.errors[2],
1751            Error::InvalidTargetType(InvalidTargetTypeError {
1752                expected: "Bytes".to_string(),
1753                got: "Int".to_string(),
1754                src: None,
1755                span: Span::DUMMY,
1756            })
1757        );
1758    }
1759
1760    #[test]
1761    fn test_min_utxo_analysis() {
1762        let mut ast = crate::parsing::parse_string(
1763            r#"
1764        party Alice;
1765        tx test() {
1766            output my_output {
1767                to: Alice,
1768                amount: min_utxo(my_output),
1769            }
1770        }
1771    "#,
1772        )
1773        .unwrap();
1774
1775        let result = analyze(&mut ast);
1776        assert!(result.errors.is_empty());
1777    }
1778
1779    #[test]
1780    fn test_alias_undefined_type_error() {
1781        let mut ast = crate::parsing::parse_string(
1782            r#"
1783        type MyAlias = UndefinedType;
1784    "#,
1785        )
1786        .unwrap();
1787
1788        let result = analyze(&mut ast);
1789
1790        assert!(!result.errors.is_empty());
1791        assert!(result
1792            .errors
1793            .iter()
1794            .any(|e| matches!(e, Error::NotInScope(_))));
1795    }
1796
1797    #[test]
1798    fn test_alias_valid_type_success() {
1799        let mut ast = crate::parsing::parse_string(
1800            r#"
1801        type Address = Bytes;
1802        type Amount = Int;
1803        type ValidAlias = Address;
1804    "#,
1805        )
1806        .unwrap();
1807
1808        let result = analyze(&mut ast);
1809
1810        assert!(result.errors.is_empty());
1811    }
1812
1813    #[test]
1814    fn test_min_utxo_undefined_output_error() {
1815        let mut ast = crate::parsing::parse_string(
1816            r#"
1817        party Alice;
1818        tx test() {
1819            output {
1820                to: Alice,
1821                amount: min_utxo(nonexistent_output),
1822            }
1823        }
1824    "#,
1825        )
1826        .unwrap();
1827
1828        let result = analyze(&mut ast);
1829        assert!(!result.errors.is_empty());
1830    }
1831
1832    #[test]
1833    fn test_time_and_slot_conversion() {
1834        let mut ast = crate::parsing::parse_string(
1835            r#"
1836        party Sender;
1837
1838        type TimestampDatum {
1839            slot_time: Int,
1840            time: Int,
1841        }
1842
1843        tx create_timestamp_tx() {
1844            input source {
1845                from: Sender,
1846                min_amount: Ada(2000000),
1847            }
1848
1849            output timestamp_output {
1850                to: Sender,
1851                amount: source - fees,
1852                datum: TimestampDatum {
1853                    slot_time: time_to_slot(1666716638000),
1854                    time: slot_to_time(60638),
1855                },
1856            }
1857        }
1858        "#,
1859        )
1860        .unwrap();
1861
1862        let result = analyze(&mut ast);
1863        assert!(result.errors.is_empty());
1864    }
1865
1866    #[test]
1867    fn test_optional_output_with_datum_error() {
1868        let mut ast = crate::parsing::parse_string(
1869            r#"
1870        party Alice;
1871        type MyDatum {
1872            field1: Int,
1873        }
1874        tx test() {
1875            output ? my_output {
1876                to: Alice,
1877                amount: Ada(1),
1878                datum: MyDatum { field1: 1, },
1879            }
1880        }
1881    "#,
1882        )
1883        .unwrap();
1884
1885        let report = analyze(&mut ast);
1886
1887        assert!(!report.errors.is_empty());
1888        assert!(report
1889            .errors
1890            .iter()
1891            .any(|e| matches!(e, Error::InvalidOptionalOutput(_))));
1892    }
1893
1894    #[test]
1895    fn test_optional_output_ok() {
1896        let mut ast = crate::parsing::parse_string(
1897            r#"
1898        party Alice;
1899
1900        tx test() {
1901            output ? my_output {
1902                to: Alice,
1903                amount: Ada(0),
1904            }
1905        }
1906    "#,
1907        )
1908        .unwrap();
1909
1910        let report = analyze(&mut ast);
1911        assert!(report.errors.is_empty());
1912    }
1913
1914    #[test]
1915    fn test_fn_call_too_few_args() {
1916        let mut ast = crate::parsing::parse_string(
1917            r#"
1918        party Alice;
1919
1920        fn double(x: Int) -> Int {
1921            x + x
1922        }
1923
1924        tx t() {
1925            input source {
1926                from: Alice,
1927                min_amount: Ada(2),
1928            }
1929            output {
1930                to: Alice,
1931                amount: Ada(double()),
1932            }
1933        }
1934    "#,
1935        )
1936        .unwrap();
1937
1938        let report = analyze(&mut ast);
1939
1940        assert!(report.errors.iter().any(|e| matches!(
1941            e,
1942            Error::Arity(a) if a.name == "double" && a.expected == 1 && a.got == 0
1943        )));
1944    }
1945
1946    #[test]
1947    fn test_fn_call_too_many_args() {
1948        let mut ast = crate::parsing::parse_string(
1949            r#"
1950        party Alice;
1951
1952        fn double(x: Int) -> Int {
1953            x + x
1954        }
1955
1956        tx t() {
1957            input source {
1958                from: Alice,
1959                min_amount: Ada(2),
1960            }
1961            output {
1962                to: Alice,
1963                amount: Ada(double(1, 2)),
1964            }
1965        }
1966    "#,
1967        )
1968        .unwrap();
1969
1970        let report = analyze(&mut ast);
1971
1972        assert!(report.errors.iter().any(|e| matches!(
1973            e,
1974            Error::Arity(a) if a.name == "double" && a.expected == 1 && a.got == 2
1975        )));
1976    }
1977
1978    #[test]
1979    fn test_fn_call_correct_arity_ok() {
1980        let mut ast = crate::parsing::parse_string(
1981            r#"
1982        party Alice;
1983
1984        fn double(x: Int) -> Int {
1985            x + x
1986        }
1987
1988        tx t() {
1989            input source {
1990                from: Alice,
1991                min_amount: Ada(2),
1992            }
1993            output {
1994                to: Alice,
1995                amount: Ada(double(2)),
1996            }
1997        }
1998    "#,
1999        )
2000        .unwrap();
2001
2002        let report = analyze(&mut ast);
2003        assert!(!report.errors.iter().any(|e| matches!(e, Error::Arity(_))));
2004    }
2005
2006    #[test]
2007    fn test_builtin_call_wrong_arity() {
2008        let mut ast = crate::parsing::parse_string(
2009            r#"
2010        party Alice;
2011
2012        tx t() {
2013            input source {
2014                from: Alice,
2015                min_amount: Ada(2),
2016            }
2017            output {
2018                to: Alice,
2019                amount: min_utxo(),
2020            }
2021        }
2022    "#,
2023        )
2024        .unwrap();
2025
2026        let report = analyze(&mut ast);
2027
2028        assert!(report.errors.iter().any(|e| matches!(
2029            e,
2030            Error::Arity(a) if a.name == "min_utxo" && a.expected == 1 && a.got == 0
2031        )));
2032    }
2033
2034    #[test]
2035    fn test_metadata_value_size_validation_string_within_limit() {
2036        let mut ast = crate::parsing::parse_string(
2037            r#"
2038        tx test() {
2039            metadata {
2040                123: "This is a short string that is within the 64-byte limit",
2041            }
2042        }
2043    "#,
2044        )
2045        .unwrap();
2046
2047        let result = analyze(&mut ast);
2048
2049        assert!(
2050            result.errors.is_empty(),
2051            "Expected no errors for string within limit, but got: {:?}",
2052            result.errors
2053        );
2054    }
2055
2056    #[test]
2057    fn test_metadata_value_size_validation_string_exceeds_limit() {
2058        let mut ast = crate::parsing::parse_string(
2059            r#"
2060        tx test() {
2061            metadata {
2062                123: "This is a very long string that definitely exceeds the 64-byte limit here",
2063            }
2064        }
2065    "#,
2066        )
2067        .unwrap();
2068
2069        let result = analyze(&mut ast);
2070        assert_eq!(result.errors.len(), 1);
2071
2072        match &result.errors[0] {
2073            Error::MetadataSizeLimitExceeded(error) => {
2074                assert_eq!(error.size, 73);
2075            }
2076            _ => panic!(
2077                "Expected MetadataSizeLimitExceeded error, got: {:?}",
2078                result.errors[0]
2079            ),
2080        }
2081    }
2082
2083    #[test]
2084    fn test_metadata_value_size_validation_hex_string_within_limit() {
2085        let mut ast = crate::parsing::parse_string(
2086            r#"
2087        tx test() {
2088            metadata {
2089                123: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef,
2090            }
2091        }
2092    "#,
2093        )
2094        .unwrap();
2095
2096        let result = analyze(&mut ast);
2097        assert!(
2098            result.errors.is_empty(),
2099            "Expected no errors for hex string within limit, but got: {:?}",
2100            result.errors
2101        );
2102    }
2103
2104    #[test]
2105    fn test_metadata_value_size_validation_hex_string_exceeds_limit() {
2106        let mut ast = crate::parsing::parse_string(
2107            r#"
2108        tx test() {
2109            metadata {
2110                123: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12,
2111            }
2112        }
2113    "#,
2114        )
2115        .unwrap();
2116
2117        let result = analyze(&mut ast);
2118        assert_eq!(result.errors.len(), 1);
2119
2120        match &result.errors[0] {
2121            Error::MetadataSizeLimitExceeded(error) => {
2122                assert_eq!(error.size, 65);
2123            }
2124            _ => panic!(
2125                "Expected MetadataSizeLimitExceeded error, got: {:?}",
2126                result.errors[0]
2127            ),
2128        }
2129    }
2130
2131    #[test]
2132    fn test_metadata_value_size_validation_multiple_fields() {
2133        let mut ast = crate::parsing::parse_string(
2134            r#"
2135        tx test() {
2136            metadata {
2137                123: "Short string",
2138                456: "This is a very long string that definitely exceeds the 64-byte limit here",
2139                789: "Another short one",
2140            }
2141        }
2142    "#,
2143        )
2144        .unwrap();
2145
2146        let result = analyze(&mut ast);
2147        assert_eq!(result.errors.len(), 1);
2148
2149        match &result.errors[0] {
2150            Error::MetadataSizeLimitExceeded(error) => {
2151                assert_eq!(error.size, 73);
2152            }
2153            _ => panic!(
2154                "Expected MetadataSizeLimitExceeded error, got: {:?}",
2155                result.errors[0]
2156            ),
2157        }
2158    }
2159
2160    #[test]
2161    fn test_metadata_value_size_validation_non_literal_expression() {
2162        let mut ast = crate::parsing::parse_string(
2163            r#"
2164        party Alice;
2165
2166        tx test(my_param: Bytes) {
2167            metadata {
2168                123: my_param,
2169            }
2170        }
2171    "#,
2172        )
2173        .unwrap();
2174
2175        let result = analyze(&mut ast);
2176        let metadata_errors: Vec<_> = result
2177            .errors
2178            .iter()
2179            .filter(|e| matches!(e, Error::MetadataSizeLimitExceeded(_)))
2180            .collect();
2181        assert!(
2182            metadata_errors.is_empty(),
2183            "Expected no metadata size errors for non-literal expressions"
2184        );
2185    }
2186
2187    #[test]
2188    fn test_metadata_key_type_validation_string_key() {
2189        let mut ast = crate::parsing::parse_string(
2190            r#"
2191        tx test() {
2192            metadata {
2193                "invalid_key": "some value",
2194            }
2195        }
2196    "#,
2197        )
2198        .unwrap();
2199
2200        let result = analyze(&mut ast);
2201        assert_eq!(result.errors.len(), 1);
2202
2203        match &result.errors[0] {
2204            Error::MetadataInvalidKeyType(error) => {
2205                assert_eq!(error.key_type, "string");
2206            }
2207            _ => panic!(
2208                "Expected MetadataInvalidKeyType error, got: {:?}",
2209                result.errors[0]
2210            ),
2211        }
2212    }
2213
2214    #[test]
2215    fn test_metadata_key_type_validation_identifier_with_int_type() {
2216        let mut ast = crate::parsing::parse_string(
2217            r#"
2218        tx test(my_key: Int) {
2219            metadata {
2220                my_key: "valid value",
2221            }
2222        }
2223    "#,
2224        )
2225        .unwrap();
2226
2227        let result = analyze(&mut ast);
2228        let key_type_errors: Vec<_> = result
2229            .errors
2230            .iter()
2231            .filter(|e| matches!(e, Error::MetadataInvalidKeyType(_)))
2232            .collect();
2233        assert!(
2234            key_type_errors.is_empty(),
2235            "Expected no key type errors for Int parameter used as key"
2236        );
2237    }
2238
2239    #[test]
2240    fn test_metadata_key_type_validation_identifier_with_wrong_type() {
2241        let mut ast = crate::parsing::parse_string(
2242            r#"
2243        tx test(my_key: Bytes) {
2244            metadata {
2245                my_key: "some value",
2246            }
2247        }
2248    "#,
2249        )
2250        .unwrap();
2251
2252        let result = analyze(&mut ast);
2253        assert_eq!(result.errors.len(), 1);
2254
2255        match &result.errors[0] {
2256            Error::MetadataInvalidKeyType(error) => {
2257                assert!(error.key_type.contains("identifier of type Bytes"));
2258            }
2259            _ => panic!(
2260                "Expected MetadataInvalidKeyType error, got: {:?}",
2261                result.errors[0]
2262            ),
2263        }
2264    }
2265}