From 2648c8a2c4f48f3d4f16110113130612711915dc Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Tue, 7 Oct 2025 13:26:17 -0300 Subject: [PATCH 1/7] feat(analyzing): enforce AnyAsset type for OutputBlockField amount and add tests --- crates/tx3-lang/src/analyzing.rs | 58 +++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 03bc326f..39abd592 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -823,7 +823,11 @@ impl Analyzable for OutputBlockField { fn analyze(&mut self, parent: Option>) -> AnalyzeReport { match self { OutputBlockField::To(x) => x.analyze(parent), - OutputBlockField::Amount(x) => x.analyze(parent), + OutputBlockField::Amount(x) => { + let expr_report = x.analyze(parent); + let type_report = AnalyzeReport::expect_data_expr_type(x, &Type::AnyAsset); + expr_report + type_report + } OutputBlockField::Datum(x) => x.analyze(parent), } } @@ -1268,4 +1272,56 @@ mod tests { let result = analyze(&mut ast); assert!(!result.errors.is_empty()); } + + #[test] + fn test_output_amount_must_be_any_asset_type() { + let mut ast = crate::parsing::parse_string( + r#" + party Alice; + tx test() { + output my_output { + to: Alice, + amount: 123, + } + } + "#, + ) + .unwrap(); + + let result = analyze(&mut ast); + assert!(!result.errors.is_empty()); + + assert_eq!( + result.errors[0], + Error::InvalidTargetType(InvalidTargetTypeError { + expected: "AnyAsset".to_string(), + got: "Int".to_string(), + src: None, + span: Span::DUMMY, + }) + ); + } + + #[test] + fn test_output_amount_accepts_any_asset_expressions() { + let mut ast = crate::parsing::parse_string( + r#" + party Alice; + tx test(quantity: Int) { + output { + to: Alice, + amount: AnyAsset(0x123, 0x456, 100), + } + output { + to: Alice, + amount: Ada(quantity), + } + } + "#, + ) + .unwrap(); + + let result = analyze(&mut ast); + assert!(result.errors.is_empty()); + } } From ef5b7a23ddd8a404f362a36d7ffb0d498d9152a5 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Fri, 17 Oct 2025 16:01:18 -0300 Subject: [PATCH 2/7] fix tests --- examples/lang_tour.ast | 898 ++++++++++++++----- examples/lang_tour.my_tx.tir | 191 +++- examples/reference_script.publish_native.tir | 34 +- examples/reference_script.publish_plutus.tir | 20 +- examples/withdrawal.transfer.tir | 16 +- 5 files changed, 879 insertions(+), 280 deletions(-) diff --git a/examples/lang_tour.ast b/examples/lang_tour.ast index 812c24be..79807a27 100644 --- a/examples/lang_tour.ast +++ b/examples/lang_tour.ast @@ -18,12 +18,32 @@ "start": 28, "end": 42 } + }, + { + "name": "field_c", + "type": "Bool", + "span": { + "dummy": false, + "start": 48, + "end": 61 + } + }, + { + "name": "field_d", + "type": { + "List": "Bytes" + }, + "span": { + "dummy": false, + "start": 67, + "end": 87 + } } ], "span": { "dummy": false, "start": 0, - "end": 45 + "end": 90 } }, "txs": [ @@ -32,8 +52,8 @@ "value": "my_tx", "span": { "dummy": false, - "start": 466, - "end": 471 + "start": 540, + "end": 545 } }, "parameters": { @@ -43,8 +63,8 @@ "value": "quantity", "span": { "dummy": false, - "start": 477, - "end": 485 + "start": 551, + "end": 559 } }, "type": "Int" @@ -54,8 +74,8 @@ "value": "validUntil", "span": { "dummy": false, - "start": 496, - "end": 506 + "start": 570, + "end": 580 } }, "type": "Int" @@ -65,8 +85,8 @@ "value": "metadata", "span": { "dummy": false, - "start": 517, - "end": 525 + "start": 591, + "end": 599 } }, "type": "Bytes" @@ -74,12 +94,88 @@ ], "span": { "dummy": false, - "start": 471, - "end": 535 + "start": 545, + "end": 609 + } + }, + "locals": { + "assigns": [ + { + "name": { + "value": "local_var", + "span": { + "dummy": false, + "start": 1914, + "end": 1923 + } + }, + "value": { + "ConcatOp": { + "lhs": { + "String": { + "value": "Lang", + "span": { + "dummy": false, + "start": 1932, + "end": 1938 + } + } + }, + "rhs": { + "String": { + "value": "Tour", + "span": { + "dummy": false, + "start": 1940, + "end": 1946 + } + } + }, + "span": { + "dummy": false, + "start": 1925, + "end": 1947 + } + } + }, + "span": { + "dummy": false, + "start": 1914, + "end": 1947 + } + } + ], + "span": { + "dummy": false, + "start": 1897, + "end": 1954 } }, - "locals": null, - "references": [], + "references": [ + { + "name": "ref_block", + "ref": { + "UtxoRef": { + "txid": [ + 171, + 205, + 239 + ], + "index": 2, + "span": { + "dummy": false, + "start": 1220, + "end": 1230 + } + } + }, + "span": { + "dummy": false, + "start": 1185, + "end": 1237 + } + } + ], "inputs": [ { "name": "source", @@ -91,8 +187,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 571, - "end": 578 + "start": 645, + "end": 652 } } } @@ -118,8 +214,8 @@ "value": "Ada", "span": { "dummy": false, - "start": 628, - "end": 631 + "start": 702, + "end": 705 } }, "amount": { @@ -127,15 +223,15 @@ "value": "quantity", "span": { "dummy": false, - "start": 632, - "end": 640 + "start": 706, + "end": 714 } } }, "span": { "dummy": false, - "start": 628, - "end": 641 + "start": 702, + "end": 715 } } }, @@ -144,15 +240,15 @@ "value": "named_output", "span": { "dummy": false, - "start": 653, - "end": 665 + "start": 727, + "end": 739 } } }, "span": { "dummy": false, - "start": 642, - "end": 643 + "start": 716, + "end": 717 } } } @@ -164,8 +260,8 @@ "value": "MyVariant", "span": { "dummy": false, - "start": 686, - "end": 695 + "start": 760, + "end": 769 } }, "case": { @@ -173,8 +269,8 @@ "value": "Case1", "span": { "dummy": false, - "start": 697, - "end": 702 + "start": 771, + "end": 776 } }, "fields": [ @@ -183,8 +279,8 @@ "value": "field1", "span": { "dummy": false, - "start": 717, - "end": 723 + "start": 791, + "end": 797 } }, "value": { @@ -192,15 +288,15 @@ "value": "field_a", "span": { "dummy": false, - "start": 725, - "end": 732 + "start": 799, + "end": 806 } } }, "span": { "dummy": false, - "start": 717, - "end": 732 + "start": 791, + "end": 806 } }, { @@ -208,8 +304,8 @@ "value": "field2", "span": { "dummy": false, - "start": 746, - "end": 752 + "start": 820, + "end": 826 } }, "value": { @@ -217,15 +313,15 @@ "value": "AFAFAF", "span": { "dummy": false, - "start": 754, - "end": 762 + "start": 828, + "end": 836 } } }, "span": { "dummy": false, - "start": 746, - "end": 762 + "start": 820, + "end": 836 } }, { @@ -233,8 +329,8 @@ "value": "field3", "span": { "dummy": false, - "start": 776, - "end": 782 + "start": 850, + "end": 856 } }, "value": { @@ -242,29 +338,29 @@ "value": "quantity", "span": { "dummy": false, - "start": 784, - "end": 792 + "start": 858, + "end": 866 } } }, "span": { "dummy": false, - "start": 776, - "end": 792 + "start": 850, + "end": 866 } } ], "spread": null, "span": { "dummy": false, - "start": 695, - "end": 803 + "start": 769, + "end": 877 } }, "span": { "dummy": false, - "start": 686, - "end": 803 + "start": 760, + "end": 877 } } } @@ -272,8 +368,8 @@ ], "span": { "dummy": false, - "start": 542, - "end": 810 + "start": 616, + "end": 884 } } ], @@ -283,8 +379,8 @@ "value": "named_output", "span": { "dummy": false, - "start": 996, - "end": 1008 + "start": 1250, + "end": 1262 } }, "fields": [ @@ -294,8 +390,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 1023, - "end": 1030 + "start": 1277, + "end": 1284 } } } @@ -307,8 +403,8 @@ "value": "MyRecord", "span": { "dummy": false, - "start": 1047, - "end": 1055 + "start": 1301, + "end": 1309 } }, "case": { @@ -326,8 +422,8 @@ "value": "field1", "span": { "dummy": false, - "start": 1070, - "end": 1076 + "start": 1324, + "end": 1330 } }, "value": { @@ -335,15 +431,15 @@ "value": "quantity", "span": { "dummy": false, - "start": 1078, - "end": 1086 + "start": 1332, + "end": 1340 } } }, "span": { "dummy": false, - "start": 1070, - "end": 1086 + "start": 1324, + "end": 1340 } }, { @@ -351,8 +447,8 @@ "value": "field2", "span": { "dummy": false, - "start": 1100, - "end": 1106 + "start": 1354, + "end": 1360 } }, "value": { @@ -367,8 +463,8 @@ }, "span": { "dummy": false, - "start": 1112, - "end": 1113 + "start": 1366, + "end": 1367 } } }, @@ -382,22 +478,22 @@ }, "span": { "dummy": false, - "start": 1123, - "end": 1124 + "start": 1377, + "end": 1378 } } }, "span": { "dummy": false, - "start": 1118, - "end": 1119 + "start": 1372, + "end": 1373 } } }, "span": { "dummy": false, - "start": 1100, - "end": 1127 + "start": 1354, + "end": 1381 } }, { @@ -405,8 +501,8 @@ "value": "field4", "span": { "dummy": false, - "start": 1141, - "end": 1147 + "start": 1395, + "end": 1401 } }, "value": { @@ -428,8 +524,8 @@ "value": "source", "span": { "dummy": false, - "start": 1159, - "end": 1165 + "start": 1413, + "end": 1419 } } }, @@ -438,30 +534,96 @@ "value": "field1", "span": { "dummy": false, - "start": 1166, - "end": 1172 + "start": 1420, + "end": 1426 } } }, "span": { "dummy": false, - "start": 1165, - "end": 1172 + "start": 1419, + "end": 1426 + } + } + } + ], + "span": { + "dummy": false, + "start": 1403, + "end": 1427 + } + } + }, + "span": { + "dummy": false, + "start": 1395, + "end": 1427 + } + }, + { + "name": { + "value": "field5", + "span": { + "dummy": false, + "start": 1441, + "end": 1447 + } + }, + "value": { + "MapConstructor": { + "fields": [ + { + "key": { + "Number": 1 + }, + "value": { + "String": { + "value": "Value1", + "span": { + "dummy": false, + "start": 1453, + "end": 1461 + } } + }, + "span": { + "dummy": false, + "start": 1450, + "end": 1461 + } + }, + { + "key": { + "Number": 2 + }, + "value": { + "String": { + "value": "Value2", + "span": { + "dummy": false, + "start": 1466, + "end": 1474 + } + } + }, + "span": { + "dummy": false, + "start": 1463, + "end": 1474 } } ], "span": { "dummy": false, - "start": 1149, - "end": 1173 + "start": 1449, + "end": 1476 } } }, "span": { "dummy": false, - "start": 1141, - "end": 1173 + "start": 1441, + "end": 1476 } } ], @@ -470,21 +632,21 @@ "value": "source", "span": { "dummy": false, - "start": 1190, - "end": 1196 + "start": 1493, + "end": 1499 } } }, "span": { "dummy": false, - "start": 1056, - "end": 1206 + "start": 1310, + "end": 1509 } }, "span": { "dummy": false, - "start": 1047, - "end": 1206 + "start": 1301, + "end": 1509 } } } @@ -503,8 +665,8 @@ "value": "source", "span": { "dummy": false, - "start": 1233, - "end": 1239 + "start": 1536, + "end": 1542 } } }, @@ -513,15 +675,15 @@ "value": "field3", "span": { "dummy": false, - "start": 1240, - "end": 1246 + "start": 1543, + "end": 1549 } } }, "span": { "dummy": false, - "start": 1239, - "end": 1246 + "start": 1542, + "end": 1549 } } }, @@ -532,8 +694,8 @@ "value": "source", "span": { "dummy": false, - "start": 1248, - "end": 1254 + "start": 1551, + "end": 1557 } } }, @@ -542,15 +704,15 @@ "value": "field2", "span": { "dummy": false, - "start": 1255, - "end": 1261 + "start": 1558, + "end": 1564 } } }, "span": { "dummy": false, - "start": 1254, - "end": 1261 + "start": 1557, + "end": 1564 } } }, @@ -561,8 +723,8 @@ "value": "source", "span": { "dummy": false, - "start": 1263, - "end": 1269 + "start": 1566, + "end": 1572 } } }, @@ -571,22 +733,22 @@ "value": "field1", "span": { "dummy": false, - "start": 1270, - "end": 1276 + "start": 1573, + "end": 1579 } } }, "span": { "dummy": false, - "start": 1269, - "end": 1276 + "start": 1572, + "end": 1579 } } }, "span": { "dummy": false, - "start": 1224, - "end": 1277 + "start": 1527, + "end": 1580 } } }, @@ -596,8 +758,8 @@ "value": "Ada", "span": { "dummy": false, - "start": 1280, - "end": 1283 + "start": 1583, + "end": 1586 } }, "amount": { @@ -605,15 +767,15 @@ }, "span": { "dummy": false, - "start": 1280, - "end": 1287 + "start": 1583, + "end": 1590 } } }, "span": { "dummy": false, - "start": 1278, - "end": 1279 + "start": 1581, + "end": 1582 } } }, @@ -622,15 +784,15 @@ "value": "named_output", "span": { "dummy": false, - "start": 1299, - "end": 1311 + "start": 1602, + "end": 1614 } } }, "span": { "dummy": false, - "start": 1288, - "end": 1289 + "start": 1591, + "end": 1592 } } } @@ -638,17 +800,15 @@ ], "span": { "dummy": false, - "start": 989, - "end": 1319 + "start": 1243, + "end": 1622 } } ], "validity": { "fields": [ { - "SinceSlot": { - "Number": 1735700400000 - } + "SinceSlot": "ComputeTipSlot" }, { "UntilSlot": { @@ -656,8 +816,8 @@ "value": "validUntil", "span": { "dummy": false, - "start": 1497, - "end": 1507 + "start": 1797, + "end": 1807 } } } @@ -665,8 +825,8 @@ ], "span": { "dummy": false, - "start": 1431, - "end": 1514 + "start": 1734, + "end": 1814 } }, "mints": [ @@ -679,8 +839,8 @@ "value": "StaticAsset", "span": { "dummy": false, - "start": 839, - "end": 850 + "start": 913, + "end": 924 } }, "amount": { @@ -688,8 +848,8 @@ }, "span": { "dummy": false, - "start": 839, - "end": 855 + "start": 913, + "end": 929 } } } @@ -700,8 +860,8 @@ ], "span": { "dummy": false, - "start": 816, - "end": 884 + "start": 890, + "end": 958 } }, { @@ -714,8 +874,8 @@ "value": "AB11223344", "span": { "dummy": false, - "start": 922, - "end": 934 + "start": 996, + "end": 1008 } } }, @@ -724,8 +884,8 @@ "value": "OTHER_TOKEN", "span": { "dummy": false, - "start": 936, - "end": 949 + "start": 1010, + "end": 1023 } } }, @@ -734,8 +894,8 @@ }, "span": { "dummy": false, - "start": 913, - "end": 954 + "start": 987, + "end": 1028 } } } @@ -746,12 +906,47 @@ ], "span": { "dummy": false, - "start": 890, - "end": 983 + "start": 964, + "end": 1057 + } + } + ], + "burns": [ + { + "fields": [ + { + "Amount": { + "StaticAssetConstructor": { + "type": { + "value": "StaticAsset", + "span": { + "dummy": false, + "start": 1086, + "end": 1097 + } + }, + "amount": { + "Number": 50 + }, + "span": { + "dummy": false, + "start": 1086, + "end": 1101 + } + } + } + }, + { + "Redeemer": "Unit" + } + ], + "span": { + "dummy": false, + "start": 1063, + "end": 1130 } } ], - "burns": [], "signers": { "signers": [ { @@ -759,8 +954,8 @@ "value": "MyParty", "span": { "dummy": false, - "start": 1343, - "end": 1350 + "start": 1646, + "end": 1653 } } }, @@ -769,25 +964,207 @@ "value": "0F5B22E57FEEB5B4FD1D501B007A427C56A76884D4978FAFEF979D9C", "span": { "dummy": false, - "start": 1360, - "end": 1418 + "start": 1663, + "end": 1721 } } } ], "span": { "dummy": false, - "start": 1325, - "end": 1425 + "start": 1628, + "end": 1728 } }, - "adhoc": [], + "adhoc": [ + { + "Cardano": { + "VoteDelegationCertificate": { + "drep": { + "HexString": { + "value": "12345678", + "span": { + "dummy": false, + "start": 2013, + "end": 2023 + } + } + }, + "stake": { + "HexString": { + "value": "87654321", + "span": { + "dummy": false, + "start": 2040, + "end": 2050 + } + } + }, + "span": { + "dummy": false, + "start": 1969, + "end": 2057 + } + } + } + }, + { + "Cardano": { + "Withdrawal": { + "fields": [ + { + "From": { + "Identifier": { + "value": "MyParty", + "span": { + "dummy": false, + "start": 2099, + "end": 2106 + } + } + } + }, + { + "Amount": { + "Number": 100 + } + }, + { + "Redeemer": "Unit" + } + ], + "span": { + "dummy": false, + "start": 2072, + "end": 2156 + } + } + } + }, + { + "Cardano": { + "PlutusWitness": { + "fields": [ + { + "Version": [ + { + "Number": 2 + }, + { + "dummy": false, + "start": 2196, + "end": 2206 + } + ] + }, + { + "Script": [ + { + "HexString": { + "value": "ABCDEF1234", + "span": { + "dummy": false, + "start": 2224, + "end": 2236 + } + } + }, + { + "dummy": false, + "start": 2216, + "end": 2236 + } + ] + } + ], + "span": { + "dummy": false, + "start": 2171, + "end": 2243 + } + } + } + }, + { + "Cardano": { + "NativeWitness": { + "fields": [ + { + "Script": [ + { + "HexString": { + "value": "12345678", + "span": { + "dummy": false, + "start": 2291, + "end": 2301 + } + } + }, + { + "dummy": false, + "start": 2283, + "end": 2301 + } + ] + } + ], + "span": { + "dummy": false, + "start": 2258, + "end": 2308 + } + } + } + }, + { + "Cardano": { + "TreasuryDonation": { + "coin": { + "Number": 500 + }, + "span": { + "dummy": false, + "start": 2323, + "end": 2367 + } + } + } + } + ], "span": { "dummy": false, - "start": 463, - "end": 1559 + "start": 537, + "end": 2369 }, - "collateral": [], + "collateral": [ + { + "fields": [ + { + "Ref": { + "UtxoRef": { + "txid": [ + 171, + 205, + 239 + ], + "index": 1, + "span": { + "dummy": false, + "start": 1162, + "end": 1172 + } + } + } + } + ], + "span": { + "dummy": false, + "start": 1136, + "end": 1179 + } + } + ], "metadata": { "fields": [ { @@ -799,22 +1176,42 @@ "value": "metadata", "span": { "dummy": false, - "start": 1542, - "end": 1550 + "start": 1842, + "end": 1850 } } }, "span": { "dummy": false, - "start": 1539, - "end": 1550 + "start": 1839, + "end": 1850 + } + }, + { + "key": { + "Number": 2 + }, + "value": { + "String": { + "value": "Additional Metadata", + "span": { + "dummy": false, + "start": 1863, + "end": 1884 + } + } + }, + "span": { + "dummy": false, + "start": 1860, + "end": 1884 } } ], "span": { "dummy": false, - "start": 1520, - "end": 1557 + "start": 1820, + "end": 1891 } } } @@ -825,8 +1222,8 @@ "value": "MyRecord", "span": { "dummy": false, - "start": 68, - "end": 76 + "start": 113, + "end": 121 } }, "cases": [ @@ -845,15 +1242,15 @@ "value": "field1", "span": { "dummy": false, - "start": 83, - "end": 89 + "start": 128, + "end": 134 } }, "type": "Int", "span": { "dummy": false, - "start": 83, - "end": 94 + "start": 128, + "end": 139 } }, { @@ -861,15 +1258,15 @@ "value": "field2", "span": { "dummy": false, - "start": 100, - "end": 106 + "start": 145, + "end": 151 } }, "type": "Bytes", "span": { "dummy": false, - "start": 100, - "end": 113 + "start": 145, + "end": 158 } }, { @@ -877,15 +1274,15 @@ "value": "field3", "span": { "dummy": false, - "start": 119, - "end": 125 + "start": 164, + "end": 170 } }, "type": "Bytes", "span": { "dummy": false, - "start": 119, - "end": 132 + "start": 164, + "end": 177 } }, { @@ -893,8 +1290,8 @@ "value": "field4", "span": { "dummy": false, - "start": 138, - "end": 144 + "start": 183, + "end": 189 } }, "type": { @@ -902,22 +1299,43 @@ }, "span": { "dummy": false, - "start": 138, - "end": 155 + "start": 183, + "end": 200 + } + }, + { + "name": { + "value": "field5", + "span": { + "dummy": false, + "start": 206, + "end": 212 + } + }, + "type": { + "Map": [ + "Int", + "Bytes" + ] + }, + "span": { + "dummy": false, + "start": 206, + "end": 229 } } ], "span": { "dummy": false, - "start": 63, - "end": 158 + "start": 108, + "end": 232 } } ], "span": { "dummy": false, - "start": 63, - "end": 158 + "start": 108, + "end": 232 } }, { @@ -925,8 +1343,8 @@ "value": "MyVariant", "span": { "dummy": false, - "start": 165, - "end": 174 + "start": 239, + "end": 248 } }, "cases": [ @@ -935,8 +1353,8 @@ "value": "Case1", "span": { "dummy": false, - "start": 181, - "end": 186 + "start": 255, + "end": 260 } }, "fields": [ @@ -945,15 +1363,15 @@ "value": "field1", "span": { "dummy": false, - "start": 197, - "end": 203 + "start": 271, + "end": 277 } }, "type": "Int", "span": { "dummy": false, - "start": 197, - "end": 208 + "start": 271, + "end": 282 } }, { @@ -961,15 +1379,15 @@ "value": "field2", "span": { "dummy": false, - "start": 218, - "end": 224 + "start": 292, + "end": 298 } }, "type": "Bytes", "span": { "dummy": false, - "start": 218, - "end": 231 + "start": 292, + "end": 305 } }, { @@ -977,22 +1395,22 @@ "value": "field3", "span": { "dummy": false, - "start": 241, - "end": 247 + "start": 315, + "end": 321 } }, "type": "Int", "span": { "dummy": false, - "start": 241, - "end": 252 + "start": 315, + "end": 326 } } ], "span": { "dummy": false, - "start": 181, - "end": 259 + "start": 255, + "end": 333 } }, { @@ -1000,22 +1418,22 @@ "value": "Case2", "span": { "dummy": false, - "start": 265, - "end": 270 + "start": 339, + "end": 344 } }, "fields": [], "span": { "dummy": false, - "start": 265, - "end": 270 + "start": 339, + "end": 344 } } ], "span": { "dummy": false, - "start": 160, - "end": 273 + "start": 234, + "end": 347 } } ], @@ -1026,8 +1444,8 @@ "value": "StaticAsset", "span": { "dummy": false, - "start": 320, - "end": 331 + "start": 394, + "end": 405 } }, "policy": { @@ -1035,8 +1453,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 334, - "end": 346 + "start": 408, + "end": 420 } } }, @@ -1045,15 +1463,15 @@ "value": "MYTOKEN", "span": { "dummy": false, - "start": 347, - "end": 356 + "start": 421, + "end": 430 } } }, "span": { "dummy": false, - "start": 314, - "end": 357 + "start": 388, + "end": 431 } } ], @@ -1063,14 +1481,14 @@ "value": "MyParty", "span": { "dummy": false, - "start": 53, - "end": 60 + "start": 98, + "end": 105 } }, "span": { "dummy": false, - "start": 47, - "end": 61 + "start": 92, + "end": 106 } } ], @@ -1080,8 +1498,8 @@ "value": "OnlyHashPolicy", "span": { "dummy": false, - "start": 282, - "end": 296 + "start": 356, + "end": 370 } }, "value": { @@ -1089,15 +1507,15 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 299, - "end": 311 + "start": 373, + "end": 385 } } }, "span": { "dummy": false, - "start": 275, - "end": 312 + "start": 349, + "end": 386 } }, { @@ -1105,8 +1523,8 @@ "value": "FullyDefinedPolicy", "span": { "dummy": false, - "start": 366, - "end": 384 + "start": 440, + "end": 458 } }, "value": { @@ -1118,8 +1536,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 397, - "end": 409 + "start": 471, + "end": 483 } } } @@ -1130,8 +1548,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 423, - "end": 435 + "start": 497, + "end": 509 } } } @@ -1142,8 +1560,8 @@ "value": "ABCDEF1234", "span": { "dummy": false, - "start": 446, - "end": 458 + "start": 520, + "end": 532 } } } @@ -1151,21 +1569,21 @@ ], "span": { "dummy": false, - "start": 385, - "end": 461 + "start": 459, + "end": 535 } } }, "span": { "dummy": false, - "start": 359, - "end": 461 + "start": 433, + "end": 535 } } ], "span": { "dummy": false, "start": 0, - "end": 1560 + "end": 2370 } } \ No newline at end of file diff --git a/examples/lang_tour.my_tx.tir b/examples/lang_tour.my_tx.tir index c1ed8b7e..b8b6d70c 100644 --- a/examples/lang_tour.my_tx.tir +++ b/examples/lang_tour.my_tx.tir @@ -2,7 +2,20 @@ "fees": { "EvalParam": "ExpectFees" }, - "references": [], + "references": [ + { + "UtxoRefs": [ + { + "txid": [ + 171, + 205, + 239 + ], + "index": 2 + } + ] + } + ], "inputs": [ { "name": "source", @@ -275,6 +288,26 @@ } } ] + }, + { + "Map": [ + [ + { + "Number": 1 + }, + { + "String": "Value1" + } + ], + [ + { + "Number": 2 + }, + { + "String": "Value2" + } + ] + ] } ] } @@ -505,7 +538,7 @@ ], "validity": { "since": { - "Number": 1735700400000 + "EvalCompiler": "ComputeTipSlot" }, "until": { "EvalParam": { @@ -576,9 +609,149 @@ } } ], - "burns": [], - "adhoc": [], - "collateral": [], + "burns": [ + { + "amount": { + "Assets": [ + { + "policy": { + "Bytes": [ + 171, + 205, + 239, + 18, + 52 + ] + }, + "asset_name": { + "String": "MYTOKEN" + }, + "amount": { + "Number": 50 + } + } + ] + }, + "redeemer": { + "Struct": { + "constructor": 0, + "fields": [] + } + } + } + ], + "adhoc": [ + { + "name": "vote_delegation_certificate", + "data": { + "drep": { + "Bytes": [ + 18, + 52, + 86, + 120 + ] + }, + "stake": { + "Bytes": [ + 135, + 101, + 67, + 33 + ] + } + } + }, + { + "name": "withdrawal", + "data": { + "credential": { + "EvalParam": { + "ExpectValue": [ + "myparty", + "Address" + ] + } + }, + "amount": { + "Number": 100 + }, + "redeemer": { + "Struct": { + "constructor": 0, + "fields": [] + } + } + } + }, + { + "name": "plutus_witness", + "data": { + "script": { + "Bytes": [ + 171, + 205, + 239, + 18, + 52 + ] + }, + "version": { + "Number": 2 + } + } + }, + { + "name": "native_witness", + "data": { + "script": { + "Bytes": [ + 18, + 52, + 86, + 120 + ] + } + } + }, + { + "name": "treasury_donation", + "data": { + "coin": { + "Number": 500 + } + } + } + ], + "collateral": [ + { + "utxos": { + "EvalParam": { + "ExpectInput": [ + "collateral", + { + "address": "None", + "min_amount": "None", + "ref": { + "UtxoRefs": [ + { + "txid": [ + 171, + 205, + 239 + ], + "index": 1 + } + ] + }, + "many": false, + "collateral": true + } + ] + } + } + } + ], "signers": { "signers": [ { @@ -636,6 +809,14 @@ ] } } + }, + { + "key": { + "Number": 2 + }, + "value": { + "String": "Additional Metadata" + } } ] } \ No newline at end of file diff --git a/examples/reference_script.publish_native.tir b/examples/reference_script.publish_native.tir index 3beadd57..2813897a 100644 --- a/examples/reference_script.publish_native.tir +++ b/examples/reference_script.publish_native.tir @@ -136,21 +136,8 @@ { "name": "cardano_publish", "data": { - "amount": { - "Assets": [ - { - "policy": "None", - "asset_name": "None", - "amount": { - "EvalParam": { - "ExpectValue": [ - "quantity", - "Int" - ] - } - } - } - ] + "version": { + "Number": 0 }, "to": { "EvalParam": { @@ -170,8 +157,21 @@ 0 ] }, - "version": { - "Number": 0 + "amount": { + "Assets": [ + { + "policy": "None", + "asset_name": "None", + "amount": { + "EvalParam": { + "ExpectValue": [ + "quantity", + "Int" + ] + } + } + } + ] } } } diff --git a/examples/reference_script.publish_plutus.tir b/examples/reference_script.publish_plutus.tir index b2562f35..90a5af0e 100644 --- a/examples/reference_script.publish_plutus.tir +++ b/examples/reference_script.publish_plutus.tir @@ -136,6 +136,14 @@ { "name": "cardano_publish", "data": { + "to": { + "EvalParam": { + "ExpectValue": [ + "receiver", + "Address" + ] + } + }, "script": { "Bytes": [ 81, @@ -158,9 +166,6 @@ 105 ] }, - "version": { - "Number": 3 - }, "amount": { "Assets": [ { @@ -177,13 +182,8 @@ } ] }, - "to": { - "EvalParam": { - "ExpectValue": [ - "receiver", - "Address" - ] - } + "version": { + "Number": 3 } } } diff --git a/examples/withdrawal.transfer.tir b/examples/withdrawal.transfer.tir index 5130bab6..d31757c7 100644 --- a/examples/withdrawal.transfer.tir +++ b/examples/withdrawal.transfer.tir @@ -163,14 +163,6 @@ { "name": "withdrawal", "data": { - "credential": { - "EvalParam": { - "ExpectValue": [ - "sender", - "Address" - ] - } - }, "amount": { "Number": 0 }, @@ -179,6 +171,14 @@ "constructor": 0, "fields": [] } + }, + "credential": { + "EvalParam": { + "ExpectValue": [ + "sender", + "Address" + ] + } } } } From 974752db25f51e49dbc07abdeb1615cf57dac851 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Fri, 17 Oct 2025 16:03:48 -0300 Subject: [PATCH 3/7] test: add test for mixed amount expressions in output --- crates/tx3-lang/src/analyzing.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 8a3451e8..d7cd6ff8 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -1453,4 +1453,36 @@ mod tests { let result = analyze(&mut ast); assert!(result.errors.is_empty()); } + #[test] + fn test_output_mixed_amount_expressions() { + let mut ast = crate::parsing::parse_string( + r#" + party Alice; + tx test() { + input source { + from: Alice, + min_amount: Ada(100), + } + output { + to: Alice, + amount: source - 50, + } + } + "#, + ) + .unwrap(); + + let result = analyze(&mut ast); + assert!(!result.errors.is_empty()); + + assert_eq!( + result.errors[0], + Error::InvalidTargetType(InvalidTargetTypeError { + expected: "AnyAsset".to_string(), + got: "Int".to_string(), + src: None, + span: Span::DUMMY, + }) + ); + } } From 9b26bda2615d0e094ed7c04c76c2ec2650b39795 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Tue, 21 Oct 2025 14:32:07 -0300 Subject: [PATCH 4/7] Refactor target_type and analyze methods to include Context parameter --- crates/tx3-lang/src/analyzing.rs | 346 +++++++++++++++++-------------- crates/tx3-lang/src/ast.rs | 72 +++---- crates/tx3-lang/src/cardano.rs | 138 ++++++++---- 3 files changed, 324 insertions(+), 232 deletions(-) diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index d7cd6ff8..2a5d6517 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -9,6 +9,19 @@ use miette::Diagnostic; use crate::ast::*; +#[derive(Debug, Clone)] +pub struct Context { + pub target_type: Vec, +} + +impl Default for Context { + fn default() -> Self { + Self { + target_type: vec![], + } + } +} + #[derive(Debug, thiserror::Error, miette::Diagnostic, PartialEq, Eq)] #[error("not in scope: {name}")] #[diagnostic(code(tx3::not_in_scope))] @@ -163,11 +176,13 @@ impl AnalyzeReport { } } - pub fn expect_data_expr_type(expr: &DataExpr, expected: &Type) -> Self { - if expr.target_type().as_ref() != Some(expected) { + pub fn expect_data_expr_type(expr: &DataExpr, expected: &Type, ctx: &mut Context) -> Self { + if expr.target_type(Some(ctx)).as_ref() != Some(expected) { Self::from(Error::invalid_target_type( expected, - expr.target_type().as_ref().unwrap_or(&Type::Undefined), + expr.target_type(Some(ctx)) + .as_ref() + .unwrap_or(&Type::Undefined), expr, )) } else { @@ -366,16 +381,16 @@ pub trait Analyzable { /// /// # Returns /// * `AnalyzeReport` of the analysis. Empty if no errors are found. - fn analyze(&mut self, parent: Option>) -> AnalyzeReport; + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport; /// Returns true if all of the symbols have been resolved . fn is_resolved(&self) -> bool; } impl Analyzable for Option { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { if let Some(item) = self { - item.analyze(parent) + item.analyze(parent, ctx) } else { AnalyzeReport::default() } @@ -387,8 +402,8 @@ impl Analyzable for Option { } impl Analyzable for Box { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.as_mut().analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.as_mut().analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -397,9 +412,9 @@ impl Analyzable for Box { } impl Analyzable for Vec { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { self.iter_mut() - .map(|item| item.analyze(parent.clone())) + .map(|item| item.analyze(parent.clone(), ctx)) .collect() } @@ -409,7 +424,7 @@ impl Analyzable for Vec { } impl Analyzable for PartyDef { - fn analyze(&mut self, _parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, _parent: Option>, _ctx: &mut Context) -> AnalyzeReport { AnalyzeReport::default() } @@ -419,11 +434,11 @@ impl Analyzable for PartyDef { } impl Analyzable for PolicyField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - PolicyField::Hash(x) => x.analyze(parent), - PolicyField::Script(x) => x.analyze(parent), - PolicyField::Ref(x) => x.analyze(parent), + PolicyField::Hash(x) => x.analyze(parent, ctx), + PolicyField::Script(x) => x.analyze(parent, ctx), + PolicyField::Ref(x) => x.analyze(parent, ctx), } } @@ -436,8 +451,8 @@ impl Analyzable for PolicyField { } } impl Analyzable for PolicyConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -446,9 +461,9 @@ impl Analyzable for PolicyConstructor { } impl Analyzable for PolicyDef { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match &mut self.value { - PolicyValue::Constructor(x) => x.analyze(parent), + PolicyValue::Constructor(x) => x.analyze(parent, ctx), PolicyValue::Assign(_) => AnalyzeReport::default(), } } @@ -462,11 +477,21 @@ impl Analyzable for PolicyDef { } impl Analyzable for AddOp { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let left = self.lhs.analyze(parent.clone()); - let right = self.rhs.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let left = self.lhs.analyze(parent.clone(), ctx); + let right = self.rhs.analyze(parent.clone(), ctx); - left + right + let left_type = self.lhs.target_type(Some(ctx)); + let right_type = self.rhs.target_type(Some(ctx)); + + let type_check = match (left_type.as_ref(), right_type.as_ref()) { + (Some(l), Some(r)) if l != r => { + AnalyzeReport::from(Error::invalid_target_type(l, r, self.rhs.as_ref())) + } + _ => AnalyzeReport::default(), + }; + + left + right + type_check } fn is_resolved(&self) -> bool { @@ -475,9 +500,9 @@ impl Analyzable for AddOp { } impl Analyzable for ConcatOp { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let left = self.lhs.analyze(parent.clone()); - let right = self.rhs.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let left = self.lhs.analyze(parent.clone(), ctx); + let right = self.rhs.analyze(parent.clone(), ctx); left + right } @@ -488,11 +513,21 @@ impl Analyzable for ConcatOp { } impl Analyzable for SubOp { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let left = self.lhs.analyze(parent.clone()); - let right = self.rhs.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let left = self.lhs.analyze(parent.clone(), ctx); + let right = self.rhs.analyze(parent.clone(), ctx); - left + right + let left_type = self.lhs.target_type(Some(ctx)); + let right_type = self.rhs.target_type(Some(ctx)); + + let type_check = match (left_type.as_ref(), right_type.as_ref()) { + (Some(l), Some(r)) if l != r => { + AnalyzeReport::from(Error::invalid_target_type(l, r, self.rhs.as_ref())) + } + _ => AnalyzeReport::default(), + }; + + left + right + type_check } fn is_resolved(&self) -> bool { @@ -501,8 +536,8 @@ impl Analyzable for SubOp { } impl Analyzable for NegateOp { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.operand.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.operand.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -511,9 +546,9 @@ impl Analyzable for NegateOp { } impl Analyzable for RecordConstructorField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let name = self.name.analyze(parent.clone()); - let value = self.value.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let name = self.name.analyze(parent.clone(), ctx); + let value = self.value.analyze(parent.clone(), ctx); name + value } @@ -524,11 +559,11 @@ impl Analyzable for RecordConstructorField { } impl Analyzable for VariantCaseConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { let name = if self.name.symbol.is_some() { AnalyzeReport::default() } else { - self.name.analyze(parent.clone()) + self.name.analyze(parent.clone(), ctx) }; let mut scope = Scope::new(parent); @@ -545,9 +580,9 @@ impl Analyzable for VariantCaseConstructor { self.scope = Some(Rc::new(scope)); - let fields = self.fields.analyze(self.scope.clone()); + let fields = self.fields.analyze(self.scope.clone(), ctx); - let spread = self.spread.analyze(self.scope.clone()); + let spread = self.spread.analyze(self.scope.clone(), ctx); name + fields + spread } @@ -558,8 +593,8 @@ impl Analyzable for VariantCaseConstructor { } impl Analyzable for StructConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let r#type = self.r#type.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let r#type = self.r#type.analyze(parent.clone(), ctx); let mut scope = Scope::new(parent); @@ -587,7 +622,7 @@ impl Analyzable for StructConstructor { self.scope = Some(Rc::new(scope)); - let case = self.case.analyze(self.scope.clone()); + let case = self.case.analyze(self.scope.clone(), ctx); r#type + case } @@ -598,8 +633,8 @@ impl Analyzable for StructConstructor { } impl Analyzable for ListConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.elements.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.elements.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -608,8 +643,8 @@ impl Analyzable for ListConstructor { } impl Analyzable for MapField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.key.analyze(parent.clone()) + self.value.analyze(parent.clone()) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.key.analyze(parent.clone(), ctx) + self.value.analyze(parent.clone(), ctx) } fn is_resolved(&self) -> bool { @@ -618,8 +653,8 @@ impl Analyzable for MapField { } impl Analyzable for MapConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -628,20 +663,20 @@ impl Analyzable for MapConstructor { } impl Analyzable for DataExpr { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - DataExpr::StructConstructor(x) => x.analyze(parent), - DataExpr::ListConstructor(x) => x.analyze(parent), - DataExpr::MapConstructor(x) => x.analyze(parent), - DataExpr::Identifier(x) => x.analyze(parent), - DataExpr::AddOp(x) => x.analyze(parent), - DataExpr::SubOp(x) => x.analyze(parent), - DataExpr::NegateOp(x) => x.analyze(parent), - DataExpr::PropertyOp(x) => x.analyze(parent), - DataExpr::StaticAssetConstructor(x) => x.analyze(parent), - DataExpr::AnyAssetConstructor(x) => x.analyze(parent), - DataExpr::MinUtxo(x) => x.analyze(parent), - DataExpr::ConcatOp(x) => x.analyze(parent), + DataExpr::StructConstructor(x) => x.analyze(parent, ctx), + DataExpr::ListConstructor(x) => x.analyze(parent, ctx), + DataExpr::MapConstructor(x) => x.analyze(parent, ctx), + DataExpr::Identifier(x) => x.analyze(parent, ctx), + DataExpr::AddOp(x) => x.analyze(parent, ctx), + DataExpr::SubOp(x) => x.analyze(parent, ctx), + DataExpr::NegateOp(x) => x.analyze(parent, ctx), + DataExpr::PropertyOp(x) => x.analyze(parent, ctx), + DataExpr::StaticAssetConstructor(x) => x.analyze(parent, ctx), + DataExpr::AnyAssetConstructor(x) => x.analyze(parent, ctx), + DataExpr::MinUtxo(x) => x.analyze(parent, ctx), + DataExpr::ConcatOp(x) => x.analyze(parent, ctx), _ => AnalyzeReport::default(), } } @@ -666,9 +701,9 @@ impl Analyzable for DataExpr { } impl Analyzable for StaticAssetConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let amount = self.amount.analyze(parent.clone()); - let r#type = self.r#type.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let amount = self.amount.analyze(parent.clone(), ctx); + let r#type = self.r#type.analyze(parent.clone(), ctx); amount + r#type } @@ -679,10 +714,10 @@ impl Analyzable for StaticAssetConstructor { } impl Analyzable for AnyAssetConstructor { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let policy = self.policy.analyze(parent.clone()); - let asset_name = self.asset_name.analyze(parent.clone()); - let amount = self.amount.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let policy = self.policy.analyze(parent.clone(), ctx); + let asset_name = self.asset_name.analyze(parent.clone(), ctx); + let amount = self.amount.analyze(parent.clone(), ctx); policy + asset_name + amount } @@ -715,9 +750,9 @@ impl Analyzable for PropertyOp { } impl Analyzable for AddressExpr { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - AddressExpr::Identifier(x) => x.analyze(parent), + AddressExpr::Identifier(x) => x.analyze(parent, ctx), _ => AnalyzeReport::default(), } } @@ -731,12 +766,13 @@ impl Analyzable for AddressExpr { } impl Analyzable for AssetDef { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let policy = self.policy.analyze(parent.clone()); - let asset_name = self.asset_name.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let policy = self.policy.analyze(parent.clone(), ctx); + let asset_name = self.asset_name.analyze(parent.clone(), ctx); - let policy_type = AnalyzeReport::expect_data_expr_type(&self.policy, &Type::Bytes); - let asset_name_type = AnalyzeReport::expect_data_expr_type(&self.asset_name, &Type::Bytes); + let policy_type = AnalyzeReport::expect_data_expr_type(&self.policy, &Type::Bytes, ctx); + let asset_name_type = + AnalyzeReport::expect_data_expr_type(&self.asset_name, &Type::Bytes, ctx); policy + asset_name + policy_type + asset_name_type } @@ -747,7 +783,7 @@ impl Analyzable for AssetDef { } impl Analyzable for Identifier { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, _ctx: &mut Context) -> AnalyzeReport { let symbol = parent.and_then(|p| p.resolve(&self.value)); if symbol.is_none() { @@ -765,12 +801,12 @@ impl Analyzable for Identifier { } impl Analyzable for Type { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - Type::Custom(x) => x.analyze(parent), - Type::List(x) => x.analyze(parent), + Type::Custom(x) => x.analyze(parent, ctx), + Type::List(x) => x.analyze(parent, ctx), Type::Map(key_type, value_type) => { - key_type.analyze(parent.clone()) + value_type.analyze(parent) + key_type.analyze(parent.clone(), ctx) + value_type.analyze(parent, ctx) } _ => AnalyzeReport::default(), } @@ -787,13 +823,13 @@ impl Analyzable for Type { } impl Analyzable for InputBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - InputBlockField::From(x) => x.analyze(parent), - InputBlockField::DatumIs(x) => x.analyze(parent), - InputBlockField::MinAmount(x) => x.analyze(parent), - InputBlockField::Redeemer(x) => x.analyze(parent), - InputBlockField::Ref(x) => x.analyze(parent), + InputBlockField::From(x) => x.analyze(parent, ctx), + InputBlockField::DatumIs(x) => x.analyze(parent, ctx), + InputBlockField::MinAmount(x) => x.analyze(parent, ctx), + InputBlockField::Redeemer(x) => x.analyze(parent, ctx), + InputBlockField::Ref(x) => x.analyze(parent, ctx), } } @@ -809,8 +845,8 @@ impl Analyzable for InputBlockField { } impl Analyzable for InputBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -819,9 +855,9 @@ impl Analyzable for InputBlock { } impl Analyzable for MetadataBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { // TODO: check keys are actually numbers - self.key.analyze(parent.clone()) + self.value.analyze(parent.clone()) + self.key.analyze(parent.clone(), ctx) + self.value.analyze(parent.clone(), ctx) } fn is_resolved(&self) -> bool { @@ -830,8 +866,8 @@ impl Analyzable for MetadataBlockField { } impl Analyzable for MetadataBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -840,10 +876,10 @@ impl Analyzable for MetadataBlock { } impl Analyzable for ValidityBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - ValidityBlockField::SinceSlot(x) => x.analyze(parent), - ValidityBlockField::UntilSlot(x) => x.analyze(parent), + ValidityBlockField::SinceSlot(x) => x.analyze(parent, ctx), + ValidityBlockField::UntilSlot(x) => x.analyze(parent, ctx), } } fn is_resolved(&self) -> bool { @@ -855,8 +891,8 @@ impl Analyzable for ValidityBlockField { } impl Analyzable for ValidityBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -887,8 +923,8 @@ impl Analyzable for OutputBlockField { } impl Analyzable for OutputBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -897,8 +933,8 @@ impl Analyzable for OutputBlock { } impl Analyzable for RecordField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.r#type.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.r#type.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -907,8 +943,8 @@ impl Analyzable for RecordField { } impl Analyzable for VariantCase { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -917,8 +953,8 @@ impl Analyzable for VariantCase { } impl Analyzable for AliasDef { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.alias_type.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.alias_type.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -927,8 +963,8 @@ impl Analyzable for AliasDef { } impl Analyzable for TypeDef { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.cases.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.cases.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -937,10 +973,10 @@ impl Analyzable for TypeDef { } impl Analyzable for MintBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - MintBlockField::Amount(x) => x.analyze(parent), - MintBlockField::Redeemer(x) => x.analyze(parent), + MintBlockField::Amount(x) => x.analyze(parent, ctx), + MintBlockField::Redeemer(x) => x.analyze(parent, ctx), } } @@ -953,8 +989,8 @@ impl Analyzable for MintBlockField { } impl Analyzable for MintBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -963,8 +999,8 @@ impl Analyzable for MintBlock { } impl Analyzable for SignersBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.signers.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.signers.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -973,8 +1009,8 @@ impl Analyzable for SignersBlock { } impl Analyzable for ReferenceBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.r#ref.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.r#ref.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -983,11 +1019,11 @@ impl Analyzable for ReferenceBlock { } impl Analyzable for CollateralBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - CollateralBlockField::From(x) => x.analyze(parent), - CollateralBlockField::MinAmount(x) => x.analyze(parent), - CollateralBlockField::Ref(x) => x.analyze(parent), + CollateralBlockField::From(x) => x.analyze(parent, ctx), + CollateralBlockField::MinAmount(x) => x.analyze(parent, ctx), + CollateralBlockField::Ref(x) => x.analyze(parent, ctx), } } @@ -1001,8 +1037,8 @@ impl Analyzable for CollateralBlockField { } impl Analyzable for CollateralBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -1011,9 +1047,9 @@ impl Analyzable for CollateralBlock { } impl Analyzable for ChainSpecificBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - ChainSpecificBlock::Cardano(x) => x.analyze(parent), + ChainSpecificBlock::Cardano(x) => x.analyze(parent, ctx), } } @@ -1025,8 +1061,8 @@ impl Analyzable for ChainSpecificBlock { } impl Analyzable for LocalsAssign { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.value.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.value.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -1035,8 +1071,8 @@ impl Analyzable for LocalsAssign { } impl Analyzable for LocalsBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.assigns.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.assigns.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -1045,8 +1081,8 @@ impl Analyzable for LocalsBlock { } impl Analyzable for ParamDef { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.r#type.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.r#type.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -1055,8 +1091,8 @@ impl Analyzable for ParamDef { } impl Analyzable for ParameterList { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.parameters.analyze(parent) + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + self.parameters.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -1065,10 +1101,10 @@ impl Analyzable for ParameterList { } impl Analyzable for TxDef { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { // analyze static types before anything else - let params = self.parameters.analyze(parent.clone()); + let params = self.parameters.analyze(parent.clone(), ctx); // create the new scope and populate its symbols @@ -1085,7 +1121,7 @@ impl Analyzable for TxDef { let mut locals = self.locals.take().unwrap_or_default(); - let locals_report = locals.analyze(Some(parent.clone())); + let locals_report = locals.analyze(Some(parent.clone()), ctx); let parent = { let mut current = Scope::new(Some(parent.clone())); @@ -1101,7 +1137,7 @@ impl Analyzable for TxDef { Rc::new(current) }; - let inputs = self.inputs.analyze(Some(parent.clone())); + let inputs = self.inputs.analyze(Some(parent.clone()), ctx); let parent = { let mut current = Scope::new(Some(parent.clone())); @@ -1117,23 +1153,23 @@ impl Analyzable for TxDef { Rc::new(current) }; - let outputs = self.outputs.analyze(Some(parent.clone())); + let outputs = self.outputs.analyze(Some(parent.clone()), ctx); - let mints = self.mints.analyze(Some(parent.clone())); + let mints = self.mints.analyze(Some(parent.clone()), ctx); - let burns = self.burns.analyze(Some(parent.clone())); + let burns = self.burns.analyze(Some(parent.clone()), ctx); - let adhoc = self.adhoc.analyze(Some(parent.clone())); + let adhoc = self.adhoc.analyze(Some(parent.clone()), ctx); - let validity = self.validity.analyze(Some(parent.clone())); + let validity = self.validity.analyze(Some(parent.clone()), ctx); - let metadata = self.metadata.analyze(Some(parent.clone())); + let metadata = self.metadata.analyze(Some(parent.clone()), ctx); - let signers = self.signers.analyze(Some(parent.clone())); + let signers = self.signers.analyze(Some(parent.clone()), ctx); - let references = self.references.analyze(Some(parent.clone())); + let references = self.references.analyze(Some(parent.clone()), ctx); - let collateral = self.collateral.analyze(Some(parent.clone())); + let collateral = self.collateral.analyze(Some(parent.clone()), ctx); self.scope = Some(parent); @@ -1182,6 +1218,7 @@ fn resolve_types_and_aliases( scope_rc: &mut Rc, types: &mut Vec, aliases: &mut Vec, + ctx: &mut Context, ) -> (AnalyzeReport, AnalyzeReport) { let mut types_report = AnalyzeReport::default(); let mut aliases_report = AnalyzeReport::default(); @@ -1201,15 +1238,15 @@ fn resolve_types_and_aliases( scope.track_alias_def(alias_def); } - types_report = types.analyze(Some(scope_rc.clone())); - aliases_report = aliases.analyze(Some(scope_rc.clone())); + types_report = types.analyze(Some(scope_rc.clone()), ctx); + aliases_report = aliases.analyze(Some(scope_rc.clone()), ctx); } (types_report, aliases_report) } impl Analyzable for Program { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { let mut scope = Scope::new(parent); if let Some(env) = self.env.take() { @@ -1242,20 +1279,20 @@ impl Analyzable for Program { self.scope = Some(Rc::new(scope)); - let parties = self.parties.analyze(self.scope.clone()); + let parties = self.parties.analyze(self.scope.clone(), ctx); - let policies = self.policies.analyze(self.scope.clone()); + let policies = self.policies.analyze(self.scope.clone(), ctx); - let assets = self.assets.analyze(self.scope.clone()); + let assets = self.assets.analyze(self.scope.clone(), ctx); let mut types = self.types.clone(); let mut aliases = self.aliases.clone(); let scope_rc = self.scope.as_mut().unwrap(); - let (types, aliases) = resolve_types_and_aliases(scope_rc, &mut types, &mut aliases); + let (types, aliases) = resolve_types_and_aliases(scope_rc, &mut types, &mut aliases, ctx); - let txs = self.txs.analyze(self.scope.clone()); + let txs = self.txs.analyze(self.scope.clone(), ctx); parties + policies + types + aliases + txs + assets } @@ -1283,7 +1320,8 @@ impl Analyzable for Program { /// # Returns /// * `AnalyzeReport` of the analysis. Empty if no errors are found. pub fn analyze(ast: &mut Program) -> AnalyzeReport { - ast.analyze(None) + let mut ctx = Context::default(); + ast.analyze(None, &mut ctx) } #[cfg(test)] diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index ad428137..eca894c3 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashMap, rc::Rc}; -use crate::cardano::PlutusWitnessBlock; +use crate::analyzing::Context; #[derive(Debug, PartialEq, Eq)] pub struct Scope { @@ -161,8 +161,8 @@ impl Identifier { } } - pub fn target_type(&self) -> Option { - self.symbol.as_ref().and_then(|x| x.target_type()) + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.symbol.as_ref().and_then(|x| x.target_type(ctx)) } } @@ -545,7 +545,7 @@ pub struct StaticAssetConstructor { } impl StaticAssetConstructor { - pub fn target_type(&self) -> Option { + pub fn target_type(&self, _ctx: Option<&Context>) -> Option { Some(Type::AnyAsset) } } @@ -559,7 +559,7 @@ pub struct AnyAssetConstructor { } impl AnyAssetConstructor { - pub fn target_type(&self) -> Option { + pub fn target_type(&self, _ctx: Option<&Context>) -> Option { Some(Type::AnyAsset) } } @@ -583,8 +583,8 @@ pub struct StructConstructor { } impl StructConstructor { - pub fn target_type(&self) -> Option { - self.r#type.symbol.as_ref().and_then(|x| x.target_type()) + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.r#type.symbol.as_ref().and_then(|x| x.target_type(ctx)) } } @@ -616,8 +616,8 @@ pub struct ListConstructor { } impl ListConstructor { - pub fn target_type(&self) -> Option { - self.elements.first().and_then(|x| x.target_type()) + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.elements.first().and_then(|x| x.target_type(ctx)) } } @@ -629,8 +629,8 @@ pub struct MapField { } impl MapField { - pub fn target_type(&self) -> Option { - self.key.target_type() + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.key.target_type(ctx) } } @@ -641,10 +641,10 @@ pub struct MapConstructor { } impl MapConstructor { - pub fn target_type(&self) -> Option { + pub fn target_type(&self, ctx: Option<&Context>) -> Option { if let Some(first_field) = self.fields.first() { - let key_type = first_field.key.target_type()?; - let value_type = first_field.value.target_type()?; + let key_type = first_field.key.target_type(ctx)?; + let value_type = first_field.value.target_type(ctx)?; Some(Type::Map(Box::new(key_type), Box::new(value_type))) } else { None @@ -666,8 +666,8 @@ pub struct NegateOp { } impl NegateOp { - pub fn target_type(&self) -> Option { - self.operand.target_type() + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.operand.target_type(ctx) } } @@ -683,8 +683,8 @@ pub struct PropertyOp { } impl PropertyOp { - pub fn target_type(&self) -> Option { - self.property.target_type() + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.property.target_type(ctx) } } @@ -696,8 +696,8 @@ pub struct AddOp { } impl AddOp { - pub fn target_type(&self) -> Option { - self.lhs.target_type() + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.lhs.target_type(ctx) } } @@ -709,8 +709,8 @@ pub struct SubOp { } impl SubOp { - pub fn target_type(&self) -> Option { - self.lhs.target_type() + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.lhs.target_type(ctx) } } @@ -722,8 +722,8 @@ pub struct ConcatOp { } impl ConcatOp { - pub fn target_type(&self) -> Option { - self.lhs.target_type() + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + self.lhs.target_type(ctx) } } @@ -759,7 +759,9 @@ impl DataExpr { } } - pub fn target_type(&self) -> Option { + pub fn target_type(&self, ctx: Option<&Context>) -> Option { + let default_ctx = Context::default(); + let ctx = ctx.unwrap_or(&default_ctx); match self { DataExpr::Identifier(x) => x.target_type(), DataExpr::None => Some(Type::Undefined), @@ -768,19 +770,19 @@ impl DataExpr { DataExpr::Bool(_) => Some(Type::Bool), DataExpr::String(_) => Some(Type::Bytes), DataExpr::HexString(_) => Some(Type::Bytes), - DataExpr::StructConstructor(x) => x.target_type(), - DataExpr::MapConstructor(x) => x.target_type(), - DataExpr::ListConstructor(x) => match x.target_type() { + DataExpr::StructConstructor(x) => x.target_type(Some(ctx)), + DataExpr::MapConstructor(x) => x.target_type(Some(ctx)), + DataExpr::ListConstructor(x) => match x.target_type(Some(ctx)) { Some(inner) => Some(Type::List(Box::new(inner))), None => None, }, - DataExpr::AddOp(x) => x.target_type(), - DataExpr::SubOp(x) => x.target_type(), - DataExpr::ConcatOp(x) => x.target_type(), - DataExpr::NegateOp(x) => x.target_type(), - DataExpr::PropertyOp(x) => x.target_type(), - DataExpr::StaticAssetConstructor(x) => x.target_type(), - DataExpr::AnyAssetConstructor(x) => x.target_type(), + DataExpr::AddOp(x) => x.target_type(Some(ctx)), + DataExpr::SubOp(x) => x.target_type(Some(ctx)), + DataExpr::ConcatOp(x) => x.target_type(Some(ctx)), + DataExpr::NegateOp(x) => x.target_type(Some(ctx)), + DataExpr::PropertyOp(x) => x.target_type(Some(ctx)), + DataExpr::StaticAssetConstructor(x) => x.target_type(Some(ctx)), + DataExpr::AnyAssetConstructor(x) => x.target_type(Some(ctx)), DataExpr::UtxoRef(_) => Some(Type::UtxoRef), DataExpr::MinUtxo(_) => Some(Type::AnyAsset), DataExpr::ComputeTipSlot => Some(Type::Int), diff --git a/crates/tx3-lang/src/cardano.rs b/crates/tx3-lang/src/cardano.rs index 77cd5861..039b8dab 100644 --- a/crates/tx3-lang/src/cardano.rs +++ b/crates/tx3-lang/src/cardano.rs @@ -90,15 +90,19 @@ impl AstNode for WithdrawalBlock { } impl Analyzable for WithdrawalField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { match self { - WithdrawalField::From(x) => x.analyze(parent), + WithdrawalField::From(x) => x.analyze(parent, ctx), WithdrawalField::Amount(x) => { - let amount = x.analyze(parent.clone()); - let amount_type = AnalyzeReport::expect_data_expr_type(x, &Type::Int); + let amount = x.analyze(parent.clone(), ctx); + let amount_type = AnalyzeReport::expect_data_expr_type(x, &Type::Int, ctx); amount + amount_type } - WithdrawalField::Redeemer(x) => x.analyze(parent), + WithdrawalField::Redeemer(x) => x.analyze(parent, ctx), } } @@ -112,8 +116,12 @@ impl Analyzable for WithdrawalField { } impl Analyzable for WithdrawalBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -204,9 +212,13 @@ impl AstNode for VoteDelegationCertificate { } impl Analyzable for VoteDelegationCertificate { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let drep = self.drep.analyze(parent.clone()); - let stake = self.stake.analyze(parent.clone()); + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + let drep = self.drep.analyze(parent.clone(), ctx); + let stake = self.stake.analyze(parent.clone(), ctx); drep + stake } @@ -260,9 +272,13 @@ impl AstNode for StakeDelegationCertificate { } impl Analyzable for StakeDelegationCertificate { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let pool = self.pool.analyze(parent.clone()); - let stake = self.stake.analyze(parent.clone()); + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + let pool = self.pool.analyze(parent.clone(), ctx); + let stake = self.stake.analyze(parent.clone(), ctx); pool + stake } @@ -329,10 +345,14 @@ impl AstNode for PlutusWitnessField { } impl Analyzable for PlutusWitnessField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { match self { - PlutusWitnessField::Version(x, _) => x.analyze(parent), - PlutusWitnessField::Script(x, _) => x.analyze(parent), + PlutusWitnessField::Version(x, _) => x.analyze(parent, ctx), + PlutusWitnessField::Script(x, _) => x.analyze(parent, ctx), } } @@ -370,8 +390,12 @@ impl AstNode for PlutusWitnessBlock { } impl Analyzable for PlutusWitnessBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -439,9 +463,13 @@ impl AstNode for NativeWitnessField { } impl Analyzable for NativeWitnessField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { match self { - NativeWitnessField::Script(x, _) => x.analyze(parent), + NativeWitnessField::Script(x, _) => x.analyze(parent, ctx), } } @@ -478,8 +506,12 @@ impl AstNode for NativeWitnessBlock { } impl Analyzable for NativeWitnessBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -531,9 +563,13 @@ impl AstNode for TreasuryDonationBlock { } impl Analyzable for TreasuryDonationBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let coin = self.coin.analyze(parent); - let coin_type = AnalyzeReport::expect_data_expr_type(&self.coin, &Type::Int); + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + let coin = self.coin.analyze(parent, ctx); + let coin_type = AnalyzeReport::expect_data_expr_type(&self.coin, &Type::Int, ctx); coin + coin_type } @@ -615,11 +651,15 @@ impl AstNode for CardanoPublishBlockField { } Rule::cardano_publish_block_version => { let pair = pair.into_inner().next().unwrap(); - Ok(CardanoPublishBlockField::Version(DataExpr::parse(pair)?.into())) + Ok(CardanoPublishBlockField::Version( + DataExpr::parse(pair)?.into(), + )) } Rule::cardano_publish_block_script => { let pair = pair.into_inner().next().unwrap(); - Ok(CardanoPublishBlockField::Script(DataExpr::parse(pair)?.into())) + Ok(CardanoPublishBlockField::Script( + DataExpr::parse(pair)?.into(), + )) } x => unreachable!("Unexpected rule in cardano_publish_block_field: {:?}", x), } @@ -656,13 +696,17 @@ impl AstNode for CardanoPublishBlock { } impl Analyzable for CardanoPublishBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { match self { - CardanoPublishBlockField::To(x) => x.analyze(parent), - CardanoPublishBlockField::Amount(x) => x.analyze(parent), - CardanoPublishBlockField::Datum(x) => x.analyze(parent), - CardanoPublishBlockField::Version(x) => x.analyze(parent), - CardanoPublishBlockField::Script(x) => x.analyze(parent), + CardanoPublishBlockField::To(x) => x.analyze(parent, ctx), + CardanoPublishBlockField::Amount(x) => x.analyze(parent, ctx), + CardanoPublishBlockField::Datum(x) => x.analyze(parent, ctx), + CardanoPublishBlockField::Version(x) => x.analyze(parent, ctx), + CardanoPublishBlockField::Script(x) => x.analyze(parent, ctx), } } @@ -678,8 +722,12 @@ impl Analyzable for CardanoPublishBlockField { } impl Analyzable for CardanoPublishBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - self.fields.analyze(parent) + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { + self.fields.analyze(parent, ctx) } fn is_resolved(&self) -> bool { @@ -782,15 +830,19 @@ impl AstNode for CardanoBlock { } impl Analyzable for CardanoBlock { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze( + &mut self, + parent: Option>, + ctx: &mut crate::analyzing::Context, + ) -> AnalyzeReport { match self { - CardanoBlock::VoteDelegationCertificate(x) => x.analyze(parent), - CardanoBlock::StakeDelegationCertificate(x) => x.analyze(parent), - CardanoBlock::Withdrawal(x) => x.analyze(parent), - CardanoBlock::PlutusWitness(x) => x.analyze(parent), - CardanoBlock::NativeWitness(x) => x.analyze(parent), - CardanoBlock::TreasuryDonation(x) => x.analyze(parent), - CardanoBlock::Publish(x) => x.analyze(parent), + CardanoBlock::VoteDelegationCertificate(x) => x.analyze(parent, ctx), + CardanoBlock::StakeDelegationCertificate(x) => x.analyze(parent, ctx), + CardanoBlock::Withdrawal(x) => x.analyze(parent, ctx), + CardanoBlock::PlutusWitness(x) => x.analyze(parent, ctx), + CardanoBlock::NativeWitness(x) => x.analyze(parent, ctx), + CardanoBlock::TreasuryDonation(x) => x.analyze(parent, ctx), + CardanoBlock::Publish(x) => x.analyze(parent, ctx), } } From 072d35bde93c2c83e13802bc6f9258a65f9bca11 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Tue, 21 Oct 2025 15:55:37 -0300 Subject: [PATCH 5/7] update tests --- examples/lang_tour.my_tx.tir | 6 +- examples/reference_script.publish_native.tir | 36 ++-- examples/reference_script.publish_plutus.tir | 44 ++--- examples/swap.ast | 190 ++++++++----------- examples/swap.swap.tir | 34 +--- examples/swap.tx3 | 12 +- examples/withdrawal.transfer.tir | 6 +- 7 files changed, 136 insertions(+), 192 deletions(-) diff --git a/examples/lang_tour.my_tx.tir b/examples/lang_tour.my_tx.tir index b8b6d70c..e7d90185 100644 --- a/examples/lang_tour.my_tx.tir +++ b/examples/lang_tour.my_tx.tir @@ -665,6 +665,9 @@ { "name": "withdrawal", "data": { + "amount": { + "Number": 100 + }, "credential": { "EvalParam": { "ExpectValue": [ @@ -673,9 +676,6 @@ ] } }, - "amount": { - "Number": 100 - }, "redeemer": { "Struct": { "constructor": 0, diff --git a/examples/reference_script.publish_native.tir b/examples/reference_script.publish_native.tir index 2813897a..9cf434b9 100644 --- a/examples/reference_script.publish_native.tir +++ b/examples/reference_script.publish_native.tir @@ -136,8 +136,21 @@ { "name": "cardano_publish", "data": { - "version": { - "Number": 0 + "amount": { + "Assets": [ + { + "policy": "None", + "asset_name": "None", + "amount": { + "EvalParam": { + "ExpectValue": [ + "quantity", + "Int" + ] + } + } + } + ] }, "to": { "EvalParam": { @@ -147,6 +160,9 @@ ] } }, + "version": { + "Number": 0 + }, "script": { "Bytes": [ 130, @@ -156,22 +172,6 @@ 4, 0 ] - }, - "amount": { - "Assets": [ - { - "policy": "None", - "asset_name": "None", - "amount": { - "EvalParam": { - "ExpectValue": [ - "quantity", - "Int" - ] - } - } - } - ] } } } diff --git a/examples/reference_script.publish_plutus.tir b/examples/reference_script.publish_plutus.tir index 90a5af0e..987e9dad 100644 --- a/examples/reference_script.publish_plutus.tir +++ b/examples/reference_script.publish_plutus.tir @@ -136,13 +136,21 @@ { "name": "cardano_publish", "data": { - "to": { - "EvalParam": { - "ExpectValue": [ - "receiver", - "Address" - ] - } + "amount": { + "Assets": [ + { + "policy": "None", + "asset_name": "None", + "amount": { + "EvalParam": { + "ExpectValue": [ + "quantity", + "Int" + ] + } + } + } + ] }, "script": { "Bytes": [ @@ -166,21 +174,13 @@ 105 ] }, - "amount": { - "Assets": [ - { - "policy": "None", - "asset_name": "None", - "amount": { - "EvalParam": { - "ExpectValue": [ - "quantity", - "Int" - ] - } - } - } - ] + "to": { + "EvalParam": { + "ExpectValue": [ + "receiver", + "Address" + ] + } }, "version": { "Number": 3 diff --git a/examples/swap.ast b/examples/swap.ast index df749c60..7e6c04ab 100644 --- a/examples/swap.ast +++ b/examples/swap.ast @@ -176,8 +176,8 @@ "value": "Buyer", "span": { "dummy": false, - "start": 421, - "end": 426 + "start": 417, + "end": 422 } } } @@ -190,8 +190,8 @@ "value": "fees", "span": { "dummy": false, - "start": 452, - "end": 456 + "start": 444, + "end": 448 } } }, @@ -200,15 +200,15 @@ "value": "bid", "span": { "dummy": false, - "start": 459, - "end": 462 + "start": 451, + "end": 454 } } }, "span": { "dummy": false, - "start": 457, - "end": 458 + "start": 449, + "end": 450 } } } @@ -216,8 +216,8 @@ ], "span": { "dummy": false, - "start": 391, - "end": 469 + "start": 387, + "end": 461 } } ], @@ -231,8 +231,8 @@ "value": "Dex", "span": { "dummy": false, - "start": 500, - "end": 503 + "start": 488, + "end": 491 } } } @@ -244,8 +244,8 @@ "value": "PoolState", "span": { "dummy": false, - "start": 520, - "end": 529 + "start": 508, + "end": 517 } }, "case": { @@ -263,8 +263,8 @@ "value": "pair_a", "span": { "dummy": false, - "start": 544, - "end": 550 + "start": 532, + "end": 538 } }, "value": { @@ -276,8 +276,8 @@ "value": "pool", "span": { "dummy": false, - "start": 552, - "end": 556 + "start": 540, + "end": 544 } } }, @@ -286,58 +286,39 @@ "value": "pair_a", "span": { "dummy": false, - "start": 557, - "end": 563 + "start": 545, + "end": 551 } } }, "span": { "dummy": false, - "start": 556, - "end": 563 + "start": 544, + "end": 551 } } }, "rhs": { - "PropertyOp": { - "operand": { - "Identifier": { - "value": "bid", - "span": { - "dummy": false, - "start": 566, - "end": 569 - } - } - }, - "property": { - "Identifier": { - "value": "amount", - "span": { - "dummy": false, - "start": 570, - "end": 576 - } - } - }, + "Identifier": { + "value": "bid", "span": { "dummy": false, - "start": 569, - "end": 576 + "start": 554, + "end": 557 } } }, "span": { "dummy": false, - "start": 564, - "end": 565 + "start": 552, + "end": 553 } } }, "span": { "dummy": false, - "start": 544, - "end": 576 + "start": 532, + "end": 557 } }, { @@ -345,8 +326,8 @@ "value": "pair_b", "span": { "dummy": false, - "start": 590, - "end": 596 + "start": 571, + "end": 577 } }, "value": { @@ -358,8 +339,8 @@ "value": "pool", "span": { "dummy": false, - "start": 598, - "end": 602 + "start": 579, + "end": 583 } } }, @@ -368,58 +349,39 @@ "value": "pair_b", "span": { "dummy": false, - "start": 603, - "end": 609 + "start": 584, + "end": 590 } } }, "span": { "dummy": false, - "start": 602, - "end": 609 + "start": 583, + "end": 590 } } }, "rhs": { - "PropertyOp": { - "operand": { - "Identifier": { - "value": "ask", - "span": { - "dummy": false, - "start": 612, - "end": 615 - } - } - }, - "property": { - "Identifier": { - "value": "amount", - "span": { - "dummy": false, - "start": 616, - "end": 622 - } - } - }, + "Identifier": { + "value": "ask", "span": { "dummy": false, - "start": 615, - "end": 622 + "start": 593, + "end": 596 } } }, "span": { "dummy": false, - "start": 610, - "end": 611 + "start": 591, + "end": 592 } } }, "span": { "dummy": false, - "start": 590, - "end": 622 + "start": 571, + "end": 596 } } ], @@ -428,21 +390,21 @@ "value": "pool", "span": { "dummy": false, - "start": 639, - "end": 643 + "start": 613, + "end": 617 } } }, "span": { "dummy": false, - "start": 530, - "end": 653 + "start": 518, + "end": 627 } }, "span": { "dummy": false, - "start": 520, - "end": 653 + "start": 508, + "end": 627 } } } @@ -453,8 +415,8 @@ "value": "pool", "span": { "dummy": false, - "start": 671, - "end": 675 + "start": 645, + "end": 649 } } } @@ -462,8 +424,8 @@ ], "span": { "dummy": false, - "start": 479, - "end": 682 + "start": 467, + "end": 656 } }, { @@ -475,8 +437,8 @@ "value": "Buyer", "span": { "dummy": false, - "start": 709, - "end": 714 + "start": 683, + "end": 688 } } } @@ -493,8 +455,8 @@ "value": "payment", "span": { "dummy": false, - "start": 732, - "end": 739 + "start": 706, + "end": 713 } } }, @@ -503,15 +465,15 @@ "value": "ask", "span": { "dummy": false, - "start": 742, - "end": 745 + "start": 716, + "end": 719 } } }, "span": { "dummy": false, - "start": 740, - "end": 741 + "start": 714, + "end": 715 } } }, @@ -520,15 +482,15 @@ "value": "bid", "span": { "dummy": false, - "start": 748, - "end": 751 + "start": 722, + "end": 725 } } }, "span": { "dummy": false, - "start": 746, - "end": 747 + "start": 720, + "end": 721 } } }, @@ -537,15 +499,15 @@ "value": "fees", "span": { "dummy": false, - "start": 754, - "end": 758 + "start": 728, + "end": 732 } } }, "span": { "dummy": false, - "start": 752, - "end": 753 + "start": 726, + "end": 727 } } } @@ -553,8 +515,8 @@ ], "span": { "dummy": false, - "start": 688, - "end": 765 + "start": 662, + "end": 739 } } ], @@ -566,7 +528,7 @@ "span": { "dummy": false, "start": 161, - "end": 767 + "end": 741 }, "collateral": [], "metadata": null @@ -744,6 +706,6 @@ "span": { "dummy": false, "start": 0, - "end": 767 + "end": 742 } } \ No newline at end of file diff --git a/examples/swap.swap.tir b/examples/swap.swap.tir index e89e2c6a..ff102142 100644 --- a/examples/swap.swap.tir +++ b/examples/swap.swap.tir @@ -145,19 +145,10 @@ } }, { - "EvalBuiltIn": { - "Property": [ - { - "EvalParam": { - "ExpectValue": [ - "bid", - "AnyAsset" - ] - } - }, - { - "Number": 0 - } + "EvalParam": { + "ExpectValue": [ + "bid", + "AnyAsset" ] } } @@ -202,19 +193,10 @@ } }, { - "EvalBuiltIn": { - "Property": [ - { - "EvalParam": { - "ExpectValue": [ - "ask", - "AnyAsset" - ] - } - }, - { - "Number": 0 - } + "EvalParam": { + "ExpectValue": [ + "ask", + "AnyAsset" ] } } diff --git a/examples/swap.tx3 b/examples/swap.tx3 index e13e4262..70abff27 100644 --- a/examples/swap.tx3 +++ b/examples/swap.tx3 @@ -25,17 +25,17 @@ tx swap( bid_value: bid, }, } - + input payment { - from: Buyer, + from: Buyer, min_amount: fees + bid, } - + output { to: Dex, datum: PoolState { - pair_a: pool.pair_a - bid.amount, - pair_b: pool.pair_b + ask.amount, + pair_a: pool.pair_a - bid, + pair_b: pool.pair_b + ask, ...pool }, amount: pool, @@ -45,4 +45,4 @@ tx swap( to: Buyer, amount: payment + ask - bid - fees, } -} \ No newline at end of file +} diff --git a/examples/withdrawal.transfer.tir b/examples/withdrawal.transfer.tir index d31757c7..fb2ac831 100644 --- a/examples/withdrawal.transfer.tir +++ b/examples/withdrawal.transfer.tir @@ -163,15 +163,15 @@ { "name": "withdrawal", "data": { - "amount": { - "Number": 0 - }, "redeemer": { "Struct": { "constructor": 0, "fields": [] } }, + "amount": { + "Number": 0 + }, "credential": { "EvalParam": { "ExpectValue": [ From f37820d84bc92e5173db76ad66e1f99afc9cf7c2 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Wed, 22 Oct 2025 13:19:38 -0300 Subject: [PATCH 6/7] update analyze methods to include Context --- crates/tx3-lang/src/analyzing.rs | 31 ++++++++++++++++++++++--------- crates/tx3-lang/src/ast.rs | 28 ++++++++++++++++++++++++---- crates/tx3-lang/src/lowering.rs | 2 +- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index 2a5d6517..d9369072 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -728,18 +728,18 @@ impl Analyzable for AnyAssetConstructor { } impl Analyzable for PropertyOp { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { - let object = self.operand.analyze(parent.clone()); + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { + let object = self.operand.analyze(parent.clone(), ctx); let mut scope = Scope::new(parent); - if let Some(ty) = self.operand.target_type() { + if let Some(ty) = self.operand.target_type(None) { scope.track_record_fields_for_type(&ty); } self.scope = Some(Rc::new(scope)); - let path = self.property.analyze(self.scope.clone()); + let path = self.property.analyze(self.scope.clone(), ctx); object + path } @@ -901,15 +901,28 @@ impl Analyzable for ValidityBlock { } impl Analyzable for OutputBlockField { - fn analyze(&mut self, parent: Option>) -> AnalyzeReport { + fn analyze(&mut self, parent: Option>, ctx: &mut Context) -> AnalyzeReport { match self { - OutputBlockField::To(x) => x.analyze(parent), + OutputBlockField::To(x) => x.analyze(parent, ctx), OutputBlockField::Amount(x) => { - let expr_report = x.analyze(parent); - let type_report = AnalyzeReport::expect_data_expr_type(x, &Type::AnyAsset); + ctx.target_type.push(Type::AnyAsset); + + let expr_report = x.analyze(parent, ctx); + let ty = x.target_type(Some(ctx)); + + let type_report = if !matches!(ty.as_ref(), Some(&Type::AnyAsset)) { + AnalyzeReport::from(Error::invalid_target_type( + &Type::AnyAsset, + ty.as_ref().unwrap_or(&Type::Undefined), + x.as_ref(), + )) + } else { + AnalyzeReport::default() + }; + expr_report + type_report } - OutputBlockField::Datum(x) => x.analyze(parent), + OutputBlockField::Datum(x) => x.analyze(parent, ctx), } } diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index eca894c3..3cb95db1 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -120,11 +120,22 @@ impl Symbol { } } - pub fn target_type(&self) -> Option { + pub fn target_type(&self, ctx: Option<&Context>) -> Option { match self { Symbol::ParamVar(_, ty) => Some(ty.as_ref().clone()), Symbol::RecordField(x) => Some(x.r#type.clone()), - Symbol::Input(x) => x.datum_is().cloned(), + Symbol::Input(x) => { + let datum_type = x.datum_is().cloned(); + + match ctx { + Some(ctx) if ctx.target_type.last() == Some(&Type::AnyAsset) => { + Some(Type::AnyAsset) + } + _ => datum_type, + } + } + Symbol::LocalExpr(expr) => expr.target_type(ctx), + Symbol::Fees => Some(Type::AnyAsset), x => { dbg!(x); None @@ -763,7 +774,16 @@ impl DataExpr { let default_ctx = Context::default(); let ctx = ctx.unwrap_or(&default_ctx); match self { - DataExpr::Identifier(x) => x.target_type(), + DataExpr::Identifier(x) => match &x.symbol { + Some(Symbol::Input(def)) => { + if ctx.target_type.last() == Some(&Type::AnyAsset) { + Some(Type::AnyAsset) + } else { + def.datum_is().cloned() + } + } + _ => x.target_type(Some(ctx)), + }, DataExpr::None => Some(Type::Undefined), DataExpr::Unit => Some(Type::Unit), DataExpr::Number(_) => Some(Type::Int), @@ -884,7 +904,7 @@ impl Type { .map(|index| DataExpr::Number(index as i64)) } Type::List(_) => property - .target_type() + .target_type(None) .filter(|ty| *ty == Type::Int) .map(|_| property), _ => None, diff --git a/crates/tx3-lang/src/lowering.rs b/crates/tx3-lang/src/lowering.rs index 1f0b189b..c7a5cb02 100644 --- a/crates/tx3-lang/src/lowering.rs +++ b/crates/tx3-lang/src/lowering.rs @@ -414,7 +414,7 @@ impl IntoLower for ast::PropertyOp { let ty = self .operand - .target_type() + .target_type(None) .ok_or(Error::MissingAnalyzePhase(format!("{0:?}", self.operand)))?; let prop_index = From f5012f4a7a73e4a45a4c2736b5b42a16f7f280cb Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Wed, 22 Oct 2025 13:56:16 -0300 Subject: [PATCH 7/7] refactor: change target_type from Vec to Type in Context --- crates/tx3-lang/src/analyzing.rs | 6 +++--- crates/tx3-lang/src/ast.rs | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/tx3-lang/src/analyzing.rs b/crates/tx3-lang/src/analyzing.rs index d9369072..e686b45d 100644 --- a/crates/tx3-lang/src/analyzing.rs +++ b/crates/tx3-lang/src/analyzing.rs @@ -11,13 +11,13 @@ use crate::ast::*; #[derive(Debug, Clone)] pub struct Context { - pub target_type: Vec, + pub target_type: Type, } impl Default for Context { fn default() -> Self { Self { - target_type: vec![], + target_type: Type::Undefined, } } } @@ -905,7 +905,7 @@ impl Analyzable for OutputBlockField { match self { OutputBlockField::To(x) => x.analyze(parent, ctx), OutputBlockField::Amount(x) => { - ctx.target_type.push(Type::AnyAsset); + ctx.target_type = Type::AnyAsset; let expr_report = x.analyze(parent, ctx); let ty = x.target_type(Some(ctx)); diff --git a/crates/tx3-lang/src/ast.rs b/crates/tx3-lang/src/ast.rs index 3cb95db1..f0dd3757 100644 --- a/crates/tx3-lang/src/ast.rs +++ b/crates/tx3-lang/src/ast.rs @@ -128,9 +128,7 @@ impl Symbol { let datum_type = x.datum_is().cloned(); match ctx { - Some(ctx) if ctx.target_type.last() == Some(&Type::AnyAsset) => { - Some(Type::AnyAsset) - } + Some(ctx) if ctx.target_type == Type::AnyAsset => Some(Type::AnyAsset), _ => datum_type, } } @@ -776,7 +774,7 @@ impl DataExpr { match self { DataExpr::Identifier(x) => match &x.symbol { Some(Symbol::Input(def)) => { - if ctx.target_type.last() == Some(&Type::AnyAsset) { + if ctx.target_type == Type::AnyAsset { Some(Type::AnyAsset) } else { def.datum_is().cloned()