diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 530faaea5d..99f1aa40d0 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,17 +1,15 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use libloading::Library; -use roc_collections::all::MutMap; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::TagName; use roc_module::operator::CalledVia; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::ProcLayout; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_region::all::{Located, Region}; -use roc_types::subs::{Content, FlatType, Subs, Variable}; -use roc_types::types::RecordField; +use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable}; struct Env<'a, 'env> { arena: &'a Bump, @@ -155,9 +153,12 @@ fn jit_to_ast_help<'a>( Content::Structure(FlatType::Record(fields, _)) => { Ok(struct_to_ast(env, ptr, field_layouts, fields)) } - Content::Structure(FlatType::EmptyRecord) => { - Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default())) - } + Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( + env, + ptr, + field_layouts, + &RecordFields::with_capacity(0), + )), Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); @@ -437,7 +438,7 @@ fn ptr_to_ast<'a>( single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[]) } Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, ptr, &[], &MutMap::default()) + struct_to_ast(env, ptr, &[], &RecordFields::with_capacity(0)) } other => { unreachable!( @@ -556,26 +557,15 @@ fn struct_to_ast<'a>( env: &Env<'a, '_>, ptr: *const u8, field_layouts: &'a [Layout<'a>], - fields: &MutMap>, + sorted_fields: &RecordFields, ) -> Expr<'a> { let arena = env.arena; let subs = env.subs; let mut output = Vec::with_capacity_in(field_layouts.len(), arena); - // The fields, sorted alphabetically - let mut sorted_fields = { - let mut vec = fields - .iter() - .collect::)>>(); - - vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); - - vec - }; - if sorted_fields.len() == 1 { // this is a 1-field wrapper record around another record or 1-tag tag union - let (label, field) = sorted_fields.pop().unwrap(); + let (label, field) = sorted_fields.into_iter().next().unwrap(); let inner_content = env.subs.get_content_without_compacting(field.into_inner()); diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index b645a5c55e..af5a4c3d36 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -2156,8 +2156,8 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: { fo : Num c - , foobar : Num a - , bar : Num e + , foobar : Num d + , bar : Num a , baz : Num b , ... } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 9376fcab70..92ab30a2fe 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -6,7 +6,7 @@ use roc_region::all::{Located, Region}; use roc_types::solved_types::Solved; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField}; +use roc_types::types::{Alias, Category, ErrorType, PatternCategory}; use roc_unify::unify::unify; use roc_unify::unify::Unified::*; @@ -673,13 +673,8 @@ fn type_to_variable( let mut field_vars = MutMap::with_capacity_and_hasher(fields.len(), default_hasher()); for (field, field_type) in fields { - use RecordField::*; - - let field_var = match field_type { - Required(typ) => Required(type_to_variable(subs, rank, pools, cached, typ)), - Optional(typ) => Optional(type_to_variable(subs, rank, pools, cached, typ)), - Demanded(typ) => Demanded(type_to_variable(subs, rank, pools, cached, typ)), - }; + let field_var = + field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ)); field_vars.insert(field.clone(), field_var); } @@ -694,7 +689,8 @@ fn type_to_variable( Err((new, _)) => new, }; - let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); + let record_fields = field_vars.into_iter().collect(); + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); register(subs, rank, pools, content) } @@ -1084,14 +1080,9 @@ fn adjust_rank_content( Record(fields, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in fields.values() { - rank = rank.max(adjust_rank( - subs, - young_mark, - visit_mark, - group_rank, - var.into_inner(), - )); + for var in fields.iter_variables() { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); } rank @@ -1238,14 +1229,8 @@ fn instantiate_rigids_help( EmptyRecord | EmptyTagUnion | Erroneous(_) => {} Record(fields, ext_var) => { - for (_, field) in fields { - use RecordField::*; - - match field { - Demanded(var) => instantiate_rigids_help(subs, max_rank, pools, var), - Required(var) => instantiate_rigids_help(subs, max_rank, pools, var), - Optional(var) => instantiate_rigids_help(subs, max_rank, pools, var), - }; + for var in fields.iter_variables() { + instantiate_rigids_help(subs, max_rank, pools, *var); } instantiate_rigids_help(subs, max_rank, pools, ext_var); @@ -1381,31 +1366,12 @@ fn deep_copy_var_help( same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, - Record(fields, ext_var) => { - let mut new_fields = MutMap::default(); - - for (label, field) in fields { - use RecordField::*; - - let new_field = match field { - Demanded(var) => { - Demanded(deep_copy_var_help(subs, max_rank, pools, var)) - } - Required(var) => { - Required(deep_copy_var_help(subs, max_rank, pools, var)) - } - Optional(var) => { - Optional(deep_copy_var_help(subs, max_rank, pools, var)) - } - }; - - new_fields.insert(label, new_field); + Record(mut fields, ext_var) => { + for var in fields.iter_variables_mut() { + *var = deep_copy_var_help(subs, max_rank, pools, *var); } - Record( - new_fields, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) + Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var)) } TagUnion(tags, ext_var) => { diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 6805c80088..9f4589c952 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -152,19 +152,9 @@ fn find_names_needed( find_names_needed(*ret_var, subs, roots, root_appearances, names_taken); } - Structure(Record(fields, ext_var)) => { - let mut sorted_fields: Vec<_> = fields.iter().collect(); - - sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); - - for (_, field) in sorted_fields { - find_names_needed( - field.into_inner(), - subs, - roots, - root_appearances, - names_taken, - ); + Structure(Record(sorted_fields, ext_var)) => { + for var in sorted_fields.iter_variables() { + find_names_needed(*var, subs, roots, root_appearances, names_taken); } find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); @@ -420,7 +410,10 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin use crate::types::{gather_fields, RecordStructure}; // If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them - let RecordStructure { fields, ext } = gather_fields(subs, fields, *ext_var); + let RecordStructure { + fields: sorted_fields, + ext, + } = gather_fields(subs, fields.clone(), *ext_var); let ext_var = ext; if fields.is_empty() { @@ -428,12 +421,6 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin } else { buf.push_str("{ "); - // Sort the fields so they always end up in the same order. - let mut sorted_fields = Vec::with_capacity(fields.len()); - - sorted_fields.extend(fields); - sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b)); - let mut any_written_yet = false; for (label, field_var) in sorted_fields { @@ -592,7 +579,7 @@ pub fn chase_ext_record( match subs.get_content_without_compacting(var) { Structure(Record(sub_fields, sub_ext)) => { for (field_name, record_field) in sub_fields { - fields.insert(field_name.clone(), *record_field); + fields.insert(field_name.clone(), record_field); } chase_ext_record(subs, *sub_ext, fields) diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index d75bb0e0d6..409e515aad 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -400,13 +400,8 @@ impl SolvedType { let mut new_fields = Vec::with_capacity(fields.len()); for (label, field) in fields { - use RecordField::*; - - let solved_type = match field { - Optional(var) => Optional(Self::from_var_help(subs, recursion_vars, *var)), - Required(var) => Required(Self::from_var_help(subs, recursion_vars, *var)), - Demanded(var) => Demanded(Self::from_var_help(subs, recursion_vars, *var)), - }; + let solved_type = + field.map(|var| Self::from_var_help(subs, recursion_vars, *var)); new_fields.push((label.clone(), solved_type)); } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 0baa5252e7..d5ca4dcd9e 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -2,8 +2,9 @@ use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt}; use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; +use std::cmp::Ordering; use std::fmt; -use std::iter::{once, Iterator}; +use std::iter::{once, Extend, FromIterator, Iterator, Map, Zip}; use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; #[derive(Clone, Copy, Hash, PartialEq, Eq)] @@ -535,7 +536,7 @@ impl From for Rank { } } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct Descriptor { pub content: Content, pub rank: Rank, @@ -573,7 +574,7 @@ impl From for Descriptor { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub enum Content { /// A type variable which the user did not name in an annotation, /// @@ -619,11 +620,11 @@ impl Content { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub enum FlatType { Apply(Symbol, Vec), Func(Vec, Variable, Variable), - Record(MutMap>, Variable), + Record(RecordFields, Variable), TagUnion(MutMap>, Variable), FunctionOrTagUnion(TagName, Symbol, Variable), RecursiveTagUnion(Variable, MutMap>, Variable), @@ -640,6 +641,232 @@ pub enum Builtin { EmptyRecord, } +#[derive(Clone, Debug)] +pub struct RecordFields { + field_names: Vec, + variables: Vec, + field_type: Vec>, +} + +impl RecordFields { + pub fn with_capacity(capacity: usize) -> Self { + Self { + field_names: Vec::with_capacity(capacity), + variables: Vec::with_capacity(capacity), + field_type: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + let answer = self.field_names.len(); + + debug_assert_eq!(answer, self.variables.len()); + debug_assert_eq!(answer, self.field_type.len()); + + answer + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn iter_variables(&self) -> impl Iterator { + self.variables.iter() + } + + pub fn iter_variables_mut(&mut self) -> impl Iterator { + self.variables.iter_mut() + } + + pub fn iter(&self) -> impl Iterator)> { + self.into_iter() + } + + pub fn has_only_optional_fields(&self) -> bool { + self.field_type + .iter() + .all(|field| matches!(field, RecordField::Optional(_))) + } + + pub fn from_vec(mut vec: Vec<(Lowercase, RecordField)>) -> Self { + // we assume there are no duplicate field names in there + vec.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); + + Self::from_sorted_vec(vec) + } + + pub fn from_sorted_vec(vec: Vec<(Lowercase, RecordField)>) -> Self { + let mut result = RecordFields::with_capacity(vec.len()); + + result.extend(vec); + + result + } + + pub fn merge(self, other: Self) -> Self { + if other.is_empty() { + return self; + } + + // maximum final size (if there is no overlap at all) + let final_size = self.len() + other.len(); + + let mut result = Self::with_capacity(final_size); + + let mut it1 = self.into_iter().peekable(); + let mut it2 = other.into_iter().peekable(); + + loop { + let which = match (it1.peek(), it2.peek()) { + (Some((l, _)), Some((r, _))) => Some(l.cmp(r)), + (Some(_), None) => Some(Ordering::Less), + (None, Some(_)) => Some(Ordering::Greater), + (None, None) => None, + }; + + let next_element = match which { + Some(Ordering::Less) => it1.next(), + Some(Ordering::Equal) => { + let _ = it2.next(); + it1.next() + } + Some(Ordering::Greater) => it2.next(), + None => break, + }; + + result.extend([next_element.unwrap()]); + } + + result + } + + pub fn separate(self, other: Self) -> SeparateRecordFields { + let max_common = self.len().min(other.len()); + + let mut result = SeparateRecordFields { + only_in_1: RecordFields::with_capacity(self.len()), + only_in_2: RecordFields::with_capacity(other.len()), + in_both: Vec::with_capacity(max_common), + }; + + let mut it1 = self.into_iter().peekable(); + let mut it2 = other.into_iter().peekable(); + + loop { + let which = match (it1.peek(), it2.peek()) { + (Some((l, _)), Some((r, _))) => Some(l.cmp(r)), + (Some(_), None) => Some(Ordering::Less), + (None, Some(_)) => Some(Ordering::Greater), + (None, None) => None, + }; + + match which { + Some(Ordering::Less) => result.only_in_1.extend(it1.next()), + Some(Ordering::Equal) => { + let (label, field1) = it1.next().unwrap(); + let (_, field2) = it2.next().unwrap(); + + result.in_both.push((label, field1, field2)); + } + Some(Ordering::Greater) => result.only_in_2.extend(it2.next()), + None => break, + }; + } + + result + } +} + +pub struct SeparateRecordFields { + pub only_in_1: RecordFields, + pub only_in_2: RecordFields, + pub in_both: Vec<(Lowercase, RecordField, RecordField)>, +} + +impl Extend<(Lowercase, RecordField)> for RecordFields { + fn extend)>>(&mut self, iter: T) { + for (name, record_field) in iter.into_iter() { + self.field_names.push(name); + self.field_type.push(record_field.map(|_| ())); + self.variables.push(record_field.into_inner()); + } + } +} + +impl FromIterator<(Lowercase, RecordField)> for RecordFields { + fn from_iter)>>(iter: T) -> Self { + let vec: Vec<_> = iter.into_iter().collect(); + Self::from_vec(vec) + } +} + +impl<'a> FromIterator<(&'a Lowercase, RecordField)> for RecordFields { + fn from_iter)>>(iter: T) -> Self { + let vec: Vec<_> = iter.into_iter().map(|(a, b)| (a.clone(), b)).collect(); + Self::from_vec(vec) + } +} + +impl IntoIterator for RecordFields { + type Item = (Lowercase, RecordField); + + #[allow(clippy::type_complexity)] + type IntoIter = Map< + Zip< + Zip, std::vec::IntoIter>, + std::vec::IntoIter>, + >, + fn(((Lowercase, Variable), RecordField<()>)) -> (Lowercase, RecordField), + >; + + fn into_iter(self) -> Self::IntoIter { + self.field_names + .into_iter() + .zip(self.variables.into_iter()) + .zip(self.field_type.into_iter()) + .map(record_fields_into_iterator_help) + } +} + +fn record_fields_into_iterator_help( + arg: ((Lowercase, Variable), RecordField<()>), +) -> (Lowercase, RecordField) { + let ((name, var), field_type) = arg; + + (name, field_type.map(|_| var)) +} + +impl<'a> IntoIterator for &'a RecordFields { + type Item = (&'a Lowercase, RecordField); + + #[allow(clippy::type_complexity)] + type IntoIter = Map< + Zip< + Zip, std::slice::Iter<'a, Variable>>, + std::slice::Iter<'a, RecordField<()>>, + >, + fn( + ((&'a Lowercase, &Variable), &RecordField<()>), + ) -> (&'a Lowercase, RecordField), + >; + + fn into_iter(self) -> Self::IntoIter { + self.field_names + .iter() + .zip(self.variables.iter()) + .zip(self.field_type.iter()) + .map(ref_record_fields_into_iterator_help) + } +} + +fn ref_record_fields_into_iterator_help<'a>( + arg: ((&'a Lowercase, &Variable), &RecordField<()>), +) -> (&'a Lowercase, RecordField) { + let ((name, var), field_type) = arg; + + (name, field_type.map(|_| *var)) +} + fn occurs( subs: &Subs, seen: &ImSet, @@ -670,12 +897,7 @@ fn occurs( short_circuit(subs, root_var, &new_seen, it) } Record(vars_by_field, ext_var) => { - let it = - once(ext_var).chain(vars_by_field.values().map(|field| match field { - RecordField::Optional(var) => var, - RecordField::Required(var) => var, - RecordField::Demanded(var) => var, - })); + let it = once(ext_var).chain(vars_by_field.iter_variables()); short_circuit(subs, root_var, &new_seen, it) } TagUnion(tags, ext_var) => { @@ -799,21 +1021,10 @@ fn explicit_substitute( Record(mut vars_by_field, ext_var) => { let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); - for (_, field) in vars_by_field.iter_mut() { - use RecordField::*; - - *field = match field { - Optional(var) => { - Optional(explicit_substitute(subs, from, to, *var, seen)) - } - Required(var) => { - Required(explicit_substitute(subs, from, to, *var, seen)) - } - Demanded(var) => { - Demanded(explicit_substitute(subs, from, to, *var, seen)) - } - }; + for var in vars_by_field.variables.iter_mut() { + *var = explicit_substitute(subs, from, to, *var, seen); } + subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var))); } @@ -1284,8 +1495,8 @@ fn restore_content(subs: &mut Subs, content: &Content) { EmptyTagUnion => (), Record(fields, ext_var) => { - for field in fields.values() { - subs.restore(field.into_inner()); + for var in fields.iter_variables() { + subs.restore(*var); } subs.restore(*ext_var); diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 9ea23be149..b600ba235d 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1,7 +1,7 @@ use crate::pretty_print::Parens; -use crate::subs::{LambdaSet, Subs, VarStore, Variable}; +use crate::subs::{LambdaSet, RecordFields, Subs, VarStore, Variable}; use inlinable_string::InlinableString; -use roc_collections::all::{ImMap, ImSet, Index, MutMap, MutSet, SendMap}; +use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -980,7 +980,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } pub struct RecordStructure { - pub fields: MutMap>, + pub fields: RecordFields, pub ext: Variable, } @@ -1522,20 +1522,18 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lower pub fn gather_fields( subs: &Subs, - other_fields: &MutMap>, + other_fields: RecordFields, mut var: Variable, ) -> RecordStructure { use crate::subs::Content::*; use crate::subs::FlatType::*; - let mut result = other_fields.clone(); + let mut result = other_fields; loop { match subs.get_content_without_compacting(var) { Structure(Record(sub_fields, sub_ext)) => { - for (lowercase, record_field) in sub_fields { - result.insert(lowercase.clone(), *record_field); - } + result = RecordFields::merge(result, sub_fields.clone()); var = *sub_ext; } @@ -1554,3 +1552,46 @@ pub fn gather_fields( ext: var, } } + +pub fn gather_fields_ref( + subs: &Subs, + other_fields: &RecordFields, + mut var: Variable, +) -> RecordStructure { + use crate::subs::Content::*; + use crate::subs::FlatType::*; + + let mut from_ext = Vec::new(); + + loop { + match subs.get_content_without_compacting(var) { + Structure(Record(sub_fields, sub_ext)) => { + from_ext.extend(sub_fields.into_iter()); + + var = *sub_ext; + } + + Alias(_, _, actual_var) => { + // TODO according to elm/compiler: "TODO may be dropping useful alias info here" + var = *actual_var; + } + + _ => break, + } + } + + if from_ext.is_empty() { + RecordStructure { + fields: other_fields.clone(), + ext: var, + } + } else { + RecordStructure { + fields: other_fields + .into_iter() + .chain(from_ext.into_iter()) + .collect(), + ext: var, + } + } +} diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index a7f31d90ad..5f82907da9 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -2,8 +2,8 @@ use roc_collections::all::{default_hasher, get_shared, relative_complement, unio use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; -use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; -use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordField, RecordStructure}; +use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, RecordFields, Subs, Variable}; +use roc_types::types::{gather_fields_ref, ErrorType, Mismatch, RecordField, RecordStructure}; macro_rules! mismatch { () => {{ @@ -262,28 +262,27 @@ fn unify_record( ) -> Outcome { let fields1 = rec1.fields; let fields2 = rec2.fields; - let shared_fields = get_shared(&fields1, &fields2); - // NOTE: don't use `difference` here. In contrast to Haskell, im's `difference` is symmetric - let unique_fields1 = relative_complement(&fields1, &fields2); - let unique_fields2 = relative_complement(&fields2, &fields1); - if unique_fields1.is_empty() { - if unique_fields2.is_empty() { + let separate = RecordFields::separate(fields1, fields2); + + let shared_fields = separate.in_both; + + if separate.only_in_1.is_empty() { + if separate.only_in_2.is_empty() { let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext); if !ext_problems.is_empty() { return ext_problems; } - let other_fields = MutMap::default(); let mut field_problems = - unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, rec1.ext); + unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, rec1.ext); field_problems.extend(ext_problems); field_problems } else { - let flat_type = FlatType::Record(unique_fields2, rec2.ext); + let flat_type = FlatType::Record(separate.only_in_2, rec2.ext); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record); @@ -291,16 +290,21 @@ fn unify_record( return ext_problems; } - let other_fields = MutMap::default(); - let mut field_problems = - unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); + let mut field_problems = unify_shared_fields( + subs, + pool, + ctx, + shared_fields, + OtherFields::None, + sub_record, + ); field_problems.extend(ext_problems); field_problems } - } else if unique_fields2.is_empty() { - let flat_type = FlatType::Record(unique_fields1, rec1.ext); + } else if separate.only_in_2.is_empty() { + let flat_type = FlatType::Record(separate.only_in_1, rec1.ext); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext); @@ -308,19 +312,29 @@ fn unify_record( return ext_problems; } - let other_fields = MutMap::default(); - let mut field_problems = - unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); + let mut field_problems = unify_shared_fields( + subs, + pool, + ctx, + shared_fields, + OtherFields::None, + sub_record, + ); field_problems.extend(ext_problems); field_problems } else { - let other_fields = union(unique_fields1.clone(), &unique_fields2); + let it = (&separate.only_in_1) + .into_iter() + .chain((&separate.only_in_2).into_iter()); + let other: RecordFields = it.collect(); + + let other_fields = OtherFields::Other(other); let ext = fresh(subs, pool, ctx, Content::FlexVar(None)); - let flat_type1 = FlatType::Record(unique_fields1, ext); - let flat_type2 = FlatType::Record(unique_fields2, ext); + let flat_type1 = FlatType::Record(separate.only_in_1, ext); + let flat_type2 = FlatType::Record(separate.only_in_2, ext); let sub1 = fresh(subs, pool, ctx, Structure(flat_type1)); let sub2 = fresh(subs, pool, ctx, Structure(flat_type2)); @@ -346,18 +360,23 @@ fn unify_record( } } +enum OtherFields { + None, + Other(RecordFields), +} + fn unify_shared_fields( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - shared_fields: MutMap, RecordField)>, - other_fields: MutMap>, + shared_fields: Vec<(Lowercase, RecordField, RecordField)>, + other_fields: OtherFields, ext: Variable, ) -> Outcome { - let mut matching_fields = MutMap::default(); + let mut matching_fields = Vec::with_capacity(shared_fields.len()); let num_shared_fields = shared_fields.len(); - for (name, (actual, expected)) in shared_fields { + for (name, actual, expected) in shared_fields { let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner()); if local_problems.is_empty() { @@ -383,18 +402,36 @@ fn unify_shared_fields( (Optional(val), Optional(_)) => Optional(val), }; - let existing = matching_fields.insert(name, actual); - debug_assert_eq!(existing, None); + matching_fields.push((name, actual)); } } if num_shared_fields == matching_fields.len() { // pull fields in from the ext_var - let mut fields = union(matching_fields, &other_fields); - let new_ext_var = match roc_types::pretty_print::chase_ext_record(subs, ext, &mut fields) { - Ok(()) => Variable::EMPTY_RECORD, - Err((new, _)) => new, + let mut ext_fields = MutMap::default(); + let new_ext_var = + match roc_types::pretty_print::chase_ext_record(subs, ext, &mut ext_fields) { + Ok(()) => Variable::EMPTY_RECORD, + Err((new, _)) => new, + }; + + let fields: RecordFields = match other_fields { + OtherFields::None => { + if ext_fields.is_empty() { + RecordFields::from_sorted_vec(matching_fields) + } else { + matching_fields + .into_iter() + .chain(ext_fields.into_iter()) + .collect() + } + } + OtherFields::Other(other_fields) => matching_fields + .into_iter() + .chain(other_fields.into_iter()) + .chain(ext_fields.into_iter()) + .collect(), }; let flat_type = FlatType::Record(fields, new_ext_var); @@ -460,8 +497,8 @@ fn unify_tag_union( if tags1.len() == 1 && tags2.len() == 1 && tags1 == tags2 - && subs.get_content_without_compacting(rec1.ext) - == subs.get_content_without_compacting(rec2.ext) + && subs.get_root_key_without_compacting(rec1.ext) + == subs.get_root_key_without_compacting(rec2.ext) { return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var); } @@ -933,18 +970,6 @@ fn unify_shared_tags_merge( merge(subs, ctx, Structure(flat_type)) } -fn has_only_optional_fields<'a, I, T>(fields: &mut I) -> bool -where - I: Iterator>, - T: 'a, -{ - fields.all(|field| match field { - RecordField::Required(_) => false, - RecordField::Demanded(_) => false, - RecordField::Optional(_) => true, - }) -} - #[inline(always)] fn unify_flat_type( subs: &mut Subs, @@ -958,17 +983,17 @@ fn unify_flat_type( match (left, right) { (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), - (Record(fields, ext), EmptyRecord) if has_only_optional_fields(&mut fields.values()) => { + (Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields() => { unify_pool(subs, pool, *ext, ctx.second) } - (EmptyRecord, Record(fields, ext)) if has_only_optional_fields(&mut fields.values()) => { + (EmptyRecord, Record(fields, ext)) if fields.has_only_optional_fields() => { unify_pool(subs, pool, ctx.first, *ext) } (Record(fields1, ext1), Record(fields2, ext2)) => { - let rec1 = gather_fields(subs, fields1, *ext1); - let rec2 = gather_fields(subs, fields2, *ext2); + let rec1 = gather_fields_ref(subs, fields1, *ext1); + let rec2 = gather_fields_ref(subs, fields2, *ext2); unify_record(subs, pool, ctx, rec1, rec2) } diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs index 501d1f7de3..5d44b4f2be 100644 --- a/editor/src/lang/solve.rs +++ b/editor/src/lang/solve.rs @@ -760,7 +760,9 @@ fn type_to_variable<'a>( Err((new, _)) => new, }; - let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); + let record_fields = field_vars.into_iter().collect(); + + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); register(subs, rank, pools, content) } @@ -1212,14 +1214,9 @@ fn adjust_rank_content( Record(fields, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in fields.values() { - rank = rank.max(adjust_rank( - subs, - young_mark, - visit_mark, - group_rank, - var.into_inner(), - )); + for var in fields.iter_variables() { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); } rank @@ -1372,28 +1369,12 @@ fn instantiate_rigids_help( same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, Record(fields, ext_var) => { - let mut new_fields = MutMap::default(); - - for (label, field) in fields { - use RecordField::*; - - let new_field = match field { - Demanded(var) => { - Demanded(instantiate_rigids_help(subs, max_rank, pools, var)) - } - Required(var) => { - Required(instantiate_rigids_help(subs, max_rank, pools, var)) - } - Optional(var) => { - Optional(instantiate_rigids_help(subs, max_rank, pools, var)) - } - }; - - new_fields.insert(label, new_field); + for var in fields.iter_variables() { + instantiate_rigids_help(subs, max_rank, pools, *var); } Record( - new_fields, + fields, instantiate_rigids_help(subs, max_rank, pools, ext_var), ) } @@ -1566,31 +1547,12 @@ fn deep_copy_var_help( same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, - Record(fields, ext_var) => { - let mut new_fields = MutMap::default(); - - for (label, field) in fields { - use RecordField::*; - - let new_field = match field { - Demanded(var) => { - Demanded(deep_copy_var_help(subs, max_rank, pools, var)) - } - Required(var) => { - Required(deep_copy_var_help(subs, max_rank, pools, var)) - } - Optional(var) => { - Optional(deep_copy_var_help(subs, max_rank, pools, var)) - } - }; - - new_fields.insert(label, new_field); + Record(mut fields, ext_var) => { + for var in fields.iter_variables_mut() { + *var = deep_copy_var_help(subs, max_rank, pools, *var); } - Record( - new_fields, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) + Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var)) } TagUnion(tags, ext_var) => {