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