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::*;
11
12#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq)]
13#[error("not in scope: {name}")]
14#[diagnostic(code(tx3::not_in_scope))]
15pub struct NotInScopeError {
16    pub name: String,
17
18    #[source_code]
19    src: Option<String>,
20
21    #[label]
22    span: Span,
23}
24
25#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq)]
26#[error("invalid symbol, expected {expected}, got {got}")]
27#[diagnostic(code(tx3::invalid_symbol))]
28pub struct InvalidSymbolError {
29    pub expected: &'static str,
30    pub got: String,
31
32    #[source_code]
33    src: Option<String>,
34
35    #[label]
36    span: Span,
37}
38
39#[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq)]
40#[error("invalid type ({got}), expected: {expected}")]
41#[diagnostic(code(tx3::invalid_type))]
42pub struct InvalidTargetTypeError {
43    pub expected: String,
44    pub got: String,
45
46    #[source_code]
47    src: Option<String>,
48
49    #[label]
50    span: Span,
51}
52
53#[derive(thiserror::Error, Debug, miette::Diagnostic, PartialEq, Eq)]
54pub enum Error {
55    #[error("duplicate definition: {0}")]
56    #[diagnostic(code(tx3::duplicate_definition))]
57    DuplicateDefinition(String),
58
59    #[error(transparent)]
60    #[diagnostic(transparent)]
61    NotInScope(#[from] NotInScopeError),
62
63    #[error("needs parent scope")]
64    #[diagnostic(code(tx3::needs_parent_scope))]
65    NeedsParentScope,
66
67    #[error(transparent)]
68    #[diagnostic(transparent)]
69    InvalidSymbol(#[from] InvalidSymbolError),
70
71    // Invalid type for extension
72    #[error(transparent)]
73    #[diagnostic(transparent)]
74    InvalidTargetType(#[from] InvalidTargetTypeError),
75}
76
77impl Error {
78    pub fn span(&self) -> &Span {
79        match self {
80            Self::NotInScope(x) => &x.span,
81            Self::InvalidSymbol(x) => &x.span,
82            Self::InvalidTargetType(x) => &x.span,
83            _ => &Span::DUMMY,
84        }
85    }
86
87    pub fn src(&self) -> Option<&str> {
88        match self {
89            Self::NotInScope(x) => x.src.as_deref(),
90            _ => None,
91        }
92    }
93
94    pub fn not_in_scope(name: String, ast: &impl crate::parsing::AstNode) -> Self {
95        Self::NotInScope(NotInScopeError {
96            name,
97            src: None,
98            span: ast.span().clone(),
99        })
100    }
101
102    pub fn invalid_symbol(
103        expected: &'static str,
104        got: &Symbol,
105        ast: &impl crate::parsing::AstNode,
106    ) -> Self {
107        Self::InvalidSymbol(InvalidSymbolError {
108            expected,
109            got: format!("{:?}", got),
110            src: None,
111            span: ast.span().clone(),
112        })
113    }
114
115    pub fn invalid_target_type(
116        expected: &Type,
117        got: &Type,
118        ast: &impl crate::parsing::AstNode,
119    ) -> Self {
120        Self::InvalidTargetType(InvalidTargetTypeError {
121            expected: expected.to_string(),
122            got: got.to_string(),
123            src: None,
124            span: ast.span().clone(),
125        })
126    }
127}
128
129#[derive(Debug, Default, thiserror::Error, Diagnostic)]
130#[error("analyze report")]
131pub struct AnalyzeReport {
132    #[related]
133    pub errors: Vec<Error>,
134}
135
136impl AnalyzeReport {
137    pub fn is_empty(&self) -> bool {
138        self.errors.is_empty()
139    }
140
141    pub fn ok(self) -> Result<(), Self> {
142        if self.is_empty() {
143            Ok(())
144        } else {
145            Err(self)
146        }
147    }
148
149    pub fn expect_data_expr_type(expr: &DataExpr, expected: &Type) -> Self {
150        if expr.target_type().as_ref() != Some(expected) {
151            Self::from(Error::invalid_target_type(
152                expected,
153                expr.target_type().as_ref().unwrap_or(&Type::Undefined),
154                expr,
155            ))
156        } else {
157            Self::default()
158        }
159    }
160}
161
162impl std::ops::Add for Error {
163    type Output = AnalyzeReport;
164
165    fn add(self, other: Self) -> Self::Output {
166        Self::Output {
167            errors: vec![self, other],
168        }
169    }
170}
171
172impl From<Error> for AnalyzeReport {
173    fn from(error: Error) -> Self {
174        Self {
175            errors: vec![error],
176        }
177    }
178}
179
180impl From<Vec<Error>> for AnalyzeReport {
181    fn from(errors: Vec<Error>) -> Self {
182        Self { errors }
183    }
184}
185
186impl std::ops::Add for AnalyzeReport {
187    type Output = AnalyzeReport;
188
189    fn add(self, other: Self) -> Self::Output {
190        [self, other].into_iter().collect()
191    }
192}
193
194impl FromIterator<Error> for AnalyzeReport {
195    fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
196        Self {
197            errors: iter.into_iter().collect(),
198        }
199    }
200}
201
202impl FromIterator<AnalyzeReport> for AnalyzeReport {
203    fn from_iter<T: IntoIterator<Item = AnalyzeReport>>(iter: T) -> Self {
204        Self {
205            errors: iter.into_iter().flat_map(|r| r.errors).collect(),
206        }
207    }
208}
209
210macro_rules! bail_report {
211    ($($args:expr),*) => {
212        { return AnalyzeReport::from(vec![$($args),*]); }
213    };
214}
215
216impl Scope {
217    pub fn new(parent: Option<Rc<Scope>>) -> Self {
218        Self {
219            symbols: HashMap::new(),
220            parent,
221        }
222    }
223
224    pub fn track_env_var(&mut self, name: &str, ty: Type) {
225        self.symbols.insert(
226            name.to_string(),
227            Symbol::EnvVar(name.to_string(), Box::new(ty)),
228        );
229    }
230
231    pub fn track_type_def(&mut self, type_: &TypeDef) {
232        self.symbols.insert(
233            type_.name.value.clone(),
234            Symbol::TypeDef(Box::new(type_.clone())),
235        );
236    }
237
238    pub fn track_variant_case(&mut self, case: &VariantCase) {
239        self.symbols.insert(
240            case.name.value.clone(),
241            Symbol::VariantCase(Box::new(case.clone())),
242        );
243    }
244
245    pub fn track_record_field(&mut self, field: &RecordField) {
246        self.symbols.insert(
247            field.name.value.clone(),
248            Symbol::RecordField(Box::new(field.clone())),
249        );
250    }
251
252    pub fn track_party_def(&mut self, party: &PartyDef) {
253        self.symbols.insert(
254            party.name.value.clone(),
255            Symbol::PartyDef(Box::new(party.clone())),
256        );
257    }
258
259    pub fn track_policy_def(&mut self, policy: &PolicyDef) {
260        self.symbols.insert(
261            policy.name.value.clone(),
262            Symbol::PolicyDef(Box::new(policy.clone())),
263        );
264    }
265
266    pub fn track_asset_def(&mut self, asset: &AssetDef) {
267        self.symbols.insert(
268            asset.name.value.clone(),
269            Symbol::AssetDef(Box::new(asset.clone())),
270        );
271    }
272
273    pub fn track_param_var(&mut self, param: &str, ty: Type) {
274        self.symbols.insert(
275            param.to_string(),
276            Symbol::ParamVar(param.to_string(), Box::new(ty)),
277        );
278    }
279
280    pub fn track_local_expr(&mut self, name: &str, expr: DataExpr) {
281        self.symbols
282            .insert(name.to_string(), Symbol::LocalExpr(Box::new(expr)));
283    }
284
285    pub fn track_input(&mut self, name: &str, input: InputBlock) {
286        self.symbols
287            .insert(name.to_string(), Symbol::Input(Box::new(input)));
288    }
289
290    pub fn track_record_fields_for_type(&mut self, ty: &Type) {
291        let schema = ty.properties();
292
293        for (name, subty) in schema {
294            self.track_record_field(&RecordField {
295                name: Identifier::new(name),
296                r#type: subty,
297                span: Span::DUMMY,
298            });
299        }
300    }
301
302    pub fn resolve(&self, name: &str) -> Option<Symbol> {
303        if let Some(symbol) = self.symbols.get(name) {
304            Some(symbol.clone())
305        } else if let Some(parent) = &self.parent {
306            parent.resolve(name)
307        } else {
308            None
309        }
310    }
311}
312
313/// A trait for types that can be semantically analyzed.
314///
315/// Types implementing this trait can validate their semantic correctness and
316/// resolve symbol references within a given scope.
317pub trait Analyzable {
318    /// Performs semantic analysis on the type.
319    ///
320    /// # Arguments
321    /// * `parent` - Optional parent scope containing symbol definitions
322    ///
323    /// # Returns
324    /// * `AnalyzeReport` of the analysis. Empty if no errors are found.
325    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport;
326
327    /// Returns true if all of the symbols have been resolved .
328    fn is_resolved(&self) -> bool;
329}
330
331impl<T: Analyzable> Analyzable for Option<T> {
332    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
333        if let Some(item) = self {
334            item.analyze(parent)
335        } else {
336            AnalyzeReport::default()
337        }
338    }
339
340    fn is_resolved(&self) -> bool {
341        self.as_ref().is_none_or(|x| x.is_resolved())
342    }
343}
344
345impl<T: Analyzable> Analyzable for Box<T> {
346    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
347        self.as_mut().analyze(parent)
348    }
349
350    fn is_resolved(&self) -> bool {
351        self.as_ref().is_resolved()
352    }
353}
354
355impl<T: Analyzable> Analyzable for Vec<T> {
356    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
357        self.iter_mut()
358            .map(|item| item.analyze(parent.clone()))
359            .collect()
360    }
361
362    fn is_resolved(&self) -> bool {
363        self.iter().all(|x| x.is_resolved())
364    }
365}
366
367impl Analyzable for PartyDef {
368    fn analyze(&mut self, _parent: Option<Rc<Scope>>) -> AnalyzeReport {
369        AnalyzeReport::default()
370    }
371
372    fn is_resolved(&self) -> bool {
373        true
374    }
375}
376
377impl Analyzable for PolicyField {
378    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
379        match self {
380            PolicyField::Hash(x) => x.analyze(parent),
381            PolicyField::Script(x) => x.analyze(parent),
382            PolicyField::Ref(x) => x.analyze(parent),
383        }
384    }
385
386    fn is_resolved(&self) -> bool {
387        match self {
388            PolicyField::Hash(x) => x.is_resolved(),
389            PolicyField::Script(x) => x.is_resolved(),
390            PolicyField::Ref(x) => x.is_resolved(),
391        }
392    }
393}
394impl Analyzable for PolicyConstructor {
395    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
396        self.fields.analyze(parent)
397    }
398
399    fn is_resolved(&self) -> bool {
400        self.fields.is_resolved()
401    }
402}
403
404impl Analyzable for PolicyDef {
405    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
406        match &mut self.value {
407            PolicyValue::Constructor(x) => x.analyze(parent),
408            PolicyValue::Assign(_) => AnalyzeReport::default(),
409        }
410    }
411
412    fn is_resolved(&self) -> bool {
413        match &self.value {
414            PolicyValue::Constructor(x) => x.is_resolved(),
415            PolicyValue::Assign(_) => true,
416        }
417    }
418}
419
420impl Analyzable for AddOp {
421    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
422        let left = self.lhs.analyze(parent.clone());
423        let right = self.rhs.analyze(parent.clone());
424
425        left + right
426    }
427
428    fn is_resolved(&self) -> bool {
429        self.lhs.is_resolved() && self.rhs.is_resolved()
430    }
431}
432
433impl Analyzable for SubOp {
434    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
435        let left = self.lhs.analyze(parent.clone());
436        let right = self.rhs.analyze(parent.clone());
437
438        left + right
439    }
440
441    fn is_resolved(&self) -> bool {
442        self.lhs.is_resolved() && self.rhs.is_resolved()
443    }
444}
445
446impl Analyzable for NegateOp {
447    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
448        self.operand.analyze(parent)
449    }
450
451    fn is_resolved(&self) -> bool {
452        self.operand.is_resolved()
453    }
454}
455
456impl Analyzable for RecordConstructorField {
457    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
458        let name = self.name.analyze(parent.clone());
459        let value = self.value.analyze(parent.clone());
460
461        name + value
462    }
463
464    fn is_resolved(&self) -> bool {
465        self.name.is_resolved() && self.value.is_resolved()
466    }
467}
468
469impl Analyzable for VariantCaseConstructor {
470    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
471        let name = self.name.analyze(parent.clone());
472
473        let mut scope = Scope::new(parent);
474
475        let case = match &self.name.symbol {
476            Some(Symbol::VariantCase(x)) => x,
477            Some(x) => bail_report!(Error::invalid_symbol("VariantCase", x, &self.name)),
478            None => bail_report!(Error::not_in_scope(self.name.value.clone(), &self.name)),
479        };
480
481        for field in case.fields.iter() {
482            scope.track_record_field(field);
483        }
484
485        self.scope = Some(Rc::new(scope));
486
487        let fields = self.fields.analyze(self.scope.clone());
488
489        let spread = self.spread.analyze(self.scope.clone());
490
491        name + fields + spread
492    }
493
494    fn is_resolved(&self) -> bool {
495        self.name.is_resolved() && self.fields.is_resolved() && self.spread.is_resolved()
496    }
497}
498
499impl Analyzable for StructConstructor {
500    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
501        let r#type = self.r#type.analyze(parent.clone());
502
503        let mut scope = Scope::new(parent);
504
505        let type_def = match &self.r#type.symbol {
506            Some(Symbol::TypeDef(x)) => x,
507            Some(x) => bail_report!(Error::invalid_symbol("TypeDef", x, &self.r#type)),
508            _ => unreachable!(),
509        };
510
511        for case in type_def.cases.iter() {
512            scope.track_variant_case(case);
513        }
514
515        self.scope = Some(Rc::new(scope));
516
517        let case = self.case.analyze(self.scope.clone());
518
519        r#type + case
520    }
521
522    fn is_resolved(&self) -> bool {
523        self.r#type.is_resolved() && self.case.is_resolved()
524    }
525}
526
527impl Analyzable for ListConstructor {
528    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
529        self.elements.analyze(parent)
530    }
531
532    fn is_resolved(&self) -> bool {
533        self.elements.is_resolved()
534    }
535}
536
537impl Analyzable for DataExpr {
538    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
539        match self {
540            DataExpr::StructConstructor(x) => x.analyze(parent),
541            DataExpr::ListConstructor(x) => x.analyze(parent),
542            DataExpr::Identifier(x) => x.analyze(parent),
543            DataExpr::AddOp(x) => x.analyze(parent),
544            DataExpr::SubOp(x) => x.analyze(parent),
545            DataExpr::NegateOp(x) => x.analyze(parent),
546            DataExpr::PropertyOp(x) => x.analyze(parent),
547            DataExpr::StaticAssetConstructor(x) => x.analyze(parent),
548            DataExpr::AnyAssetConstructor(x) => x.analyze(parent),
549            _ => AnalyzeReport::default(),
550        }
551    }
552
553    fn is_resolved(&self) -> bool {
554        match self {
555            DataExpr::StructConstructor(x) => x.is_resolved(),
556            DataExpr::ListConstructor(x) => x.is_resolved(),
557            DataExpr::Identifier(x) => x.is_resolved(),
558            DataExpr::AddOp(x) => x.is_resolved(),
559            DataExpr::SubOp(x) => x.is_resolved(),
560            DataExpr::NegateOp(x) => x.is_resolved(),
561            DataExpr::PropertyOp(x) => x.is_resolved(),
562            DataExpr::StaticAssetConstructor(x) => x.is_resolved(),
563            DataExpr::AnyAssetConstructor(x) => x.is_resolved(),
564            _ => true,
565        }
566    }
567}
568
569impl Analyzable for StaticAssetConstructor {
570    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
571        let amount = self.amount.analyze(parent.clone());
572        let r#type = self.r#type.analyze(parent.clone());
573
574        amount + r#type
575    }
576
577    fn is_resolved(&self) -> bool {
578        self.amount.is_resolved() && self.r#type.is_resolved()
579    }
580}
581
582impl Analyzable for AnyAssetConstructor {
583    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
584        let policy = self.policy.analyze(parent.clone());
585        let asset_name = self.asset_name.analyze(parent.clone());
586        let amount = self.amount.analyze(parent.clone());
587
588        policy + asset_name + amount
589    }
590
591    fn is_resolved(&self) -> bool {
592        self.policy.is_resolved() && self.asset_name.is_resolved() && self.amount.is_resolved()
593    }
594}
595
596impl Analyzable for PropertyOp {
597    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
598        let object = self.operand.analyze(parent.clone());
599
600        let mut scope = Scope::new(parent);
601
602        if let Some(ty) = self.operand.target_type() {
603            scope.track_record_fields_for_type(&ty);
604        }
605
606        self.scope = Some(Rc::new(scope));
607
608        let path = self.property.analyze(self.scope.clone());
609
610        object + path
611    }
612
613    fn is_resolved(&self) -> bool {
614        self.operand.is_resolved() && self.property.is_resolved()
615    }
616}
617
618impl Analyzable for AddressExpr {
619    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
620        match self {
621            AddressExpr::Identifier(x) => x.analyze(parent),
622            _ => AnalyzeReport::default(),
623        }
624    }
625
626    fn is_resolved(&self) -> bool {
627        match self {
628            AddressExpr::Identifier(x) => x.is_resolved(),
629            _ => true,
630        }
631    }
632}
633
634impl Analyzable for AssetDef {
635    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
636        let policy = self.policy.analyze(parent.clone());
637        let asset_name = self.asset_name.analyze(parent.clone());
638
639        let policy_type = AnalyzeReport::expect_data_expr_type(&self.policy, &Type::Bytes);
640        let asset_name_type = AnalyzeReport::expect_data_expr_type(&self.asset_name, &Type::Bytes);
641
642        policy + asset_name + policy_type + asset_name_type
643    }
644
645    fn is_resolved(&self) -> bool {
646        self.policy.is_resolved() && self.asset_name.is_resolved()
647    }
648}
649
650impl Analyzable for Identifier {
651    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
652        let symbol = parent.and_then(|p| p.resolve(&self.value));
653
654        if symbol.is_none() {
655            bail_report!(Error::not_in_scope(self.value.clone(), self));
656        }
657
658        self.symbol = symbol;
659
660        AnalyzeReport::default()
661    }
662
663    fn is_resolved(&self) -> bool {
664        self.symbol.is_some()
665    }
666}
667
668impl Analyzable for Type {
669    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
670        match self {
671            Type::Custom(x) => x.analyze(parent),
672            Type::List(x) => x.analyze(parent),
673            _ => AnalyzeReport::default(),
674        }
675    }
676
677    fn is_resolved(&self) -> bool {
678        match self {
679            Type::Custom(x) => x.is_resolved(),
680            Type::List(x) => x.is_resolved(),
681            _ => true,
682        }
683    }
684}
685
686impl Analyzable for InputBlockField {
687    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
688        match self {
689            InputBlockField::From(x) => x.analyze(parent),
690            InputBlockField::DatumIs(x) => x.analyze(parent),
691            InputBlockField::MinAmount(x) => x.analyze(parent),
692            InputBlockField::Redeemer(x) => x.analyze(parent),
693            InputBlockField::Ref(x) => x.analyze(parent),
694        }
695    }
696
697    fn is_resolved(&self) -> bool {
698        match self {
699            InputBlockField::From(x) => x.is_resolved(),
700            InputBlockField::DatumIs(x) => x.is_resolved(),
701            InputBlockField::MinAmount(x) => x.is_resolved(),
702            InputBlockField::Redeemer(x) => x.is_resolved(),
703            InputBlockField::Ref(x) => x.is_resolved(),
704        }
705    }
706}
707
708impl Analyzable for InputBlock {
709    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
710        self.fields.analyze(parent)
711    }
712
713    fn is_resolved(&self) -> bool {
714        self.fields.is_resolved()
715    }
716}
717
718impl Analyzable for MetadataBlockField {
719    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
720        // TODO: check keys are actually numbers
721        self.key.analyze(parent.clone()) + self.value.analyze(parent.clone())
722    }
723
724    fn is_resolved(&self) -> bool {
725        self.key.is_resolved() && self.value.is_resolved()
726    }
727}
728
729impl Analyzable for MetadataBlock {
730    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
731        self.fields.analyze(parent)
732    }
733
734    fn is_resolved(&self) -> bool {
735        self.fields.is_resolved()
736    }
737}
738
739impl Analyzable for ValidityBlockField {
740    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
741        match self {
742            ValidityBlockField::SinceSlot(x) => x.analyze(parent),
743            ValidityBlockField::UntilSlot(x) => x.analyze(parent),
744        }
745    }
746    fn is_resolved(&self) -> bool {
747        match self {
748            ValidityBlockField::SinceSlot(x) => x.is_resolved(),
749            ValidityBlockField::UntilSlot(x) => x.is_resolved(),
750        }
751    }
752}
753
754impl Analyzable for ValidityBlock {
755    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
756        self.fields.analyze(parent)
757    }
758
759    fn is_resolved(&self) -> bool {
760        self.fields.is_resolved()
761    }
762}
763
764impl Analyzable for OutputBlockField {
765    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
766        match self {
767            OutputBlockField::To(x) => x.analyze(parent),
768            OutputBlockField::Amount(x) => x.analyze(parent),
769            OutputBlockField::Datum(x) => x.analyze(parent),
770        }
771    }
772
773    fn is_resolved(&self) -> bool {
774        match self {
775            OutputBlockField::To(x) => x.is_resolved(),
776            OutputBlockField::Amount(x) => x.is_resolved(),
777            OutputBlockField::Datum(x) => x.is_resolved(),
778        }
779    }
780}
781
782impl Analyzable for OutputBlock {
783    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
784        self.fields.analyze(parent)
785    }
786
787    fn is_resolved(&self) -> bool {
788        self.fields.is_resolved()
789    }
790}
791
792impl Analyzable for RecordField {
793    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
794        self.r#type.analyze(parent)
795    }
796
797    fn is_resolved(&self) -> bool {
798        self.r#type.is_resolved()
799    }
800}
801
802impl Analyzable for VariantCase {
803    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
804        self.fields.analyze(parent)
805    }
806
807    fn is_resolved(&self) -> bool {
808        self.fields.is_resolved()
809    }
810}
811
812impl Analyzable for TypeDef {
813    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
814        self.cases.analyze(parent)
815    }
816
817    fn is_resolved(&self) -> bool {
818        self.cases.is_resolved()
819    }
820}
821
822impl Analyzable for MintBlockField {
823    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
824        match self {
825            MintBlockField::Amount(x) => x.analyze(parent),
826            MintBlockField::Redeemer(x) => x.analyze(parent),
827        }
828    }
829
830    fn is_resolved(&self) -> bool {
831        match self {
832            MintBlockField::Amount(x) => x.is_resolved(),
833            MintBlockField::Redeemer(x) => x.is_resolved(),
834        }
835    }
836}
837
838impl Analyzable for MintBlock {
839    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
840        self.fields.analyze(parent)
841    }
842
843    fn is_resolved(&self) -> bool {
844        self.fields.is_resolved()
845    }
846}
847
848impl Analyzable for SignersBlock {
849    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
850        self.signers.analyze(parent)
851    }
852
853    fn is_resolved(&self) -> bool {
854        self.signers.is_resolved()
855    }
856}
857
858impl Analyzable for ReferenceBlock {
859    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
860        self.r#ref.analyze(parent)
861    }
862
863    fn is_resolved(&self) -> bool {
864        self.r#ref.is_resolved()
865    }
866}
867
868impl Analyzable for CollateralBlockField {
869    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
870        match self {
871            CollateralBlockField::From(x) => x.analyze(parent),
872            CollateralBlockField::MinAmount(x) => x.analyze(parent),
873            CollateralBlockField::Ref(x) => x.analyze(parent),
874        }
875    }
876
877    fn is_resolved(&self) -> bool {
878        match self {
879            CollateralBlockField::From(x) => x.is_resolved(),
880            CollateralBlockField::MinAmount(x) => x.is_resolved(),
881            CollateralBlockField::Ref(x) => x.is_resolved(),
882        }
883    }
884}
885
886impl Analyzable for CollateralBlock {
887    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
888        self.fields.analyze(parent)
889    }
890
891    fn is_resolved(&self) -> bool {
892        self.fields.is_resolved()
893    }
894}
895
896impl Analyzable for ChainSpecificBlock {
897    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
898        match self {
899            ChainSpecificBlock::Cardano(x) => x.analyze(parent),
900        }
901    }
902
903    fn is_resolved(&self) -> bool {
904        match self {
905            ChainSpecificBlock::Cardano(x) => x.is_resolved(),
906        }
907    }
908}
909
910impl Analyzable for LocalsAssign {
911    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
912        self.value.analyze(parent)
913    }
914
915    fn is_resolved(&self) -> bool {
916        self.value.is_resolved()
917    }
918}
919
920impl Analyzable for LocalsBlock {
921    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
922        self.assigns.analyze(parent)
923    }
924
925    fn is_resolved(&self) -> bool {
926        self.assigns.is_resolved()
927    }
928}
929
930impl Analyzable for ParamDef {
931    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
932        self.r#type.analyze(parent)
933    }
934
935    fn is_resolved(&self) -> bool {
936        self.r#type.is_resolved()
937    }
938}
939
940impl Analyzable for ParameterList {
941    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
942        self.parameters.analyze(parent)
943    }
944
945    fn is_resolved(&self) -> bool {
946        self.parameters.is_resolved()
947    }
948}
949
950impl Analyzable for TxDef {
951    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
952        // analyze static types before anything else
953
954        let params = self.parameters.analyze(parent.clone());
955
956        // create the new scope and populate its symbols
957
958        let parent = {
959            let mut current = Scope::new(parent.clone());
960
961            current.symbols.insert("fees".to_string(), Symbol::Fees);
962
963            for param in self.parameters.parameters.iter() {
964                current.track_param_var(&param.name.value, param.r#type.clone());
965            }
966
967            Rc::new(current)
968        };
969
970        let mut locals = self.locals.take().unwrap_or_default();
971
972        let locals_report = locals.analyze(Some(parent.clone()));
973
974        let parent = {
975            let mut current = Scope::new(Some(parent.clone()));
976
977            for assign in locals.assigns.iter() {
978                current.track_local_expr(&assign.name.value, assign.value.clone());
979            }
980
981            Rc::new(current)
982        };
983
984        let inputs = self.inputs.analyze(Some(parent.clone()));
985
986        let parent = {
987            let mut current = Scope::new(Some(parent.clone()));
988
989            for input in self.inputs.iter() {
990                current.track_input(&input.name, input.clone());
991            }
992
993            Rc::new(current)
994        };
995
996        let outputs = self.outputs.analyze(Some(parent.clone()));
997
998        let mints = self.mints.analyze(Some(parent.clone()));
999
1000        let adhoc = self.adhoc.analyze(Some(parent.clone()));
1001
1002        let validity = self.validity.analyze(Some(parent.clone()));
1003
1004        let metadata = self.metadata.analyze(Some(parent.clone()));
1005
1006        let signers = self.signers.analyze(Some(parent.clone()));
1007
1008        let references = self.references.analyze(Some(parent.clone()));
1009
1010        let collateral = self.collateral.analyze(Some(parent.clone()));
1011
1012        self.scope = Some(parent);
1013
1014        params
1015            + locals_report
1016            + inputs
1017            + outputs
1018            + mints
1019            + adhoc
1020            + validity
1021            + metadata
1022            + signers
1023            + references
1024            + collateral
1025    }
1026
1027    fn is_resolved(&self) -> bool {
1028        self.inputs.is_resolved()
1029            && self.outputs.is_resolved()
1030            && self.mints.is_resolved()
1031            && self.adhoc.is_resolved()
1032            && self.validity.is_resolved()
1033            && self.metadata.is_resolved()
1034            && self.signers.is_resolved()
1035            && self.references.is_resolved()
1036            && self.collateral.is_resolved()
1037    }
1038}
1039
1040fn ada_asset_def() -> AssetDef {
1041    AssetDef {
1042        name: Identifier {
1043            value: "Ada".to_string(),
1044            symbol: None,
1045            span: Span::DUMMY,
1046        },
1047        policy: DataExpr::None,
1048        asset_name: DataExpr::None,
1049        span: Span::DUMMY,
1050    }
1051}
1052
1053impl Analyzable for Program {
1054    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
1055        let mut scope = Scope::new(parent);
1056
1057        if let Some(env) = self.env.take() {
1058            for field in env.fields.iter() {
1059                scope.track_env_var(&field.name, field.r#type.clone());
1060            }
1061        }
1062
1063        for party in self.parties.iter() {
1064            scope.track_party_def(party);
1065        }
1066
1067        for policy in self.policies.iter() {
1068            scope.track_policy_def(policy);
1069        }
1070
1071        scope.track_asset_def(&ada_asset_def());
1072
1073        for asset in self.assets.iter() {
1074            scope.track_asset_def(asset);
1075        }
1076
1077        for type_def in self.types.iter() {
1078            scope.track_type_def(type_def);
1079        }
1080
1081        self.scope = Some(Rc::new(scope));
1082
1083        let parties = self.parties.analyze(self.scope.clone());
1084
1085        let policies = self.policies.analyze(self.scope.clone());
1086
1087        let assets = self.assets.analyze(self.scope.clone());
1088
1089        let types = self.types.analyze(self.scope.clone());
1090
1091        let txs = self.txs.analyze(self.scope.clone());
1092
1093        parties + policies + types + txs + assets
1094    }
1095
1096    fn is_resolved(&self) -> bool {
1097        self.policies.is_resolved()
1098            && self.types.is_resolved()
1099            && self.txs.is_resolved()
1100            && self.assets.is_resolved()
1101    }
1102}
1103
1104/// Performs semantic analysis on a Tx3 program AST.
1105///
1106/// This function validates the entire program structure, checking for:
1107/// - Duplicate definitions
1108/// - Unknown symbol references
1109/// - Type correctness
1110/// - Other semantic constraints
1111///
1112/// # Arguments
1113/// * `ast` - Mutable reference to the program AST to analyze
1114///
1115/// # Returns
1116/// * `AnalyzeReport` of the analysis. Empty if no errors are found.
1117pub fn analyze(ast: &mut Program) -> AnalyzeReport {
1118    ast.analyze(None)
1119}
1120
1121#[cfg(test)]
1122mod tests {
1123    use crate::parsing::parse_well_known_example;
1124
1125    use super::*;
1126
1127    #[test]
1128    fn test_program_with_semantic_errors() {
1129        let mut ast = parse_well_known_example("semantic_errors");
1130
1131        let report = analyze(&mut ast);
1132
1133        assert_eq!(report.errors.len(), 3);
1134
1135        assert_eq!(
1136            report.errors[0],
1137            Error::NotInScope(NotInScopeError {
1138                name: "missing_symbol".to_string(),
1139                src: None,
1140                span: Span::DUMMY,
1141            })
1142        );
1143
1144        assert_eq!(
1145            report.errors[1],
1146            Error::InvalidTargetType(InvalidTargetTypeError {
1147                expected: "Bytes".to_string(),
1148                got: "Int".to_string(),
1149                src: None,
1150                span: Span::DUMMY,
1151            })
1152        );
1153
1154        assert_eq!(
1155            report.errors[2],
1156            Error::InvalidTargetType(InvalidTargetTypeError {
1157                expected: "Bytes".to_string(),
1158                got: "Int".to_string(),
1159                src: None,
1160                span: Span::DUMMY,
1161            })
1162        );
1163    }
1164}