1use 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 #[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
427pub trait Analyzable {
432 fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport;
440
441 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 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 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 let params = self.parameters.analyze(parent.clone());
1278
1279 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(¶m.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; 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
1441pub 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}