Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
713659e
feat: add metadata size validation and error handling
sofia-bobbiesi Sep 16, 2025
b8044f3
feat: add validation for metadata key types and improve error handling
sofia-bobbiesi Sep 16, 2025
11f6a9a
feat: add optional field to OutputBlock and related parsing logic
sofia-bobbiesi Oct 3, 2025
6d8ca5d
feat(lang): add error handling for optional outputs with datum
sofia-bobbiesi Oct 3, 2025
15ceff8
feat: add 'optional' field to various AST and TIR examples
sofia-bobbiesi Oct 3, 2025
2648c8a
feat(analyzing): enforce AnyAsset type for OutputBlockField amount an…
sofia-bobbiesi Oct 7, 2025
2ac8bf9
Merge branch 'main' into feat/optional-output
sofia-bobbiesi Oct 14, 2025
724c433
update tests
sofia-bobbiesi Oct 14, 2025
9fa8b8d
Merge branch 'main' into fix/amount-field-restriction
sofia-bobbiesi Oct 15, 2025
ef5b7a2
fix tests
sofia-bobbiesi Oct 17, 2025
974752d
test: add test for mixed amount expressions in output
sofia-bobbiesi Oct 17, 2025
9b26bda
Refactor target_type and analyze methods to include Context parameter
sofia-bobbiesi Oct 21, 2025
072d35b
update tests
sofia-bobbiesi Oct 21, 2025
0533019
Merge branch 'main' into 220-metadata-validation-does-not-enforce-64-…
sofia-bobbiesi Oct 21, 2025
1dc3613
update tests
sofia-bobbiesi Oct 21, 2025
f37820d
update analyze methods to include Context
sofia-bobbiesi Oct 22, 2025
f5012f4
refactor: change target_type from Vec<Type> to Type in Context
sofia-bobbiesi Oct 22, 2025
4561355
Merge branch 'main' into fix/amount-field-restriction
sofia-bobbiesi Oct 22, 2025
a05cee4
Merge branch 'main' into feat/optional-output
sofia-bobbiesi Oct 22, 2025
9347b27
refactor: rename InvalidOutputDatum to InvalidOptionalOutput for clarity
sofia-bobbiesi Oct 22, 2025
e156d09
refactor: streamline output validation for optional outputs
sofia-bobbiesi Oct 22, 2025
dae62af
Merge branch 'fix/amount-field-restriction' into feat/introduce-rules…
sofia-bobbiesi Oct 24, 2025
8b396bb
feat: add validation rules for semantic analysis
sofia-bobbiesi Oct 24, 2025
8fd809b
Merge branch '220-metadata-validation-does-not-enforce-64-byte-limit-…
sofia-bobbiesi Oct 24, 2025
48f718c
feat: add metadata validation rules for key type and value size
sofia-bobbiesi Oct 24, 2025
7780262
Merge branch 'main' into feat/introduce-rules-module
sofia-bobbiesi Dec 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
650 changes: 352 additions & 298 deletions crates/tx3-lang/src/analyzing.rs

Large diffs are not rendered by default.

98 changes: 60 additions & 38 deletions crates/tx3-lang/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, rc::Rc};

use crate::analyzing::Context;

#[derive(Debug, PartialEq, Eq)]
pub struct Scope {
pub(crate) symbols: HashMap<String, Symbol>,
Expand Down Expand Up @@ -118,11 +120,20 @@ impl Symbol {
}
}

pub fn target_type(&self) -> Option<Type> {
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
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 == Type::AnyAsset => Some(Type::AnyAsset),
_ => datum_type,
}
}
Symbol::LocalExpr(expr) => expr.target_type(ctx),
Symbol::Fees => Some(Type::AnyAsset),
x => {
dbg!(x);
None
Expand Down Expand Up @@ -159,8 +170,8 @@ impl Identifier {
}
}

pub fn target_type(&self) -> Option<Type> {
self.symbol.as_ref().and_then(|x| x.target_type())
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.symbol.as_ref().and_then(|x| x.target_type(ctx))
}
}

Expand Down Expand Up @@ -544,7 +555,7 @@ pub struct StaticAssetConstructor {
}

impl StaticAssetConstructor {
pub fn target_type(&self) -> Option<Type> {
pub fn target_type(&self, _ctx: Option<&Context>) -> Option<Type> {
Some(Type::AnyAsset)
}
}
Expand All @@ -558,7 +569,7 @@ pub struct AnyAssetConstructor {
}

impl AnyAssetConstructor {
pub fn target_type(&self) -> Option<Type> {
pub fn target_type(&self, _ctx: Option<&Context>) -> Option<Type> {
Some(Type::AnyAsset)
}
}
Expand All @@ -582,8 +593,8 @@ pub struct StructConstructor {
}

impl StructConstructor {
pub fn target_type(&self) -> Option<Type> {
self.r#type.symbol.as_ref().and_then(|x| x.target_type())
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.r#type.symbol.as_ref().and_then(|x| x.target_type(ctx))
}
}

Expand Down Expand Up @@ -615,8 +626,8 @@ pub struct ListConstructor {
}

impl ListConstructor {
pub fn target_type(&self) -> Option<Type> {
self.elements.first().and_then(|x| x.target_type())
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.elements.first().and_then(|x| x.target_type(ctx))
}
}

Expand All @@ -628,8 +639,8 @@ pub struct MapField {
}

impl MapField {
pub fn target_type(&self) -> Option<Type> {
self.key.target_type()
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.key.target_type(ctx)
}
}

Expand All @@ -640,10 +651,10 @@ pub struct MapConstructor {
}

impl MapConstructor {
pub fn target_type(&self) -> Option<Type> {
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
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
Expand All @@ -665,8 +676,8 @@ pub struct NegateOp {
}

impl NegateOp {
pub fn target_type(&self) -> Option<Type> {
self.operand.target_type()
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.operand.target_type(ctx)
}
}

Expand All @@ -682,8 +693,8 @@ pub struct PropertyOp {
}

impl PropertyOp {
pub fn target_type(&self) -> Option<Type> {
self.property.target_type()
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.property.target_type(ctx)
}
}

Expand All @@ -695,8 +706,8 @@ pub struct AddOp {
}

impl AddOp {
pub fn target_type(&self) -> Option<Type> {
self.lhs.target_type()
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.lhs.target_type(ctx)
}
}

Expand All @@ -708,8 +719,8 @@ pub struct SubOp {
}

impl SubOp {
pub fn target_type(&self) -> Option<Type> {
self.lhs.target_type()
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.lhs.target_type(ctx)
}
}

Expand All @@ -721,8 +732,8 @@ pub struct ConcatOp {
}

impl ConcatOp {
pub fn target_type(&self) -> Option<Type> {
self.lhs.target_type()
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
self.lhs.target_type(ctx)
}
}

Expand Down Expand Up @@ -760,28 +771,39 @@ impl DataExpr {
}
}

pub fn target_type(&self) -> Option<Type> {
pub fn target_type(&self, ctx: Option<&Context>) -> Option<Type> {
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 == 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),
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),
Expand Down Expand Up @@ -885,7 +907,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,
Expand Down
Loading