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