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