1use 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 #[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
313pub trait Analyzable {
318 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport;
326
327 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 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 let params = self.parameters.analyze(parent.clone());
955
956 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(¶m.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
1104pub 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}