From 3852ebc4ba184a53d69d2469d24e5386f2b0398d Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 4 Aug 2018 19:40:19 +0200 Subject: [PATCH 1/7] Return a type from subsumtion even on errors --- check/src/lib.rs | 2 +- check/src/typecheck.rs | 22 ++++++++++++++++++---- check/src/unify_type.rs | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/check/src/lib.rs b/check/src/lib.rs index 24b089bb35..54e3c046a9 100644 --- a/check/src/lib.rs +++ b/check/src/lib.rs @@ -48,7 +48,7 @@ pub fn check_signature(env: &TypeEnv, signature: &ArcType, actual: &ArcType) -> let actual = unify_type::new_skolem_scope(&subs, actual); let actual = actual.instantiate_generics(&mut FnvMap::default()); let result = unify_type::subsumes(&subs, &mut ScopedMap::new(), 0, state, signature, &actual); - if let Err(ref err) = result { + if let Err((_, ref err)) = result { warn!("Check signature error: {}", err); } result.is_ok() diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index d9cac2d9d3..aa2a5fa7ff 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -968,10 +968,24 @@ impl<'a> Typecheck<'a> { exprs: ref mut fields, ref mut base, } => { - let expected_type = expected_type.map(|expected_type| { - let typ = resolve::remove_aliases_cow(&self.environment, expected_type); + let maybe_expected_record_type = expected_type.map(|expected_type| { + let expected_type = self.subs.real(expected_type).clone(); + let typ = resolve::remove_aliases_cow(&self.environment, &expected_type); self.new_skolem_scope(&typ) }); + let expected_type = match maybe_expected_record_type { + Some(t) => match *t { + Type::Record(_) => { + // We will check `expected_type` here so avoid unifying a second time + // later + expected_type.take(); + Some(t) + } + _ => None, + }, + _ => None, + }; + let expected_type = expected_type.as_ref(); let mut new_types: Vec> = Vec::with_capacity(types.len()); @@ -2346,7 +2360,7 @@ impl<'a> Typecheck<'a> { &actual, ) { Ok(typ) => typ, - Err(errors) => { + Err((typ, errors)) => { debug!( "Error '{:?}' between:\n>> {}\n>> {}", errors, expected, actual @@ -2357,7 +2371,7 @@ impl<'a> Typecheck<'a> { // TODO Help what caused this unification failure value: err.into(), }); - self.subs.new_var() + typ } } } diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs index 5bd562a5c2..a01ff284e5 100644 --- a/check/src/unify_type.rs +++ b/check/src/unify_type.rs @@ -1092,7 +1092,7 @@ pub fn subsumes( state: State, l: &ArcType, r: &ArcType, -) -> Result>> { +) -> Result>)> { debug!("Subsume {} <=> {}", l, r); let mut unifier = UnifierState { state: state, @@ -1106,7 +1106,7 @@ pub fn subsumes( let typ = unifier.try_match(l, r); if unifier.unifier.errors.has_errors() { - Err(unifier.unifier.errors) + Err((typ.unwrap_or_else(|| l.clone()), unifier.unifier.errors)) } else { Ok(typ.unwrap_or_else(|| l.clone())) } From 7976bcd5d33ee92cc72281ef13e859498c4423ed Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 4 Aug 2018 19:52:05 +0200 Subject: [PATCH 2/7] feat: Prevent type errors from creating cascading errors Adds error types and kinds which always succeeds unification by always returning that they are the other parameter. Since these can only be created from an error that occurred earlier this does not cause unsoundness and ensures that errors that actually are more local (since they disregard erroneous info from other failed unifications). --- base/src/ast.rs | 2 +- base/src/kind.rs | 10 +++++-- base/src/types/mod.rs | 14 +++++++++- check/src/implicits.rs | 3 ++- check/src/kindcheck.rs | 19 +++++++++----- check/src/lib.rs | 5 ++-- check/src/typecheck.rs | 53 +++++++++++++++----------------------- check/src/unify.rs | 15 +++++++---- check/src/unify_type.rs | 36 ++++++++++++++++++-------- check/tests/forall.rs | 29 +++++++++++++++------ check/tests/implicits.rs | 2 +- check/tests/support/mod.rs | 32 ++++++++++++++++++++++- format/src/pretty_print.rs | 1 + 13 files changed, 150 insertions(+), 71 deletions(-) diff --git a/base/src/ast.rs b/base/src/ast.rs index edc3ea3d48..396cd2dcdf 100644 --- a/base/src/ast.rs +++ b/base/src/ast.rs @@ -634,7 +634,7 @@ pub fn walk_mut_ast_type<'a, V: ?Sized + MutVisitor<'a>>( s: &'a mut SpannedAstType, ) { match s.value { - Type::Hole | Type::Opaque | Type::Builtin(_) => (), + Type::Hole | Type::Opaque | Type::Error | Type::Builtin(_) => (), Type::Forall(_, ref mut ast_type, ref mut ast_types) => { v.visit_ast_type(&mut ast_type._typ.1); if let &mut Some(ref mut ast_types) = ast_types { diff --git a/base/src/kind.rs b/base/src/kind.rs index 2eec11a527..50b0ce6632 100644 --- a/base/src/kind.rs +++ b/base/src/kind.rs @@ -50,6 +50,7 @@ impl KindEnv for EmptyEnv { )] pub enum Kind { Hole, + Error, /// Representation for a kind which is yet to be inferred. Variable(u32), /// The simplest possible kind. All values in a program have this kind. @@ -68,6 +69,10 @@ impl Kind { ArcKind::new(Kind::Hole) } + pub fn error() -> ArcKind { + ArcKind::new(Kind::Error) + } + pub fn variable(v: u32) -> ArcKind { ArcKind::new(Kind::Variable(v)) } @@ -115,6 +120,7 @@ impl<'a> fmt::Display for DisplayKind<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.1 { Kind::Hole => "_".fmt(f), + Kind::Error => "!".fmt(f), Kind::Variable(i) => i.fmt(f), Kind::Type => "Type".fmt(f), Kind::Row => "Row".fmt(f), @@ -186,7 +192,7 @@ impl fmt::Display for ArcKind { } } -type_cache! { KindCache() () { ArcKind, Kind } row hole typ } +type_cache! { KindCache() () { ArcKind, Kind } row hole error typ } impl<'a, F: ?Sized> Walker<'a, ArcKind> for F where @@ -207,6 +213,6 @@ where f.walk(a); f.walk(r); } - Kind::Hole | Kind::Variable(_) | Kind::Type | Kind::Row => (), + Kind::Hole | Kind::Error | Kind::Variable(_) | Kind::Type | Kind::Row => (), } } diff --git a/base/src/types/mod.rs b/base/src/types/mod.rs index 4fc9840c8d..b71779e870 100644 --- a/base/src/types/mod.rs +++ b/base/src/types/mod.rs @@ -75,7 +75,7 @@ type_cache! { TypeCache(Id, T) (kind_cache: ::kind::KindCache) { T, Type } - hole opaque int byte float string char + hole opaque error int byte float string char function_builtin array_builtin unit empty_row } @@ -694,6 +694,8 @@ pub enum Type> { Hole, /// An opaque type Opaque, + /// A type used to mark type errors + Error, /// A builtin type Builtin(BuiltinType), /// Universally quantified types @@ -774,6 +776,10 @@ where T::from(Type::Opaque) } + pub fn error() -> T { + T::from(Type::Error) + } + pub fn builtin(typ: BuiltinType) -> T { T::from(Type::Builtin(typ)) } @@ -1036,6 +1042,7 @@ where let mut immediate_kind = match *self { Type::Function(_, _, _) => Cow::Owned(Kind::typ()), Type::App(ref t, ref args) => t.kind_(args.len()), + Type::Error => Cow::Owned(Kind::error()), Type::Hole | Type::Opaque | Type::Builtin(_) | Type::Record(_) | Type::Variant(_) => { Cow::Owned(Kind::typ()) } @@ -1881,6 +1888,7 @@ where let doc = match **typ { Type::Hole => arena.text("_"), + Type::Error => arena.text("!"), Type::Opaque => arena.text(""), Type::Forall(ref args, ref typ, _) => { let doc = chain![arena; @@ -2258,6 +2266,7 @@ where } Type::Hole | Type::Opaque + | Type::Error | Type::Builtin(_) | Type::Variable(_) | Type::Generic(_) @@ -2304,6 +2313,7 @@ where } Type::Hole | Type::Opaque + | Type::Error | Type::Builtin(_) | Type::Variable(_) | Type::Generic(_) @@ -2467,6 +2477,7 @@ where } Type::Hole | Type::Opaque + | Type::Error | Type::Builtin(_) | Type::Variable(_) | Type::Skolem(_) @@ -2584,6 +2595,7 @@ where ), Type::Hole => cache.hole(), Type::Opaque => cache.opaque(), + Type::Error => cache.error(), Type::Builtin(ref builtin) => cache.builtin_type(builtin.clone()), Type::Variable(ref var) => Type::variable(var.clone()), Type::Generic(ref gen) => Type::generic(gen.clone()), diff --git a/check/src/implicits.rs b/check/src/implicits.rs index fd08a78857..b9353f64d9 100644 --- a/check/src/implicits.rs +++ b/check/src/implicits.rs @@ -342,7 +342,8 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> { constraint, })); - let state = ::unify_type::State::new(&self.tc.environment, &self.tc.subs); + let state = + ::unify_type::State::new(&self.tc.environment, &self.tc.subs, &self.tc.type_cache); ::unify_type::subsumes( &self.tc.subs, &mut ScopedMap::new(), diff --git a/check/src/kindcheck.rs b/check/src/kindcheck.rs index f761a83600..f1b8573a1d 100644 --- a/check/src/kindcheck.rs +++ b/check/src/kindcheck.rs @@ -53,7 +53,7 @@ where let ret_new = walk_move_kind2(ret, f); merge::merge(arg, arg_new, ret, ret_new, Kind::function) } - Kind::Hole | Kind::Type | Kind::Variable(_) | Kind::Row => None, + Kind::Hole | Kind::Error | Kind::Type | Kind::Variable(_) | Kind::Row => None, } }; new2.or(new) @@ -115,7 +115,7 @@ impl<'a> KindCheck<'a> { self.instantiate_kinds(rhs); return; } - Kind::Row | Kind::Type => return, + Kind::Row | Kind::Error | Kind::Type => return, } *kind = self.subs.new_var(); } @@ -191,6 +191,7 @@ impl<'a> KindCheck<'a> { fn kindcheck(&mut self, typ: &mut AstType) -> Result { let span = typ.span(); match **typ { + Type::Error => Ok(self.kind_cache.error()), Type::Hole | Type::Opaque | Type::Variable(_) => Ok(self.subs.new_var()), Type::Skolem(ref mut skolem) => { skolem.kind = self.find(span, &skolem.name)?; @@ -297,7 +298,7 @@ impl<'a> KindCheck<'a> { mut actual: ArcKind, ) -> Result { debug!("Unify {:?} <=> {:?}", expected, actual); - let result = unify::unify(&self.subs, (), expected, &actual); + let result = unify::unify(&self.subs, &self.kind_cache, expected, &actual); match result { Ok(k) => Ok(k), Err(_errors) => { @@ -423,18 +424,20 @@ impl Substitutable for ArcKind { } } -impl Unifiable for ArcKind { +impl<'a> Unifiable<&'a KindCache> for ArcKind { type Error = KindError; fn zip_match( &self, other: &Self, - unifier: &mut UnifierState, + unifier: &mut UnifierState<&'a KindCache, U>, ) -> StdResult, Error> where - UnifierState: Unifier, + UnifierState<&'a KindCache, U>: Unifier<&'a KindCache, Self>, { match (&**self, &**other) { + (_, &Kind::Error) => Ok(None), + (&Kind::Error, _) => Ok(Some(other.clone())), (&Kind::Function(ref l1, ref l2), &Kind::Function(ref r1, ref r2)) => { let a = unifier.try_match(l1, r1); let r = unifier.try_match(l2, r2); @@ -447,4 +450,8 @@ impl Unifiable for ArcKind { }, } } + + fn error_type(state: &&'a KindCache) -> Self { + state.error() + } } diff --git a/check/src/lib.rs b/check/src/lib.rs index 54e3c046a9..b0e5929b17 100644 --- a/check/src/lib.rs +++ b/check/src/lib.rs @@ -33,7 +33,7 @@ pub mod unify_type; mod implicits; -use base::types::{ArcType, TypeEnv}; +use base::types::{ArcType, TypeCache, TypeEnv}; /// Checks if `actual` can be assigned to a binding with the type signature `signature` pub fn check_signature(env: &TypeEnv, signature: &ArcType, actual: &ArcType) -> bool { @@ -44,7 +44,8 @@ pub fn check_signature(env: &TypeEnv, signature: &ArcType, actual: &ArcType) -> use substitution::Substitution; let subs = Substitution::new(Kind::typ()); - let state = unify_type::State::new(env, &subs); + let type_cache = TypeCache::new(); + let state = unify_type::State::new(env, &subs, &type_cache); let actual = unify_type::new_skolem_scope(&subs, actual); let actual = actual.instantiate_generics(&mut FnvMap::default()); let result = unify_type::subsumes(&subs, &mut ScopedMap::new(), 0, state, signature, &actual); diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index aa2a5fa7ff..bdb40a7b97 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -326,7 +326,7 @@ pub struct Typecheck<'a> { pub(crate) errors: Errors>, /// Type variables `let test: a -> b` (`a` and `b`) type_variables: ScopedMap, - type_cache: TypeCache, + pub(crate) type_cache: TypeCache, kind_cache: KindCache, pub(crate) implicit_resolver: ::implicits::ImplicitResolver<'a>, @@ -374,7 +374,7 @@ impl<'a> Typecheck<'a> { span: span, value: error.into(), }); - self.subs.new_var() + self.type_cache.error() } fn bool(&self) -> ArcType { @@ -393,6 +393,7 @@ impl<'a> Typecheck<'a> { Some(typ) => { self.named_variables.clear(); let typ = new_skolem_scope(&self.subs, &typ); + let typ = self.instantiate_generics(&typ); debug!("Find {} : {}", self.symbols.string(id), typ); Ok(typ) } @@ -739,7 +740,7 @@ impl<'a> Typecheck<'a> { } } Err(err) => { - returned_type = self.subs.new_var(); + returned_type = self.type_cache.error(); self.errors.push(Spanned { span: expr_check_span(expr), value: err.into(), @@ -906,7 +907,10 @@ impl<'a> Typecheck<'a> { .find(|field| field.name.name_eq(field_id)) .map(|field| field.typ.clone()); *ast_field_typ = match field_type { - Some(typ) => self.new_skolem_scope(&typ), + Some(mut typ) => { + typ = self.new_skolem_scope(&typ); + self.instantiate_generics(&typ) + } None => { // FIXME As the polymorphic `record_type` do not have the type // fields which `typ` this unification is only done after we @@ -968,23 +972,11 @@ impl<'a> Typecheck<'a> { exprs: ref mut fields, ref mut base, } => { - let maybe_expected_record_type = expected_type.map(|expected_type| { + let expected_type = expected_type.map(|expected_type| { let expected_type = self.subs.real(expected_type).clone(); let typ = resolve::remove_aliases_cow(&self.environment, &expected_type); self.new_skolem_scope(&typ) }); - let expected_type = match maybe_expected_record_type { - Some(t) => match *t { - Type::Record(_) => { - // We will check `expected_type` here so avoid unifying a second time - // later - expected_type.take(); - Some(t) - } - _ => None, - }, - _ => None, - }; let expected_type = expected_type.as_ref(); @@ -1017,19 +1009,14 @@ impl<'a> Typecheck<'a> { }) .map(|field| &field.typ); - let typ = match field.value { - Some(ref mut expr) => { - let mut typ = self.typecheck_opt(expr, expected_field_type); - - self.generalize_type(level, &mut typ); - new_skolem_scope(&self.subs, &typ) - } + let mut typ = match field.value { + Some(ref mut expr) => self.typecheck_opt(expr, expected_field_type), None => { let typ = self.find_at(field.name.span, &field.name.value); match expected_field_type { Some(expected_field_type) => { let mut implicit_args = Vec::new(); - let typ = self.subsumes_implicit( + let mut typ = self.subsumes_implicit( field.name.span, level, &expected_field_type, @@ -1063,6 +1050,9 @@ impl<'a> Typecheck<'a> { } } }; + self.generalize_type(level, &mut typ); + typ = new_skolem_scope(&self.subs, &typ); + if self.error_on_duplicated_field(&mut duplicated_fields, field.name.clone()) { new_fields.push(Field::new(field.name.value.clone(), typ)); } @@ -1122,7 +1112,7 @@ impl<'a> Typecheck<'a> { help: Some(Help::UndefinedFlatMapInDo), }, ); - self.subs.new_var() + self.type_cache.error() } }, _ => ice!("flat_map_id not inserted during renaming"), @@ -1233,6 +1223,7 @@ impl<'a> Typecheck<'a> { let args_len = args.len() as u32; func_type = self.new_skolem_scope(&func_type); + func_type = self.instantiate_generics(&func_type); for arg in &mut **implicit_args { let arg_ty = self.subs.new_var(); @@ -2350,7 +2341,7 @@ impl<'a> Typecheck<'a> { ) -> ArcType { debug!("Merge {} : {}", expected, actual); let expected = self.skolemize(&expected); - let state = unify_type::State::new(&self.environment, &self.subs); + let state = unify_type::State::new(&self.environment, &self.subs, &self.type_cache); match unify_type::subsumes( &self.subs, &mut self.type_variables, @@ -2385,14 +2376,14 @@ impl<'a> Typecheck<'a> { // TODO Help what caused this unification failure value: err.into(), }); - self.subs.new_var() + self.type_cache.error() } } } fn unify(&self, expected: &ArcType, actual: ArcType) -> TcResult { debug!("Unify start {} <=> {}", expected, actual); - let state = unify_type::State::new(&self.environment, &self.subs); + let state = unify_type::State::new(&self.environment, &self.subs, &self.type_cache); match unify::unify(&self.subs, state, expected, &actual) { Ok(typ) => Ok(typ), Err(errors) => { @@ -2567,9 +2558,7 @@ impl<'a, 'b> Iterator for FunctionArgIter<'a, 'b> { fn next(&mut self) -> Option { let mut last_alias = None; loop { - if let Type::Forall(_, _, None) = *self.typ { - panic!("Found forall without scope in function argument iterator") - } + self.typ = self.tc.new_skolem_scope(&self.typ); self.typ = self.tc.skolemize(&self.typ); let (arg, new) = match self.typ.as_function_with_type() { Some((arg_type, arg, ret)) => (Some((arg_type, arg.clone())), ret.clone()), diff --git a/check/src/unify.rs b/check/src/unify.rs index 10e9392885..e3b8ab54df 100644 --- a/check/src/unify.rs +++ b/check/src/unify.rs @@ -69,7 +69,7 @@ where Ok(typ) => typ, Err(err) => { Self::report_error(self, err); - Self::error_type(self) + Some(Self::error_type(self)) } } } @@ -79,12 +79,12 @@ where r: &Type, ) -> Result, Error>; - fn error_type(&mut self) -> Option; - /// `true` if the returned type can be replaced by the caller fn allow_returned_type_replacement(&self) -> bool { true } + + fn error_type(&self) -> Type; } /// A type which can be unified by checking for equivalence between the top level of @@ -106,6 +106,8 @@ pub trait Unifiable: Substitutable + Sized { ) -> Result, Error> where UnifierState: Unifier; + + fn error_type(state: &S) -> Self; } /// Unify `l` and `r` taking into account and updating the substitution `subs` using the @@ -187,8 +189,8 @@ where } } - fn error_type(&mut self) -> Option { - Some(self.unifier.subs.new_var()) + fn error_type(&self) -> T { + T::error_type(&self.state) } } @@ -287,6 +289,9 @@ mod test { _ => Err(Error::TypeMismatch(self.clone(), other.clone())), } } + fn error_type(_: &()) -> Self { + TType(Box::new(Type::Variable(0))) + } } fn unify( diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs index a01ff284e5..dfd4e5155a 100644 --- a/check/src/unify_type.rs +++ b/check/src/unify_type.rs @@ -9,8 +9,8 @@ use base::resolve::{self, Error as ResolveError}; use base::scoped_map::ScopedMap; use base::symbol::{Symbol, SymbolRef}; use base::types::{ - self, AppVec, ArcType, ArgType, BuiltinType, Field, Filter, Generic, Skolem, Type, TypeEnv, - TypeFormatter, TypeVariable, + self, AppVec, ArcType, ArgType, BuiltinType, Field, Filter, Generic, Skolem, Type, TypeCache, + TypeEnv, TypeFormatter, TypeVariable, }; use substitution::{Substitutable, Substitution, Variable, VariableFactory}; @@ -46,15 +46,21 @@ pub struct State<'a> { reduced_aliases: Vec, subs: &'a Substitution, record_context: Option<(ArcType, ArcType)>, + type_cache: &'a TypeCache, pub in_alias: bool, } impl<'a> State<'a> { - pub fn new(env: &'a (TypeEnv + 'a), subs: &'a Substitution) -> State<'a> { + pub fn new( + env: &'a (TypeEnv + 'a), + subs: &'a Substitution, + type_cache: &'a TypeCache, + ) -> State<'a> { State { - env: env, + env, reduced_aliases: Vec::new(), - subs: subs, + subs, + type_cache, record_context: None, in_alias: false, } @@ -313,6 +319,10 @@ impl<'a> Unifiable> for ArcType { unifier.state.reduced_aliases.truncate(reduced_aliases); result } + + fn error_type(state: &State<'a>) -> Self { + state.type_cache.error() + } } fn do_zip_match<'a, U>( @@ -325,6 +335,8 @@ where { debug!("Unifying:\n{} <=> {}", expected, actual); match (&**expected, &**actual) { + (&Type::Error, _) => Ok(Some(actual.clone())), + (_, &Type::Error) => Ok(None), ( &Type::Function(l_arg_type, ref l_arg, ref l_ret), &Type::Function(r_arg_type, ref r_arg, ref r_ret), @@ -1177,7 +1189,7 @@ impl<'a, 'e> Unifier, ArcType> for UnifierState<'a, Subsume<'e>> { } (_, &Type::Forall(_, _, Some(_))) => { - let r = r.instantiate_generics(&mut FnvMap::default()); + let r = r.skolemize(&mut FnvMap::default()); Ok(self.try_match_res(l, &r)?) } @@ -1224,8 +1236,8 @@ impl<'a, 'e> Unifier, ArcType> for UnifierState<'a, Subsume<'e>> { } } - fn error_type(&mut self) -> Option { - Some(self.unifier.subs.new_var()) + fn error_type(&self) -> ArcType { + ArcType::error_type(&self.state) } } @@ -1280,9 +1292,10 @@ mod tests { Field::new(y.clone(), Type::int()), ], ); - let subs = Substitution::new(Kind::typ()); let env = MockEnv; - let state = State::new(&env, &subs); + let type_cache = TypeCache::new(); + let subs = Substitution::new(Kind::typ()); + let state = State::new(&env, &subs, &type_cache); let result = unify(&subs, state, &l, &r); assert_eq!( result, @@ -1298,8 +1311,9 @@ mod tests { let _ = ::env_logger::try_init(); let env = MockEnv; + let type_cache = TypeCache::new(); let subs = Substitution::new(Kind::typ()); - let state = State::new(&env, &subs); + let state = State::new(&env, &subs, &type_cache); let x = Field::new(intern("x"), Type::int()); let y = Field::new(intern("y"), Type::int()); diff --git a/check/tests/forall.rs b/check/tests/forall.rs index 599552a7c1..f4a42af24f 100644 --- a/check/tests/forall.rs +++ b/check/tests/forall.rs @@ -9,10 +9,9 @@ extern crate gluon_check as check; extern crate gluon_parser as parser; use base::ast::{Expr, Pattern, SpannedExpr}; -use base::kind::Kind; use base::pos::{BytePos, Span}; use base::symbol::Symbol; -use base::types::{Field, Generic, Type}; +use base::types::{Field, Type}; use support::{alias, intern, typ, MockEnv}; @@ -111,12 +110,9 @@ a.id }, _ => panic!(), }; - let expected = Type::forall( - vec![Generic::new(intern("a"), Kind::typ())], - Type::function(vec![typ("a")], typ("a")), - ); + let expected = Type::function(vec![typ("a")], typ("a")); - assert_eq!(*t, expected); + assert_eq2!(*t, expected); } #[test] @@ -698,7 +694,7 @@ make assert_req!( result.map(|x| x.to_string()), - Ok("forall b . b -> { f : b -> b }".to_string()) + Ok("forall a . a -> { f : a -> a }".to_string()) ); } @@ -1032,3 +1028,20 @@ append (Cons "" Nil) Nil assert!(result.is_ok(), "{}", result.unwrap_err()); } + +#[test] +fn dont_let_forall_escape() { + let _ = ::env_logger::try_init(); + + let text = r#" +let foo f: forall b. (forall a. a -> b) -> b = f () +let id x: forall a. a -> a = x +let false: forall a. a = foo id // Oops +let some_int: Int = false +some_int // Undefined behaviour +"#; + let (expr, result) = support::typecheck_expr(text); + + support::print_ident_types(&expr); + assert!(result.is_err(), "{}", result.unwrap()); +} diff --git a/check/tests/implicits.rs b/check/tests/implicits.rs index 10860a47fe..153efb7bd9 100644 --- a/check/tests/implicits.rs +++ b/check/tests/implicits.rs @@ -539,7 +539,7 @@ wrap let result = support::typecheck(text); assert_req!( result.map(|typ| typ.to_string()), - Ok("forall a f . [test.Applicative f] -> a -> f a") + Ok("forall a a0 . [test.Applicative a] -> a0 -> a a0") ); } diff --git a/check/tests/support/mod.rs b/check/tests/support/mod.rs index ace9d0462a..46e495f686 100644 --- a/check/tests/support/mod.rs +++ b/check/tests/support/mod.rs @@ -1,8 +1,10 @@ #![allow(unused_macros)] +#![allow(dead_code)] extern crate codespan; -use base::ast::{DisplayEnv, IdentEnv, SpannedExpr}; +use base; +use base::ast::{DisplayEnv, Expr, IdentEnv, SpannedExpr}; use base::error::InFile; use base::kind::{ArcKind, Kind, KindEnv}; use base::metadata::{Metadata, MetadataEnv}; @@ -328,6 +330,17 @@ macro_rules! assert_req { }; } +#[macro_export] +macro_rules! assert_eq2 { + ($lhs:expr, $rhs:expr) => {{ + let ref lhs = $lhs; + let ref rhs = $rhs; + if lhs != rhs { + assert_failed!($lhs, $rhs, lhs, rhs) + } + }}; +} + macro_rules! test_check { ($name:ident, $source:expr, $typ:expr) => { #[test] @@ -455,3 +468,20 @@ macro_rules! assert_multi_unify_err { } }} } + +pub fn print_ident_types(expr: &SpannedExpr) { + struct Visitor; + impl<'a> base::ast::Visitor<'a> for Visitor { + type Ident = Symbol; + + fn visit_expr(&mut self, expr: &'a SpannedExpr) { + match expr.value { + Expr::Ident(ref id) => { + println!("{} : {}", id.name, id.typ); + } + _ => base::ast::walk_expr(self, expr), + } + } + } + base::ast::Visitor::visit_expr(&mut Visitor, &expr) +} diff --git a/format/src/pretty_print.rs b/format/src/pretty_print.rs index b543114e0e..a8361ab4de 100644 --- a/format/src/pretty_print.rs +++ b/format/src/pretty_print.rs @@ -944,6 +944,7 @@ fn pretty_kind<'a, A>( ) -> DocBuilder<'a, Arena<'a, A>, A> { match *kind { Kind::Type => arena.text("Type"), + Kind::Error => arena.text("!"), Kind::Row => arena.text("Row"), Kind::Hole => arena.text("_"), Kind::Variable(ref id) => arena.text(id.to_string()), From dd14c0ed61ee031a44a4d3ada70dbfe7908693cd Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 5 Aug 2018 14:25:08 +0200 Subject: [PATCH 3/7] chore: Fix html_root_url in vm --- vm/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 8257b93ad5..fedf1d0fa9 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -1,6 +1,5 @@ //! Crate which contain the virtual machine which executes gluon programs -#![doc(html_root_url = "https://docs.rs/gluon_vm/0.5.0")] -// # GLUON +#![doc(html_root_url = "https://docs.rs/gluon_vm/0.8.1")] // # GLUON #![recursion_limit = "1024"] #[macro_use] From b0f283f2e045a70e4ec40c04268e8b82a2f36fd6 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 5 Aug 2018 16:03:09 +0200 Subject: [PATCH 4/7] fix(check): Don't return non-skolemized types from lambda --- check/src/typecheck.rs | 46 ++++++++++++++--------------------------- check/src/unify_type.rs | 12 +++++------ check/tests/forall.rs | 17 +++++++++++++++ 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index bdb40a7b97..a3f1599d8d 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -112,8 +112,7 @@ impl + Clone> fmt::Display for TypeError { .filter_map(|err| match *err { UnifyError::Other(ref err) => Some(err.make_filter()), _ => None, - }) - .collect::>(); + }).collect::>(); let filter = move |field: &I| { if filters.is_empty() { Filter::Retain @@ -272,8 +271,7 @@ impl<'a> KindEnv for Environment<'a> { kind = Kind::function(arg.kind.clone(), kind); } kind - }) - .or_else(|| self.environment.find_kind(type_name)) + }).or_else(|| self.environment.find_kind(type_name)) } } @@ -614,8 +612,7 @@ impl<'a> Typecheck<'a> { let mut typ = typ.clone(); self.generalize_type(0, &mut typ); typ - }) - .collect(); + }).collect(); } Unification(ref mut expected, ref mut actual, ref mut errors) => { self.generalize_type_without_forall(0, expected); @@ -857,8 +854,7 @@ impl<'a> Typecheck<'a> { name: self.symbols.symbol(format!("_{}", i)), typ: typ, } - }) - .collect(); + }).collect(); Type::record(vec![], fields) } }; @@ -959,6 +955,7 @@ impl<'a> Typecheck<'a> { ); self.generalize_type(level, &mut typ); + typ = self.new_skolem_scope(&typ); lambda.id.typ = typ.clone(); Ok(TailCall::Type(typ)) } @@ -1006,8 +1003,7 @@ impl<'a> Typecheck<'a> { expected_type .row_iter() .find(|expected_field| expected_field.name.name_eq(&name)) - }) - .map(|field| &field.typ); + }).map(|field| &field.typ); let mut typ = match field.value { Some(ref mut expr) => self.typecheck_opt(expr, expected_field_type), @@ -1279,8 +1275,7 @@ impl<'a> Typecheck<'a> { .map(|arg| { span_end = arg.span.end(); self.infer_expr(arg.borrow_mut()) - }) - .collect::>(); + }).collect::>(); let actual = self.type_cache.function(arg_types, self.subs.new_var()); let span = Span::new(span_start, span_end); @@ -1484,8 +1479,7 @@ impl<'a> Typecheck<'a> { associated_types .iter() .any(|other| other.name.value.name_eq(&field.name)) - }) - .cloned() + }).cloned() .collect(); let fields = fields @@ -1650,15 +1644,13 @@ impl<'a> Typecheck<'a> { self.translate_ast_type(type_cache, typ) })) }, - }) - .collect(), + }).collect(), fields .iter() .map(|field| Field { name: field.name.clone(), typ: self.translate_ast_type(type_cache, &field.typ), - }) - .collect(), + }).collect(), self.translate_ast_type(type_cache, rest), ), Type::Ident(ref id) if id.name().module().as_str() != "" => { @@ -2134,8 +2126,7 @@ impl<'a> Typecheck<'a> { .map(|(id, var)| { let kind = var.kind().into_owned(); Generic::new(id, kind) - }) - .collect::>(); + }).collect::>(); if params.is_empty() { result_type @@ -2186,8 +2177,7 @@ impl<'a> Typecheck<'a> { .map(|(new, old)| match new { Some(new) => Field::new(new.clone(), old.typ.clone()), None => old.clone(), - }) - .collect(), + }).collect(), ), ) } else { @@ -2472,15 +2462,13 @@ pub fn translate_projected_type( } else { None } - }) - .chain(aliased_type.row_iter().filter_map(|field| { + }).chain(aliased_type.row_iter().filter_map(|field| { if field.name.name_eq(&symbol) { Some(field.typ.clone()) } else { None } - })) - .next() + })).next() .ok_or_else(|| TypeError::UndefinedField(typ, symbol))?, ) } @@ -2490,8 +2478,7 @@ pub fn translate_projected_type( .or_else(|| { env.find_type_info(&symbol) .map(|alias| alias.typ().into_owned()) - }) - .ok_or_else(|| TypeError::UndefinedVariable(symbol.clone()))?, + }).ok_or_else(|| TypeError::UndefinedVariable(symbol.clone()))?, ), }; } @@ -2528,8 +2515,7 @@ pub fn extract_generics(args: &[ArcType]) -> Vec> { .map(|arg| match **arg { Type::Generic(ref gen) => gen.clone(), _ => ice!("The type on the lhs of a type binding did not have all generic arguments"), - }) - .collect() + }).collect() } fn get_alias_app<'a>( diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs index dfd4e5155a..7e1670b0e1 100644 --- a/check/src/unify_type.rs +++ b/check/src/unify_type.rs @@ -155,12 +155,10 @@ where .iter() .map(|missing_field| { ::strsim::jaro_winkler(missing_field.as_ref(), field_in_type.as_ref()) - }) - .max_by(|l, r| l.partial_cmp(&r).unwrap()) + }).max_by(|l, r| l.partial_cmp(&r).unwrap()) .expect("At least one missing field"); (field_in_type, (similarity * 1000000.) as i32) - }) - .collect::>(); + }).collect::>(); field_similarity.sort_by_key(|t| ::std::cmp::Reverse(t.1)); Box::new(move |field: &I| { @@ -395,7 +393,8 @@ where && l_row .iter() .zip(r_row) - .all(|(l, r)| l.name.name_eq(&r.name)) && l_rest == r_rest + .all(|(l, r)| l.name.name_eq(&r.name)) + && l_rest == r_rest { let iter = l_row.iter().zip(r_row); let new_fields = merge::merge_tuple_iter(iter, |l, r| { @@ -439,7 +438,8 @@ where && l_args .iter() .zip(r_args) - .all(|(l, r)| l.name.name_eq(&r.name)) && l_types == r_types + .all(|(l, r)| l.name.name_eq(&r.name)) + && l_types == r_types { let new_args = merge::merge_tuple_iter(l_args.iter().zip(r_args), |l, r| { unifier diff --git a/check/tests/forall.rs b/check/tests/forall.rs index f4a42af24f..6934cb93a5 100644 --- a/check/tests/forall.rs +++ b/check/tests/forall.rs @@ -1045,3 +1045,20 @@ some_int // Undefined behaviour support::print_ident_types(&expr); assert!(result.is_err(), "{}", result.unwrap()); } + +#[test] +fn unify_with_inferred_forall_in_record() { + let _ = ::env_logger::try_init(); + + let text = r#" +type Option a = | None | Some a +type Record b = { x : Option b } + +let f = \_ -> + { x = None } +f +"#; + let result = support::typecheck(text); + + assert!(result.is_ok(), "{}", result.unwrap_err()); +} From 731a9ecd609ffedf22f9b35dac139493ee233e93 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 5 Aug 2018 16:28:58 +0200 Subject: [PATCH 5/7] fix(check): Plug soundness holes with higher ranked types --- base/src/types/mod.rs | 50 +++++++++++++++---------- check/src/substitution.rs | 12 ++++++ check/src/typecheck.rs | 76 ++++++++++++++++++++++++++++---------- check/src/unify_type.rs | 24 +++++++++--- check/tests/fail.rs | 40 +++++++++++++++++--- check/tests/forall.rs | 41 ++++++++++++++++++-- check/tests/implicits.rs | 32 ++++++++++++++++ check/tests/support/mod.rs | 2 +- tests/vm.rs | 8 ++++ 9 files changed, 230 insertions(+), 55 deletions(-) diff --git a/base/src/types/mod.rs b/base/src/types/mod.rs index b71779e870..d335305cac 100644 --- a/base/src/types/mod.rs +++ b/base/src/types/mod.rs @@ -114,8 +114,7 @@ where .map(|(i, typ)| Field { name: symbols.from_str(&format!("_{}", i)), typ: typ, - }) - .collect(); + }).collect(); if fields.is_empty() { self.unit() } else { @@ -388,8 +387,7 @@ where group: group.clone(), })), _marker: PhantomData, - }) - .collect() + }).collect() } pub fn as_type(&self) -> &T { @@ -486,14 +484,16 @@ where Type::Ident(ref id) => { // Replace `Ident` with the alias it resolves to so that a `TypeEnv` is not // needed to resolve the type later on - let replacement = self.group.iter().position(|alias| alias.name == *id).map( - |index| { - T::from(Type::Alias(AliasRef { - index: index, - group: self.group.clone(), - })) - }, - ); + let replacement = + self.group + .iter() + .position(|alias| alias.name == *id) + .map(|index| { + T::from(Type::Alias(AliasRef { + index: index, + group: self.group.clone(), + })) + }); if replacement.is_none() { info!("Alias group were not able to resolve an identifier"); } @@ -844,8 +844,7 @@ where .map(|(i, typ)| Field { name: symbols.from_str(&format!("_{}", i)), typ: typ, - }) - .collect(), + }).collect(), Type::empty_row(), )) } @@ -1271,6 +1270,20 @@ impl ArcType { { match **self { Type::Generic(ref generic) => named_variables.get(&generic.id).cloned(), + Type::Forall(ref params, ref typ, ref vars) => { + let removed: AppVec<_> = params + .iter() + .flat_map(|param| named_variables.remove_entry(¶m.id)) + .collect(); + + let new_typ = typ.skolemize_(named_variables); + let new_typ = + new_typ.map(|typ| Type::forall_with_vars(params.clone(), typ, vars.clone())); + + named_variables.extend(removed); + + new_typ + } _ => walk_move_type_opt( self, &mut ControlVisitation(|typ: &ArcType| typ.skolemize_(named_variables)), @@ -1425,8 +1438,7 @@ impl ArcType { .take_while(|&(l, r)| match **l { Type::Generic(ref g) => g == r, _ => false, - }) - .count(); + }).count(); let typ = if params.len() <= allowed_missing_args + args.len() { // Remove the args at the end of the aliased type @@ -2582,15 +2594,13 @@ where .map(|field| Field { name: field.name.clone(), typ: Alias::from(translate_alias(&field.typ, &mut translate)), - }) - .collect(), + }).collect(), fields .iter() .map(|field| Field { name: field.name.clone(), typ: translate(&field.typ), - }) - .collect(), + }).collect(), translate(rest), ), Type::Hole => cache.hole(), diff --git a/check/src/substitution.rs b/check/src/substitution.rs index 7ad4324aa8..0a34317ebe 100644 --- a/check/src/substitution.rs +++ b/check/src/substitution.rs @@ -86,11 +86,18 @@ pub trait Substitutable: Sized { type Factory: VariableFactory; /// Constructs a new object from its variable type fn from_variable(x: Self::Variable) -> Self; + /// Retrieves the variable if `self` is a variable otherwise returns `None` fn get_var(&self) -> Option<&Self::Variable>; + + fn get_id(&self) -> Option { + self.get_var().map(|var| var.get_id()) + } + fn traverse<'a, F>(&'a self, f: &mut F) where F: Walker<'a, Self>; + fn instantiate(&self, subs: &Substitution) -> Self; fn on_union(&self) -> Option<&Self> { @@ -339,6 +346,7 @@ impl Substitution { *typ = self.real(typ).clone(); } } + impl Substitution { /// Takes `id` and updates the substitution to say that it should have the same type as `typ` pub fn union(&self, id: &T::Variable, typ: &T) -> Result, Error> @@ -379,6 +387,10 @@ impl Substitution { self.update_level(other_id, id.get_id()); } _ => { + if let Some(other_id) = typ.get_id() { + self.update_level(id.get_id(), other_id); + } + self.insert(id.get_id(), typ.clone()); } } diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index a3f1599d8d..b8f01a02c0 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -379,6 +379,15 @@ impl<'a> Typecheck<'a> { self.environment.get_bool().clone() } + fn is_variable_in_scope(&self, symbol: &Symbol, id: u32) -> bool { + self.type_variables + .get(symbol) + .map(|typ| match **typ { + Type::Variable(ref var) => var.id == id, + _ => false, + }).unwrap_or(false) + } + fn find_at(&mut self, span: Span, id: &Symbol) -> ArcType { match self.find(id) { Ok(typ) => typ, @@ -728,7 +737,13 @@ impl<'a> Typecheck<'a> { returned_type = match expected_type { Some(expected_type) => { let level = self.subs.var_id(); - self.subsumes_expr(expr.span, level, &expected_type, typ, expr) + self.subsumes_expr( + expr.span, + level, + &typ, + expected_type.clone(), + expr, + ) } None => typ, }; @@ -862,25 +877,25 @@ impl<'a> Typecheck<'a> { } Expr::Match(ref mut expr, ref mut alts) => { let typ = self.infer_expr(&mut **expr); - let mut expected_alt_type = expected_type.cloned(); + let expected_type = expected_type.take().cloned(); - let expected_type = expected_type.take(); + let mut expr_type = None; for alt in alts.iter_mut() { self.enter_scope(); self.typecheck_pattern(&mut alt.pattern, typ.clone()); - let mut alt_type = self.typecheck_opt(&mut alt.expr, expected_type); + let mut alt_type = self.typecheck_opt(&mut alt.expr, expected_type.as_ref()); alt_type = self.instantiate_generics(&alt_type); - self.exit_scope(); - // All alternatives must unify to the same type - if let Some(ref expected) = expected_alt_type { - alt_type = self.unify(expected, alt_type)?; + match expr_type { + Some(ref expr_type) if expected_type.is_none() => { + alt_type = self.unify_span(alt.expr.span, expr_type, alt_type); + } + _ => (), } - expected_alt_type = Some(alt_type); + self.exit_scope(); + expr_type = Some(alt_type); } - expected_alt_type - .ok_or(TypeError::EmptyCase) - .map(TailCall::Type) + expr_type.ok_or(TypeError::EmptyCase).map(TailCall::Type) } Expr::LetBindings(ref mut bindings, _) => { self.typecheck_bindings(bindings)?; @@ -944,6 +959,7 @@ impl<'a> Typecheck<'a> { lambda.id.name = self.symbols.symbol(loc); let level = self.subs.var_id(); let function_type = expected_type + .take() .cloned() .unwrap_or_else(|| self.subs.new_var()); @@ -969,6 +985,8 @@ impl<'a> Typecheck<'a> { exprs: ref mut fields, ref mut base, } => { + let level = self.subs.var_id(); + let expected_type = expected_type.map(|expected_type| { let expected_type = self.subs.real(expected_type).clone(); let typ = resolve::remove_aliases_cow(&self.environment, &expected_type); @@ -995,8 +1013,6 @@ impl<'a> Typecheck<'a> { let mut new_fields: Vec> = Vec::with_capacity(fields.len()); for field in fields { - let level = self.subs.var_id(); - let name = &field.name.value; let expected_field_type = expected_type .and_then(|expected_type| { @@ -1047,7 +1063,7 @@ impl<'a> Typecheck<'a> { } }; self.generalize_type(level, &mut typ); - typ = new_skolem_scope(&self.subs, &typ); + typ = self.new_skolem_scope(&typ); if self.error_on_duplicated_field(&mut duplicated_fields, field.name.clone()) { new_fields.push(Field::new(field.name.value.clone(), typ)); @@ -2045,7 +2061,7 @@ impl<'a> Typecheck<'a> { } fn generalize_type(&mut self, level: u32, typ: &mut ArcType) { - debug!("Start generalize {}", typ); + debug!("Start generalize {} >> {}", level, typ); self.type_variables.enter_scope(); let mut generalizer = TypeGeneralizer::new(level, self, typ); @@ -2319,7 +2335,29 @@ impl<'a> Typecheck<'a> { _ => break, }; } - self.subsumes(span, level, expected, actual) + let mut expected = expected.clone(); + loop { + let temp = self.instantiate_generics(&expected); + expected = match *temp { + Type::Function(ArgType::Implicit, ref arg_type, ref r_ret) => { + match **self.subs.real(&actual) { + Type::Variable(_) | Type::Function(ArgType::Implicit, _, _) => break, + _ => { + let name = self.implicit_resolver.make_implicit_ident(arg_type); + + receiver(Expr::Ident(TypedIdent { + name, + typ: arg_type.clone(), + })); + + r_ret.clone() + } + } + } + _ => break, + }; + } + self.subsumes(span, level, &expected, actual) } fn subsumes( @@ -2799,13 +2837,13 @@ impl<'a, 'b> TypeGeneralizer<'a, 'b> { )) } - Type::Skolem(ref skolem) if skolem.id >= self.level => { + Type::Skolem(ref skolem) if self.subs.get_level(skolem.id) >= self.level => { let generic = Generic { id: skolem.name.clone(), kind: skolem.kind.clone(), }; - if self.type_variables.get(&generic.id).is_none() { + if !self.is_variable_in_scope(&generic.id, skolem.id) { self.unbound_variables .insert(generic.id.clone(), generic.clone()); } diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs index 7e1670b0e1..f37b43131e 100644 --- a/check/src/unify_type.rs +++ b/check/src/unify_type.rs @@ -259,6 +259,14 @@ impl Substitutable for ArcType { } } + fn get_id(&self) -> Option { + match **self { + Type::Variable(ref var) => Some(var.id), + Type::Skolem(ref skolem) => Some(skolem.id), + _ => None, + } + } + fn traverse<'a, F>(&'a self, f: &mut F) where F: types::Walker<'a, Self>, @@ -1151,12 +1159,16 @@ impl<'a, 'e> Unifier, ArcType> for UnifierState<'a, Subsume<'e>> { match (&**l, &**r) { (&Type::Hole, _) => Ok(Some(r.clone())), (&Type::Variable(ref l), &Type::Variable(ref r)) if l.id == r.id => Ok(None), - (&Type::Skolem(ref skolem), &Type::Variable(ref r_var)) - if subs.get_level(skolem.id) > r_var.id => - { - return Err(UnifyError::Other(TypeError::UnableToGeneralize( - skolem.name.clone(), - ))); + (&Type::Variable(ref l_var), &Type::Skolem(ref skolem)) => { + let skolem_level = subs.get_level(skolem.id); + if skolem_level > l_var.id { + Err(UnifyError::Other(TypeError::UnableToGeneralize( + skolem.name.clone(), + ))) + } else { + subs.union(l_var, r)?; + Ok(None) + } } (&Type::Generic(ref l_gen), &Type::Variable(ref r_var)) => { let left = match self.unifier.variables.get(&l_gen.id) { diff --git a/check/tests/fail.rs b/check/tests/fail.rs index 9258683d7c..aa3cf1bfa1 100644 --- a/check/tests/fail.rs +++ b/check/tests/fail.rs @@ -28,6 +28,34 @@ match { x = 1 } with assert_unify_err!(result, Other(MissingFields(..))); } +#[test] +fn match_different_alt_types() { + let _ = env_logger::try_init(); + let text = r#" +match () with +| () -> 1 +| () -> "" +"#; + let result = support::typecheck(text); + + assert_unify_err!(result, TypeMismatch(..)); +} + +#[test] +fn match_different_alt_types_expected() { + let _ = env_logger::try_init(); + let text = r#" +let x : _ = + match () with + | () -> 1 + | () -> "" +() +"#; + let result = support::typecheck(text); + + assert_unify_err!(result, TypeMismatch(..)); +} + #[test] fn undefined_type_not_in_scope() { let _ = env_logger::try_init(); @@ -393,12 +421,12 @@ eq (A 0) (B 0.0) assert_eq!( &*format!("{}", result.unwrap_err()).replace("\t", " "), r#"error: Expected the following types to be equal -Expected: test.A -Found: test.B +Expected: test.B +Found: test.A 1 errors were found during unification: Types do not match: - Expected: test.A - Found: test.B + Expected: test.B + Found: test.A - :5:11 5 | eq (A 0) (B 0.0) | ^^^^^ @@ -420,8 +448,8 @@ f { } { x = 1 } assert_eq!( &*format!("{}", result.unwrap_err()).replace("\t", " "), r#"error: Expected the following types to be equal -Expected: () -Found: { x : Int } +Expected: { x : Int } +Found: () 1 errors were found during unification: The type `()` lacks the following fields: x - :4:7 diff --git a/check/tests/forall.rs b/check/tests/forall.rs index 6934cb93a5..404b688ff1 100644 --- a/check/tests/forall.rs +++ b/check/tests/forall.rs @@ -1052,13 +1052,48 @@ fn unify_with_inferred_forall_in_record() { let text = r#" type Option a = | None | Some a -type Record b = { x : Option b } -let f = \_ -> - { x = None } +let f : forall a . () -> Option { x : Option a } = \x -> + match () with + | _ -> Some { x = None } f "#; let result = support::typecheck(text); assert!(result.is_ok(), "{}", result.unwrap_err()); } + +#[test] +fn unify_with_inferred_forall_in_nested_call() { + let _ = ::env_logger::try_init(); + + let text = r#" +type Option a = | None | Some a + +type Deserializer i a = i -> Option { value : a, input : i } + +/// Deserializer which extracts the data from the `Value` type +type ValueDeserializer a = Deserializer String a + +let any x = any x + +let deserializer : Deserializer i a -> Deserializer i a = any () + +type Functor f = { + map : forall a b . (a -> b) -> f a -> f b +} + +let functor : Functor (Deserializer i) = { + map = \f m -> any () +} + +let option a : ValueDeserializer a -> ValueDeserializer (Option a) = \input -> + match input with + | "null" -> Some { value = None, input } + | _ -> (functor.map Some a) input +() +"#; + let result = support::typecheck(text); + + assert!(result.is_ok(), "{}", result.unwrap_err()); +} diff --git a/check/tests/implicits.rs b/check/tests/implicits.rs index 153efb7bd9..dced1da99b 100644 --- a/check/tests/implicits.rs +++ b/check/tests/implicits.rs @@ -806,3 +806,35 @@ f { x = 1 } assert!(result.is_ok(), "{}", result.unwrap_err()); } + +#[test] +fn type_hole_applicative() { + let _ = ::env_logger::try_init(); + let text = r#" +#[implicit] +type Functor f = { + map : forall a b . (a -> b) -> f a -> f b +} + +#[implicit] +type Applicative (f : Type -> Type) = { + functor : Functor f, + apply : forall a b . f (a -> b) -> f a -> f b, +} + +let map ?f : [Functor f] -> (a -> b) -> f a -> f b = f.map + +let apply ?app : [Applicative f] -> f (a -> b) -> f a -> f b = app.apply + +#[infix(left, 4)] +let (<*>) : [Applicative f] -> f (a -> b) -> f a -> f b = apply + +let map2 fn a b : [Applicative f] -> _ + = map fn a <*> b + +{} +"#; + let result = support::typecheck(text); + + assert!(result.is_ok(), "{}", result.unwrap_err()); +} diff --git a/check/tests/support/mod.rs b/check/tests/support/mod.rs index 46e495f686..05a1cf5653 100644 --- a/check/tests/support/mod.rs +++ b/check/tests/support/mod.rs @@ -405,7 +405,7 @@ macro_rules! assert_multi_unify_err { #[allow(unused_imports)] use check::substitution::Error::Occurs; #[allow(unused_imports)] - use check::unify_type::TypeError::{FieldMismatch, SelfRecursiveAlias, MissingFields}; + use check::unify_type::TypeError::{FieldMismatch, UnableToGeneralize, SelfRecursiveAlias, MissingFields}; match $e { Ok(x) => assert!(false, "Expected error, got {}", x), diff --git a/tests/vm.rs b/tests/vm.rs index 8d18cc3f48..192204d728 100644 --- a/tests/vm.rs +++ b/tests/vm.rs @@ -453,6 +453,14 @@ let _ = import! std.option () } +test_expr!{ load_applicative, +r#" +let _ = import! std.applicative +() +"#, +() +} + test_expr!{ prelude do_expression_option_some, r#" let { monad = { flat_map } } = import! std.option From d336cac8ff7ec3d5f9e1b42dd74af696953657e0 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Wed, 22 Aug 2018 23:45:10 +0200 Subject: [PATCH 6/7] test(check): Add test for #604 Fixed in previous higher ranked fixes. Closes #604 --- check/tests/forall.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/check/tests/forall.rs b/check/tests/forall.rs index 404b688ff1..b36b74b589 100644 --- a/check/tests/forall.rs +++ b/check/tests/forall.rs @@ -1097,3 +1097,18 @@ let option a : ValueDeserializer a -> ValueDeserializer (Option a) = \input -> assert!(result.is_ok(), "{}", result.unwrap_err()); } + +#[test] +fn forall_scope() { + let _ = ::env_logger::try_init(); + + let text = r#" +type Proxy h = | Proxy +let foo : (forall i . Proxy i -> ()) -> Proxy i -> () = + \m p -> m p +() +"#; + let result = support::typecheck(text); + + assert!(result.is_ok(), "{}", result.unwrap_err()); +} From f7ffc701c1076c8044c120e1ed5dc3673fed17d9 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 5 Aug 2018 14:39:14 +0200 Subject: [PATCH 7/7] fix(check): Always inserted implicit arguments to implicit_args This makes it consistent with the implicit arguments inserted during function application. Fixes #601 --- check/src/typecheck.rs | 12 ++++++++++-- check/tests/implicits.rs | 7 +++++-- tests/vm.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index b8f01a02c0..8a3661d518 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -1171,7 +1171,12 @@ impl<'a> Typecheck<'a> { let bound_type = self.typecheck(bound, &arg2); - self.unify_span(bound.span, &arg2, bound_type); + self.unify_span( + // Point to the `do` token + expr.span.subspan(0.into(), 2.into()), + &arg2, + bound_type, + ); self.stack_var(id.value.name.clone(), id.value.typ.clone()); @@ -2297,7 +2302,10 @@ impl<'a> Typecheck<'a> { } match expr.value { - Expr::App { ref mut args, .. } => args.push(pos::spanned(expr.span, arg)), + Expr::App { + ref mut implicit_args, + .. + } => implicit_args.push(pos::spanned(expr.span, arg)), _ => (), } }) diff --git a/check/tests/implicits.rs b/check/tests/implicits.rs index dced1da99b..bb5e8e7127 100644 --- a/check/tests/implicits.rs +++ b/check/tests/implicits.rs @@ -402,9 +402,12 @@ f (==) 1 2 assert_eq!(args.len(), 3); match args[0].value { ast::Expr::App { - args: ref args_eq, .. + args: ref args_eq, + implicit_args: ref implicit_args_eq, + .. } => { - assert_eq!(args_eq.len(), 1); + assert_eq!(args_eq.len(), 0); + assert_eq!(implicit_args_eq.len(), 1); } _ => panic!(), } diff --git a/tests/vm.rs b/tests/vm.rs index 192204d728..c4ddefc47a 100644 --- a/tests/vm.rs +++ b/tests/vm.rs @@ -920,3 +920,33 @@ fn deep_clone_partial_application() { global_memory_with_closures ); } + +test_expr!{ prelude issue_601, +r" +let { wrap } = import! std.applicative +let { flat_map } = import! std.monad + +type Id a = a +let id_functor: Functor Id = { + map = \f x -> f x, +} +let id_applicative: Applicative Id = { + functor = id_functor, + apply = \f x -> f x, + wrap = \x -> x, +} +let id_monad: Monad Id = { + applicative = id_applicative, + flat_map = \f x -> f x, +} + +let foo: [Functor f] -> Id () = () + +let bar: Id () = + do _ = foo + wrap () + +in () +", +() +}