Merge pull request #869 from aiken-lang/non-serialisable-types
Forbid non-serializable inhabitants in compound data-types.
This commit is contained in:
commit
ec18127191
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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#"
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue