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            _ => unreachable!(),
656        };
657
658        for case in type_def.cases.iter() {
659            scope.track_variant_case(case);
660        }
661
662        self.scope = Some(Rc::new(scope));
663
664        let case = self.case.analyze(self.scope.clone());
665
666        r#type + case
667    }
668
669    fn is_resolved(&self) -> bool {
670        self.r#type.is_resolved() && self.case.is_resolved()
671    }
672}
673
674impl Analyzable for ListConstructor {
675    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
676        self.elements.analyze(parent)
677    }
678
679    fn is_resolved(&self) -> bool {
680        self.elements.is_resolved()
681    }
682}
683
684impl Analyzable for MapField {
685    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
686        self.key.analyze(parent.clone()) + self.value.analyze(parent.clone())
687    }
688
689    fn is_resolved(&self) -> bool {
690        self.key.is_resolved() && self.value.is_resolved()
691    }
692}
693
694impl Analyzable for MapConstructor {
695    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
696        self.fields.analyze(parent)
697    }
698
699    fn is_resolved(&self) -> bool {
700        self.fields.is_resolved()
701    }
702}
703
704impl Analyzable for DataExpr {
705    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
706        match self {
707            DataExpr::StructConstructor(x) => x.analyze(parent),
708            DataExpr::ListConstructor(x) => x.analyze(parent),
709            DataExpr::MapConstructor(x) => x.analyze(parent),
710            DataExpr::Identifier(x) => x.analyze(parent),
711            DataExpr::AddOp(x) => x.analyze(parent),
712            DataExpr::SubOp(x) => x.analyze(parent),
713            DataExpr::NegateOp(x) => x.analyze(parent),
714            DataExpr::PropertyOp(x) => x.analyze(parent),
715            DataExpr::AnyAssetConstructor(x) => x.analyze(parent),
716            DataExpr::FnCall(x) => x.analyze(parent),
717            DataExpr::MinUtxo(x) => x.analyze(parent),
718            DataExpr::SlotToTime(x) => x.analyze(parent),
719            DataExpr::TimeToSlot(x) => x.analyze(parent),
720            DataExpr::ConcatOp(x) => x.analyze(parent),
721            _ => AnalyzeReport::default(),
722        }
723    }
724
725    fn is_resolved(&self) -> bool {
726        match self {
727            DataExpr::StructConstructor(x) => x.is_resolved(),
728            DataExpr::ListConstructor(x) => x.is_resolved(),
729            DataExpr::MapConstructor(x) => x.is_resolved(),
730            DataExpr::Identifier(x) => x.is_resolved(),
731            DataExpr::AddOp(x) => x.is_resolved(),
732            DataExpr::SubOp(x) => x.is_resolved(),
733            DataExpr::NegateOp(x) => x.is_resolved(),
734            DataExpr::PropertyOp(x) => x.is_resolved(),
735            DataExpr::AnyAssetConstructor(x) => x.is_resolved(),
736            DataExpr::FnCall(x) => x.is_resolved(),
737            DataExpr::MinUtxo(x) => x.is_resolved(),
738            DataExpr::SlotToTime(x) => x.is_resolved(),
739            DataExpr::TimeToSlot(x) => x.is_resolved(),
740            DataExpr::ConcatOp(x) => x.is_resolved(),
741            _ => true,
742        }
743    }
744}
745
746impl Analyzable for crate::ast::FnCall {
747    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
748        let callee = self.callee.analyze(parent.clone());
749
750        let mut args_report = AnalyzeReport::default();
751
752        for arg in &mut self.args {
753            args_report = args_report + arg.analyze(parent.clone());
754        }
755
756        callee + args_report
757    }
758
759    fn is_resolved(&self) -> bool {
760        self.callee.is_resolved() && self.args.iter().all(|arg| arg.is_resolved())
761    }
762}
763
764impl Analyzable for AnyAssetConstructor {
765    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
766        let policy = self.policy.analyze(parent.clone());
767        let asset_name = self.asset_name.analyze(parent.clone());
768        let amount = self.amount.analyze(parent.clone());
769
770        policy + asset_name + amount
771    }
772
773    fn is_resolved(&self) -> bool {
774        self.policy.is_resolved() && self.asset_name.is_resolved() && self.amount.is_resolved()
775    }
776}
777
778impl Analyzable for PropertyOp {
779    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
780        let object = self.operand.analyze(parent.clone());
781
782        let mut scope = Scope::new(parent);
783
784        if let Some(ty) = self.operand.target_type() {
785            scope.track_record_fields_for_type(&ty);
786        }
787
788        self.scope = Some(Rc::new(scope));
789
790        let path = self.property.analyze(self.scope.clone());
791
792        object + path
793    }
794
795    fn is_resolved(&self) -> bool {
796        self.operand.is_resolved() && self.property.is_resolved()
797    }
798}
799
800impl Analyzable for AddressExpr {
801    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
802        match self {
803            AddressExpr::Identifier(x) => x.analyze(parent),
804            _ => AnalyzeReport::default(),
805        }
806    }
807
808    fn is_resolved(&self) -> bool {
809        match self {
810            AddressExpr::Identifier(x) => x.is_resolved(),
811            _ => true,
812        }
813    }
814}
815
816impl Analyzable for AssetDef {
817    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
818        let policy = self.policy.analyze(parent.clone());
819        let asset_name = self.asset_name.analyze(parent.clone());
820
821        let policy_type = AnalyzeReport::expect_data_expr_type(&self.policy, &Type::Bytes);
822        let asset_name_type = AnalyzeReport::expect_data_expr_type(&self.asset_name, &Type::Bytes);
823
824        policy + asset_name + policy_type + asset_name_type
825    }
826
827    fn is_resolved(&self) -> bool {
828        self.policy.is_resolved() && self.asset_name.is_resolved()
829    }
830}
831
832impl Analyzable for Identifier {
833    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
834        let symbol = parent.and_then(|p| p.resolve(&self.value));
835
836        if symbol.is_none() {
837            bail_report!(Error::not_in_scope(self.value.clone(), self));
838        }
839
840        self.symbol = symbol;
841
842        AnalyzeReport::default()
843    }
844
845    fn is_resolved(&self) -> bool {
846        self.symbol.is_some()
847    }
848}
849
850impl Analyzable for Type {
851    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
852        match self {
853            Type::Custom(x) => x.analyze(parent),
854            Type::List(x) => x.analyze(parent),
855            Type::Map(key_type, value_type) => {
856                key_type.analyze(parent.clone()) + value_type.analyze(parent)
857            }
858            _ => AnalyzeReport::default(),
859        }
860    }
861
862    fn is_resolved(&self) -> bool {
863        match self {
864            Type::Custom(x) => x.is_resolved(),
865            Type::List(x) => x.is_resolved(),
866            Type::Map(key_type, value_type) => key_type.is_resolved() && value_type.is_resolved(),
867            _ => true,
868        }
869    }
870}
871
872impl Analyzable for InputBlockField {
873    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
874        match self {
875            InputBlockField::From(x) => x.analyze(parent),
876            InputBlockField::DatumIs(x) => x.analyze(parent),
877            InputBlockField::MinAmount(x) => x.analyze(parent),
878            InputBlockField::Redeemer(x) => x.analyze(parent),
879            InputBlockField::Ref(x) => x.analyze(parent),
880        }
881    }
882
883    fn is_resolved(&self) -> bool {
884        match self {
885            InputBlockField::From(x) => x.is_resolved(),
886            InputBlockField::DatumIs(x) => x.is_resolved(),
887            InputBlockField::MinAmount(x) => x.is_resolved(),
888            InputBlockField::Redeemer(x) => x.is_resolved(),
889            InputBlockField::Ref(x) => x.is_resolved(),
890        }
891    }
892}
893
894impl Analyzable for InputBlock {
895    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
896        self.fields.analyze(parent)
897    }
898
899    fn is_resolved(&self) -> bool {
900        self.fields.is_resolved()
901    }
902}
903
904fn validate_metadata_value_size(expr: &DataExpr) -> Result<(), MetadataSizeLimitError> {
905    match expr {
906        DataExpr::String(string_literal) => {
907            let utf8_bytes = string_literal.value.as_bytes();
908            if utf8_bytes.len() > METADATA_MAX_SIZE_BYTES {
909                return Err(MetadataSizeLimitError {
910                    size: utf8_bytes.len(),
911                    src: None,
912                    span: string_literal.span.clone(),
913                });
914            }
915        }
916        DataExpr::HexString(hex_literal) => {
917            let hex_str = &hex_literal.value;
918            let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
919            let byte_length = hex_str.len() / 2;
920
921            if byte_length > METADATA_MAX_SIZE_BYTES {
922                return Err(MetadataSizeLimitError {
923                    size: byte_length,
924                    src: None,
925                    span: hex_literal.span.clone(),
926                });
927            }
928        }
929        _ => {}
930    }
931    Ok(())
932}
933
934fn validate_metadata_key_type(expr: &DataExpr) -> Result<(), MetadataInvalidKeyTypeError> {
935    match expr {
936        DataExpr::Number(_) => Ok(()),
937        DataExpr::Identifier(id) => match id.target_type() {
938            Some(Type::Int) => Ok(()),
939            Some(other_type) => Err(MetadataInvalidKeyTypeError {
940                key_type: format!("identifier of type {}", other_type),
941                src: None,
942                span: id.span().clone(),
943            }),
944            None => Err(MetadataInvalidKeyTypeError {
945                key_type: "unresolved identifier".to_string(),
946                src: None,
947                span: id.span().clone(),
948            }),
949        },
950        _ => {
951            let key_type = match expr {
952                DataExpr::String(_) => "string",
953                DataExpr::HexString(_) => "hex string",
954                DataExpr::ListConstructor(_) => "list",
955                DataExpr::MapConstructor(_) => "map",
956                DataExpr::StructConstructor(_) => "struct",
957                _ => "unknown",
958            };
959
960            Err(MetadataInvalidKeyTypeError {
961                key_type: key_type.to_string(),
962                src: None,
963                span: expr.span().clone(),
964            })
965        }
966    }
967}
968
969impl Analyzable for MetadataBlockField {
970    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
971        let mut report = self.key.analyze(parent.clone()) + self.value.analyze(parent.clone());
972
973        validate_metadata_key_type(&self.key)
974            .map_err(Error::MetadataInvalidKeyType)
975            .err()
976            .map(|e| report.errors.push(e));
977
978        validate_metadata_value_size(&self.value)
979            .map_err(Error::MetadataSizeLimitExceeded)
980            .err()
981            .map(|e| report.errors.push(e));
982
983        report
984    }
985
986    fn is_resolved(&self) -> bool {
987        self.key.is_resolved() && self.value.is_resolved()
988    }
989}
990
991impl Analyzable for MetadataBlock {
992    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
993        self.fields.analyze(parent)
994    }
995
996    fn is_resolved(&self) -> bool {
997        self.fields.is_resolved()
998    }
999}
1000
1001impl Analyzable for ValidityBlockField {
1002    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1003        match self {
1004            ValidityBlockField::SinceSlot(x) => x.analyze(parent),
1005            ValidityBlockField::UntilSlot(x) => x.analyze(parent),
1006        }
1007    }
1008    fn is_resolved(&self) -> bool {
1009        match self {
1010            ValidityBlockField::SinceSlot(x) => x.is_resolved(),
1011            ValidityBlockField::UntilSlot(x) => x.is_resolved(),
1012        }
1013    }
1014}
1015
1016impl Analyzable for ValidityBlock {
1017    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1018        self.fields.analyze(parent)
1019    }
1020
1021    fn is_resolved(&self) -> bool {
1022        self.fields.is_resolved()
1023    }
1024}
1025
1026impl Analyzable for OutputBlockField {
1027    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1028        match self {
1029            OutputBlockField::To(x) => x.analyze(parent),
1030            OutputBlockField::Amount(x) => x.analyze(parent),
1031            OutputBlockField::Datum(x) => x.analyze(parent),
1032        }
1033    }
1034
1035    fn is_resolved(&self) -> bool {
1036        match self {
1037            OutputBlockField::To(x) => x.is_resolved(),
1038            OutputBlockField::Amount(x) => x.is_resolved(),
1039            OutputBlockField::Datum(x) => x.is_resolved(),
1040        }
1041    }
1042}
1043
1044impl Analyzable for OutputBlock {
1045    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1046        validate_optional_output(self)
1047            .map(AnalyzeReport::from)
1048            .unwrap_or_else(|| AnalyzeReport::default())
1049            + self.fields.analyze(parent)
1050    }
1051
1052    fn is_resolved(&self) -> bool {
1053        self.fields.is_resolved()
1054    }
1055}
1056
1057fn validate_optional_output(output: &OutputBlock) -> Option<Error> {
1058    if output.optional {
1059        if let Some(_field) = output.find("datum") {
1060            return Some(Error::InvalidOptionalOutput(OptionalOutputError {
1061                name: output
1062                    .name
1063                    .as_ref()
1064                    .map(|i| i.value.clone())
1065                    .unwrap_or_else(|| "<anonymous>".to_string()),
1066                src: None,
1067                span: output.span.clone(),
1068            }));
1069        }
1070    }
1071
1072    None
1073}
1074
1075impl Analyzable for RecordField {
1076    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1077        self.r#type.analyze(parent)
1078    }
1079
1080    fn is_resolved(&self) -> bool {
1081        self.r#type.is_resolved()
1082    }
1083}
1084
1085impl Analyzable for VariantCase {
1086    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1087        self.fields.analyze(parent)
1088    }
1089
1090    fn is_resolved(&self) -> bool {
1091        self.fields.is_resolved()
1092    }
1093}
1094
1095impl Analyzable for AliasDef {
1096    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1097        self.alias_type.analyze(parent)
1098    }
1099
1100    fn is_resolved(&self) -> bool {
1101        self.alias_type.is_resolved() && self.is_alias_chain_resolved()
1102    }
1103}
1104
1105impl Analyzable for TypeDef {
1106    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1107        self.cases.analyze(parent)
1108    }
1109
1110    fn is_resolved(&self) -> bool {
1111        self.cases.is_resolved()
1112    }
1113}
1114
1115impl Analyzable for MintBlockField {
1116    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1117        match self {
1118            MintBlockField::Amount(x) => x.analyze(parent),
1119            MintBlockField::Redeemer(x) => x.analyze(parent),
1120        }
1121    }
1122
1123    fn is_resolved(&self) -> bool {
1124        match self {
1125            MintBlockField::Amount(x) => x.is_resolved(),
1126            MintBlockField::Redeemer(x) => x.is_resolved(),
1127        }
1128    }
1129}
1130
1131impl Analyzable for MintBlock {
1132    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1133        self.fields.analyze(parent)
1134    }
1135
1136    fn is_resolved(&self) -> bool {
1137        self.fields.is_resolved()
1138    }
1139}
1140
1141impl Analyzable for SignersBlock {
1142    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1143        self.signers.analyze(parent)
1144    }
1145
1146    fn is_resolved(&self) -> bool {
1147        self.signers.is_resolved()
1148    }
1149}
1150
1151impl Analyzable for ReferenceBlock {
1152    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1153        self.r#ref.analyze(parent.clone()) + self.datum_is.analyze(parent)
1154    }
1155
1156    fn is_resolved(&self) -> bool {
1157        self.r#ref.is_resolved() && self.datum_is.is_resolved()
1158    }
1159}
1160
1161impl Analyzable for CollateralBlockField {
1162    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1163        match self {
1164            CollateralBlockField::From(x) => x.analyze(parent),
1165            CollateralBlockField::MinAmount(x) => x.analyze(parent),
1166            CollateralBlockField::Ref(x) => x.analyze(parent),
1167        }
1168    }
1169
1170    fn is_resolved(&self) -> bool {
1171        match self {
1172            CollateralBlockField::From(x) => x.is_resolved(),
1173            CollateralBlockField::MinAmount(x) => x.is_resolved(),
1174            CollateralBlockField::Ref(x) => x.is_resolved(),
1175        }
1176    }
1177}
1178
1179impl Analyzable for CollateralBlock {
1180    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1181        self.fields.analyze(parent)
1182    }
1183
1184    fn is_resolved(&self) -> bool {
1185        self.fields.is_resolved()
1186    }
1187}
1188
1189impl Analyzable for ChainSpecificBlock {
1190    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1191        match self {
1192            ChainSpecificBlock::Cardano(x) => x.analyze(parent),
1193        }
1194    }
1195
1196    fn is_resolved(&self) -> bool {
1197        match self {
1198            ChainSpecificBlock::Cardano(x) => x.is_resolved(),
1199        }
1200    }
1201}
1202
1203impl Analyzable for LocalsAssign {
1204    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1205        self.value.analyze(parent)
1206    }
1207
1208    fn is_resolved(&self) -> bool {
1209        self.value.is_resolved()
1210    }
1211}
1212
1213impl Analyzable for LocalsBlock {
1214    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1215        self.assigns.analyze(parent)
1216    }
1217
1218    fn is_resolved(&self) -> bool {
1219        self.assigns.is_resolved()
1220    }
1221}
1222
1223impl Analyzable for ParamDef {
1224    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1225        self.r#type.analyze(parent)
1226    }
1227
1228    fn is_resolved(&self) -> bool {
1229        self.r#type.is_resolved()
1230    }
1231}
1232
1233impl Analyzable for ParameterList {
1234    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1235        self.parameters.analyze(parent)
1236    }
1237
1238    fn is_resolved(&self) -> bool {
1239        self.parameters.is_resolved()
1240    }
1241}
1242
1243impl TxDef {
1244    // best effort to analyze artifacts that might be circularly dependent on each other
1245    fn best_effort_analyze_circular_dependencies(&mut self, mut scope: Scope) -> Scope {
1246        if let Some(locals) = &self.locals {
1247            for assign in locals.assigns.iter() {
1248                scope.track_local_expr(&assign.name.value, assign.value.clone());
1249            }
1250        }
1251
1252        for (_, input) in self.inputs.iter().enumerate() {
1253            scope.track_input(&input.name, input.clone())
1254        }
1255
1256        for reference in self.references.iter() {
1257            scope.track_reference(&reference.name, reference.clone());
1258        }
1259
1260        for (index, output) in self.outputs.iter().enumerate() {
1261            scope.track_output(index, output.clone())
1262        }
1263
1264        let scope_snapshot = Rc::new(scope);
1265        let _ = self.locals.analyze(Some(scope_snapshot.clone()));
1266        let _ = self.references.analyze(Some(scope_snapshot.clone()));
1267        let _ = self.inputs.analyze(Some(scope_snapshot.clone()));
1268        let _ = self.outputs.analyze(Some(scope_snapshot.clone()));
1269
1270        Scope::new(Some(scope_snapshot))
1271    }
1272}
1273
1274impl Analyzable for TxDef {
1275    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1276        // analyze static types before anything else
1277        let params = self.parameters.analyze(parent.clone());
1278
1279        // create the new scope and populate its symbols
1280
1281        let mut scope = Scope::new(parent.clone());
1282
1283        scope.symbols.insert("fees".to_string(), Symbol::Fees);
1284
1285        for param in self.parameters.parameters.iter() {
1286            scope.track_param_var(&param.name.value, param.r#type.clone());
1287        }
1288
1289        for _ in 0..9 {
1290            scope = self.best_effort_analyze_circular_dependencies(scope);
1291        }
1292
1293        let final_scope = Rc::new(scope);
1294
1295        let locals = self.locals.analyze(Some(final_scope.clone()));
1296        let inputs = self.inputs.analyze(Some(final_scope.clone()));
1297        let outputs = self.outputs.analyze(Some(final_scope.clone()));
1298        let mints = self.mints.analyze(Some(final_scope.clone()));
1299        let burns = self.burns.analyze(Some(final_scope.clone()));
1300        let adhoc = self.adhoc.analyze(Some(final_scope.clone()));
1301        let validity = self.validity.analyze(Some(final_scope.clone()));
1302        let metadata = self.metadata.analyze(Some(final_scope.clone()));
1303        let signers = self.signers.analyze(Some(final_scope.clone()));
1304        let references = self.references.analyze(Some(final_scope.clone()));
1305        let collateral = self.collateral.analyze(Some(final_scope.clone()));
1306
1307        self.scope = Some(final_scope);
1308
1309        params
1310            + locals
1311            + inputs
1312            + outputs
1313            + mints
1314            + burns
1315            + adhoc
1316            + validity
1317            + metadata
1318            + signers
1319            + references
1320            + collateral
1321    }
1322
1323    fn is_resolved(&self) -> bool {
1324        self.inputs.is_resolved()
1325            && self.outputs.is_resolved()
1326            && self.mints.is_resolved()
1327            && self.locals.is_resolved()
1328            && self.adhoc.is_resolved()
1329            && self.validity.is_resolved()
1330            && self.metadata.is_resolved()
1331            && self.signers.is_resolved()
1332            && self.references.is_resolved()
1333            && self.collateral.is_resolved()
1334    }
1335}
1336
1337fn ada_asset_def() -> AssetDef {
1338    AssetDef {
1339        name: Identifier {
1340            value: "Ada".to_string(),
1341            symbol: None,
1342            span: Span::DUMMY,
1343        },
1344        policy: DataExpr::None,
1345        asset_name: DataExpr::None,
1346        span: Span::DUMMY,
1347    }
1348}
1349
1350fn resolve_types_and_aliases(
1351    scope_rc: &mut Rc<Scope>,
1352    types: &mut Vec<TypeDef>,
1353    aliases: &mut Vec<AliasDef>,
1354) -> (AnalyzeReport, AnalyzeReport) {
1355    let mut types_report = AnalyzeReport::default();
1356    let mut aliases_report = AnalyzeReport::default();
1357
1358    let mut pass_count = 0usize;
1359    let max_passes = 100usize; // prevent infinite loops
1360
1361    while pass_count < max_passes && !(types.is_resolved() && aliases.is_resolved()) {
1362        pass_count += 1;
1363
1364        let scope = Rc::get_mut(scope_rc).expect("scope should be unique during resolution");
1365
1366        for type_def in types.iter() {
1367            scope.track_type_def(type_def);
1368        }
1369        for alias_def in aliases.iter() {
1370            scope.track_alias_def(alias_def);
1371        }
1372
1373        types_report = types.analyze(Some(scope_rc.clone()));
1374        aliases_report = aliases.analyze(Some(scope_rc.clone()));
1375    }
1376
1377    (types_report, aliases_report)
1378}
1379
1380impl Analyzable for Program {
1381    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1382        let mut scope = Scope::new(parent);
1383
1384        if let Some(env) = self.env.as_ref() {
1385            for field in env.fields.iter() {
1386                scope.track_env_var(&field.name, field.r#type.clone());
1387            }
1388        }
1389
1390        for party in self.parties.iter() {
1391            scope.track_party_def(party);
1392        }
1393
1394        for policy in self.policies.iter() {
1395            scope.track_policy_def(policy);
1396        }
1397
1398        scope.track_asset_def(&ada_asset_def());
1399
1400        for asset in self.assets.iter() {
1401            scope.track_asset_def(asset);
1402        }
1403
1404        for type_def in self.types.iter() {
1405            scope.track_type_def(type_def);
1406        }
1407
1408        for alias_def in self.aliases.iter() {
1409            scope.track_alias_def(alias_def);
1410        }
1411
1412        self.scope = Some(Rc::new(scope));
1413
1414        let parties = self.parties.analyze(self.scope.clone());
1415
1416        let policies = self.policies.analyze(self.scope.clone());
1417
1418        let assets = self.assets.analyze(self.scope.clone());
1419
1420        let mut types = self.types.clone();
1421        let mut aliases = self.aliases.clone();
1422
1423        let scope_rc = self.scope.as_mut().unwrap();
1424
1425        let (types, aliases) = resolve_types_and_aliases(scope_rc, &mut types, &mut aliases);
1426
1427        let txs = self.txs.analyze(self.scope.clone());
1428
1429        parties + policies + types + aliases + txs + assets
1430    }
1431
1432    fn is_resolved(&self) -> bool {
1433        self.policies.is_resolved()
1434            && self.types.is_resolved()
1435            && self.aliases.is_resolved()
1436            && self.txs.is_resolved()
1437            && self.assets.is_resolved()
1438    }
1439}
1440
1441/// Performs semantic analysis on a Tx3 program AST.
1442///
1443/// This function validates the entire program structure, checking for:
1444/// - Duplicate definitions
1445/// - Unknown symbol references
1446/// - Type correctness
1447/// - Other semantic constraints
1448///
1449/// # Arguments
1450/// * `ast` - Mutable reference to the program AST to analyze
1451///
1452/// # Returns
1453/// * `AnalyzeReport` of the analysis. Empty if no errors are found.
1454pub fn analyze(ast: &mut Program) -> AnalyzeReport {
1455    ast.analyze(None)
1456}
1457
1458#[cfg(test)]
1459mod tests {
1460    use crate::parsing::parse_well_known_example;
1461
1462    use super::*;
1463
1464    #[test]
1465    fn test_program_with_semantic_errors() {
1466        let mut ast = parse_well_known_example("semantic_errors");
1467
1468        let report = analyze(&mut ast);
1469
1470        assert_eq!(report.errors.len(), 3);
1471
1472        assert_eq!(
1473            report.errors[0],
1474            Error::NotInScope(NotInScopeError {
1475                name: "missing_symbol".to_string(),
1476                src: None,
1477                span: Span::DUMMY,
1478            })
1479        );
1480
1481        assert_eq!(
1482            report.errors[1],
1483            Error::InvalidTargetType(InvalidTargetTypeError {
1484                expected: "Bytes".to_string(),
1485                got: "Int".to_string(),
1486                src: None,
1487                span: Span::DUMMY,
1488            })
1489        );
1490
1491        assert_eq!(
1492            report.errors[2],
1493            Error::InvalidTargetType(InvalidTargetTypeError {
1494                expected: "Bytes".to_string(),
1495                got: "Int".to_string(),
1496                src: None,
1497                span: Span::DUMMY,
1498            })
1499        );
1500    }
1501
1502    #[test]
1503    fn test_min_utxo_analysis() {
1504        let mut ast = crate::parsing::parse_string(
1505            r#"
1506        party Alice;
1507        tx test() {
1508            output my_output {
1509                to: Alice,
1510                amount: min_utxo(my_output),
1511            }
1512        }
1513    "#,
1514        )
1515        .unwrap();
1516
1517        let result = analyze(&mut ast);
1518        assert!(result.errors.is_empty());
1519    }
1520
1521    #[test]
1522    fn test_alias_undefined_type_error() {
1523        let mut ast = crate::parsing::parse_string(
1524            r#"
1525        type MyAlias = UndefinedType;
1526    "#,
1527        )
1528        .unwrap();
1529
1530        let result = analyze(&mut ast);
1531
1532        assert!(!result.errors.is_empty());
1533        assert!(result
1534            .errors
1535            .iter()
1536            .any(|e| matches!(e, Error::NotInScope(_))));
1537    }
1538
1539    #[test]
1540    fn test_alias_valid_type_success() {
1541        let mut ast = crate::parsing::parse_string(
1542            r#"
1543        type Address = Bytes;
1544        type Amount = Int;
1545        type ValidAlias = Address;
1546    "#,
1547        )
1548        .unwrap();
1549
1550        let result = analyze(&mut ast);
1551
1552        assert!(result.errors.is_empty());
1553    }
1554
1555    #[test]
1556    fn test_min_utxo_undefined_output_error() {
1557        let mut ast = crate::parsing::parse_string(
1558            r#"
1559        party Alice;
1560        tx test() {
1561            output {
1562                to: Alice,
1563                amount: min_utxo(nonexistent_output),
1564            }
1565        }
1566    "#,
1567        )
1568        .unwrap();
1569
1570        let result = analyze(&mut ast);
1571        assert!(!result.errors.is_empty());
1572    }
1573
1574    #[test]
1575    fn test_time_and_slot_conversion() {
1576        let mut ast = crate::parsing::parse_string(
1577            r#"
1578        party Sender;
1579
1580        type TimestampDatum {
1581            slot_time: Int,
1582            time: Int,
1583        }
1584
1585        tx create_timestamp_tx() {
1586            input source {
1587                from: Sender,
1588                min_amount: Ada(2000000),
1589            }
1590
1591            output timestamp_output {
1592                to: Sender,
1593                amount: source - fees,
1594                datum: TimestampDatum {
1595                    slot_time: time_to_slot(1666716638000),
1596                    time: slot_to_time(60638),
1597                },
1598            }
1599        }
1600        "#,
1601        )
1602        .unwrap();
1603
1604        let result = analyze(&mut ast);
1605        assert!(result.errors.is_empty());
1606    }
1607
1608    #[test]
1609    fn test_optional_output_with_datum_error() {
1610        let mut ast = crate::parsing::parse_string(
1611            r#"
1612        party Alice;
1613        type MyDatum {
1614            field1: Int,
1615        }
1616        tx test() {
1617            output ? my_output {
1618                to: Alice,
1619                amount: Ada(1),
1620                datum: MyDatum { field1: 1, },
1621            }
1622        }
1623    "#,
1624        )
1625        .unwrap();
1626
1627        let report = analyze(&mut ast);
1628
1629        assert!(!report.errors.is_empty());
1630        assert!(report
1631            .errors
1632            .iter()
1633            .any(|e| matches!(e, Error::InvalidOptionalOutput(_))));
1634    }
1635
1636    #[test]
1637    fn test_optional_output_ok() {
1638        let mut ast = crate::parsing::parse_string(
1639            r#"
1640        party Alice;
1641
1642        tx test() {
1643            output ? my_output {
1644                to: Alice,
1645                amount: Ada(0),
1646            }
1647        }
1648    "#,
1649        )
1650        .unwrap();
1651
1652        let report = analyze(&mut ast);
1653        assert!(report.errors.is_empty());
1654    }
1655
1656    #[test]
1657    fn test_metadata_value_size_validation_string_within_limit() {
1658        let mut ast = crate::parsing::parse_string(
1659            r#"
1660        tx test() {
1661            metadata {
1662                123: "This is a short string that is within the 64-byte limit",
1663            }
1664        }
1665    "#,
1666        )
1667        .unwrap();
1668
1669        let result = analyze(&mut ast);
1670
1671        assert!(
1672            result.errors.is_empty(),
1673            "Expected no errors for string within limit, but got: {:?}",
1674            result.errors
1675        );
1676    }
1677
1678    #[test]
1679    fn test_metadata_value_size_validation_string_exceeds_limit() {
1680        let mut ast = crate::parsing::parse_string(
1681            r#"
1682        tx test() {
1683            metadata {
1684                123: "This is a very long string that definitely exceeds the 64-byte limit here",
1685            }
1686        }
1687    "#,
1688        )
1689        .unwrap();
1690
1691        let result = analyze(&mut ast);
1692        assert_eq!(result.errors.len(), 1);
1693
1694        match &result.errors[0] {
1695            Error::MetadataSizeLimitExceeded(error) => {
1696                assert_eq!(error.size, 73);
1697            }
1698            _ => panic!(
1699                "Expected MetadataSizeLimitExceeded error, got: {:?}",
1700                result.errors[0]
1701            ),
1702        }
1703    }
1704
1705    #[test]
1706    fn test_metadata_value_size_validation_hex_string_within_limit() {
1707        let mut ast = crate::parsing::parse_string(
1708            r#"
1709        tx test() {
1710            metadata {
1711                123: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef,
1712            }
1713        }
1714    "#,
1715        )
1716        .unwrap();
1717
1718        let result = analyze(&mut ast);
1719        assert!(
1720            result.errors.is_empty(),
1721            "Expected no errors for hex string within limit, but got: {:?}",
1722            result.errors
1723        );
1724    }
1725
1726    #[test]
1727    fn test_metadata_value_size_validation_hex_string_exceeds_limit() {
1728        let mut ast = crate::parsing::parse_string(
1729            r#"
1730        tx test() {
1731            metadata {
1732                123: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12,
1733            }
1734        }
1735    "#,
1736        )
1737        .unwrap();
1738
1739        let result = analyze(&mut ast);
1740        assert_eq!(result.errors.len(), 1);
1741
1742        match &result.errors[0] {
1743            Error::MetadataSizeLimitExceeded(error) => {
1744                assert_eq!(error.size, 65);
1745            }
1746            _ => panic!(
1747                "Expected MetadataSizeLimitExceeded error, got: {:?}",
1748                result.errors[0]
1749            ),
1750        }
1751    }
1752
1753    #[test]
1754    fn test_metadata_value_size_validation_multiple_fields() {
1755        let mut ast = crate::parsing::parse_string(
1756            r#"
1757        tx test() {
1758            metadata {
1759                123: "Short string",
1760                456: "This is a very long string that definitely exceeds the 64-byte limit here",
1761                789: "Another short one",
1762            }
1763        }
1764    "#,
1765        )
1766        .unwrap();
1767
1768        let result = analyze(&mut ast);
1769        assert_eq!(result.errors.len(), 1);
1770
1771        match &result.errors[0] {
1772            Error::MetadataSizeLimitExceeded(error) => {
1773                assert_eq!(error.size, 73);
1774            }
1775            _ => panic!(
1776                "Expected MetadataSizeLimitExceeded error, got: {:?}",
1777                result.errors[0]
1778            ),
1779        }
1780    }
1781
1782    #[test]
1783    fn test_metadata_value_size_validation_non_literal_expression() {
1784        let mut ast = crate::parsing::parse_string(
1785            r#"
1786        party Alice;
1787
1788        tx test(my_param: Bytes) {
1789            metadata {
1790                123: my_param,
1791            }
1792        }
1793    "#,
1794        )
1795        .unwrap();
1796
1797        let result = analyze(&mut ast);
1798        let metadata_errors: Vec<_> = result
1799            .errors
1800            .iter()
1801            .filter(|e| matches!(e, Error::MetadataSizeLimitExceeded(_)))
1802            .collect();
1803        assert!(
1804            metadata_errors.is_empty(),
1805            "Expected no metadata size errors for non-literal expressions"
1806        );
1807    }
1808
1809    #[test]
1810    fn test_metadata_key_type_validation_string_key() {
1811        let mut ast = crate::parsing::parse_string(
1812            r#"
1813        tx test() {
1814            metadata {
1815                "invalid_key": "some value",
1816            }
1817        }
1818    "#,
1819        )
1820        .unwrap();
1821
1822        let result = analyze(&mut ast);
1823        assert_eq!(result.errors.len(), 1);
1824
1825        match &result.errors[0] {
1826            Error::MetadataInvalidKeyType(error) => {
1827                assert_eq!(error.key_type, "string");
1828            }
1829            _ => panic!(
1830                "Expected MetadataInvalidKeyType error, got: {:?}",
1831                result.errors[0]
1832            ),
1833        }
1834    }
1835
1836    #[test]
1837    fn test_metadata_key_type_validation_identifier_with_int_type() {
1838        let mut ast = crate::parsing::parse_string(
1839            r#"
1840        tx test(my_key: Int) {
1841            metadata {
1842                my_key: "valid value",
1843            }
1844        }
1845    "#,
1846        )
1847        .unwrap();
1848
1849        let result = analyze(&mut ast);
1850        let key_type_errors: Vec<_> = result
1851            .errors
1852            .iter()
1853            .filter(|e| matches!(e, Error::MetadataInvalidKeyType(_)))
1854            .collect();
1855        assert!(
1856            key_type_errors.is_empty(),
1857            "Expected no key type errors for Int parameter used as key"
1858        );
1859    }
1860
1861    #[test]
1862    fn test_metadata_key_type_validation_identifier_with_wrong_type() {
1863        let mut ast = crate::parsing::parse_string(
1864            r#"
1865        tx test(my_key: Bytes) {
1866            metadata {
1867                my_key: "some value",
1868            }
1869        }
1870    "#,
1871        )
1872        .unwrap();
1873
1874        let result = analyze(&mut ast);
1875        assert_eq!(result.errors.len(), 1);
1876
1877        match &result.errors[0] {
1878            Error::MetadataInvalidKeyType(error) => {
1879                assert!(error.key_type.contains("identifier of type Bytes"));
1880            }
1881            _ => panic!(
1882                "Expected MetadataInvalidKeyType error, got: {:?}",
1883                result.errors[0]
1884            ),
1885        }
1886    }
1887}