diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b896313..e7a73ed2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@
- **aiken-lang**: allow zero arg mutually recursive functions. @Microproofs
- **aiken-lang**: function aliases now resolved to the module and function name in codegen. @Microproofs
- **aiken-lang**: fix indentation of pipelines to remain a multiple of the base indent increment. @KtorZ
+- **aiken-lang**: forbid presence of non-serialisable data-types in compound structures like List and Tuple. @KtorZ
### Changed
diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs
index 7812785e..33afd01a 100644
--- a/crates/aiken-lang/src/tests/check.rs
+++ b/crates/aiken-lang/src/tests/check.rs
@@ -122,6 +122,70 @@ fn validator_illegal_arity() {
))
}
+#[test]
+fn list_illegal_inhabitants() {
+ let source_code = r#"
+ fn main() {
+ [identity]
+ }
+ "#;
+
+ assert!(matches!(
+ check_validator(parse(source_code)),
+ Err((_, Error::IllegalTypeInData { .. }))
+ ))
+}
+
+#[test]
+fn tuple_illegal_inhabitants() {
+ let source_code = r#"
+ fn main() {
+ (identity, always)
+ }
+ "#;
+
+ assert!(matches!(
+ check_validator(parse(source_code)),
+ Err((_, Error::IllegalTypeInData { .. }))
+ ))
+}
+
+#[test]
+fn illegal_inhabitants_nested() {
+ let source_code = r#"
+ fn main() {
+ [(identity, always)]
+ }
+ "#;
+
+ assert!(matches!(
+ check_validator(parse(source_code)),
+ Err((_, Error::IllegalTypeInData { .. }))
+ ))
+}
+
+#[test]
+fn illegal_inhabitants_returned() {
+ let source_code = r#"
+ type Fuzzer = fn(PRNG) -> (a, PRNG)
+
+ fn constant(a: a) -> Fuzzer {
+ fn (prng) {
+ (a, prng)
+ }
+ }
+
+ fn main() -> Fuzzer> {
+ constant(constant(42))
+ }
+ "#;
+
+ assert!(matches!(
+ check_validator(parse(source_code)),
+ Err((_, Error::IllegalTypeInData { .. }))
+ ))
+}
+
#[test]
fn mark_constructors_as_used_via_field_access() {
let source_code = r#"
diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs
index c8c49c21..19c56c96 100644
--- a/crates/aiken-lang/src/tipo/error.rs
+++ b/crates/aiken-lang/src/tipo/error.rs
@@ -269,11 +269,14 @@ You can use '{discard}' and numbers to distinguish between similar names.
location: Span,
},
- #[error("I found a type definition that has an unsupported type in it.\n")]
+ #[error("I found a type definition that has unsupported inhabitants.\n")]
#[diagnostic(code("illegal::type_in_data"))]
#[diagnostic(help(
- r#"Data-types can't contain type {type_info} because it isn't serializable into a Plutus Data. Yet, this is a strong requirement for types found in compound structures such as List or Tuples."#,
- type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
+ r#"Data-types cannot contain values of type {type_info} because they aren't serialisable into a Plutus Data. Yet this is necessary for inhabitants of compound structures like {List}, {Tuple} or {Fuzzer}."#,
+ type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red()),
+ List = "List".if_supports_color(Stdout, |s| s.cyan()),
+ Tuple = "Tuple".if_supports_color(Stdout, |s| s.cyan()),
+ Fuzzer = "Fuzzer".if_supports_color(Stdout, |s| s.cyan()),
))]
IllegalTypeInData {
#[label]
diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs
index ae16cecb..1b0e4680 100644
--- a/crates/aiken-lang/src/tipo/expr.rs
+++ b/crates/aiken-lang/src/tipo/expr.rs
@@ -21,9 +21,9 @@ use crate::{
expr::{FnStyle, TypedExpr, UntypedExpr},
format,
line_numbers::LineNumbers,
- tipo::{fields::FieldMap, PatternConstructor},
+ tipo::{fields::FieldMap, PatternConstructor, TypeVar},
};
-use std::{cmp::Ordering, collections::HashMap, rc::Rc};
+use std::{cmp::Ordering, collections::HashMap, ops::Deref, rc::Rc};
use vec1::Vec1;
#[derive(Debug)]
@@ -1571,6 +1571,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
})?;
}
+ // Ensure elements are serialisable to Data.
+ ensure_serialisable(true, body.tipo(), body.type_defining_location())?;
+
Ok((args, body))
}
@@ -1601,6 +1604,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
elems.push(element)
}
+ // Ensure elements are serialisable to Data.
+ ensure_serialisable(false, tipo.clone(), location)?;
+
// Type check the ..tail, if there is one
let tipo = list(tipo);
@@ -1768,6 +1774,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
for elem in elems {
let typed_elem = self.infer(elem)?;
+ // Ensure elements are serialisable to Data.
+ ensure_serialisable(false, typed_elem.tipo(), location)?;
+
typed_elems.push(typed_elem);
}
@@ -2116,3 +2125,52 @@ fn assert_assignment(expr: TypedExpr) -> Result {
Ok(expr)
}
+
+pub fn ensure_serialisable(allow_fn: bool, t: Rc, location: Span) -> Result<(), Error> {
+ match t.deref() {
+ Type::App { args, .. } => {
+ if t.is_ml_result() {
+ return Err(Error::IllegalTypeInData {
+ tipo: t.clone(),
+ location,
+ });
+ }
+
+ args.iter()
+ .map(|e| ensure_serialisable(false, e.clone(), location))
+ .collect::, _>>()?;
+
+ Ok(())
+ }
+
+ Type::Tuple { elems, .. } => {
+ elems
+ .iter()
+ .map(|e| ensure_serialisable(false, e.clone(), location))
+ .collect::, _>>()?;
+
+ Ok(())
+ }
+
+ Type::Fn { args, ret, .. } => {
+ if !allow_fn {
+ return Err(Error::IllegalTypeInData {
+ tipo: t.clone(),
+ location,
+ });
+ }
+
+ args.iter()
+ .map(|e| ensure_serialisable(allow_fn, e.clone(), location))
+ .collect::, _>>()?;
+
+ ensure_serialisable(allow_fn, ret.clone(), location)
+ }
+
+ Type::Var { tipo, .. } => match tipo.borrow().deref() {
+ TypeVar::Unbound { .. } => Ok(()),
+ TypeVar::Generic { .. } => Ok(()),
+ TypeVar::Link { tipo } => ensure_serialisable(allow_fn, tipo.clone(), location),
+ },
+ }
+}