From e71470747f9c1a5e86cb394c010dc2508737f387 Mon Sep 17 00:00:00 2001 From: rvcas Date: Wed, 13 Mar 2024 20:15:27 -0400 Subject: [PATCH] feat: fix some tests and add a failing one --- crates/aiken-lang/src/builtins.rs | 28 +++--- crates/aiken-lang/src/tests/check.rs | 17 +++- crates/aiken-lang/src/tipo.rs | 57 +++++++----- crates/aiken-lang/src/tipo/environment.rs | 35 +++++-- crates/aiken-lang/src/tipo/expr.rs | 8 +- crates/aiken-lang/src/tipo/infer.rs | 107 +++++++++++----------- crates/aiken-lang/src/tipo/pattern.rs | 1 + crates/aiken-lang/src/tipo/pretty.rs | 30 +++--- 8 files changed, 164 insertions(+), 119 deletions(-) diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index 962e4ca4..6b18f941 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -1267,7 +1267,7 @@ pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, name: INT.to_string(), module: "".to_string(), args: vec![], @@ -1278,7 +1278,7 @@ pub fn int() -> Rc { pub fn data() -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, name: DATA.to_string(), module: "".to_string(), args: vec![], @@ -1290,7 +1290,7 @@ pub fn byte_array() -> Rc { Rc::new(Type::App { args: vec![], public: true, - opaque: false, + contains_opaque: false, name: BYTE_ARRAY.to_string(), module: "".to_string(), alias: None, @@ -1300,7 +1300,7 @@ pub fn byte_array() -> Rc { pub fn g1_element() -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, module: "".to_string(), name: G1_ELEMENT.to_string(), args: vec![], @@ -1311,7 +1311,7 @@ pub fn g1_element() -> Rc { pub fn g2_element() -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, module: "".to_string(), name: G2_ELEMENT.to_string(), args: vec![], @@ -1322,7 +1322,7 @@ pub fn g2_element() -> Rc { pub fn miller_loop_result() -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, module: "".to_string(), name: MILLER_LOOP_RESULT.to_string(), args: vec![], @@ -1338,7 +1338,7 @@ pub fn bool() -> Rc { Rc::new(Type::App { args: vec![], public: true, - opaque: false, + contains_opaque: false, name: BOOL.to_string(), module: "".to_string(), alias: None, @@ -1349,7 +1349,7 @@ pub fn prng() -> Rc { Rc::new(Type::App { args: vec![], public: true, - opaque: false, + contains_opaque: false, name: PRNG.to_string(), module: "".to_string(), alias: None, @@ -1400,7 +1400,7 @@ pub fn fuzzer(a: Rc) -> Rc { pub fn list(t: Rc) -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, name: LIST.to_string(), module: "".to_string(), args: vec![t], @@ -1412,7 +1412,7 @@ pub fn string() -> Rc { Rc::new(Type::App { args: vec![], public: true, - opaque: false, + contains_opaque: false, name: STRING.to_string(), module: "".to_string(), alias: None, @@ -1423,7 +1423,7 @@ pub fn void() -> Rc { Rc::new(Type::App { args: vec![], public: true, - opaque: false, + contains_opaque: false, name: VOID.to_string(), module: "".to_string(), alias: None, @@ -1433,7 +1433,7 @@ pub fn void() -> Rc { pub fn option(a: Rc) -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, name: OPTION.to_string(), module: "".to_string(), args: vec![a], @@ -1444,7 +1444,7 @@ pub fn option(a: Rc) -> Rc { pub fn ordering() -> Rc { Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, name: ORDERING.to_string(), module: "".to_string(), args: vec![], @@ -1475,7 +1475,7 @@ pub fn unbound_var(id: u64) -> Rc { pub fn wrapped_redeemer(redeemer: Rc) -> Rc { Rc::new(Type::App { public: true, - opaque: false, + 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 f794d7b2..9d635cec 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1858,7 +1858,7 @@ fn forbid_expect_into_nested_opaque_in_record_without_typecasting() { type Foo { foo: Thing } - fn bar(thing: Thing) { + fn bar(thing: Foo) { expect Foo { foo: Thing { inner } } : Foo = thing Void } @@ -1905,3 +1905,18 @@ fn forbid_expect_into_nested_opaque_in_list() { 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 + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index 2144c644..ad206b9e 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -42,7 +42,7 @@ pub enum Type { /// App { public: bool, - opaque: bool, + contains_opaque: bool, module: String, name: String, args: Vec>, @@ -81,7 +81,7 @@ impl PartialEq for Type { module, name, args, - opaque, + contains_opaque: opaque, alias: _, } => { if let Type::App { @@ -89,7 +89,7 @@ impl PartialEq for Type { module: module2, name: name2, args: args2, - opaque: opaque2, + contains_opaque: opaque2, alias: _, } = other { @@ -160,14 +160,14 @@ impl Type { Rc::new(match self { Type::App { public, - opaque, + contains_opaque: opaque, module, name, args, alias: _, } => Type::App { public, - opaque, + contains_opaque: opaque, module, name, args, @@ -195,17 +195,30 @@ impl Type { } } - pub fn is_or_holds_opaque(&self) -> bool { + pub fn contains_opaque(&self) -> bool { match self { Type::Var { tipo, .. } => tipo.borrow().is_or_holds_opaque(), - Type::App { opaque, args, .. } => { - *opaque || args.iter().any(|arg| arg.is_or_holds_opaque()) - } - Type::Tuple { elems, .. } => elems.iter().any(|elem| elem.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 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 { matches!(self, Self::Var { tipo, .. } if tipo.borrow().is_unbound()) } @@ -511,7 +524,7 @@ impl Type { *tipo.borrow_mut() = TypeVar::Link { tipo: Rc::new(Self::App { public, - opaque, + contains_opaque: opaque, name: name.to_string(), module: module.to_owned(), args: args.clone(), @@ -666,7 +679,7 @@ pub fn convert_opaque_type( match t.as_ref() { Type::App { public, - opaque, + contains_opaque: opaque, module, name, args, @@ -679,7 +692,7 @@ pub fn convert_opaque_type( } Type::App { public: *public, - opaque: *opaque, + contains_opaque: *opaque, module: module.clone(), name: name.clone(), args: new_args, @@ -754,7 +767,7 @@ pub fn find_and_replace_generics( Type::App { args, public, - opaque, + contains_opaque: opaque, module, name, alias, @@ -767,7 +780,7 @@ pub fn find_and_replace_generics( let t = Type::App { args: new_args, public: *public, - opaque: *opaque, + contains_opaque: *opaque, module: module.clone(), name: name.clone(), alias: alias.clone(), @@ -851,7 +864,7 @@ impl TypeVar { pub fn is_or_holds_opaque(&self) -> bool { match self { - Self::Link { tipo } => tipo.is_or_holds_opaque(), + Self::Link { tipo } => tipo.contains_opaque(), _ => false, } } @@ -981,13 +994,11 @@ impl TypeVar { Self::Link { tipo } => tipo.get_inner_types(), Self::Unbound { .. } => vec![], var => { - vec![ - Type::Var { - tipo: RefCell::new(var.clone()).into(), - alias: None, - } - .into(), - ] + vec![Type::Var { + tipo: RefCell::new(var.clone()).into(), + alias: None, + } + .into()] } } } diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 99fd073c..ec104130 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -301,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, @@ -551,7 +570,7 @@ impl<'a> Environment<'a> { match t.deref() { Type::App { public, - opaque, + contains_opaque: opaque, name, module, args, @@ -564,7 +583,7 @@ impl<'a> Environment<'a> { Rc::new(Type::App { public: *public, - opaque: *opaque, + contains_opaque: *opaque, name: name.clone(), module: module.clone(), alias: alias.clone(), @@ -1006,7 +1025,7 @@ impl<'a> Environment<'a> { let tipo = Rc::new(Type::App { public: *public, - opaque: *opaque, + contains_opaque: *opaque, module: module.to_owned(), name: name.clone(), args: parameters.clone(), @@ -1471,7 +1490,7 @@ impl<'a> Environment<'a> { name: n1, args: args1, public: _, - opaque: _, + contains_opaque: _, alias: _, }, Type::App { @@ -1479,7 +1498,7 @@ impl<'a> Environment<'a> { name: n2, args: args2, public: _, - opaque: _, + contains_opaque: _, alias: _, }, ) if m1 == m2 && n1 == n2 && args1.len() == args2.len() => { @@ -1742,7 +1761,7 @@ fn unify_unbound_type(tipo: Rc, own_id: u64, location: Span) -> Result<(), name: _, public: _, alias: _, - opaque: _, + contains_opaque: _, } => { for arg in args { unify_unbound_type(arg.clone(), own_id, location)? @@ -1906,7 +1925,7 @@ pub(crate) fn generalise(t: Rc, ctx_level: usize) -> Rc { Type::App { public, - opaque, + contains_opaque: opaque, module, name, args, @@ -1919,7 +1938,7 @@ pub(crate) fn generalise(t: Rc, ctx_level: usize) -> Rc { Rc::new(Type::App { public: *public, - opaque: *opaque, + contains_opaque: *opaque, module: module.clone(), name: name.clone(), args, diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 459ae756..40e5f3e1 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -976,11 +976,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { kind.is_let(), )?; - // FIXME: This check is insufficient as we need to also assert the type - // definition itself since there might be nested opaque types on the rhs. - // - // For that, we must lookup - if kind.is_expect() && value_typ.is_or_holds_opaque() { + if kind.is_expect() && value_typ.contains_opaque() { return Err(Error::ExpectOnOpaqueType { location }); } @@ -2347,7 +2343,7 @@ pub fn ensure_serialisable(allow_fn: bool, t: Rc, location: Span) -> Resul name: _, module: _, public: _, - opaque: _, + contains_opaque: _, alias: _, } => { if t.is_ml_result() { diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index e40c92f6..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); } @@ -495,60 +496,62 @@ fn infer_definition( }) => { 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, - tipo: _, - }, - 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) @@ -797,7 +800,7 @@ fn infer_fuzzer( name, args, public: _, - opaque: _, + contains_opaque: _, alias: _, } if module.is_empty() && name == "Option" && args.len() == 1 => { match args.first().expect("args.len() == 1").borrow() { @@ -851,7 +854,7 @@ fn annotate_fuzzer(tipo: &Type, location: &Span) -> Result { module, args, public: _, - opaque: _, + contains_opaque: _, alias: _, } => { let arguments = args diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index a8b055fe..f2f7582f 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -154,6 +154,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> { location, }); }; + Ok(Pattern::Discard { name, location }) } diff --git a/crates/aiken-lang/src/tipo/pretty.rs b/crates/aiken-lang/src/tipo/pretty.rs index 3a6d7af5..65745820 100644 --- a/crates/aiken-lang/src/tipo/pretty.rs +++ b/crates/aiken-lang/src/tipo/pretty.rs @@ -368,7 +368,7 @@ mod tests { module: "whatever".to_string(), name: "Int".to_string(), public: true, - opaque: false, + contains_opaque: false, args: vec![], alias: None }, @@ -379,14 +379,14 @@ mod tests { module: "".to_string(), name: "Pair".to_string(), public: true, - opaque: false, + contains_opaque: false, alias: None, args: vec![ Rc::new(Type::App { module: "whatever".to_string(), name: "Int".to_string(), public: true, - opaque: false, + contains_opaque: false, args: vec![], alias: None }), @@ -394,7 +394,7 @@ mod tests { module: "whatever".to_string(), name: "Bool".to_string(), public: true, - opaque: false, + contains_opaque: false, args: vec![], alias: None }), @@ -410,7 +410,7 @@ mod tests { module: "whatever".to_string(), name: "Int".to_string(), public: true, - opaque: false, + contains_opaque: false, alias: None, }), Rc::new(Type::App { @@ -418,7 +418,7 @@ mod tests { module: "whatever".to_string(), name: "Bool".to_string(), public: true, - opaque: false, + contains_opaque: false, alias: None, }), ], @@ -427,7 +427,7 @@ mod tests { module: "whatever".to_string(), name: "Bool".to_string(), public: true, - opaque: false, + contains_opaque: false, alias: None, }), alias: None, @@ -444,7 +444,7 @@ mod tests { module: "whatever".to_string(), name: "Int".to_string(), public: true, - opaque: false, + contains_opaque: false, }), })), }, @@ -487,7 +487,7 @@ mod tests { Type::Fn { args: vec![Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], @@ -495,14 +495,14 @@ mod tests { })], ret: Rc::new(Type::App { public: true, - opaque: false, + contains_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, + contains_opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], @@ -510,7 +510,7 @@ mod tests { }), Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, module: "".to_string(), name: "Bool".to_string(), args: vec![], @@ -561,7 +561,7 @@ mod tests { Type::Fn { args: vec![Rc::new(Type::App { public: true, - opaque: false, + contains_opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![], @@ -569,14 +569,14 @@ mod tests { })], ret: Rc::new(Type::App { public: true, - opaque: false, + contains_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, + contains_opaque: false, module: "".to_string(), name: "PRNG".to_string(), args: vec![],