From ee54266d1f5c3f777cd5d305bd7cfbe4dc41ab66 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 9 Mar 2024 22:23:44 +0100 Subject: [PATCH] Forbid non-serializable inhabitants in compound data-types. --- CHANGELOG.md | 1 + crates/aiken-lang/src/tests/check.rs | 64 ++++++++++++++++++++++++++++ crates/aiken-lang/src/tipo/error.rs | 9 ++-- crates/aiken-lang/src/tipo/expr.rs | 62 ++++++++++++++++++++++++++- 4 files changed, 131 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d87793d0..32d8d2ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,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), + }, + } +}