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