Do not allow casting when rhs or lhs contain an opaque type.

Also slightly extended the check test 'framework' to allow registering side-dependency and using them from another module. This allows to check the interplay between opaque type from within and outside of their host module.
This commit is contained in:
KtorZ 2024-03-14 11:00:17 +01:00
parent 191a3e9134
commit 3055c5ef52
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 75 additions and 11 deletions

View File

@ -16,6 +16,7 @@ fn parse(source_code: &str) -> UntypedModule {
fn check_module( fn check_module(
ast: UntypedModule, ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
kind: ModuleKind, kind: ModuleKind,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> { ) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
let id_gen = IdGenerator::new(); 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".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&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( let result = ast.infer(
&id_gen, &id_gen,
kind, kind,
@ -41,13 +57,20 @@ fn check_module(
} }
fn check(ast: UntypedModule) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> { 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( fn check_validator(
ast: UntypedModule, ast: UntypedModule,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> { ) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, ModuleKind::Validator) check_module(ast, Vec::new(), ModuleKind::Validator)
} }
#[test] #[test]
@ -1813,7 +1836,7 @@ fn forbid_expect_into_opaque_type_from_data() {
} }
#[test] #[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#" let source_code = r#"
opaque type Thing { opaque type Thing {
Foo(Int) 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!( assert!(matches!(
check(parse(source_code)), check_with_deps(
Err((_, Error::ExpectOnOpaqueType { .. })) 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] #[test]
@ -1914,7 +1978,6 @@ fn allow_expect_on_var_patterns_that_are_opaque() {
fn bar(a: Option<Thing>) { fn bar(a: Option<Thing>) {
expect Some(thing) = a expect Some(thing) = a
thing.inner thing.inner
} }
"#; "#;

View File

@ -1415,10 +1415,15 @@ impl<'a> Environment<'a> {
&& !(t1.is_function() || t2.is_function()) && !(t1.is_function() || t2.is_function())
&& !(t1.is_generic() || t2.is_generic()) && !(t1.is_generic() || t2.is_generic())
&& !(t1.is_string() || t2.is_string()) && !(t1.is_string() || t2.is_string())
&& !(t1.contains_opaque() || t2.contains_opaque())
{ {
return Ok(()); 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. // 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 Type::Var { tipo, alias } = t2.deref() {
if let TypeVar::Link { tipo } = tipo.borrow().deref() { if let TypeVar::Link { tipo } = tipo.borrow().deref() {

View File

@ -976,10 +976,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
kind.is_let(), 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 // 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. // error we emit a warning which explains that using `expect` is unnecessary.
match kind { match kind {