diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 750d7cc4..e2dec29f 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1225,6 +1225,7 @@ impl TypedPattern { } pub fn tipo(&self, value: &TypedExpr) -> Option> { + // expect thing: Wow = thing match self { Pattern::Int { .. } => Some(builtins::int()), Pattern::Constructor { tipo, .. } => Some(tipo.clone()), diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index da98a870..962e4ca4 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -1267,6 +1267,7 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap Rc { Rc::new(Type::App { public: true, + opaque: false, name: INT.to_string(), module: "".to_string(), args: vec![], @@ -1277,6 +1278,7 @@ pub fn int() -> Rc { pub fn data() -> Rc { Rc::new(Type::App { public: true, + opaque: false, name: DATA.to_string(), module: "".to_string(), args: vec![], @@ -1288,6 +1290,7 @@ pub fn byte_array() -> Rc { Rc::new(Type::App { args: vec![], public: true, + opaque: false, name: BYTE_ARRAY.to_string(), module: "".to_string(), alias: None, @@ -1297,6 +1300,7 @@ pub fn byte_array() -> Rc { pub fn g1_element() -> Rc { Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: G1_ELEMENT.to_string(), args: vec![], @@ -1307,6 +1311,7 @@ pub fn g1_element() -> Rc { pub fn g2_element() -> Rc { Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: G2_ELEMENT.to_string(), args: vec![], @@ -1317,6 +1322,7 @@ pub fn g2_element() -> Rc { pub fn miller_loop_result() -> Rc { Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: MILLER_LOOP_RESULT.to_string(), args: vec![], @@ -1332,6 +1338,7 @@ pub fn bool() -> Rc { Rc::new(Type::App { args: vec![], public: true, + opaque: false, name: BOOL.to_string(), module: "".to_string(), alias: None, @@ -1342,6 +1349,7 @@ pub fn prng() -> Rc { Rc::new(Type::App { args: vec![], public: true, + opaque: false, name: PRNG.to_string(), module: "".to_string(), alias: None, @@ -1355,6 +1363,7 @@ pub fn fuzzer(a: Rc) -> Rc { 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) -> Rc { pub fn list(t: Rc) -> Rc { Rc::new(Type::App { public: true, + opaque: false, name: LIST.to_string(), module: "".to_string(), args: vec![t], @@ -1402,6 +1412,7 @@ pub fn string() -> Rc { Rc::new(Type::App { args: vec![], public: true, + opaque: false, name: STRING.to_string(), module: "".to_string(), alias: None, @@ -1412,6 +1423,7 @@ pub fn void() -> Rc { Rc::new(Type::App { args: vec![], public: true, + opaque: false, name: VOID.to_string(), module: "".to_string(), alias: None, @@ -1421,6 +1433,7 @@ pub fn void() -> Rc { pub fn option(a: Rc) -> Rc { Rc::new(Type::App { public: true, + opaque: false, name: OPTION.to_string(), module: "".to_string(), args: vec![a], @@ -1431,6 +1444,7 @@ pub fn option(a: Rc) -> Rc { pub fn ordering() -> Rc { Rc::new(Type::App { public: true, + opaque: false, name: ORDERING.to_string(), module: "".to_string(), args: vec![], @@ -1448,17 +1462,20 @@ pub fn function(args: Vec>, ret: Rc) -> Rc { pub fn generic_var(id: u64) -> Rc { let tipo = Rc::new(RefCell::new(TypeVar::Generic { id })); + Rc::new(Type::Var { tipo, alias: None }) } pub fn unbound_var(id: u64) -> Rc { let tipo = Rc::new(RefCell::new(TypeVar::Unbound { id })); + Rc::new(Type::Var { tipo, alias: None }) } pub fn wrapped_redeemer(redeemer: Rc) -> Rc { Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: REDEEMER_WRAPPER.to_string(), args: vec![redeemer], diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 6aac1771..0bc67def 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1793,3 +1793,41 @@ fn backpassing_type_annotation() { assert!(check(parse(source_code)).is_ok()) } + +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() { + let source_code = r#" + opaque type Thing { + Foo(Int) + Bar(Int) + } + + fn bar(n: Thing) { + expect Foo(a) = n + + a + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::ExpectOnOpaqueType { .. })) + )) +} diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index 95442cae..9a113632 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -42,6 +42,7 @@ pub enum Type { /// App { public: bool, + opaque: bool, module: String, name: String, args: Vec>, @@ -152,12 +153,14 @@ impl Type { Rc::new(match self { Type::App { public, + opaque, module, name, args, .. } => Type::App { public, + opaque, module, name, args, @@ -181,17 +184,13 @@ impl Type { } } - pub fn is_result_constructor(&self) -> bool { + pub fn is_opaque(&self) -> bool { match self { - Type::Fn { ret, .. } => ret.is_result(), + Type::App { opaque, .. } => *opaque, _ => false, } } - pub fn is_result(&self) -> bool { - matches!(self, Self::App { name, module, .. } if "Result" == name && module.is_empty()) - } - pub fn is_unbound(&self) -> bool { matches!(self, Self::Var { tipo, .. } if tipo.borrow().is_unbound()) } @@ -459,6 +458,7 @@ impl Type { pub fn get_app_args( &self, public: bool, + opaque: bool, module: &str, name: &str, arity: usize, @@ -481,7 +481,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 +496,7 @@ impl Type { *tipo.borrow_mut() = TypeVar::Link { tipo: Rc::new(Self::App { public, + opaque, name: name.to_string(), module: module.to_owned(), args: args.clone(), @@ -650,6 +651,7 @@ pub fn convert_opaque_type( match t.as_ref() { Type::App { public, + opaque, module, name, args, @@ -662,6 +664,7 @@ pub fn convert_opaque_type( } Type::App { public: *public, + opaque: *opaque, module: module.clone(), name: name.clone(), args: new_args, @@ -736,6 +739,7 @@ pub fn find_and_replace_generics( Type::App { args, public, + opaque, module, name, alias, @@ -748,6 +752,7 @@ pub fn find_and_replace_generics( let t = Type::App { args: new_args, public: *public, + opaque: *opaque, module: module.clone(), name: name.clone(), alias: alias.clone(), diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 1b240269..b83482a6 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -546,6 +546,7 @@ impl<'a> Environment<'a> { match t.deref() { Type::App { public, + opaque, name, module, args, @@ -555,8 +556,10 @@ impl<'a> Environment<'a> { .iter() .map(|t| self.instantiate(t.clone(), ids, hydrator)) .collect(); + Rc::new(Type::App { public: *public, + opaque: *opaque, name: name.clone(), module: module.clone(), alias: alias.clone(), @@ -983,6 +986,7 @@ impl<'a> Environment<'a> { Definition::DataType(DataType { name, public, + opaque, parameters, location, constructors, @@ -997,6 +1001,7 @@ impl<'a> Environment<'a> { let tipo = Rc::new(Type::App { public: *public, + opaque: *opaque, module: module.to_owned(), name: name.clone(), args: parameters.clone(), @@ -1856,6 +1861,7 @@ pub(crate) fn generalise(t: Rc, ctx_level: usize) -> Rc { Type::App { public, + opaque, module, name, args, @@ -1868,6 +1874,7 @@ pub(crate) fn generalise(t: Rc, ctx_level: usize) -> Rc { Rc::new(Type::App { public: *public, + opaque: *opaque, module: module.clone(), name: name.clone(), args, diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index ca1fbe7c..a8298cb0 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -259,6 +259,13 @@ You can use '{discard}' and numbers to distinguish between similar names. name: String, }, + #[error("I found an expect assignment involving an opaque type. This is not allowed.\n")] + #[diagnostic(code("illegal::expect_on_opaque"))] + ExpectOnOpaqueType { + #[label] + 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 +1052,7 @@ impl ExtraData for Error { | Error::IncorrectTestArity { .. } | Error::GenericLeftAtBoundary { .. } | Error::UnexpectedMultiPatternAssignment { .. } + | Error::ExpectOnOpaqueType { .. } | Error::ValidatorMustReturnBool { .. } => None, Error::UnknownType { name, .. } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index b54e32c0..9a7e1abd 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -932,6 +932,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let mut value_typ = typed_value.tipo(); let value_is_data = value_typ.is_data(); + let value_is_opaque = value_typ.is_opaque(); // Check that any type annotation is accurate. let pattern = if let Some(ann) = annotation { @@ -939,6 +940,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .type_from_annotation(ann) .map(|t| self.instantiate(t, &mut HashMap::new()))?; + if kind.is_expect() && (ann_typ.is_opaque() || value_is_opaque) { + return Err(Error::ExpectOnOpaqueType { location }); + } + self.unify( ann_typ.clone(), value_typ.clone(), @@ -975,6 +980,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }); } + if kind.is_expect() && value_is_opaque { + return Err(Error::ExpectOnOpaqueType { location }); + } + // Ensure the pattern matches the type of the value PatternTyper::new(self.environment, &self.hydrator).unify( untyped_pattern.clone(), diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index cf437ec7..3fb595c3 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -202,7 +202,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() diff --git a/crates/aiken-lang/src/tipo/pretty.rs b/crates/aiken-lang/src/tipo/pretty.rs index 27da2f6e..3a6d7af5 100644 --- a/crates/aiken-lang/src/tipo/pretty.rs +++ b/crates/aiken-lang/src/tipo/pretty.rs @@ -368,6 +368,7 @@ mod tests { module: "whatever".to_string(), name: "Int".to_string(), public: true, + opaque: false, args: vec![], alias: None }, @@ -378,12 +379,14 @@ mod tests { module: "".to_string(), name: "Pair".to_string(), public: true, + opaque: false, alias: None, args: vec![ Rc::new(Type::App { module: "whatever".to_string(), name: "Int".to_string(), public: true, + opaque: false, args: vec![], alias: None }), @@ -391,6 +394,7 @@ mod tests { module: "whatever".to_string(), name: "Bool".to_string(), public: true, + opaque: false, args: vec![], alias: None }), @@ -406,6 +410,7 @@ mod tests { module: "whatever".to_string(), name: "Int".to_string(), public: true, + opaque: false, alias: None, }), Rc::new(Type::App { @@ -413,6 +418,7 @@ mod tests { module: "whatever".to_string(), name: "Bool".to_string(), public: true, + opaque: false, alias: None, }), ], @@ -421,6 +427,7 @@ mod tests { module: "whatever".to_string(), name: "Bool".to_string(), public: true, + opaque: false, alias: None, }), alias: None, @@ -437,6 +444,7 @@ mod tests { module: "whatever".to_string(), name: "Int".to_string(), public: true, + opaque: false, }), })), }, @@ -479,6 +487,7 @@ mod tests { Type::Fn { args: vec![Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], @@ -486,12 +495,14 @@ mod tests { })], ret: Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: "Option".to_string(), args: vec![Rc::new(Type::Tuple { elems: vec![ Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], @@ -499,6 +510,7 @@ mod tests { }), Rc::new(Type::App { public: true, + 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, + opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], @@ -556,12 +569,14 @@ mod tests { })], ret: Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: "Option".to_string(), args: vec![Rc::new(Type::Tuple { elems: vec![ Rc::new(Type::App { public: true, + opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap index 333f1d8b..1b65548e 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap @@ -8,6 +8,7 @@ Schema { breadcrumbs: [ App { public: true, + opaque: true, module: "test_module", name: "Rational", args: [], diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap index f5d9e83c..bcfa9f67 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap @@ -8,6 +8,7 @@ Schema { breadcrumbs: [ App { public: true, + opaque: true, module: "test_module", name: "Dict", args: [ @@ -16,6 +17,7 @@ Schema { value: Link { tipo: App { public: false, + opaque: false, module: "test_module", name: "UUID", args: [], @@ -30,6 +32,7 @@ Schema { value: Link { tipo: App { public: true, + opaque: false, module: "", name: "Int", args: [],