diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 620141b0..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] @@ -1813,7 +1836,7 @@ fn forbid_expect_into_opaque_type_from_data() { } #[test] -fn forbid_expect_into_opaque_type_constructor_without_typecasting() { +fn forbid_expect_into_opaque_type_constructor_without_typecasting_in_module() { let source_code = r#" opaque type Thing { Foo(Int) @@ -1826,10 +1849,51 @@ fn forbid_expect_into_opaque_type_constructor_without_typecasting() { } "#; + 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(parse(source_code)), - Err((_, Error::ExpectOnOpaqueType { .. })) - )) + 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] @@ -1914,7 +1978,6 @@ fn allow_expect_on_var_patterns_that_are_opaque() { fn bar(a: Option) { expect Some(thing) = a - thing.inner } "#; diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index ec104130..f35779cd 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -1415,10 +1415,15 @@ 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, alias } = t2.deref() { if let TypeVar::Link { tipo } = tipo.borrow().deref() { diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 40e5f3e1..f1cedb2a 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -976,10 +976,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { kind.is_let(), )?; - if kind.is_expect() && value_typ.contains_opaque() { - return Err(Error::ExpectOnOpaqueType { location }); - } - // 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 {