diff --git a/base/src/types.rs b/base/src/types.rs index 20b54abbd5..d7175fb5d9 100644 --- a/base/src/types.rs +++ b/base/src/types.rs @@ -449,21 +449,28 @@ impl Type } pub fn record(types: Vec>>, fields: Vec>) -> T { + Type::poly_record(types, fields, Type::empty_row()) + } + + pub fn poly_record(types: Vec>>, + fields: Vec>, + rest: T) + -> T { T::from(Type::Record { types: types, - row: if fields.is_empty() { - Type::empty_row() - } else { - Type::extend_row(fields, Type::empty_row()) - }, + row: Type::extend_row(fields, rest), }) } pub fn extend_row(fields: Vec>, rest: T) -> T { - T::from(Type::ExtendRow { - fields: fields, - rest: rest, - }) + if fields.is_empty() { + rest + } else { + T::from(Type::ExtendRow { + fields: fields, + rest: rest, + }) + } } pub fn empty_row() -> T { @@ -557,8 +564,10 @@ impl Type _ => None, } } +} - pub fn field_iter(&self) -> FieldIterator { +pub trait TypeRef: Sized { + fn field_iter(&self) -> FieldIterator { FieldIterator { typ: self, current: 0, @@ -566,6 +575,8 @@ impl Type } } +impl TypeRef for T where T: Deref> {} + impl Type where T: Deref>, { @@ -589,29 +600,35 @@ impl Type } } -pub struct FieldIterator<'a, Id: 'a, T: 'a> { - typ: &'a Type, +pub struct FieldIterator<'a, T: 'a> { + typ: &'a T, current: usize, } -impl<'a, Id, T> Iterator for FieldIterator<'a, Id, T> +impl<'a, T> FieldIterator<'a, T> { + pub fn current_type(&self) -> &'a T { + self.typ + } +} + +impl<'a, Id: 'a, T> Iterator for FieldIterator<'a, T> where T: Deref> { type Item = &'a Field; fn next(&mut self) -> Option<&'a Field> { - match *self.typ { + match **self.typ { Type::Record { ref row, .. } => { - self.typ = &**row; + self.typ = row; self.next() - }, + } Type::ExtendRow { ref fields, ref rest } => { let current = self.current; self.current += 1; fields.get(current) .or_else(|| { self.current = 0; - self.typ = &**rest; + self.typ = rest; self.next() }) } diff --git a/check/src/rename.rs b/check/src/rename.rs index 63c81bc02f..b62f8e433e 100644 --- a/check/src/rename.rs +++ b/check/src/rename.rs @@ -6,7 +6,7 @@ use base::error::Errors; use base::fnv::FnvMap; use base::scoped_map::ScopedMap; use base::symbol::{Symbol, SymbolRef, SymbolModule}; -use base::types::{self, Alias, ArcType, Type, RcKind, KindEnv, TypeEnv}; +use base::types::{self, Alias, ArcType, Type, TypeRef, RcKind, KindEnv, TypeEnv}; use unify_type::{TypeError, State}; use unify::{Error as UnifyError, Unifier, Unifiable, UnifierState}; @@ -319,8 +319,11 @@ pub fn rename(symbols: &mut SymbolModule, } pub fn equivalent(env: &TypeEnv, actual: &ArcType, inferred: &ArcType) -> bool { + use substitution::Substitution; + // FIXME Inneficient and possible wrong + let subs = Substitution::new(); let mut unifier = UnifierState { - state: State::new(env), + state: State::new(env, &subs), unifier: Equivalent { map: FnvMap::default(), equiv: true, diff --git a/check/src/substitution.rs b/check/src/substitution.rs index 9b22a3c525..fb7a7d5e7e 100644 --- a/check/src/substitution.rs +++ b/check/src/substitution.rs @@ -290,7 +290,8 @@ impl Substitution { pub fn union(&self, id: &T::Variable, typ: &T) -> Result<(), ()> where T::Variable: Clone, { - // Nothing needs to be done if both are the same variable already (also prevents the occurs check from failing) + // Nothing needs to be done if both are the same variable already (also prevents the occurs + // check from failing) if typ.get_var().map_or(false, |other| other.get_id() == id.get_id()) { return Ok(()); } diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index 0b22d8f682..d3509bdbae 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -11,7 +11,7 @@ use base::error::Errors; use base::instantiate::{self, Instantiator}; use base::pos::{BytePos, Span, Spanned}; use base::symbol::{Symbol, SymbolRef, SymbolModule, Symbols}; -use base::types::{self, ArcType, RcKind, Type, Generic, Kind, merge}; +use base::types::{self, ArcType, Field, RcKind, Type, TypeRef, Generic, Kind, merge}; use base::types::{KindEnv, TypeEnv, PrimitiveEnv, Alias, AliasData, TypeVariable}; use kindcheck::{self, KindCheck}; use substitution::Substitution; @@ -576,31 +576,39 @@ impl<'a> Typecheck<'a> { try!(self.typecheck_bindings(bindings)); Ok(TailCall::TailCall) } - Expr::Projection(ref mut expr, ref field_id, ref mut field_typ) => { + Expr::Projection(ref mut expr, ref field_id, ref mut ast_field_typ) => { let mut expr_typ = self.typecheck(&mut **expr); debug!("Projection {} . {:?}", types::display_type(&self.symbols, &expr_typ), self.symbols.string(field_id)); self.subs.make_real(&mut expr_typ); - if let Type::Variable(_) = *expr_typ { - // Attempt to find a record with `field_access` since inferring to a record - // with only `field_access` as the field is probably useless - let (record_type, _) = try!(self.find_record(&[field_id.clone()]) - .map(|t| (t.0.clone(), t.1.clone()))); - let record_type = self.instantiate(&record_type); - expr_typ = try!(self.unify(&record_type, expr_typ)); - } + let record = self.remove_aliases(expr_typ.clone()); match *record { + Type::Variable(..) | Type::Record { .. } => { let field_type = record.field_iter() .find(|field| field.name.name_eq(field_id)) .map(|field| field.typ.clone()); - *field_typ = match field_type { + *ast_field_typ = match field_type { Some(typ) => self.instantiate(&typ), - None => return Err(UndefinedField(expr_typ.clone(), field_id.clone())), + None => { + // FIXME As the polymorphic `record_type` do not have the type + // fields which `typ` this unification is only done after we + // checked if the field exists which lets field accesses on + // types with type fields still work + let field_var = self.subs.new_var(); + let field = Field { + name: field_id.clone(), + typ: field_var.clone(), + }; + let record_type = + Type::poly_record(vec![], vec![field], self.subs.new_var()); + try!(self.unify(&record_type, record)); + field_var + } }; - Ok(TailCall::Type(field_typ.clone())) + Ok(TailCall::Type(ast_field_typ.clone())) } _ => Err(InvalidProjection(record)), } @@ -1037,7 +1045,7 @@ impl<'a> Typecheck<'a> { debug!("Intersect\n{} <> {}", types::display_type(&self.symbols, existing_type), types::display_type(&self.symbols, symbol_type)); - let state = unify_type::State::new(&self.environment); + let state = unify_type::State::new(&self.environment, &self.subs); let result = unify::intersection(&self.subs, state, existing_type, symbol_type); debug!("Intersect result {}", result); result @@ -1224,7 +1232,7 @@ impl<'a> Typecheck<'a> { expected: &ArcType, mut actual: ArcType) -> ArcType { - let state = unify_type::State::new(&self.environment); + let state = unify_type::State::new(&self.environment, &self.subs); match unify_type::merge_signature(&self.subs, &mut self.type_variables, level, @@ -1264,7 +1272,7 @@ impl<'a> Typecheck<'a> { debug!("Unify {} <=> {}", types::display_type(&self.symbols, expected), types::display_type(&self.symbols, &actual)); - let state = unify_type::State::new(&self.environment); + let state = unify_type::State::new(&self.environment, &self.subs); match unify::unify(&self.subs, state, expected, &actual) { Ok(typ) => Ok(self.subs.set_type(typ)), Err(errors) => { @@ -1312,8 +1320,8 @@ fn with_pattern_types(fields: &[(Symbol, Option)], typ: &ArcType, mut if let Type::Record { ref row, .. } = **typ { if let Type::ExtendRow { fields: ref field_types, .. } = **row { for field in fields { - // If the field in the pattern does not exist (undefined field error) then skip it as - // the error itself will already have been reported + // If the field in the pattern does not exist (undefined field error) then skip it + // as the error itself will already have been reported if let Some(associated_type) = field_types.iter() .find(|type_field| type_field.name.name_eq(&field.0)) { f(&field.0, &field.1, &associated_type.typ); @@ -1455,7 +1463,7 @@ pub fn unroll_app(typ: &Type) -> Option { args.extend(rest.iter().rev().cloned()); l } - _ => return None, + _ => return unroll_record(typ), }; while let Type::App(ref l, ref rest) = **current { args.extend(rest.iter().rev().cloned()); @@ -1468,3 +1476,28 @@ pub fn unroll_app(typ: &Type) -> Option { Some(Type::app(current.clone(), args)) } } + +pub fn unroll_record(typ: &Type) -> Option { + let mut args = Vec::new(); + let mut current = match *typ { + Type::ExtendRow { ref fields, ref rest } => { + match **rest { + Type::ExtendRow { .. } => { + args.extend_from_slice(fields); + rest + } + _ => return None, + } + } + _ => return None, + }; + while let Type::ExtendRow { ref fields, ref rest } = **current { + args.extend_from_slice(fields); + current = rest; + } + if args.is_empty() { + None + } else { + Some(Type::extend_row(args, current.clone())) + } +} diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs index f7af53b919..9a5464e011 100644 --- a/check/src/unify_type.rs +++ b/check/src/unify_type.rs @@ -1,7 +1,7 @@ use std::fmt; use base::error::Errors; -use base::types::{self, ArcType, Type, TypeVariable, TypeEnv, merge}; +use base::types::{self, ArcType, Type, TypeRef, TypeVariable, TypeEnv, merge}; use base::symbol::{Symbol, SymbolRef}; use base::instantiate; use base::scoped_map::ScopedMap; @@ -17,13 +17,15 @@ pub struct State<'a> { /// A stack of which aliases are currently expanded. Used to determine when an alias is /// recursively expanded in which case the unification fails. reduced_aliases: Vec, + subs: &'a Substitution, } impl<'a> State<'a> { - pub fn new(env: &'a (TypeEnv + 'a)) -> State<'a> { + pub fn new(env: &'a (TypeEnv + 'a), subs: &'a Substitution) -> State<'a> { State { env: env, reduced_aliases: Vec::new(), + subs: subs, } } } @@ -189,24 +191,29 @@ fn do_zip_match<'a, U>(self_: &ArcType, } (&Type::ExtendRow { fields: ref l_args, rest: ref l_rest }, &Type::ExtendRow { fields: ref r_args, rest: ref r_rest }) => { - let new_args = walk_move_types(l_args.iter().zip(r_args.iter()), |l, r| { - let opt_type = if !l.name.name_eq(&r.name) { - - let err = TypeError::FieldMismatch(l.name.clone(), r.name.clone()); - unifier.report_error(UnifyError::Other(err)); - None - } else { - unifier.try_match(&l.typ, &r.typ) - }; - opt_type.map(|typ| { - types::Field { - name: l.name.clone(), - typ: typ, - } - }) - }); - let new_rest = unifier.try_match(l_rest, r_rest); - Ok(merge(l_args, new_args, l_rest, new_rest, Type::extend_row)) + if l_args.len() == r_args.len() && + l_args.iter().zip(r_args).all(|(l, r)| l.name.name_eq(&r.name)) { + let new_args = walk_move_types(l_args.iter().zip(r_args), |l, r| { + let opt_type = if !l.name.name_eq(&r.name) { + + let err = TypeError::FieldMismatch(l.name.clone(), r.name.clone()); + unifier.report_error(UnifyError::Other(err)); + None + } else { + unifier.try_match(&l.typ, &r.typ) + }; + opt_type.map(|typ| { + types::Field { + name: l.name.clone(), + typ: typ, + } + }) + }); + let new_rest = unifier.try_match(l_rest, r_rest); + Ok(merge(l_args, new_args, l_rest, new_rest, Type::extend_row)) + } else { + Ok(unify_rows(unifier, self_, other)) + } } (&Type::Ident(ref id), &Type::Alias(ref alias)) if *id == alias.name => { Ok(Some(other.clone())) @@ -223,6 +230,52 @@ fn do_zip_match<'a, U>(self_: &ArcType, } } +fn unify_rows<'a, U>(unifier: &mut UnifierState<'a, U>, l: &ArcType, r: &ArcType) -> Option + where U: Unifier, ArcType>, +{ + let subs = unifier.state.subs; + let mut both = Vec::new(); + let mut missing_left = Vec::new(); + let mut l_iter = l.field_iter(); + for l in l_iter.by_ref() { + match r.field_iter().find(|r| l.name.name_eq(&r.name)) { + Some(r) => both.push((l, r)), + None => missing_left.push(l.clone()), + } + } + + let mut r_iter = r.field_iter(); + let missing_right = r_iter.by_ref() + .filter(|r| l.field_iter().all(|l| !l.name.name_eq(&r.name))) + .cloned() + .collect(); + // Unify the fields that exists in both records + let new_both = walk_move_types(both.iter().cloned(), |l, r| { + unifier.try_match(&l.typ, &r.typ) + .map(|typ| { + types::Field { + name: l.name.clone(), + typ: typ, + } + }) + }); + + let l = Type::extend_row(missing_left, subs.new_var()); + let left = unifier.try_match(&l, r_iter.current_type()); + + let r = Type::extend_row(missing_right, subs.new_var()); + unifier.try_match(l_iter.current_type(), &r); + + let mut fields = match new_both { + Some(fields) => fields, + None => both.iter().map(|pair| pair.0.clone()).collect(), + }; + let mut rest_fields = left.as_ref().unwrap_or(&l).field_iter(); + fields.extend(rest_fields.by_ref().cloned()); + fields.extend(r.field_iter().cloned()); + Some(Type::extend_row(fields, rest_fields.current_type().clone())) +} + /// Attempt to unify two alias types. /// To find a possible successful unification we walk through the alias expansions of `l` to find /// an expansion which has `r_id` in the spine of the expanded type @@ -497,17 +550,16 @@ mod tests { use super::*; use base::error::Errors; - use super::TypeError::FieldMismatch; use unify::Error::*; use unify::unify; use substitution::Substitution; - use base::types::{self, ArcType, Type}; + use base::types::{self, ArcType, Type, TypeRef}; use tests::*; #[test] fn detect_multiple_type_errors_in_single_type() { let _ = ::env_logger::init(); - let (x, y, z, w) = (intern("x"), intern("y"), intern("z"), intern("w")); + let (x, y) = (intern("x"), intern("y")); let l: ArcType = Type::record(vec![], vec![types::Field { name: x.clone(), @@ -519,20 +571,21 @@ mod tests { }]); let r = Type::record(vec![], vec![types::Field { - name: z.clone(), - typ: Type::int(), + name: x.clone(), + typ: Type::string(), }, types::Field { - name: w.clone(), - typ: Type::string(), + name: y.clone(), + typ: Type::int(), }]); let subs = Substitution::new(); let env = MockEnv; - let state = State::new(&env); + let state = State::new(&env, &subs); let result = unify(&subs, state, &l, &r); assert_eq!(result, Err(Errors { - errors: vec![Other(FieldMismatch(x, z)), Other(FieldMismatch(y, w))], + errors: vec![TypeMismatch(Type::int(), Type::string()), + TypeMismatch(Type::string(), Type::int())], })); } @@ -547,12 +600,24 @@ mod tests { name: intern("y"), typ: Type::int(), }; - let l: TcType = Type::record(vec![], vec![x.clone()]); - let r = Type::record(vec![], vec![y.clone()]); let subs = Substitution::new(); + let l: ArcType = Type::poly_record(vec![], vec![x.clone()], subs.new_var()); + let r = Type::poly_record(vec![], vec![y.clone()], subs.new_var()); + let env = MockEnv; - let state = State::new(&env); + let state = State::new(&env, &subs); let result = unify(&subs, state, &l, &r); - assert_eq!(result, Ok(Type::record(vec![], vec![x.clone(), y.clone()]))); + match result { + Ok(result) => { + // Get the row variable at the end of the resulting type so we can compare the types + let mut iter = result.field_iter(); + for _ in iter.by_ref() { + } + let row_variable = iter.current_type().clone(); + let expected = Type::poly_record(vec![], vec![x.clone(), y.clone()], row_variable); + assert_eq!(result, expected); + } + Err(err) => panic!("{}", err), + } } } diff --git a/check/tests/completion.rs b/check/tests/completion.rs index b815736942..a7d59333aa 100644 --- a/check/tests/completion.rs +++ b/check/tests/completion.rs @@ -136,7 +136,7 @@ r.x name: intern("x"), typ: typ("Int"), }])); - assert_eq!(result, expected); + assert_eq!(result.map(support::close_record), expected); let result = completion::find(&typ_env, &mut expr, BytePos(22)); let expected = Ok(typ("Int")); diff --git a/check/tests/pass.rs b/check/tests/pass.rs index bb552ac5cc..31345b98a3 100644 --- a/check/tests/pass.rs +++ b/check/tests/pass.rs @@ -495,7 +495,7 @@ type Test = | Test String Int in { Test, x = 1 } }]; let expected = Ok(Type::record(types, fields)); - assert_eq!(result, expected); + assert_eq!(result.map(support::close_record), expected); } #[test] @@ -652,7 +652,7 @@ in ]; let expected = Ok(Type::record(vec![], fields)); - assert_eq!(result, expected); + assert_eq!(result.map(support::close_record), expected); } #[test] @@ -679,7 +679,7 @@ in ]; let expected = Ok(Type::record(vec![], fields)); - assert_eq!(result, expected); + assert_eq!(result.map(support::close_record), expected); } #[test] diff --git a/check/tests/row_polymorphism.rs b/check/tests/row_polymorphism.rs index af748580fe..2d61ab726d 100644 --- a/check/tests/row_polymorphism.rs +++ b/check/tests/row_polymorphism.rs @@ -4,7 +4,7 @@ extern crate gluon_base as base; extern crate gluon_parser as parser; extern crate gluon_check as check; -use base::types::{Field, Type, TcType}; +use base::types::{Field, Type}; mod support; use support::{intern, typ}; @@ -17,56 +17,71 @@ macro_rules! assert_pass { }}; } -#[test] -fn if_else_different_records() { - let text = r#" -if True then - { y = "" } -else - { x = 1 } -"#; - let result = support::typecheck(text); - - assert_eq!(result, - Ok(TcType::from(Type::Record { - types: vec![], - row: Type::extend_row(vec![Field { - name: intern("x"), - typ: typ("Int"), - }, - Field { - name: intern("y"), - typ: typ("String"), - }], - typ("a")), - }))); -} - - #[test] fn infer_fields() { + let _ = env_logger::init(); + let text = r#" let f vec = vec.x #Int+ vec.y f "#; let result = support::typecheck(text); - let record = TcType::from(Type::Record { - types: vec![], - row: Type::extend_row(vec![Field { + let record = Type::record(vec![], + vec![Field { name: intern("x"), typ: typ("Int"), }, Field { name: intern("y"), typ: typ("Int"), - }], - typ("a")), - }); - assert_eq!(result, Ok(Type::function(vec![record], typ("Int")))); + }]); + assert_eq!(result.map(support::close_record), + Ok(Type::function(vec![record], typ("Int")))); +} + +#[test] +fn infer_additional_fields() { + let _ = env_logger::init(); + + let text = r#" +let f vec = vec.x #Int+ vec.y +f { x = 1, y = 2, z = 3 } +"#; + let result = support::typecheck(text); + assert_eq!(result, Ok(typ("Int"))); +} + +#[test] +fn field_access_on_record_with_type() { + let _ = env_logger::init(); + + let text = r#" +type Test = Int +let record = { Test, x = 1, y = "" } +record.y +"#; + let result = support::typecheck(text); + assert_eq!(result, Ok(typ("String"))); +} + +#[test] +fn if_else_different_records() { + let _ = env_logger::init(); + + let text = r#" +if True then + { y = "" } +else + { x = 1 } +"#; + let result = support::typecheck(text); + assert!(result.is_err()); } #[test] fn missing_field() { + let _ = env_logger::init(); + let text = r#" let f vec = vec.x #Int+ vec.y f { x = 1 } diff --git a/check/tests/support/mod.rs b/check/tests/support/mod.rs index 2535a69f9e..08a075310d 100644 --- a/check/tests/support/mod.rs +++ b/check/tests/support/mod.rs @@ -1,7 +1,7 @@ use base::ast::SpannedExpr; use base::symbol::{Symbols, SymbolModule, Symbol, SymbolRef}; use base::types::{Alias, Generic, Kind, Type, KindEnv}; -use base::types::{ArcType, TypeEnv, PrimitiveEnv, RcKind}; +use base::types::{ArcType, TypeEnv, PrimitiveEnv, RcKind, walk_move_type}; use check::typecheck::{self, Typecheck}; use parser; @@ -176,3 +176,19 @@ pub fn alias(s: &str, args: &[&str], typ: ArcType) -> ArcType { .collect(), typ) } + +#[allow(dead_code)] +pub fn close_record(typ: ArcType) -> ArcType { + walk_move_type(typ, + &mut |typ| { + match *typ { + Type::ExtendRow { ref fields, ref rest } => { + match **rest { + Type::ExtendRow { .. } => None, + _ => Some(Type::extend_row(fields.clone(), Type::empty_row())), + } + } + _ => None, + } + }) +} diff --git a/vm/src/compiler.rs b/vm/src/compiler.rs index 3764feb818..ca5c77459d 100644 --- a/vm/src/compiler.rs +++ b/vm/src/compiler.rs @@ -5,7 +5,7 @@ use base::instantiate; use base::symbol::{Symbol, SymbolRef, SymbolModule}; use base::ast::{Typed, DisplayEnv, SpannedExpr, Expr}; use base::types; -use base::types::{Alias, KindEnv, ArcType, Type, TypeEnv}; +use base::types::{Alias, KindEnv, ArcType, Type, TypeEnv, TypeRef}; use base::scoped_map::ScopedMap; use types::*; use vm::GlobalVmState; @@ -334,17 +334,11 @@ impl<'a> Compiler<'a> { fn find_field(&self, typ: &ArcType, field: &Symbol) -> Option { // Walk through all type aliases - match **instantiate::remove_aliases_cow(self, typ) { - ref typ @ Type::Record { .. } => { - typ.field_iter() - .position(|f| f.name.name_eq(field)) - .map(|i| i as VmIndex) - } - ref typ => { - panic!("ICE: Projection on {}", - types::display_type(&self.symbols, typ)) - } - } + let typ = instantiate::remove_aliases_cow(self, typ); + // FIXME Cannot use indexing anymore with row polymorphism + typ.field_iter() + .position(|f| f.name.name_eq(field)) + .map(|i| i as VmIndex) } fn find_tag(&self, typ: &ArcType, constructor: &Symbol) -> Option { diff --git a/vm/src/types.rs b/vm/src/types.rs index a9a4cea2a8..6753c3ca9f 100644 --- a/vm/src/types.rs +++ b/vm/src/types.rs @@ -1,6 +1,6 @@ use base::symbol::{Symbol, SymbolRef}; use base::types; -use base::types::{Alias, KindEnv, TypeEnv, ArcType, Type}; +use base::types::{Alias, KindEnv, TypeEnv, ArcType, Type, TypeRef}; use base::fnv::FnvMap; pub use self::Instruction::*; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index d2265a2916..7e1811c834 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -8,7 +8,7 @@ use std::usize; use base::metadata::{Metadata, MetadataEnv}; use base::symbol::{Name, Symbol, SymbolRef}; use base::types::{Alias, AliasData, ArcType, Generic, Type, Kind, KindEnv, TypeEnv, PrimitiveEnv, - RcKind}; + RcKind, TypeRef}; use base::fnv::FnvMap; use macros::MacroEnv;