diff --git a/src/can/expr.rs b/src/can/expr.rs index 219899c468..5e14d17258 100644 --- a/src/can/expr.rs +++ b/src/can/expr.rs @@ -1,5 +1,6 @@ use crate::can::def::{can_defs_with_return, Def, Info}; use crate::can::env::Env; +use crate::can::ident::Lowercase; use crate::can::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, int_expr_from_result, @@ -637,8 +638,37 @@ pub fn canonicalize_expr( (expr, output, And(constraints)) } - ast::Expr::Access(_, _) - | ast::Expr::AccessorFunction(_) + ast::Expr::Access(record_expr, field) => { + let ext_var = var_store.fresh(); + let field_var = var_store.fresh(); + let ext_type = Type::Variable(ext_var); + let field_type = Type::Variable(field_var); + + let mut rec_field_types = SendMap::default(); + + rec_field_types.insert(Lowercase::from_unqualified_ident(field), field_type.clone()); + + let record_type = Type::Record(rec_field_types, Box::new(ext_type)); + let record_expected = Expected::NoExpectation(record_type); + + let (loc_expr, output, mut constraint) = canonicalize_expr( + &ImMap::default(), + env, + var_store, + scope, + region, + record_expr, + record_expected, + ); + + constraint = exists( + vec![field_var, ext_var], + And(vec![constraint, Eq(field_type, expected, region)]), + ); + + (loc_expr.value, output, constraint) + } + ast::Expr::AccessorFunction(_) | ast::Expr::If(_) | ast::Expr::GlobalTag(_) | ast::Expr::PrivateTag(_) diff --git a/src/can/mod.rs b/src/can/mod.rs index 5f92ccfa3d..c26f7d00a9 100644 --- a/src/can/mod.rs +++ b/src/can/mod.rs @@ -1,6 +1,7 @@ pub mod def; pub mod env; pub mod expr; +pub mod ident; pub mod module; pub mod num; pub mod operator; diff --git a/src/infer.rs b/src/infer.rs index 80d6998846..30757cc68e 100644 --- a/src/infer.rs +++ b/src/infer.rs @@ -3,11 +3,17 @@ use crate::collections::ImMap; use crate::solve::solve; use crate::subs::{Content, Subs, Variable}; use crate::types::Constraint; +use crate::unify::Problems; -pub fn infer_expr(subs: &mut Subs, constraint: &Constraint, expr_var: Variable) -> Content { +pub fn infer_expr( + subs: &mut Subs, + problems: &mut Problems, + constraint: &Constraint, + expr_var: Variable, +) -> Content { let env: ImMap = ImMap::default(); - solve(&env, subs, constraint); + solve(&env, problems, subs, constraint); subs.get(expr_var).content } diff --git a/src/load/mod.rs b/src/load/mod.rs index b6770703da..be87f8623a 100644 --- a/src/load/mod.rs +++ b/src/load/mod.rs @@ -13,6 +13,7 @@ use crate::solve::solve; use crate::subs::VarStore; use crate::subs::{Subs, Variable}; use crate::types::Constraint; +use crate::unify::Problems; use bumpalo::Bump; use futures::future::join_all; use std::io; @@ -348,7 +349,12 @@ fn expose( } } -pub fn solve_loaded(module: &Module, subs: &mut Subs, loaded_deps: LoadedDeps) { +pub fn solve_loaded( + module: &Module, + problems: &mut Problems, + subs: &mut Subs, + loaded_deps: LoadedDeps, +) { use LoadedModule::*; let mut vars_by_symbol: ImMap = ImMap::default(); @@ -403,8 +409,8 @@ pub fn solve_loaded(module: &Module, subs: &mut Subs, loaded_deps: LoadedDeps) { } for constraint in constraints { - solve(&vars_by_symbol, subs, &constraint); + solve(&vars_by_symbol, problems, subs, &constraint); } - solve(&vars_by_symbol, subs, &module.constraint); + solve(&vars_by_symbol, problems, subs, &module.constraint); } diff --git a/src/parse/number_literal.rs b/src/parse/number_literal.rs index 4130e838ec..f0e542309d 100644 --- a/src/parse/number_literal.rs +++ b/src/parse/number_literal.rs @@ -139,7 +139,7 @@ where let next_state = state.advance_without_indenting(bytes_parsed)?; - Ok((dbg!(expr), next_state)) + Ok((expr, next_state)) } #[derive(Debug, PartialEq, Eq)] diff --git a/src/pretty_print_types.rs b/src/pretty_print_types.rs index b74fee553c..d285ca64f3 100644 --- a/src/pretty_print_types.rs +++ b/src/pretty_print_types.rs @@ -89,11 +89,17 @@ fn find_names_needed( find_names_needed(ret_var, subs, roots, root_appearances, names_taken); } + Structure(Record(_, _)) => { + panic!("TODO find_names_needed Record"); + } RigidVar(name) => { // User-defined names are already taken. // We must not accidentally generate names that collide with them! names_taken.insert(name.to_string()); } + Alias(_, _, _, _) => { + panic!("TODO find_names_needed Alias"); + } Error(_) | Structure(Erroneous(_)) | Structure(EmptyRecord) => { // Errors and empty records don't need names. } @@ -173,6 +179,9 @@ fn write_content(content: Content, subs: &mut Subs, buf: &mut String, parens: Pa FlexVar(None) => buf.push_str(WILDCARD), RigidVar(name) => buf.push_str(&name), Structure(flat_type) => write_flat_type(flat_type, subs, buf, parens), + Alias(_, _, _, _) => { + panic!("TODO write_content Alias"); + } Error(_) => buf.push_str(""), } } @@ -195,6 +204,9 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren ), EmptyRecord => buf.push_str(EMPTY_RECORD), Func(args, ret) => write_fn(args, ret, subs, buf, parens), + Record(_, _) => { + panic!("TODO write_flat_type Record"); + } Erroneous(problem) => { buf.push_str(&format!("", problem)); } diff --git a/src/solve.rs b/src/solve.rs index 048287d483..c8d5259716 100644 --- a/src/solve.rs +++ b/src/solve.rs @@ -3,11 +3,16 @@ use crate::collections::ImMap; use crate::subs::{Content, Descriptor, FlatType, Subs, Variable}; use crate::types::Constraint::{self, *}; use crate::types::Type::{self, *}; -use crate::unify::unify; +use crate::unify::{unify, Problems}; type Env = ImMap; -pub fn solve(vars_by_symbol: &Env, subs: &mut Subs, constraint: &Constraint) { +pub fn solve( + vars_by_symbol: &Env, + problems: &mut Problems, + subs: &mut Subs, + constraint: &Constraint, +) { match constraint { True => (), Eq(typ, expected_type, _region) => { @@ -15,7 +20,7 @@ pub fn solve(vars_by_symbol: &Env, subs: &mut Subs, constraint: &Constraint) { let actual = type_to_var(subs, typ.clone()); let expected = type_to_var(subs, expected_type.clone().get_type()); - unify(subs, actual, expected); + unify(subs, problems, actual, expected); } Lookup(symbol, expected_type, _region) => { // TODO use region? @@ -29,11 +34,11 @@ pub fn solve(vars_by_symbol: &Env, subs: &mut Subs, constraint: &Constraint) { })); let expected = type_to_var(subs, expected_type.clone().get_type()); - unify(subs, actual, expected); + unify(subs, problems, actual, expected); } And(sub_constraints) => { for sub_constraint in sub_constraints.iter() { - solve(vars_by_symbol, subs, sub_constraint); + solve(vars_by_symbol, problems, subs, sub_constraint); } } Pattern(_region, _category, typ, expected) => { @@ -41,18 +46,18 @@ pub fn solve(vars_by_symbol: &Env, subs: &mut Subs, constraint: &Constraint) { let actual = type_to_var(subs, typ.clone()); let expected = type_to_var(subs, expected.clone().get_type()); - unify(subs, actual, expected); + unify(subs, problems, actual, expected); } Let(let_con) => { match &let_con.ret_constraint { True => { // If the return expression is guaranteed to solve, // solve the assignments themselves and move on. - solve(vars_by_symbol, subs, &let_con.defs_constraint) + solve(vars_by_symbol, problems, subs, &let_con.defs_constraint) } ret_con => { // Solve the assignments' constraints first. - solve(vars_by_symbol, subs, &let_con.defs_constraint); + solve(vars_by_symbol, problems, subs, &let_con.defs_constraint); // Add a variable for each assignment to the vars_by_symbol. let mut new_vars_by_symbol = vars_by_symbol.clone(); @@ -69,7 +74,7 @@ pub fn solve(vars_by_symbol: &Env, subs: &mut Subs, constraint: &Constraint) { // Now solve the body, using the new vars_by_symbol which includes // the assignments' name-to-variable mappings. - solve(&new_vars_by_symbol, subs, &ret_con); + solve(&new_vars_by_symbol, problems, subs, &ret_con); // TODO do an occurs check for each of the assignments! } @@ -122,6 +127,34 @@ fn type_to_variable(subs: &mut Subs, aliases: &ImMap, Variable>, typ: T subs.fresh(Descriptor::from(content)) } + Record(fields, ext) => { + let mut field_vars = ImMap::default(); + + for (field, field_type) in fields { + field_vars.insert(field, type_to_variable(subs, aliases, field_type)); + } + + let ext_var = type_to_variable(subs, aliases, *ext); + let content = Content::Structure(FlatType::Record(field_vars, ext_var)); + + subs.fresh(Descriptor::from(content)) + } + Alias(home, name, args, alias_type) => { + let mut arg_vars = Vec::with_capacity(args.len()); + let mut new_aliases = ImMap::default(); + + for (arg, arg_type) in args { + let arg_var = type_to_variable(subs, aliases, arg_type.clone()); + + arg_vars.push((arg.clone(), arg_var)); + new_aliases.insert(arg.into(), arg_var); + } + + let alias_var = type_to_variable(subs, &new_aliases, *alias_type); + let content = Content::Alias(home, name, arg_vars, alias_var); + + subs.fresh(Descriptor::from(content)) + } Erroneous(problem) => { let content = Content::Structure(FlatType::Erroneous(problem)); diff --git a/src/subs.rs b/src/subs.rs index 62592e48fa..97cfef067f 100644 --- a/src/subs.rs +++ b/src/subs.rs @@ -1,3 +1,5 @@ +use crate::can::ident::{Lowercase, ModuleName, Uppercase}; +use crate::collections::ImMap; use crate::ena::unify::{InPlace, UnificationTable, UnifyKey}; use crate::types::Problem; use std::fmt; @@ -128,6 +130,10 @@ impl Subs { self.utable.new_key(value) } + pub fn fresh_unnamed_flex_var(&mut self) -> Variable { + self.fresh(unnamed_flex_var().into()) + } + /// Unions two keys without the possibility of failure. pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) { let l_root = self.utable.get_root_key(left); @@ -206,6 +212,7 @@ pub enum Content { /// name given in a user-written annotation RigidVar(Box), Structure(FlatType), + Alias(ModuleName, Uppercase, Vec<(Lowercase, Variable)>, Variable), Error(Problem), } @@ -217,6 +224,7 @@ pub enum FlatType { args: Vec, }, Func(Vec, Variable), + Record(ImMap, Variable), Erroneous(Problem), EmptyRecord, } diff --git a/src/types.rs b/src/types.rs index 246f5c23a1..9bf9fb394b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use crate::can::ident::{Lowercase, ModuleName, Uppercase}; use crate::can::symbol::Symbol; use crate::collections::SendMap; use crate::operator::{ArgSide, BinOp}; @@ -26,6 +27,8 @@ pub enum Type { EmptyRec, /// A function. The types of its arguments, then the type of its return value. Function(Vec, Box), + Record(SendMap, Box), + Alias(ModuleName, Uppercase, Vec<(Lowercase, Type)>, Box), /// Applying a type to some arguments (e.g. Map.Map String Int) Apply { module_name: Box, @@ -82,6 +85,12 @@ impl fmt::Debug for Type { write!(f, ")") } + Type::Alias(_, _, _, _) => { + panic!("TODO fmt type aliases"); + } + Type::Record(_, _) => { + panic!("TODO fmt record types"); + } } } } diff --git a/src/unify.rs b/src/unify.rs index 1281665b68..c8ed563716 100644 --- a/src/unify.rs +++ b/src/unify.rs @@ -1,3 +1,5 @@ +use crate::can::ident::{Lowercase, ModuleName, Uppercase}; +use crate::collections::ImMap; use crate::subs::Content::{self, *}; use crate::subs::{Descriptor, FlatType, Mark, Subs, Variable}; use crate::types::Problem; @@ -9,8 +11,15 @@ struct Context { second_desc: Descriptor, } +struct RecordStructure { + fields: ImMap, + ext: Variable, +} + +pub type Problems = Vec; + #[inline(always)] -pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) { +pub fn unify(subs: &mut Subs, problems: &mut Problems, var1: Variable, var2: Variable) { if !subs.equivalent(var1, var2) { let ctx = Context { first: var1, @@ -19,50 +28,248 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) { second_desc: subs.get(var2), }; - unify_context(subs, &ctx) + unify_context(subs, problems, ctx) } } -fn unify_context(subs: &mut Subs, ctx: &Context) { - match ctx.first_desc.content { - FlexVar(ref opt_name) => unify_flex(subs, ctx, opt_name, &ctx.second_desc.content), - RigidVar(ref name) => unify_rigid(subs, ctx, name, &ctx.second_desc.content), - Structure(ref flat_type) => unify_structure(subs, ctx, flat_type, &ctx.second_desc.content), - Error(ref problem) => { +fn unify_context(subs: &mut Subs, problems: &mut Problems, ctx: Context) { + match &ctx.first_desc.content { + FlexVar(opt_name) => unify_flex(subs, problems, &ctx, opt_name, &ctx.second_desc.content), + RigidVar(name) => unify_rigid(subs, problems, &ctx, name, &ctx.second_desc.content), + Structure(flat_type) => { + unify_structure(subs, problems, &ctx, flat_type, &ctx.second_desc.content) + } + Alias(home, name, args, real_var) => { + unify_alias(subs, problems, &ctx, home, name, args, *real_var) + } + Error(problem) => { // Error propagates. Whatever we're comparing it to doesn't matter! - merge(subs, ctx, Error(problem.clone())) + merge(subs, &ctx, Error(problem.clone())); + problems.push(problem.clone()); } } } #[inline(always)] -fn unify_structure(subs: &mut Subs, ctx: &Context, flat_type: &FlatType, other: &Content) { +fn unify_alias( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + home: &ModuleName, + name: &Uppercase, + args: &[(Lowercase, Variable)], + real_var: Variable, +) { + let other_content = &ctx.second_desc.content; + + match other_content { + FlexVar(_) => { + // Alias wins + merge( + subs, + &ctx, + Alias(home.clone(), name.clone(), args.to_owned(), real_var), + ); + } + RigidVar(_) => unify(subs, problems, real_var, ctx.second), + Alias(other_home, other_name, other_args, other_real_var) => { + if name == other_name && home == other_home { + if args.len() == other_args.len() { + for ((_, l_var), (_, r_var)) in args.iter().zip(other_args.iter()) { + unify(subs, problems, *l_var, *r_var); + } + + merge(subs, &ctx, other_content.clone()); + } else if args.len() > other_args.len() { + let problem = Problem::ExtraArguments; + + merge(subs, &ctx, Error(problem.clone())); + problems.push(problem.clone()); + } else { + let problem = Problem::MissingArguments; + + merge(subs, &ctx, Error(problem.clone())); + problems.push(problem.clone()); + } + } else { + unify(subs, problems, real_var, *other_real_var) + } + } + Structure(_) => unify(subs, problems, real_var, ctx.second), + Error(problem) => { + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); + } + } +} + +#[inline(always)] +fn unify_structure( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + flat_type: &FlatType, + other: &Content, +) { match other { FlexVar(_) => { // If the other is flex, Structure wins! - merge(subs, ctx, Structure(flat_type.clone())) + merge(subs, ctx, Structure(flat_type.clone())); } RigidVar(_) => { + let problem = Problem::GenericMismatch; // Type mismatch! Rigid can only unify with flex. - merge(subs, ctx, Error(Problem::GenericMismatch)) + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); } Structure(ref other_flat_type) => { // Unify the two flat types - unify_flat_type(subs, ctx, flat_type, other_flat_type) + unify_flat_type(subs, problems, ctx, flat_type, other_flat_type) } + Alias(_, _, _, real_var) => unify(subs, problems, ctx.first, *real_var), Error(problem) => { // Error propagates. - merge(subs, ctx, Error(problem.clone())) + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); } } } +fn unify_record( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + rec1: RecordStructure, + rec2: RecordStructure, +) { + let fields1 = rec1.fields; + let fields2 = rec2.fields; + let shared_fields = fields1 + .clone() + .intersection_with(fields2.clone(), |one, two| (one, two)); + let unique_fields1 = fields1.clone().difference(fields2.clone()); + let unique_fields2 = fields2.difference(fields1); + + if unique_fields1.is_empty() { + if unique_fields2.is_empty() { + unify(subs, problems, rec1.ext, rec2.ext); + unify_shared_fields( + subs, + problems, + ctx, + shared_fields, + ImMap::default(), + rec1.ext, + ) + } else { + let flat_type = FlatType::Record(unique_fields2, rec2.ext); + let sub_record = subs.fresh(Structure(flat_type).into()); + + unify(subs, problems, rec1.ext, sub_record); + + unify_shared_fields( + subs, + problems, + ctx, + shared_fields, + ImMap::default(), + sub_record, + ); + } + } else if unique_fields2.is_empty() { + let flat_type = FlatType::Record(unique_fields1, rec1.ext); + let sub_record = subs.fresh(Structure(flat_type).into()); + + unify(subs, problems, sub_record, rec2.ext); + + unify_shared_fields( + subs, + problems, + ctx, + shared_fields, + ImMap::default(), + sub_record, + ); + } else { + let other_fields = unique_fields1.clone().union(unique_fields2.clone()); + let ext = subs.fresh_unnamed_flex_var(); + let flat_type1 = FlatType::Record(unique_fields1, rec1.ext); + let sub1 = subs.fresh(Structure(flat_type1).into()); + let flat_type2 = FlatType::Record(unique_fields2, rec2.ext); + let sub2 = subs.fresh(Structure(flat_type2).into()); + + unify(subs, problems, rec1.ext, sub2); + unify(subs, problems, sub1, rec2.ext); + + unify_shared_fields(subs, problems, ctx, shared_fields, other_fields, ext); + } +} + +fn unify_shared_fields( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + shared_fields: ImMap, + other_fields: ImMap, + ext: Variable, +) { + let mut matching_fields = ImMap::default(); + let num_shared_fields = shared_fields.len(); + + for (name, (actual, expected)) in shared_fields { + let prev_problem_count = problems.len(); + + // TODO another way to do this might be to pass around a problems vec + // and check to see if its length increased after doing this unification. + unify(subs, problems, actual, expected); + + if problems.len() == prev_problem_count { + matching_fields.insert(name, actual); + } + } + + if num_shared_fields == matching_fields.len() { + let flat_type = FlatType::Record(matching_fields.union(other_fields), ext); + + merge(subs, ctx, Structure(flat_type)); + } else { + let problem = Problem::GenericMismatch; + + // Type mismatch! Rigid can only unify with flex. + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); + } +} + #[inline(always)] -fn unify_flat_type(subs: &mut Subs, ctx: &Context, left: &FlatType, right: &FlatType) { +fn unify_flat_type( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + left: &FlatType, + right: &FlatType, +) { use crate::subs::FlatType::*; match (left, right) { - (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), + (EmptyRecord, EmptyRecord) => { + merge(subs, ctx, Structure(left.clone())); + } + + (Record(fields, ext), EmptyRecord) if fields.is_empty() => { + unify(subs, problems, *ext, ctx.second) + } + + (EmptyRecord, Record(fields, ext)) if fields.is_empty() => { + unify(subs, problems, ctx.first, *ext) + } + + (Record(fields1, ext1), Record(fields2, ext2)) => { + let rec1 = gather_fields(subs, problems, fields1.clone(), *ext1); + let rec2 = gather_fields(subs, problems, fields2.clone(), *ext2); + + unify_record(subs, problems, ctx, rec1, rec2) + } ( Apply { module_name: l_module_name, @@ -75,7 +282,7 @@ fn unify_flat_type(subs: &mut Subs, ctx: &Context, left: &FlatType, right: &Flat args: r_args, }, ) if l_module_name == r_module_name && l_type_name == r_type_name => { - unify_zip(subs, l_args.iter(), r_args.iter()); + unify_zip(subs, problems, l_args.iter(), r_args.iter()); merge( subs, @@ -85,64 +292,110 @@ fn unify_flat_type(subs: &mut Subs, ctx: &Context, left: &FlatType, right: &Flat name: (*r_type_name).clone(), args: (*r_args).clone(), }), - ) + ); } (Func(l_args, l_ret), Func(r_args, r_ret)) => { if l_args.len() == r_args.len() { - unify_zip(subs, l_args.iter(), r_args.iter()); - unify(subs, *l_ret, *r_ret); - - merge(subs, ctx, Structure(Func((*r_args).clone(), *r_ret))) + unify_zip(subs, problems, l_args.iter(), r_args.iter()); + unify(subs, problems, *l_ret, *r_ret); + merge(subs, ctx, Structure(Func((*r_args).clone(), *r_ret))); } else if l_args.len() > r_args.len() { - merge(subs, ctx, Error(Problem::ExtraArguments)) + merge(subs, ctx, Error(Problem::ExtraArguments)); } else { - merge(subs, ctx, Error(Problem::MissingArguments)) + merge(subs, ctx, Error(Problem::MissingArguments)); } } - _ => merge(subs, ctx, Error(Problem::GenericMismatch)), + _ => { + let problem = Problem::GenericMismatch; + + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); + } } } -fn unify_zip<'a, I>(subs: &mut Subs, left_iter: I, right_iter: I) +fn unify_zip<'a, I>(subs: &mut Subs, problems: &mut Problems, left_iter: I, right_iter: I) where I: Iterator, { for (&l_var, &r_var) in left_iter.zip(right_iter) { - unify(subs, l_var, r_var); + unify(subs, problems, l_var, r_var); } } #[inline(always)] -fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &str, other: &Content) { +fn unify_rigid( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + name: &str, + other: &Content, +) { match other { FlexVar(_) => { // If the other is flex, rigid wins! - merge(subs, ctx, RigidVar(name.into())) + merge(subs, ctx, RigidVar(name.into())); } RigidVar(_) | Structure(_) => { // Type mismatch! Rigid can only unify with flex, even if the // rigid names are the same. - merge(subs, ctx, Error(Problem::GenericMismatch)) + merge(subs, ctx, Error(Problem::GenericMismatch)); + } + Alias(_, _, _, _) => { + panic!("TODO unify_rigid Alias"); } Error(problem) => { // Error propagates. - merge(subs, ctx, Error(problem.clone())) + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); } } } #[inline(always)] -fn unify_flex(subs: &mut Subs, ctx: &Context, opt_name: &Option>, other: &Content) { +fn unify_flex( + subs: &mut Subs, + problems: &mut Problems, + ctx: &Context, + opt_name: &Option>, + other: &Content, +) { match other { FlexVar(None) => { // If both are flex, and only left has a name, keep the name around. - merge(subs, ctx, FlexVar(opt_name.clone())) + merge(subs, ctx, FlexVar(opt_name.clone())); } - FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Error(_) => { + FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Alias(_, _, _, _) => { // In all other cases, if left is flex, defer to right. // (This includes using right's name if both are flex and named.) - merge(subs, ctx, other.clone()) + merge(subs, ctx, other.clone()); } + Error(problem) => { + merge(subs, ctx, Error(problem.clone())); + problems.push(problem.clone()); + } + } +} + +fn gather_fields( + subs: &mut Subs, + problems: &mut Problems, + fields: ImMap, + var: Variable, +) -> RecordStructure { + use crate::subs::FlatType::*; + + match subs.get(var).content { + Structure(Record(sub_fields, sub_ext)) => { + gather_fields(subs, problems, fields.union(sub_fields), sub_ext) + } + + Alias(_, _, _, var) => { + // TODO according to elm/compiler: "TODO may be dropping useful alias info here" + gather_fields(subs, problems, fields, var) + } + + _ => RecordStructure { fields, ext: var }, } } diff --git a/tests/test_eval.rs b/tests/test_eval.rs index 25467f5ffa..2e8f32e5da 100644 --- a/tests/test_eval.rs +++ b/tests/test_eval.rs @@ -25,7 +25,8 @@ mod test_gen { ($src:expr, $expected:expr, $ty:ty) => { let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src); let mut subs = Subs::new(var_store.into()); - let content = infer_expr(&mut subs, &constraint, variable); + let mut unify_problems = Vec::new(); + let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable); let context = Context::create(); let builder = context.create_builder(); diff --git a/tests/test_infer.rs b/tests/test_infer.rs index 63a27e752e..b1877d9cee 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -20,7 +20,8 @@ mod test_infer { fn infer_eq(src: &str, expected: &str) { let (_, _output, _, var_store, variable, constraint) = can_expr(src); let mut subs = Subs::new(var_store.into()); - let content = infer_expr(&mut subs, &constraint, variable); + let mut unify_problems = Vec::new(); + let content = infer_expr(&mut subs, &mut unify_problems, &constraint, variable); name_all_type_vars(variable, &mut subs); diff --git a/tests/test_load.rs b/tests/test_load.rs index 277af9458e..b51bb59f48 100644 --- a/tests/test_load.rs +++ b/tests/test_load.rs @@ -154,7 +154,8 @@ mod test_load { assert_eq!(module.name, Some("WithBuiltins".into())); - solve_loaded(&module, &mut subs, deps); + let mut unify_problems = Vec::new(); + solve_loaded(&module, &mut unify_problems, &mut subs, deps); let expected_types = hashmap! { "WithBuiltins.floatTest" => "Float", diff --git a/tests/test_uniqueness_infer.rs b/tests/test_uniqueness_infer.rs index e99348f4ed..29c47bf40b 100644 --- a/tests/test_uniqueness_infer.rs +++ b/tests/test_uniqueness_infer.rs @@ -33,8 +33,9 @@ mod test_infer_uniq { let mut subs1 = Subs::new(var_store1.into()); let mut subs2 = Subs::new(var_store2.into()); - let content1 = infer_expr(&mut subs1, &constraint1, variable1); - let content2 = infer_expr(&mut subs2, &constraint2, variable2); + let mut unify_problems = Vec::new(); + let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1); + let content2 = infer_expr(&mut subs2, &mut unify_problems, &constraint2, variable2); name_all_type_vars(variable1, &mut subs1); name_all_type_vars(variable2, &mut subs2);