Merge pull request #1514 from rtfeldman/records-soa

Records struct of arrays
This commit is contained in:
Richard Feldman 2021-08-01 22:24:35 -04:00 committed by GitHub
commit 44d5551259
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 412 additions and 235 deletions

View file

@ -1,17 +1,15 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use libloading::Library; use libloading::Library;
use roc_collections::all::MutMap;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; 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::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout; use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable};
use roc_types::types::RecordField;
struct Env<'a, 'env> { struct Env<'a, 'env> {
arena: &'a Bump, arena: &'a Bump,
@ -155,9 +153,12 @@ fn jit_to_ast_help<'a>(
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
Ok(struct_to_ast(env, ptr, field_layouts, fields)) Ok(struct_to_ast(env, ptr, field_layouts, fields))
} }
Content::Structure(FlatType::EmptyRecord) => { Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default())) env,
} ptr,
field_layouts,
&RecordFields::with_capacity(0),
)),
Content::Structure(FlatType::TagUnion(tags, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1); 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(), &[]) single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[])
} }
Content::Structure(FlatType::EmptyRecord) => { Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, &[], &MutMap::default()) struct_to_ast(env, ptr, &[], &RecordFields::with_capacity(0))
} }
other => { other => {
unreachable!( unreachable!(
@ -556,26 +557,15 @@ fn struct_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const u8, ptr: *const u8,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
fields: &MutMap<Lowercase, RecordField<Variable>>, sorted_fields: &RecordFields,
) -> Expr<'a> { ) -> Expr<'a> {
let arena = env.arena; let arena = env.arena;
let subs = env.subs; let subs = env.subs;
let mut output = Vec::with_capacity_in(field_layouts.len(), arena); 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::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
vec
};
if sorted_fields.len() == 1 { if sorted_fields.len() == 1 {
// this is a 1-field wrapper record around another record or 1-tag tag union // 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()); let inner_content = env.subs.get_content_without_compacting(field.into_inner());

View file

@ -2156,8 +2156,8 @@ mod test_reporting {
This is usually a typo. Here are the `x` fields that are most similar: This is usually a typo. Here are the `x` fields that are most similar:
{ fo : Num c { fo : Num c
, foobar : Num a , foobar : Num d
, bar : Num e , bar : Num a
, baz : Num b , baz : Num b
, ... , ...
} }

View file

@ -6,7 +6,7 @@ use roc_region::all::{Located, Region};
use roc_types::solved_types::Solved; use roc_types::solved_types::Solved;
use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable};
use roc_types::types::Type::{self, *}; 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::unify;
use roc_unify::unify::Unified::*; 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()); let mut field_vars = MutMap::with_capacity_and_hasher(fields.len(), default_hasher());
for (field, field_type) in fields { for (field, field_type) in fields {
use RecordField::*; let field_var =
field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ));
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)),
};
field_vars.insert(field.clone(), field_var); field_vars.insert(field.clone(), field_var);
} }
@ -694,7 +689,8 @@ fn type_to_variable(
Err((new, _)) => new, 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) register(subs, rank, pools, content)
} }
@ -1084,14 +1080,9 @@ fn adjust_rank_content(
Record(fields, ext_var) => { Record(fields, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for var in fields.values() { for var in fields.iter_variables() {
rank = rank.max(adjust_rank( rank =
subs, rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
young_mark,
visit_mark,
group_rank,
var.into_inner(),
));
} }
rank rank
@ -1238,14 +1229,8 @@ fn instantiate_rigids_help(
EmptyRecord | EmptyTagUnion | Erroneous(_) => {} EmptyRecord | EmptyTagUnion | Erroneous(_) => {}
Record(fields, ext_var) => { Record(fields, ext_var) => {
for (_, field) in fields { for var in fields.iter_variables() {
use RecordField::*; instantiate_rigids_help(subs, max_rank, pools, *var);
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),
};
} }
instantiate_rigids_help(subs, max_rank, pools, ext_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, same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(fields, ext_var) => { Record(mut fields, ext_var) => {
let mut new_fields = MutMap::default(); for var in fields.iter_variables_mut() {
*var = deep_copy_var_help(subs, max_rank, pools, *var);
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( Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
new_fields,
deep_copy_var_help(subs, max_rank, pools, ext_var),
)
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {

View file

@ -152,19 +152,9 @@ fn find_names_needed(
find_names_needed(*ret_var, subs, roots, root_appearances, names_taken); find_names_needed(*ret_var, subs, roots, root_appearances, names_taken);
} }
Structure(Record(fields, ext_var)) => { Structure(Record(sorted_fields, ext_var)) => {
let mut sorted_fields: Vec<_> = fields.iter().collect(); for var in sorted_fields.iter_variables() {
find_names_needed(*var, subs, roots, root_appearances, names_taken);
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,
);
} }
find_names_needed(*ext_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}; use crate::types::{gather_fields, RecordStructure};
// If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them // 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; let ext_var = ext;
if fields.is_empty() { if fields.is_empty() {
@ -428,12 +421,6 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
} else { } else {
buf.push_str("{ "); 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; let mut any_written_yet = false;
for (label, field_var) in sorted_fields { for (label, field_var) in sorted_fields {
@ -592,7 +579,7 @@ pub fn chase_ext_record(
match subs.get_content_without_compacting(var) { match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => { Structure(Record(sub_fields, sub_ext)) => {
for (field_name, record_field) in sub_fields { 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) chase_ext_record(subs, *sub_ext, fields)

View file

@ -400,13 +400,8 @@ impl SolvedType {
let mut new_fields = Vec::with_capacity(fields.len()); let mut new_fields = Vec::with_capacity(fields.len());
for (label, field) in fields { for (label, field) in fields {
use RecordField::*; let solved_type =
field.map(|var| Self::from_var_help(subs, recursion_vars, *var));
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)),
};
new_fields.push((label.clone(), solved_type)); new_fields.push((label.clone(), solved_type));
} }

View file

@ -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_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::cmp::Ordering;
use std::fmt; 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}; use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
#[derive(Clone, Copy, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Hash, PartialEq, Eq)]
@ -535,7 +536,7 @@ impl From<usize> for Rank {
} }
} }
#[derive(Clone, PartialEq, Eq)] #[derive(Clone)]
pub struct Descriptor { pub struct Descriptor {
pub content: Content, pub content: Content,
pub rank: Rank, pub rank: Rank,
@ -573,7 +574,7 @@ impl From<Content> for Descriptor {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub enum Content { pub enum Content {
/// A type variable which the user did not name in an annotation, /// 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 { pub enum FlatType {
Apply(Symbol, Vec<Variable>), Apply(Symbol, Vec<Variable>),
Func(Vec<Variable>, Variable, Variable), Func(Vec<Variable>, Variable, Variable),
Record(MutMap<Lowercase, RecordField<Variable>>, Variable), Record(RecordFields, Variable),
TagUnion(MutMap<TagName, Vec<Variable>>, Variable), TagUnion(MutMap<TagName, Vec<Variable>>, Variable),
FunctionOrTagUnion(TagName, Symbol, Variable), FunctionOrTagUnion(TagName, Symbol, Variable),
RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable), RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable),
@ -640,6 +641,232 @@ pub enum Builtin {
EmptyRecord, EmptyRecord,
} }
#[derive(Clone, Debug)]
pub struct RecordFields {
field_names: Vec<Lowercase>,
variables: Vec<Variable>,
field_type: Vec<RecordField<()>>,
}
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<Item = &Variable> {
self.variables.iter()
}
pub fn iter_variables_mut(&mut self) -> impl Iterator<Item = &mut Variable> {
self.variables.iter_mut()
}
pub fn iter(&self) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> {
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<Variable>)>) -> 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<Variable>)>) -> 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<Variable>, RecordField<Variable>)>,
}
impl Extend<(Lowercase, RecordField<Variable>)> for RecordFields {
fn extend<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(&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<Variable>)> for RecordFields {
fn from_iter<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
let vec: Vec<_> = iter.into_iter().collect();
Self::from_vec(vec)
}
}
impl<'a> FromIterator<(&'a Lowercase, RecordField<Variable>)> for RecordFields {
fn from_iter<T: IntoIterator<Item = (&'a Lowercase, RecordField<Variable>)>>(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<Variable>);
#[allow(clippy::type_complexity)]
type IntoIter = Map<
Zip<
Zip<std::vec::IntoIter<Lowercase>, std::vec::IntoIter<Variable>>,
std::vec::IntoIter<RecordField<()>>,
>,
fn(((Lowercase, Variable), RecordField<()>)) -> (Lowercase, RecordField<Variable>),
>;
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<Variable>) {
let ((name, var), field_type) = arg;
(name, field_type.map(|_| var))
}
impl<'a> IntoIterator for &'a RecordFields {
type Item = (&'a Lowercase, RecordField<Variable>);
#[allow(clippy::type_complexity)]
type IntoIter = Map<
Zip<
Zip<std::slice::Iter<'a, Lowercase>, std::slice::Iter<'a, Variable>>,
std::slice::Iter<'a, RecordField<()>>,
>,
fn(
((&'a Lowercase, &Variable), &RecordField<()>),
) -> (&'a Lowercase, RecordField<Variable>),
>;
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<Variable>) {
let ((name, var), field_type) = arg;
(name, field_type.map(|_| *var))
}
fn occurs( fn occurs(
subs: &Subs, subs: &Subs,
seen: &ImSet<Variable>, seen: &ImSet<Variable>,
@ -670,12 +897,7 @@ fn occurs(
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
Record(vars_by_field, ext_var) => { Record(vars_by_field, ext_var) => {
let it = let it = once(ext_var).chain(vars_by_field.iter_variables());
once(ext_var).chain(vars_by_field.values().map(|field| match field {
RecordField::Optional(var) => var,
RecordField::Required(var) => var,
RecordField::Demanded(var) => var,
}));
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
@ -799,21 +1021,10 @@ fn explicit_substitute(
Record(mut vars_by_field, ext_var) => { Record(mut vars_by_field, ext_var) => {
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
for (_, field) in vars_by_field.iter_mut() { for var in vars_by_field.variables.iter_mut() {
use RecordField::*; *var = explicit_substitute(subs, from, to, *var, seen);
*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))
}
};
} }
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var))); 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 => (), EmptyTagUnion => (),
Record(fields, ext_var) => { Record(fields, ext_var) => {
for field in fields.values() { for var in fields.iter_variables() {
subs.restore(field.into_inner()); subs.restore(*var);
} }
subs.restore(*ext_var); subs.restore(*ext_var);

View file

@ -1,7 +1,7 @@
use crate::pretty_print::Parens; 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 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::ident::{ForeignSymbol, Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -980,7 +980,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
} }
pub struct RecordStructure { pub struct RecordStructure {
pub fields: MutMap<Lowercase, RecordField<Variable>>, pub fields: RecordFields,
pub ext: Variable, pub ext: Variable,
} }
@ -1522,20 +1522,18 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lower
pub fn gather_fields( pub fn gather_fields(
subs: &Subs, subs: &Subs,
other_fields: &MutMap<Lowercase, RecordField<Variable>>, other_fields: RecordFields,
mut var: Variable, mut var: Variable,
) -> RecordStructure { ) -> RecordStructure {
use crate::subs::Content::*; use crate::subs::Content::*;
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
let mut result = other_fields.clone(); let mut result = other_fields;
loop { loop {
match subs.get_content_without_compacting(var) { match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => { Structure(Record(sub_fields, sub_ext)) => {
for (lowercase, record_field) in sub_fields { result = RecordFields::merge(result, sub_fields.clone());
result.insert(lowercase.clone(), *record_field);
}
var = *sub_ext; var = *sub_ext;
} }
@ -1554,3 +1552,46 @@ pub fn gather_fields(
ext: var, 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,
}
}
}

View file

@ -2,8 +2,8 @@ use roc_collections::all::{default_hasher, get_shared, relative_complement, unio
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_types::subs::Content::{self, *}; use roc_types::subs::Content::{self, *};
use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, RecordFields, Subs, Variable};
use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordField, RecordStructure}; use roc_types::types::{gather_fields_ref, ErrorType, Mismatch, RecordField, RecordStructure};
macro_rules! mismatch { macro_rules! mismatch {
() => {{ () => {{
@ -262,28 +262,27 @@ fn unify_record(
) -> Outcome { ) -> Outcome {
let fields1 = rec1.fields; let fields1 = rec1.fields;
let fields2 = rec2.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() { let separate = RecordFields::separate(fields1, fields2);
if unique_fields2.is_empty() {
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); let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
if !ext_problems.is_empty() { if !ext_problems.is_empty() {
return ext_problems; return ext_problems;
} }
let other_fields = MutMap::default();
let mut field_problems = 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.extend(ext_problems);
field_problems field_problems
} else { } 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 sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record); let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record);
@ -291,16 +290,21 @@ fn unify_record(
return ext_problems; return ext_problems;
} }
let other_fields = MutMap::default(); let mut field_problems = unify_shared_fields(
let mut field_problems = subs,
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); pool,
ctx,
shared_fields,
OtherFields::None,
sub_record,
);
field_problems.extend(ext_problems); field_problems.extend(ext_problems);
field_problems field_problems
} }
} else if unique_fields2.is_empty() { } else if separate.only_in_2.is_empty() {
let flat_type = FlatType::Record(unique_fields1, rec1.ext); let flat_type = FlatType::Record(separate.only_in_1, rec1.ext);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext); let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext);
@ -308,19 +312,29 @@ fn unify_record(
return ext_problems; return ext_problems;
} }
let other_fields = MutMap::default(); let mut field_problems = unify_shared_fields(
let mut field_problems = subs,
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); pool,
ctx,
shared_fields,
OtherFields::None,
sub_record,
);
field_problems.extend(ext_problems); field_problems.extend(ext_problems);
field_problems field_problems
} else { } 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 ext = fresh(subs, pool, ctx, Content::FlexVar(None));
let flat_type1 = FlatType::Record(unique_fields1, ext); let flat_type1 = FlatType::Record(separate.only_in_1, ext);
let flat_type2 = FlatType::Record(unique_fields2, ext); let flat_type2 = FlatType::Record(separate.only_in_2, ext);
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1)); let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2)); 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( fn unify_shared_fields(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool, pool: &mut Pool,
ctx: &Context, ctx: &Context,
shared_fields: MutMap<Lowercase, (RecordField<Variable>, RecordField<Variable>)>, shared_fields: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
other_fields: MutMap<Lowercase, RecordField<Variable>>, other_fields: OtherFields,
ext: Variable, ext: Variable,
) -> Outcome { ) -> Outcome {
let mut matching_fields = MutMap::default(); let mut matching_fields = Vec::with_capacity(shared_fields.len());
let num_shared_fields = 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()); let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner());
if local_problems.is_empty() { if local_problems.is_empty() {
@ -383,18 +402,36 @@ fn unify_shared_fields(
(Optional(val), Optional(_)) => Optional(val), (Optional(val), Optional(_)) => Optional(val),
}; };
let existing = matching_fields.insert(name, actual); matching_fields.push((name, actual));
debug_assert_eq!(existing, None);
} }
} }
if num_shared_fields == matching_fields.len() { if num_shared_fields == matching_fields.len() {
// pull fields in from the ext_var // 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) { let mut ext_fields = MutMap::default();
Ok(()) => Variable::EMPTY_RECORD, let new_ext_var =
Err((new, _)) => new, 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); let flat_type = FlatType::Record(fields, new_ext_var);
@ -460,8 +497,8 @@ fn unify_tag_union(
if tags1.len() == 1 if tags1.len() == 1
&& tags2.len() == 1 && tags2.len() == 1
&& tags1 == tags2 && tags1 == tags2
&& subs.get_content_without_compacting(rec1.ext) && subs.get_root_key_without_compacting(rec1.ext)
== subs.get_content_without_compacting(rec2.ext) == subs.get_root_key_without_compacting(rec2.ext)
{ {
return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var); 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)) merge(subs, ctx, Structure(flat_type))
} }
fn has_only_optional_fields<'a, I, T>(fields: &mut I) -> bool
where
I: Iterator<Item = &'a RecordField<T>>,
T: 'a,
{
fields.all(|field| match field {
RecordField::Required(_) => false,
RecordField::Demanded(_) => false,
RecordField::Optional(_) => true,
})
}
#[inline(always)] #[inline(always)]
fn unify_flat_type( fn unify_flat_type(
subs: &mut Subs, subs: &mut Subs,
@ -958,17 +983,17 @@ fn unify_flat_type(
match (left, right) { match (left, right) {
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), (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) 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) unify_pool(subs, pool, ctx.first, *ext)
} }
(Record(fields1, ext1), Record(fields2, ext2)) => { (Record(fields1, ext1), Record(fields2, ext2)) => {
let rec1 = gather_fields(subs, fields1, *ext1); let rec1 = gather_fields_ref(subs, fields1, *ext1);
let rec2 = gather_fields(subs, fields2, *ext2); let rec2 = gather_fields_ref(subs, fields2, *ext2);
unify_record(subs, pool, ctx, rec1, rec2) unify_record(subs, pool, ctx, rec1, rec2)
} }

View file

@ -760,7 +760,9 @@ fn type_to_variable<'a>(
Err((new, _)) => new, 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) register(subs, rank, pools, content)
} }
@ -1212,14 +1214,9 @@ fn adjust_rank_content(
Record(fields, ext_var) => { Record(fields, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for var in fields.values() { for var in fields.iter_variables() {
rank = rank.max(adjust_rank( rank =
subs, rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
young_mark,
visit_mark,
group_rank,
var.into_inner(),
));
} }
rank rank
@ -1372,28 +1369,12 @@ fn instantiate_rigids_help(
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(fields, ext_var) => { Record(fields, ext_var) => {
let mut new_fields = MutMap::default(); for var in fields.iter_variables() {
instantiate_rigids_help(subs, max_rank, pools, *var);
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);
} }
Record( Record(
new_fields, fields,
instantiate_rigids_help(subs, max_rank, pools, ext_var), 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, same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(fields, ext_var) => { Record(mut fields, ext_var) => {
let mut new_fields = MutMap::default(); for var in fields.iter_variables_mut() {
*var = deep_copy_var_help(subs, max_rank, pools, *var);
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( Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
new_fields,
deep_copy_var_help(subs, max_rank, pools, ext_var),
)
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {