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