Merge pull request #869 from aiken-lang/non-serialisable-types

Forbid non-serializable inhabitants in compound data-types.
This commit is contained in:
Matthias Benkort 2024-03-09 22:39:41 +01:00 committed by GitHub
commit ec18127191
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 131 additions and 5 deletions

View File

@ -39,6 +39,7 @@
- **aiken-lang**: allow zero arg mutually recursive functions. @Microproofs - **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**: 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**: 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 ### Changed

View File

@ -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<a> = fn(PRNG) -> (a, PRNG)
fn constant(a: a) -> Fuzzer<a> {
fn (prng) {
(a, prng)
}
}
fn main() -> Fuzzer<Fuzzer<Int>> {
constant(constant(42))
}
"#;
assert!(matches!(
check_validator(parse(source_code)),
Err((_, Error::IllegalTypeInData { .. }))
))
}
#[test] #[test]
fn mark_constructors_as_used_via_field_access() { fn mark_constructors_as_used_via_field_access() {
let source_code = r#" let source_code = r#"

View File

@ -269,11 +269,14 @@ You can use '{discard}' and numbers to distinguish between similar names.
location: Span, 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(code("illegal::type_in_data"))]
#[diagnostic(help( #[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."#, 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()) 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 { IllegalTypeInData {
#[label] #[label]

View File

@ -21,9 +21,9 @@ use crate::{
expr::{FnStyle, TypedExpr, UntypedExpr}, expr::{FnStyle, TypedExpr, UntypedExpr},
format, format,
line_numbers::LineNumbers, 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; use vec1::Vec1;
#[derive(Debug)] #[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)) Ok((args, body))
} }
@ -1601,6 +1604,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
elems.push(element) elems.push(element)
} }
// Ensure elements are serialisable to Data.
ensure_serialisable(false, tipo.clone(), location)?;
// Type check the ..tail, if there is one // Type check the ..tail, if there is one
let tipo = list(tipo); let tipo = list(tipo);
@ -1768,6 +1774,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
for elem in elems { for elem in elems {
let typed_elem = self.infer(elem)?; let typed_elem = self.infer(elem)?;
// Ensure elements are serialisable to Data.
ensure_serialisable(false, typed_elem.tipo(), location)?;
typed_elems.push(typed_elem); typed_elems.push(typed_elem);
} }
@ -2116,3 +2125,52 @@ fn assert_assignment(expr: TypedExpr) -> Result<TypedExpr, Error> {
Ok(expr) Ok(expr)
} }
pub fn ensure_serialisable(allow_fn: bool, t: Rc<Type>, 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::<Result<Vec<_>, _>>()?;
Ok(())
}
Type::Tuple { elems, .. } => {
elems
.iter()
.map(|e| ensure_serialisable(false, e.clone(), location))
.collect::<Result<Vec<_>, _>>()?;
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::<Result<Vec<_>, _>>()?;
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),
},
}
}