diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index da98a870..6b18f941 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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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, + contains_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..53bc42c0 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -16,6 +16,7 @@ fn parse(source_code: &str) -> UntypedModule { fn check_module( ast: UntypedModule, + extra: Vec<(String, UntypedModule)>, kind: ModuleKind, ) -> Result<(Vec, TypedModule), (Vec, 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, TypedModule), (Vec, 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, TypedModule), (Vec, Error)> { + check_module(ast, extra, ModuleKind::Lib) } fn check_validator( ast: UntypedModule, ) -> Result<(Vec, TypedModule), (Vec, 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 = [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) { + expect Some(thing) = a + thing.inner } "#; diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index 95442cae..ad206b9e 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, + contains_opaque: bool, module: String, name: String, args: Vec>, @@ -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(), diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 1b240269..f35779cd 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -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), 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, 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, 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, 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, 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(args: &[CallArg]) -> Option<(Spa } pub(super) fn collapse_links(t: Rc) -> Rc { - 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, ctx_level: usize) -> Rc { Type::App { public, + contains_opaque: opaque, module, name, args, @@ -1868,6 +1943,7 @@ pub(crate) fn generalise(t: Rc, ctx_level: usize) -> Rc { Rc::new(Type::App { public: *public, + contains_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..c9f41e88 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -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, .. } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index b54e32c0..f1cedb2a 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -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 { pub fn ensure_serialisable(allow_fn: bool, t: Rc, 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, 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, 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, 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, + ), }, } } diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 08f17972..e9369acb 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -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::>()?; 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 { 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 { }) } - 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 { }) } - 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, diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index cf437ec7..f2f7582f 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -141,11 +141,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> { pattern: UntypedPattern, tipo: Rc, ann_type: Option>, - is_assignment: bool, + is_let: bool, ) -> Result { 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() diff --git a/crates/aiken-lang/src/tipo/pretty.rs b/crates/aiken-lang/src/tipo/pretty.rs index 27da2f6e..65745820 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, + 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![], 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..e90fb15b 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, + contains_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..2fc9e2ff 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, + 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: [],