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