Merge pull request #875 from aiken-lang/rvcas/expect_opaque

block expects on opaque types
This commit is contained in:
Matthias Benkort 2024-03-14 19:43:47 +01:00 committed by GitHub
commit 3f254dbe6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 581 additions and 172 deletions

View File

@ -1267,6 +1267,7 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap<DataTypeKey, TypedDa
pub fn int() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
name: INT.to_string(),
module: "".to_string(),
args: vec![],
@ -1277,6 +1278,7 @@ pub fn int() -> Rc<Type> {
pub fn data() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
name: DATA.to_string(),
module: "".to_string(),
args: vec![],
@ -1288,6 +1290,7 @@ pub fn byte_array() -> Rc<Type> {
Rc::new(Type::App {
args: vec![],
public: true,
contains_opaque: false,
name: BYTE_ARRAY.to_string(),
module: "".to_string(),
alias: None,
@ -1297,6 +1300,7 @@ pub fn byte_array() -> Rc<Type> {
pub fn g1_element() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: G1_ELEMENT.to_string(),
args: vec![],
@ -1307,6 +1311,7 @@ pub fn g1_element() -> Rc<Type> {
pub fn g2_element() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: G2_ELEMENT.to_string(),
args: vec![],
@ -1317,6 +1322,7 @@ pub fn g2_element() -> Rc<Type> {
pub fn miller_loop_result() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: MILLER_LOOP_RESULT.to_string(),
args: vec![],
@ -1332,6 +1338,7 @@ pub fn bool() -> Rc<Type> {
Rc::new(Type::App {
args: vec![],
public: true,
contains_opaque: false,
name: BOOL.to_string(),
module: "".to_string(),
alias: None,
@ -1342,6 +1349,7 @@ pub fn prng() -> Rc<Type> {
Rc::new(Type::App {
args: vec![],
public: true,
contains_opaque: false,
name: PRNG.to_string(),
module: "".to_string(),
alias: None,
@ -1355,6 +1363,7 @@ pub fn fuzzer(a: Rc<Type>) -> Rc<Type> {
name: "PRNG".to_string(),
arguments: vec![],
};
Rc::new(Type::Fn {
args: vec![prng()],
ret: option(tuple(vec![prng(), a])),
@ -1391,6 +1400,7 @@ pub fn fuzzer(a: Rc<Type>) -> Rc<Type> {
pub fn list(t: Rc<Type>) -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
name: LIST.to_string(),
module: "".to_string(),
args: vec![t],
@ -1402,6 +1412,7 @@ pub fn string() -> Rc<Type> {
Rc::new(Type::App {
args: vec![],
public: true,
contains_opaque: false,
name: STRING.to_string(),
module: "".to_string(),
alias: None,
@ -1412,6 +1423,7 @@ pub fn void() -> Rc<Type> {
Rc::new(Type::App {
args: vec![],
public: true,
contains_opaque: false,
name: VOID.to_string(),
module: "".to_string(),
alias: None,
@ -1421,6 +1433,7 @@ pub fn void() -> Rc<Type> {
pub fn option(a: Rc<Type>) -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
name: OPTION.to_string(),
module: "".to_string(),
args: vec![a],
@ -1431,6 +1444,7 @@ pub fn option(a: Rc<Type>) -> Rc<Type> {
pub fn ordering() -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
name: ORDERING.to_string(),
module: "".to_string(),
args: vec![],
@ -1448,17 +1462,20 @@ pub fn function(args: Vec<Rc<Type>>, ret: Rc<Type>) -> Rc<Type> {
pub fn generic_var(id: u64) -> Rc<Type> {
let tipo = Rc::new(RefCell::new(TypeVar::Generic { id }));
Rc::new(Type::Var { tipo, alias: None })
}
pub fn unbound_var(id: u64) -> Rc<Type> {
let tipo = Rc::new(RefCell::new(TypeVar::Unbound { id }));
Rc::new(Type::Var { tipo, alias: None })
}
pub fn wrapped_redeemer(redeemer: Rc<Type>) -> Rc<Type> {
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: REDEEMER_WRAPPER.to_string(),
args: vec![redeemer],

View File

@ -16,6 +16,7 @@ fn parse(source_code: &str) -> UntypedModule {
fn check_module(
ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
kind: ModuleKind,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
let id_gen = IdGenerator::new();
@ -26,6 +27,21 @@ fn check_module(
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
for (package, module) in extra {
let mut warnings = vec![];
let typed_module = module
.infer(
&id_gen,
kind,
&package,
&module_types,
Tracing::All(TraceLevel::Verbose),
&mut warnings,
)
.expect("extra dependency did not compile");
module_types.insert(package.clone(), typed_module.type_info.clone());
}
let result = ast.infer(
&id_gen,
kind,
@ -41,13 +57,20 @@ fn check_module(
}
fn check(ast: UntypedModule) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, ModuleKind::Lib)
check_module(ast, Vec::new(), ModuleKind::Lib)
}
fn check_with_deps(
ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, extra, ModuleKind::Lib)
}
fn check_validator(
ast: UntypedModule,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, ModuleKind::Validator)
check_module(ast, Vec::new(), ModuleKind::Validator)
}
#[test]
@ -1761,7 +1784,7 @@ fn backpassing_type_annotation() {
(Foo(1), inputs)
}
[input, ..remaining_inputs] -> {
callback(input)(
fn(foo) {
transition_fold4(
@ -1779,7 +1802,7 @@ fn backpassing_type_annotation() {
transition_fold4(
x,
)
fn(g){
g(if input.foo == 1{
1
@ -1787,7 +1810,175 @@ fn backpassing_type_annotation() {
2
})
}
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn forbid_expect_into_opaque_type_from_data() {
let source_code = r#"
opaque type Thing { inner: Int }
fn bar(n: Data) {
expect a: Thing = n
a
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::ExpectOnOpaqueType { .. }))
))
}
#[test]
fn forbid_expect_into_opaque_type_constructor_without_typecasting_in_module() {
let source_code = r#"
opaque type Thing {
Foo(Int)
Bar(Int)
}
fn bar(thing: Thing) {
expect Foo(a) = thing
a
}
"#;
assert!(check(parse(source_code)).is_ok());
}
#[test]
fn forbid_importing_or_using_opaque_constructors() {
let dependency = r#"
pub opaque type Thing {
Foo(Int)
Bar(Int)
}
"#;
let source_code = r#"
use foo/thing.{Thing, Foo}
fn bar(thing: Thing) {
expect Foo(a) = thing
a
}
"#;
assert!(matches!(
check_with_deps(
parse(source_code),
vec![("foo/thing".to_string(), parse(dependency))],
),
Err((_, Error::UnknownModuleField { .. })),
));
let source_code = r#"
use foo/thing.{Thing}
fn bar(thing: Thing) {
expect Foo(a) = thing
a
}
"#;
assert!(matches!(
check_with_deps(
parse(source_code),
vec![("foo/thing".to_string(), parse(dependency))],
),
Err((_, Error::UnknownTypeConstructor { .. })),
));
}
#[test]
fn forbid_expect_into_opaque_type_constructor_with_typecasting() {
let source_code = r#"
opaque type Thing {
Foo(Int)
Bar(Int)
}
fn bar(data: Data) {
expect Foo(a): Thing = data
a
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::ExpectOnOpaqueType { .. }))
))
}
#[test]
fn forbid_expect_into_nested_opaque_in_record_without_typecasting() {
let source_code = r#"
opaque type Thing { inner: Int }
type Foo { foo: Thing }
fn bar(thing: Foo) {
expect Foo { foo: Thing { inner } } : Foo = thing
Void
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::ExpectOnOpaqueType { .. }))
))
}
#[test]
fn forbid_expect_into_nested_opaque_in_record_with_typecasting() {
let source_code = r#"
opaque type Thing { inner: Int }
type Foo { foo: Thing }
fn bar(a: Data) {
expect Foo { foo: Thing { inner } } : Foo = a
Void
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::ExpectOnOpaqueType { .. }))
))
}
#[test]
fn forbid_expect_into_nested_opaque_in_list() {
let source_code = r#"
opaque type Thing { inner: Int }
fn bar(a: Data) {
expect [x]: List<Thing> = [a]
Void
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::ExpectOnOpaqueType { .. }))
))
}
#[test]
fn allow_expect_on_var_patterns_that_are_opaque() {
let source_code = r#"
opaque type Thing { inner: Int }
fn bar(a: Option<Thing>) {
expect Some(thing) = a
thing.inner
}
"#;

View File

@ -42,6 +42,7 @@ pub enum Type {
///
App {
public: bool,
contains_opaque: bool,
module: String,
name: String,
args: Vec<Rc<Type>>,
@ -80,19 +81,22 @@ impl PartialEq for Type {
module,
name,
args,
..
contains_opaque: opaque,
alias: _,
} => {
if let Type::App {
public: public2,
module: module2,
name: name2,
args: args2,
..
contains_opaque: opaque2,
alias: _,
} = other
{
name == name2
&& module == module2
&& public == public2
&& opaque == opaque2
&& args.iter().zip(args2).all(|(left, right)| left == right)
} else {
false
@ -103,7 +107,7 @@ impl PartialEq for Type {
if let Type::Fn {
args: args2,
ret: ret2,
..
alias: _,
} = other
{
ret == ret2 && args.iter().zip(args2).all(|(left, right)| left == right)
@ -112,7 +116,7 @@ impl PartialEq for Type {
}
}
Type::Tuple { elems, .. } => {
Type::Tuple { elems, alias: _ } => {
if let Type::Tuple { elems: elems2, .. } = other {
elems.iter().zip(elems2).all(|(left, right)| left == right)
} else {
@ -120,8 +124,12 @@ impl PartialEq for Type {
}
}
Type::Var { tipo, .. } => {
if let Type::Var { tipo: tipo2, .. } = other {
Type::Var { tipo, alias: _ } => {
if let Type::Var {
tipo: tipo2,
alias: _,
} = other
{
tipo == tipo2
} else {
false
@ -152,20 +160,26 @@ impl Type {
Rc::new(match self {
Type::App {
public,
contains_opaque: opaque,
module,
name,
args,
..
alias: _,
} => Type::App {
public,
contains_opaque: opaque,
module,
name,
args,
alias,
},
Type::Fn { args, ret, .. } => Type::Fn { args, ret, alias },
Type::Var { tipo, .. } => Type::Var { tipo, alias },
Type::Tuple { elems, .. } => Type::Tuple { elems, alias },
Type::Fn {
args,
ret,
alias: _,
} => Type::Fn { args, ret, alias },
Type::Var { tipo, alias: _ } => Type::Var { tipo, alias },
Type::Tuple { elems, alias: _ } => Type::Tuple { elems, alias },
})
}
@ -181,15 +195,28 @@ impl Type {
}
}
pub fn is_result_constructor(&self) -> bool {
pub fn contains_opaque(&self) -> bool {
match self {
Type::Fn { ret, .. } => ret.is_result(),
_ => false,
Type::Var { tipo, .. } => tipo.borrow().is_or_holds_opaque(),
Type::App {
contains_opaque: opaque,
args,
..
} => *opaque || args.iter().any(|arg| arg.contains_opaque()),
Type::Tuple { elems, .. } => elems.iter().any(|elem| elem.contains_opaque()),
Type::Fn { .. } => false,
}
}
pub fn is_result(&self) -> bool {
matches!(self, Self::App { name, module, .. } if "Result" == name && module.is_empty())
pub fn set_opaque(&mut self, opaque: bool) {
match self {
Type::App {
contains_opaque, ..
} => {
*contains_opaque = opaque;
}
Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => (),
}
}
pub fn is_unbound(&self) -> bool {
@ -459,6 +486,7 @@ impl Type {
pub fn get_app_args(
&self,
public: bool,
opaque: bool,
module: &str,
name: &str,
arity: usize,
@ -481,7 +509,7 @@ impl Type {
Self::Var { tipo, alias } => {
let args: Vec<_> = match tipo.borrow().deref() {
TypeVar::Link { tipo } => {
return tipo.get_app_args(public, module, name, arity, environment);
return tipo.get_app_args(public, opaque, module, name, arity, environment);
}
TypeVar::Unbound { .. } => {
@ -496,6 +524,7 @@ impl Type {
*tipo.borrow_mut() = TypeVar::Link {
tipo: Rc::new(Self::App {
public,
contains_opaque: opaque,
name: name.to_string(),
module: module.to_owned(),
args: args.clone(),
@ -650,6 +679,7 @@ pub fn convert_opaque_type(
match t.as_ref() {
Type::App {
public,
contains_opaque: opaque,
module,
name,
args,
@ -662,6 +692,7 @@ pub fn convert_opaque_type(
}
Type::App {
public: *public,
contains_opaque: *opaque,
module: module.clone(),
name: name.clone(),
args: new_args,
@ -736,6 +767,7 @@ pub fn find_and_replace_generics(
Type::App {
args,
public,
contains_opaque: opaque,
module,
name,
alias,
@ -748,6 +780,7 @@ pub fn find_and_replace_generics(
let t = Type::App {
args: new_args,
public: *public,
contains_opaque: *opaque,
module: module.clone(),
name: name.clone(),
alias: alias.clone(),
@ -829,6 +862,13 @@ impl TypeVar {
matches!(self, Self::Unbound { .. })
}
pub fn is_or_holds_opaque(&self) -> bool {
match self {
Self::Link { tipo } => tipo.contains_opaque(),
_ => false,
}
}
pub fn is_void(&self) -> bool {
match self {
Self::Link { tipo } => tipo.is_void(),

View File

@ -145,7 +145,12 @@ impl<'a> Environment<'a> {
}
}
if let Type::Fn { args, ret, .. } = tipo.deref() {
if let Type::Fn {
args,
ret,
alias: _,
} = tipo.deref()
{
return if args.len() != arity {
Err(Error::IncorrectFunctionCallArity {
expected: args.len(),
@ -296,6 +301,25 @@ impl<'a> Environment<'a> {
}
}
pub fn get_type_constructor_mut(
&mut self,
name: &str,
location: Span,
) -> Result<&mut TypeConstructor, Error> {
let types = self.module_types.keys().map(|t| t.to_string()).collect();
let constructor = self
.module_types
.get_mut(name)
.ok_or_else(|| Error::UnknownType {
location,
name: name.to_string(),
types,
})?;
Ok(constructor)
}
/// Lookup a type in the current scope.
pub fn get_type_constructor(
&mut self,
@ -546,6 +570,7 @@ impl<'a> Environment<'a> {
match t.deref() {
Type::App {
public,
contains_opaque: opaque,
name,
module,
args,
@ -555,8 +580,10 @@ impl<'a> Environment<'a> {
.iter()
.map(|t| self.instantiate(t.clone(), ids, hydrator))
.collect();
Rc::new(Type::App {
public: *public,
contains_opaque: *opaque,
name: name.clone(),
module: module.clone(),
alias: alias.clone(),
@ -727,7 +754,7 @@ impl<'a> Environment<'a> {
as_name,
unqualified,
location,
..
package: _,
}) => {
let name = module.join("/");
@ -762,7 +789,6 @@ impl<'a> Environment<'a> {
name,
location,
as_name,
..
} in unqualified
{
let mut type_imported = false;
@ -983,10 +1009,12 @@ impl<'a> Environment<'a> {
Definition::DataType(DataType {
name,
public,
opaque,
parameters,
location,
constructors,
..
doc: _,
typed_parameters: _,
}) => {
assert_unique_type_name(names, name, location)?;
@ -997,6 +1025,7 @@ impl<'a> Environment<'a> {
let tipo = Rc::new(Type::App {
public: *public,
contains_opaque: *opaque,
module: module.to_owned(),
name: name.clone(),
args: parameters.clone(),
@ -1032,7 +1061,8 @@ impl<'a> Environment<'a> {
parameters: args,
alias: name,
annotation: resolved_type,
..
doc: _,
tipo: _,
}) => {
assert_unique_type_name(names, name, location)?;
@ -1173,7 +1203,9 @@ impl<'a> Environment<'a> {
fun,
other_fun,
params,
..
doc: _,
location: _,
end_position: _,
}) if kind.is_validator() => {
let default_annotation = |mut arg: UntypedArg| {
if arg.annotation.is_none() {
@ -1251,7 +1283,10 @@ impl<'a> Environment<'a> {
opaque,
name,
constructors,
..
doc: _,
location: _,
parameters: _,
typed_parameters: _,
}) => {
let mut hydrator = hydrators
.remove(name)
@ -1294,7 +1329,8 @@ impl<'a> Environment<'a> {
label,
annotation,
location,
..
tipo: _,
doc: _,
},
) in constructor.arguments.iter().enumerate()
{
@ -1379,18 +1415,28 @@ impl<'a> Environment<'a> {
&& !(t1.is_function() || t2.is_function())
&& !(t1.is_generic() || t2.is_generic())
&& !(t1.is_string() || t2.is_string())
&& !(t1.contains_opaque() || t2.contains_opaque())
{
return Ok(());
}
if allow_cast && (t1.contains_opaque() || t2.contains_opaque()) {
return Err(Error::ExpectOnOpaqueType { location });
}
// Collapse right hand side type links. Left hand side will be collapsed in the next block.
if let Type::Var { tipo, .. } = t2.deref() {
if let TypeVar::Link { tipo, .. } = tipo.borrow().deref() {
return self.unify(t1, tipo.clone(), location, allow_cast);
if let Type::Var { tipo, alias } = t2.deref() {
if let TypeVar::Link { tipo } = tipo.borrow().deref() {
return self.unify(
t1,
Type::with_alias(tipo.clone(), alias.clone()),
location,
allow_cast,
);
}
}
if let Type::Var { tipo, .. } = t1.deref() {
if let Type::Var { tipo, alias } = t1.deref() {
enum Action {
Unify(Rc<Type>),
CouldNotUnify,
@ -1398,7 +1444,9 @@ impl<'a> Environment<'a> {
}
let action = match tipo.borrow().deref() {
TypeVar::Link { tipo } => Action::Unify(tipo.clone()),
TypeVar::Link { tipo } => {
Action::Unify(Type::with_alias(tipo.clone(), alias.clone()))
}
TypeVar::Unbound { id } => {
unify_unbound_type(t2.clone(), *id, location)?;
@ -1406,7 +1454,7 @@ impl<'a> Environment<'a> {
}
TypeVar::Generic { id } => {
if let Type::Var { tipo, .. } = t2.deref() {
if let Type::Var { tipo, alias: _ } = t2.deref() {
if tipo.borrow().is_unbound() {
*tipo.borrow_mut() = TypeVar::Generic { id: *id };
return Ok(());
@ -1446,13 +1494,17 @@ impl<'a> Environment<'a> {
module: m1,
name: n1,
args: args1,
..
public: _,
contains_opaque: _,
alias: _,
},
Type::App {
module: m2,
name: n2,
args: args2,
..
public: _,
contains_opaque: _,
alias: _,
},
) if m1 == m2 && n1 == n2 && args1.len() == args2.len() => {
for (a, b) in args1.iter().zip(args2) {
@ -1465,9 +1517,16 @@ impl<'a> Environment<'a> {
Ok(())
}
(Type::Tuple { elems: elems1, .. }, Type::Tuple { elems: elems2, .. })
if elems1.len() == elems2.len() =>
{
(
Type::Tuple {
elems: elems1,
alias: _,
},
Type::Tuple {
elems: elems2,
alias: _,
},
) if elems1.len() == elems2.len() => {
for (a, b) in elems1.iter().zip(elems2) {
unify_enclosed_type(
t1.clone(),
@ -1482,12 +1541,12 @@ impl<'a> Environment<'a> {
Type::Fn {
args: args1,
ret: retrn1,
..
alias: _,
},
Type::Fn {
args: args2,
ret: retrn2,
..
alias: _,
},
) if args1.len() == args2.len() => {
for (a, b) in args1.iter().zip(args2) {
@ -1673,10 +1732,14 @@ pub enum EntityKind {
/// could cause naively-implemented type checking to diverge.
/// While traversing the type tree.
fn unify_unbound_type(tipo: Rc<Type>, own_id: u64, location: Span) -> Result<(), Error> {
if let Type::Var { tipo, .. } = tipo.deref() {
if let Type::Var { tipo, alias } = tipo.deref() {
let new_value = match tipo.borrow().deref() {
TypeVar::Link { tipo, .. } => {
return unify_unbound_type(tipo.clone(), own_id, location);
TypeVar::Link { tipo } => {
return unify_unbound_type(
Type::with_alias(tipo.clone(), alias.clone()),
own_id,
location,
);
}
TypeVar::Unbound { id } => {
@ -1697,7 +1760,14 @@ fn unify_unbound_type(tipo: Rc<Type>, own_id: u64, location: Span) -> Result<(),
}
match tipo.deref() {
Type::App { args, .. } => {
Type::App {
args,
module: _,
name: _,
public: _,
alias: _,
contains_opaque: _,
} => {
for arg in args {
unify_unbound_type(arg.clone(), own_id, location)?
}
@ -1705,7 +1775,11 @@ fn unify_unbound_type(tipo: Rc<Type>, own_id: u64, location: Span) -> Result<(),
Ok(())
}
Type::Fn { args, ret, .. } => {
Type::Fn {
args,
ret,
alias: _,
} => {
for arg in args {
unify_unbound_type(arg.clone(), own_id, location)?;
}
@ -1713,7 +1787,7 @@ fn unify_unbound_type(tipo: Rc<Type>, own_id: u64, location: Span) -> Result<(),
unify_unbound_type(ret.clone(), own_id, location)
}
Type::Tuple { elems, .. } => {
Type::Tuple { elems, alias: _ } => {
for elem in elems {
unify_unbound_type(elem.clone(), own_id, location)?
}
@ -1800,9 +1874,9 @@ pub(super) fn assert_no_labeled_arguments<A>(args: &[CallArg<A>]) -> Option<(Spa
}
pub(super) fn collapse_links(t: Rc<Type>) -> Rc<Type> {
if let Type::Var { tipo, .. } = t.deref() {
if let Type::Var { tipo, alias } = t.deref() {
if let TypeVar::Link { tipo } = tipo.borrow().deref() {
return tipo.clone();
return Type::with_alias(tipo.clone(), alias.clone());
}
}
t
@ -1856,6 +1930,7 @@ pub(crate) fn generalise(t: Rc<Type>, ctx_level: usize) -> Rc<Type> {
Type::App {
public,
contains_opaque: opaque,
module,
name,
args,
@ -1868,6 +1943,7 @@ pub(crate) fn generalise(t: Rc<Type>, ctx_level: usize) -> Rc<Type> {
Rc::new(Type::App {
public: *public,
contains_opaque: *opaque,
module: module.clone(),
name: name.clone(),
args,

View File

@ -259,6 +259,19 @@ You can use '{discard}' and numbers to distinguish between similar names.
name: String,
},
#[error("I caught an opaque type possibly breaking its abstraction boundary.\n")]
#[diagnostic(code("illegal::expect_on_opaque"))]
#[diagnostic(url("https://aiken-lang.org/language-tour/modules#opaque-types"))]
#[diagnostic(help(
"This expression is trying to convert something unknown into an opaque type. An opaque type is a data-type which hides its internal details; usually because it enforces some specific invariant on its internal structure. For example, you might define a {Natural} type that holds an {Integer} but ensures that it never gets negative.\n\nA direct consequence means that it isn't generally possible, nor safe, to turn *any* value into an opaque type. Instead, use the constructors and methods provided for lifting values into that opaque type while ensuring that any structural invariant is checked for.",
Natural = "Natural".if_supports_color(Stdout, |s| s.cyan()),
Integer = "Integer".if_supports_color(Stdout, |s| s.cyan()),
))]
ExpectOnOpaqueType {
#[label("reckless opaque cast")]
location: Span,
},
#[error("I found a type definition that has a function type in it. This is not allowed.\n")]
#[diagnostic(code("illegal::function_in_type"))]
#[diagnostic(help(
@ -1045,6 +1058,7 @@ impl ExtraData for Error {
| Error::IncorrectTestArity { .. }
| Error::GenericLeftAtBoundary { .. }
| Error::UnexpectedMultiPatternAssignment { .. }
| Error::ExpectOnOpaqueType { .. }
| Error::ValidatorMustReturnBool { .. } => None,
Error::UnknownType { name, .. }

View File

@ -211,10 +211,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
match expr {
UntypedExpr::ErrorTerm { location } => Ok(self.infer_error_term(location)),
UntypedExpr::Var { location, name, .. } => self.infer_var(name, location),
UntypedExpr::Var { location, name } => self.infer_var(name, location),
UntypedExpr::UInt {
location, value, ..
location,
value,
base: _,
} => Ok(self.infer_uint(value, location)),
UntypedExpr::Sequence {
@ -222,13 +224,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location,
} => self.infer_seq(location, expressions),
UntypedExpr::Tuple {
location, elems, ..
} => self.infer_tuple(elems, location),
UntypedExpr::Tuple { location, elems } => self.infer_tuple(elems, location),
UntypedExpr::String {
location, value, ..
} => Ok(self.infer_string(value, location)),
UntypedExpr::String { location, value } => Ok(self.infer_string(value, location)),
UntypedExpr::LogicalOpChain {
kind,
@ -244,7 +242,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
arguments: args,
body,
return_annotation,
..
} => self.infer_fn(
args,
&[],
@ -265,7 +262,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
patterns,
value,
kind,
..
} => {
// at this point due to backpassing rewrites,
// patterns is guaranteed to have one item
@ -295,14 +291,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location,
elements,
tail,
..
} => self.infer_list(elements, tail, location),
UntypedExpr::Call {
location,
fun,
arguments: args,
..
} => self.infer_call(*fun, args, location),
UntypedExpr::BinOp {
@ -310,21 +304,18 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
name,
left,
right,
..
} => self.infer_binop(name, *left, *right, location),
UntypedExpr::FieldAccess {
location,
label,
container,
..
} => self.infer_field_access(*container, label, location),
UntypedExpr::TupleIndex {
location,
index,
tuple,
..
} => self.infer_tuple_index(*tuple, index, location),
UntypedExpr::ByteArray {
@ -334,7 +325,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
} => self.infer_bytearray(bytes, preferred_format, location),
UntypedExpr::CurvePoint {
location, point, ..
location,
point,
preferred_format: _,
} => self.infer_curve_point(*point, location),
UntypedExpr::RecordUpdate {
@ -578,7 +571,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
ValueConstructorVariant::Record {
field_map: Some(field_map),
constructors_count,
..
name: _,
arity: _,
location: _,
module: _,
} => (field_map, *constructors_count),
_ => {
return Err(Error::RecordUpdateInvalidConstructor {
@ -704,7 +700,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Ok(record_access) => Ok(record_access),
Err(err) => match container {
UntypedExpr::Var { name, location, .. } => {
UntypedExpr::Var { name, location } => {
let module_access =
self.infer_module_access(&name, label, &location, access_location);
@ -894,7 +890,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
annotation,
location,
doc,
..
tipo: _,
} = arg;
let tipo = annotation
@ -934,7 +930,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
let value_is_data = value_typ.is_data();
// Check that any type annotation is accurate.
let pattern = if let Some(ann) = annotation {
let ann_typ = if let Some(ann) = annotation {
let ann_typ = self
.type_from_annotation(ann)
.map(|t| self.instantiate(t, &mut HashMap::new()))?;
@ -943,18 +939,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
ann_typ.clone(),
value_typ.clone(),
typed_value.type_defining_location(),
(kind.is_let() && ann_typ.is_data()) || (kind.is_expect() && value_is_data),
(kind.is_let() && ann_typ.is_data()) || kind.is_expect(),
)?;
value_typ = ann_typ.clone();
// Ensure the pattern matches the type of the value
PatternTyper::new(self.environment, &self.hydrator).unify(
untyped_pattern.clone(),
value_typ.clone(),
Some(ann_typ),
kind.is_let(),
)?
Some(ann_typ)
} else {
if value_is_data && !untyped_pattern.is_var() && !untyped_pattern.is_discard() {
let ann = Annotation::Constructor {
@ -975,15 +965,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
});
}
// Ensure the pattern matches the type of the value
PatternTyper::new(self.environment, &self.hydrator).unify(
untyped_pattern.clone(),
value_typ.clone(),
None,
kind.is_let(),
)?
None
};
// Ensure the pattern matches the type of the value
let pattern = PatternTyper::new(self.environment, &self.hydrator).unify(
untyped_pattern.clone(),
value_typ.clone(),
ann_typ,
kind.is_let(),
)?;
// If `expect` is explicitly used, we still check exhaustiveness but instead of returning an
// error we emit a warning which explains that using `expect` is unnecessary.
match kind {
@ -1078,7 +1070,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
(
Type::Fn {
args: expected_arguments,
..
ret: _,
alias: _,
},
UntypedExpr::Fn {
arguments,
@ -1086,7 +1079,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
return_annotation,
location,
fn_style,
..
},
) if fn_style != FnStyle::Capture && expected_arguments.len() == arguments.len() => {
self.infer_fn(
@ -1389,9 +1381,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
base,
}),
Constant::String {
location, value, ..
} => Ok(Constant::String { location, value }),
Constant::String { location, value } => Ok(Constant::String { location, value }),
Constant::ByteArray {
location,
@ -1739,7 +1729,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
value,
kind,
patterns,
..
} = breakpoint
else {
unreachable!("backpass misuse: breakpoint isn't an Assignment ?!");
@ -1767,7 +1756,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
// in front of the continuation sequence. This is because we do not support patterns in function argument
// (which is perhaps something we should support?).
match pattern {
Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => {
Pattern::Var { name, location: _ } | Pattern::Discard { name, location: _ }
if kind.is_let() =>
{
names.push((name.clone(), annotation));
}
_ => {
@ -1797,7 +1788,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}
match *value {
UntypedExpr::Call { fun, arguments, .. } => {
UntypedExpr::Call {
fun,
arguments,
location: _,
} => {
let mut new_arguments = Vec::new();
new_arguments.extend(arguments);
new_arguments.push(CallArg {
@ -1823,7 +1818,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
fn_style,
ref arguments,
ref return_annotation,
..
location: _,
body: _,
} => {
let return_annotation = return_annotation.clone();
@ -1882,7 +1878,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
breakpoint = Some(expression);
}
UntypedExpr::Assignment {
patterns, location, ..
patterns,
location,
value: _,
kind: _,
} if patterns.len() > 1 => {
return Err(Error::UnexpectedMultiPatternAssignment {
arrow: patterns
@ -2003,7 +2002,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
let tuple = self.infer(tuple)?;
let tipo = match *tuple.tipo() {
Type::Tuple { ref elems, .. } => {
Type::Tuple {
ref elems,
alias: _,
} => {
let size = elems.len();
if index >= size {
Err(Error::TupleIndexOutOfBound {
@ -2332,7 +2334,14 @@ fn assert_assignment(expr: TypedExpr) -> Result<TypedExpr, Error> {
pub fn ensure_serialisable(allow_fn: bool, t: Rc<Type>, location: Span) -> Result<(), Error> {
match t.deref() {
Type::App { args, .. } => {
Type::App {
args,
name: _,
module: _,
public: _,
contains_opaque: _,
alias: _,
} => {
if t.is_ml_result() {
return Err(Error::IllegalTypeInData {
tipo: t.clone(),
@ -2347,7 +2356,7 @@ pub fn ensure_serialisable(allow_fn: bool, t: Rc<Type>, location: Span) -> Resul
Ok(())
}
Type::Tuple { elems, .. } => {
Type::Tuple { elems, alias: _ } => {
elems
.iter()
.map(|e| ensure_serialisable(false, e.clone(), location))
@ -2356,7 +2365,11 @@ pub fn ensure_serialisable(allow_fn: bool, t: Rc<Type>, location: Span) -> Resul
Ok(())
}
Type::Fn { args, ret, .. } => {
Type::Fn {
args,
ret,
alias: _,
} => {
if !allow_fn {
return Err(Error::IllegalTypeInData {
tipo: t.clone(),
@ -2371,10 +2384,14 @@ pub fn ensure_serialisable(allow_fn: bool, t: Rc<Type>, location: Span) -> Resul
ensure_serialisable(allow_fn, ret.clone(), location)
}
Type::Var { tipo, .. } => match tipo.borrow().deref() {
Type::Var { tipo, alias } => match tipo.borrow().deref() {
TypeVar::Unbound { .. } => Ok(()),
TypeVar::Generic { .. } => Ok(()),
TypeVar::Link { tipo } => ensure_serialisable(allow_fn, tipo.clone(), location),
TypeVar::Link { tipo } => ensure_serialisable(
allow_fn,
Type::with_alias(tipo.clone(), alias.clone()),
location,
),
},
}
}

View File

@ -89,6 +89,7 @@ impl UntypedModule {
&self.lines,
tracing,
)?;
definitions.push(definition);
}
@ -197,7 +198,8 @@ fn infer_definition(
ArgName::Named {
name,
is_validator_param,
..
label: _,
location: _,
} if *is_validator_param => {
environment.insert_variable(
name.to_string(),
@ -383,7 +385,9 @@ fn infer_definition(
.get_mut(&f.name)
.expect("Could not find preregistered type for test");
if let Type::Fn {
ref ret, ref alias, ..
ref ret,
ref alias,
args: _,
} = scope.tipo.as_ref()
{
scope.tipo = Rc::new(Type::Fn {
@ -425,7 +429,11 @@ fn infer_definition(
arguments: match typed_via {
Some((via, tipo)) => {
let Arg {
arg_name, location, ..
arg_name,
location,
annotation: _,
doc: _,
tipo: _,
} = typed_f
.arguments
.first()
@ -457,7 +465,7 @@ fn infer_definition(
alias,
parameters,
annotation,
..
tipo: _,
}) => {
let tipo = environment
.get_type_constructor(&None, &alias, location)
@ -484,64 +492,66 @@ fn infer_definition(
name,
parameters,
constructors: untyped_constructors,
..
typed_parameters: _,
}) => {
let constructors = untyped_constructors
.into_iter()
.map(
|RecordConstructor {
location,
name,
arguments: args,
doc,
sugar,
}| {
let preregistered_fn = environment
.get_variable(&name)
.expect("Could not find preregistered type for function");
.map(|constructor| {
let preregistered_fn = environment
.get_variable(&constructor.name)
.expect("Could not find preregistered type for function");
let preregistered_type = preregistered_fn.tipo.clone();
let preregistered_type = preregistered_fn.tipo.clone();
let args = if let Some((args_types, _return_type)) =
preregistered_type.function_types()
{
args.into_iter()
let args = preregistered_type.function_types().map_or(
Ok(vec![]),
|(args_types, _return_type)| {
constructor
.arguments
.into_iter()
.zip(&args_types)
.map(
|(
RecordConstructorArg {
label,
annotation,
location,
doc,
..
},
t,
)| {
RecordConstructorArg {
label,
annotation,
location,
tipo: t.clone(),
doc,
}
},
)
.collect()
} else {
vec![]
};
.map(|(arg, t)| {
if t.is_function() {
return Err(Error::FunctionTypeInData {
location: arg.location,
});
}
RecordConstructor {
location,
name,
arguments: args,
doc,
sugar,
}
},
)
.collect();
if t.is_ml_result() {
return Err(Error::IllegalTypeInData {
location: arg.location,
tipo: t.clone(),
});
}
if t.contains_opaque() {
let parent = environment
.get_type_constructor_mut(&name, location)?;
Rc::make_mut(&mut parent.tipo).set_opaque(true)
}
Ok(RecordConstructorArg {
label: arg.label,
annotation: arg.annotation,
location: arg.location,
doc: arg.doc,
tipo: t.clone(),
})
})
.collect()
},
)?;
Ok(RecordConstructor {
location: constructor.location,
name: constructor.name,
arguments: args,
doc: constructor.doc,
sugar: constructor.sugar,
})
})
.collect::<Result<_, Error>>()?;
let typed_parameters = environment
.get_type_constructor(&None, &name, location)
@ -561,7 +571,14 @@ fn infer_definition(
};
for constr in &typed_data.constructors {
for RecordConstructorArg { tipo, location, .. } in &constr.arguments {
for RecordConstructorArg {
tipo,
location,
doc: _,
label: _,
annotation: _,
} in &constr.arguments
{
if tipo.is_function() {
return Err(Error::FunctionTypeInData {
location: *location,
@ -585,7 +602,7 @@ fn infer_definition(
module,
as_name,
unqualified,
..
package: _,
}) => {
let name = module.join("/");
@ -616,7 +633,7 @@ fn infer_definition(
annotation,
public,
value,
..
tipo: _,
}) => {
let typed_expr =
ExprTyper::new(environment, lines, tracing).infer_const(&annotation, *value)?;
@ -672,7 +689,7 @@ fn infer_function(
return_annotation,
end_position,
can_error,
..
return_type: _,
} = f;
let preregistered_fn = environment
@ -773,9 +790,18 @@ fn infer_fuzzer(
};
match tipo.borrow() {
Type::Fn { ret, .. } => match ret.borrow() {
Type::Fn {
ret,
args: _,
alias: _,
} => match ret.borrow() {
Type::App {
module, name, args, ..
module,
name,
args,
public: _,
contains_opaque: _,
alias: _,
} if module.is_empty() && name == "Option" && args.len() == 1 => {
match args.first().expect("args.len() == 1").borrow() {
Type::Tuple { elems, .. } if elems.len() == 2 => {
@ -805,10 +831,13 @@ fn infer_fuzzer(
_ => Err(could_not_unify()),
},
Type::Var { tipo, .. } => match &*tipo.deref().borrow() {
TypeVar::Link { tipo } => {
infer_fuzzer(environment, expected_inner_type, tipo, location)
}
Type::Var { tipo, alias } => match &*tipo.deref().borrow() {
TypeVar::Link { tipo } => infer_fuzzer(
environment,
expected_inner_type,
&Type::with_alias(tipo.clone(), alias.clone()),
location,
),
_ => Err(Error::GenericLeftAtBoundary {
location: *location,
}),
@ -821,7 +850,12 @@ fn infer_fuzzer(
fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
match tipo {
Type::App {
name, module, args, ..
name,
module,
args,
public: _,
contains_opaque: _,
alias: _,
} => {
let arguments = args
.iter()
@ -839,7 +873,7 @@ fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
})
}
Type::Tuple { elems, .. } => {
Type::Tuple { elems, alias: _ } => {
let elems = elems
.iter()
.map(|arg| annotate_fuzzer(arg, location))
@ -850,7 +884,7 @@ fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result<Annotation, Error> {
})
}
Type::Var { tipo, .. } => match &*tipo.deref().borrow() {
Type::Var { tipo, alias: _ } => match &*tipo.deref().borrow() {
TypeVar::Link { tipo } => annotate_fuzzer(tipo, location),
_ => Err(Error::GenericLeftAtBoundary {
location: *location,

View File

@ -141,11 +141,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
pattern: UntypedPattern,
tipo: Rc<Type>,
ann_type: Option<Rc<Type>>,
is_assignment: bool,
is_let: bool,
) -> Result<TypedPattern, Error> {
match pattern {
Pattern::Discard { name, location } => {
if is_assignment {
if is_let {
// Register declaration for the unused variable detection
self.environment
.warnings
@ -154,6 +154,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
location,
});
};
Ok(Pattern::Discard { name, location })
}
@ -202,7 +203,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
location,
elements,
tail,
} => match tipo.get_app_args(true, "", "List", 1, self.environment) {
} => match tipo.get_app_args(true, false, "", "List", 1, self.environment) {
Some(args) => {
let tipo = args
.first()

View File

@ -368,6 +368,7 @@ mod tests {
module: "whatever".to_string(),
name: "Int".to_string(),
public: true,
contains_opaque: false,
args: vec![],
alias: None
},
@ -378,12 +379,14 @@ mod tests {
module: "".to_string(),
name: "Pair".to_string(),
public: true,
contains_opaque: false,
alias: None,
args: vec![
Rc::new(Type::App {
module: "whatever".to_string(),
name: "Int".to_string(),
public: true,
contains_opaque: false,
args: vec![],
alias: None
}),
@ -391,6 +394,7 @@ mod tests {
module: "whatever".to_string(),
name: "Bool".to_string(),
public: true,
contains_opaque: false,
args: vec![],
alias: None
}),
@ -406,6 +410,7 @@ mod tests {
module: "whatever".to_string(),
name: "Int".to_string(),
public: true,
contains_opaque: false,
alias: None,
}),
Rc::new(Type::App {
@ -413,6 +418,7 @@ mod tests {
module: "whatever".to_string(),
name: "Bool".to_string(),
public: true,
contains_opaque: false,
alias: None,
}),
],
@ -421,6 +427,7 @@ mod tests {
module: "whatever".to_string(),
name: "Bool".to_string(),
public: true,
contains_opaque: false,
alias: None,
}),
alias: None,
@ -437,6 +444,7 @@ mod tests {
module: "whatever".to_string(),
name: "Int".to_string(),
public: true,
contains_opaque: false,
}),
})),
},
@ -479,6 +487,7 @@ mod tests {
Type::Fn {
args: vec![Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
@ -486,12 +495,14 @@ mod tests {
})],
ret: Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "Option".to_string(),
args: vec![Rc::new(Type::Tuple {
elems: vec![
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
@ -499,6 +510,7 @@ mod tests {
}),
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "Bool".to_string(),
args: vec![],
@ -549,6 +561,7 @@ mod tests {
Type::Fn {
args: vec![Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
@ -556,12 +569,14 @@ mod tests {
})],
ret: Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "Option".to_string(),
args: vec![Rc::new(Type::Tuple {
elems: vec![
Rc::new(Type::App {
public: true,
contains_opaque: false,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],

View File

@ -8,6 +8,7 @@ Schema {
breadcrumbs: [
App {
public: true,
contains_opaque: true,
module: "test_module",
name: "Rational",
args: [],

View File

@ -8,6 +8,7 @@ Schema {
breadcrumbs: [
App {
public: true,
contains_opaque: true,
module: "test_module",
name: "Dict",
args: [
@ -16,6 +17,7 @@ Schema {
value: Link {
tipo: App {
public: false,
contains_opaque: false,
module: "test_module",
name: "UUID",
args: [],
@ -30,6 +32,7 @@ Schema {
value: Link {
tipo: App {
public: true,
contains_opaque: false,
module: "",
name: "Int",
args: [],