Merge pull request #4940 from joshuawarner32/tuple-solve

Initial implementation of tuples in type checking
This commit is contained in:
Ayaz 2023-01-23 16:25:35 -06:00 committed by GitHub
commit a7c415dc35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1812 additions and 112 deletions

View file

@ -90,7 +90,7 @@ impl AbilityMemberData<Resolved> {
pub type SpecializationLambdaSets = VecMap<u8, Variable>;
/// A particular specialization of an ability member.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemberSpecializationInfo<Phase: ResolvePhase> {
_phase: std::marker::PhantomData<Phase>,
pub symbol: Symbol,

View file

@ -482,7 +482,7 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel)
// if-condition
no_region(
// arg_2.b
Access {
RecordAccess {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
@ -505,7 +505,7 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel)
"Ok",
vec![
// arg_2.a
Access {
RecordAccess {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),

View file

@ -1,6 +1,9 @@
use crate::{
def::Def,
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranchPattern},
expr::{
ClosureData, Expr, Field, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData,
WhenBranchPattern,
},
pattern::{DestructType, ListPatterns, Pattern, RecordDestruct},
};
use roc_module::{
@ -10,7 +13,7 @@ use roc_module::{
use roc_types::{
subs::{
self, AliasVariables, Descriptor, GetSubsSlice, OptVariable, RecordFields, Subs, SubsIndex,
SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
SubsSlice, TupleElems, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
},
types::{RecordField, Uls},
};
@ -62,6 +65,11 @@ trait CopyEnv {
fn clone_field_names(&mut self, field_names: SubsSlice<Lowercase>) -> SubsSlice<Lowercase>;
fn clone_tuple_elem_indices(
&mut self,
tuple_elem_indices: SubsSlice<usize>,
) -> SubsSlice<usize>;
fn clone_tag_names(&mut self, tag_names: SubsSlice<TagName>) -> SubsSlice<TagName>;
fn clone_lambda_names(&mut self, lambda_names: SubsSlice<Symbol>) -> SubsSlice<Symbol>;
@ -98,6 +106,14 @@ impl CopyEnv for Subs {
field_names
}
#[inline(always)]
fn clone_tuple_elem_indices(
&mut self,
tuple_elem_indices: SubsSlice<usize>,
) -> SubsSlice<usize> {
tuple_elem_indices
}
#[inline(always)]
fn clone_tag_names(&mut self, tag_names: SubsSlice<TagName>) -> SubsSlice<TagName> {
tag_names
@ -151,6 +167,20 @@ impl<'a> CopyEnv for AcrossSubs<'a> {
)
}
#[inline(always)]
fn clone_tuple_elem_indices(
&mut self,
tuple_elem_indices: SubsSlice<usize>,
) -> SubsSlice<usize> {
SubsSlice::extend_new(
&mut self.target.tuple_elem_indices,
self.source
.get_subs_slice(tuple_elem_indices)
.iter()
.cloned(),
)
}
#[inline(always)]
fn clone_tag_names(&mut self, tag_names: SubsSlice<TagName>) -> SubsSlice<TagName> {
SubsSlice::extend_new(
@ -461,13 +491,21 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
EmptyRecord => EmptyRecord,
Access {
Tuple { tuple_var, elems } => Tuple {
tuple_var: sub!(*tuple_var),
elems: elems
.iter()
.map(|(var, loc_expr)| (sub!(*var), Box::new(loc_expr.map(|e| go_help!(e)))))
.collect(),
},
RecordAccess {
record_var,
ext_var,
field_var,
loc_expr,
field,
} => Access {
} => RecordAccess {
record_var: sub!(*record_var),
ext_var: sub!(*ext_var),
field_var: sub!(*field_var),
@ -475,7 +513,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
field: field.clone(),
},
Accessor(AccessorData {
RecordAccessor(RecordAccessorData {
name,
function_var,
record_var,
@ -483,7 +521,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
ext_var,
field_var,
field,
}) => Accessor(AccessorData {
}) => RecordAccessor(RecordAccessorData {
name: *name,
function_var: sub!(*function_var),
record_var: sub!(*record_var),
@ -493,12 +531,44 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
field: field.clone(),
}),
Update {
TupleAccess {
tuple_var,
ext_var,
elem_var,
loc_expr,
index,
} => TupleAccess {
tuple_var: sub!(*tuple_var),
ext_var: sub!(*ext_var),
elem_var: sub!(*elem_var),
loc_expr: Box::new(loc_expr.map(|e| go_help!(e))),
index: *index,
},
TupleAccessor(TupleAccessorData {
name,
function_var,
tuple_var: record_var,
closure_var,
ext_var,
elem_var: field_var,
index,
}) => TupleAccessor(TupleAccessorData {
name: *name,
function_var: sub!(*function_var),
tuple_var: sub!(*record_var),
closure_var: sub!(*closure_var),
ext_var: sub!(*ext_var),
elem_var: sub!(*field_var),
index: *index,
}),
RecordUpdate {
record_var,
ext_var,
symbol,
updates,
} => Update {
} => RecordUpdate {
record_var: sub!(*record_var),
ext_var: sub!(*ext_var),
symbol: *symbol,
@ -861,7 +931,7 @@ fn deep_copy_type_vars<C: CopyEnv>(
// Everything else is a mechanical descent.
Structure(flat_type) => match flat_type {
EmptyRecord | EmptyTagUnion => Structure(flat_type),
EmptyRecord | EmptyTuple | EmptyTagUnion => Structure(flat_type),
Apply(symbol, arguments) => {
descend_slice!(arguments);
@ -903,6 +973,26 @@ fn deep_copy_type_vars<C: CopyEnv>(
Structure(Record(new_fields, new_ext_var))
})
}
Tuple(elems, ext_var) => {
let new_ext_var = descend_var!(ext_var);
descend_slice!(elems.variables());
perform_clone!({
let new_variables = clone_var_slice!(elems.variables());
let new_elem_indices = env.clone_tuple_elem_indices(elems.elem_indices());
let new_elems = {
TupleElems {
length: elems.length,
variables_start: new_variables.start,
elem_index_start: new_elem_indices.start,
}
};
Structure(Tuple(new_elems, new_ext_var))
})
}
TagUnion(tags, ext_var) => {
let new_ext_var = ext_var.map(|v| descend_var!(v));

View file

@ -306,17 +306,37 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
.append(f.line())
.append(f.text("}"))
.group(),
Tuple { elems, .. } => f
.reflow("(")
.append(
f.intersperse(
elems.iter().map(|(_var, elem)| {
f.line()
.append(expr(c, Free, f, &elem.value))
.nest(2)
.group()
}),
f.reflow(","),
)
.nest(2)
.group(),
)
.append(f.line())
.append(f.text(")"))
.group(),
EmptyRecord => f.text("{}"),
Access {
RecordAccess {
loc_expr, field, ..
} => expr(c, AppArg, f, &loc_expr.value)
.append(f.text(format!(".{}", field.as_str())))
.group(),
TupleAccess { .. } => todo!(),
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
f.text(format!("@{}", opaque_name.as_str(c.interns)))
}
Accessor(_) => todo!(),
Update {
RecordAccessor(_) => todo!(),
TupleAccessor(_) => todo!(),
RecordUpdate {
symbol, updates, ..
} => f
.reflow("{")

View file

@ -182,6 +182,14 @@ fn index_var(
return Ok(field_types);
}
FlatType::Tuple(elems, ext) => {
let elem_types = elems
.sorted_iterator(subs, *ext)
.map(|(_, elem)| elem)
.collect();
return Ok(elem_types);
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
let tag_ctor = match ctor {
IndexCtor::Tag(name) => name,
@ -213,6 +221,9 @@ fn index_var(
};
return Ok(std::iter::repeat(Variable::NULL).take(num_fields).collect());
}
FlatType::EmptyTuple => {
return Ok(std::iter::repeat(Variable::NULL).take(0).collect());
}
FlatType::EmptyTagUnion => {
internal_error!("empty tag unions are not indexable")
}

View file

@ -166,6 +166,11 @@ pub enum Expr {
/// Empty record constant
EmptyRecord,
Tuple {
tuple_var: Variable,
elems: Vec<(Variable, Box<Loc<Expr>>)>,
},
/// The "crash" keyword
Crash {
msg: Box<Loc<Expr>>,
@ -173,17 +178,29 @@ pub enum Expr {
},
/// Look up exactly one field on a record, e.g. (expr).foo.
Access {
RecordAccess {
record_var: Variable,
ext_var: Variable,
field_var: Variable,
loc_expr: Box<Loc<Expr>>,
field: Lowercase,
},
/// field accessor as a function, e.g. (.foo) expr
Accessor(AccessorData),
Update {
/// field accessor as a function, e.g. (.foo) expr
RecordAccessor(RecordAccessorData),
TupleAccess {
tuple_var: Variable,
ext_var: Variable,
elem_var: Variable,
loc_expr: Box<Loc<Expr>>,
index: usize,
},
/// tuple accessor as a function, e.g. (.1) expr
TupleAccessor(TupleAccessorData),
RecordUpdate {
record_var: Variable,
ext_var: Variable,
symbol: Symbol,
@ -294,11 +311,14 @@ impl Expr {
&Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op),
Self::ForeignCall { .. } => Category::ForeignCall,
Self::Closure(..) => Category::Lambda,
Self::Tuple { .. } => Category::Tuple,
Self::Record { .. } => Category::Record,
Self::EmptyRecord => Category::Record,
Self::Access { field, .. } => Category::Access(field.clone()),
Self::Accessor(data) => Category::Accessor(data.field.clone()),
Self::Update { .. } => Category::Record,
Self::RecordAccess { field, .. } => Category::RecordAccess(field.clone()),
Self::RecordAccessor(data) => Category::RecordAccessor(data.field.clone()),
Self::TupleAccess { index, .. } => Category::TupleAccess(*index),
Self::TupleAccessor(data) => Category::TupleAccessor(data.index),
Self::RecordUpdate { .. } => Category::Record,
Self::Tag {
name, arguments, ..
} => Category::TagApply {
@ -363,14 +383,31 @@ pub struct ClosureData {
pub loc_body: Box<Loc<Expr>>,
}
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
/// Accessors are desugared to closures; they need to have a name
/// A tuple accessor like `.2`, which is equivalent to `\x -> x.2`
/// TupleAccessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccessorData {
pub struct TupleAccessorData {
pub name: Symbol,
pub function_var: Variable,
pub tuple_var: Variable,
pub closure_var: Variable,
pub ext_var: Variable,
pub elem_var: Variable,
pub index: usize,
}
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
/// RecordAccessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RecordAccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
@ -380,9 +417,9 @@ pub struct AccessorData {
pub field: Lowercase,
}
impl AccessorData {
impl RecordAccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
let AccessorData {
let RecordAccessorData {
name,
function_var,
record_var,
@ -399,7 +436,7 @@ impl AccessorData {
// into
//
// (\r -> r.foo)
let body = Expr::Access {
let body = Expr::RecordAccess {
record_var,
ext_var,
field_var,
@ -620,9 +657,7 @@ pub fn canonicalize_expr<'a>(
}
}
}
ast::Expr::Tuple(_fields) => {
todo!("canonicalize tuple");
}
ast::Expr::RecordUpdate {
fields,
update: loc_update,
@ -634,7 +669,7 @@ pub fn canonicalize_expr<'a>(
Ok((can_fields, mut output)) => {
output.references.union_mut(&update_out.references);
let answer = Update {
let answer = RecordUpdate {
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
symbol: *symbol,
@ -670,6 +705,35 @@ pub fn canonicalize_expr<'a>(
(answer, Output::default())
}
}
ast::Expr::Tuple(fields) => {
let mut can_elems = Vec::with_capacity(fields.len());
let mut references = References::new();
for loc_elem in fields.iter() {
let (can_expr, elem_out) =
canonicalize_expr(env, var_store, scope, loc_elem.region, &loc_elem.value);
references.union_mut(&elem_out.references);
can_elems.push((var_store.fresh(), Box::new(can_expr)));
}
let output = Output {
references,
tail_call: None,
..Default::default()
};
(
Tuple {
tuple_var: var_store.fresh(),
elems: can_elems,
},
output,
)
}
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::SingleQuote(string) => {
@ -1006,7 +1070,7 @@ pub fn canonicalize_expr<'a>(
let (loc_expr, output) = canonicalize_expr(env, var_store, scope, region, record_expr);
(
Access {
RecordAccess {
record_var: var_store.fresh(),
field_var: var_store.fresh(),
ext_var: var_store.fresh(),
@ -1017,7 +1081,7 @@ pub fn canonicalize_expr<'a>(
)
}
ast::Expr::RecordAccessorFunction(field) => (
Accessor(AccessorData {
RecordAccessor(RecordAccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
@ -1028,8 +1092,32 @@ pub fn canonicalize_expr<'a>(
}),
Output::default(),
),
ast::Expr::TupleAccess(_record_expr, _field) => todo!("handle TupleAccess"),
ast::Expr::TupleAccessorFunction(_) => todo!("handle TupleAccessorFunction"),
ast::Expr::TupleAccess(tuple_expr, field) => {
let (loc_expr, output) = canonicalize_expr(env, var_store, scope, region, tuple_expr);
(
TupleAccess {
tuple_var: var_store.fresh(),
ext_var: var_store.fresh(),
elem_var: var_store.fresh(),
loc_expr: Box::new(loc_expr),
index: field.parse().unwrap(),
},
output,
)
}
ast::Expr::TupleAccessorFunction(index) => (
TupleAccessor(TupleAccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
tuple_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
elem_var: var_store.fresh(),
index: index.parse().unwrap(),
}),
Output::default(),
),
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
@ -1785,8 +1873,9 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ SingleQuote(..)
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ Accessor { .. }
| other @ Update { .. }
| other @ RecordAccessor { .. }
| other @ TupleAccessor { .. }
| other @ RecordUpdate { .. }
| other @ Var(..)
| other @ AbilityMember(..)
| other @ RunLowLevel { .. }
@ -2047,14 +2136,32 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
);
}
Access {
RecordAccess {
record_var,
ext_var,
field_var,
loc_expr,
field,
} => {
todo!("Inlining for Access with record_var {:?}, ext_var {:?}, field_var {:?}, loc_expr {:?}, field {:?}", record_var, ext_var, field_var, loc_expr, field);
todo!("Inlining for RecordAccess with record_var {:?}, ext_var {:?}, field_var {:?}, loc_expr {:?}, field {:?}", record_var, ext_var, field_var, loc_expr, field);
}
Tuple { tuple_var, elems } => {
todo!(
"Inlining for Tuple with tuple_var {:?} and elems {:?}",
tuple_var,
elems
);
}
TupleAccess {
tuple_var,
ext_var,
elem_var,
loc_expr,
index,
} => {
todo!("Inlining for TupleAccess with tuple_var {:?}, ext_var {:?}, elem_var {:?}, loc_expr {:?}, index {:?}", tuple_var, ext_var, elem_var, loc_expr, index);
}
Tag {
@ -2772,7 +2879,7 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
while let Some(expr) = stack.pop() {
match expr {
Expr::Var(symbol, var)
| Expr::Update {
| Expr::RecordUpdate {
symbol,
record_var: var,
..
@ -2863,7 +2970,8 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
Expr::OpaqueRef { argument, .. } => {
stack.push(&argument.1.value);
}
Expr::Access { loc_expr, .. }
Expr::RecordAccess { loc_expr, .. }
| Expr::TupleAccess { loc_expr, .. }
| Expr::Closure(ClosureData {
loc_body: loc_expr, ..
}) => {
@ -2872,6 +2980,9 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
Expr::Record { fields, .. } => {
stack.extend(fields.iter().map(|(_, field)| &field.loc_expr.value));
}
Expr::Tuple { elems, .. } => {
stack.extend(elems.iter().map(|(_, elem)| &elem.value));
}
Expr::Expect {
loc_continuation, ..
}
@ -2892,7 +3003,8 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
| Expr::Int(_, _, _, _, _)
| Expr::Str(_)
| Expr::ZeroArgumentTag { .. }
| Expr::Accessor(_)
| Expr::RecordAccessor(_)
| Expr::TupleAccessor(_)
| Expr::SingleQuote(..)
| Expr::EmptyRecord
| Expr::TypedHole(_)

View file

@ -1087,7 +1087,8 @@ fn fix_values_captured_in_closure_expr(
| TypedHole { .. }
| RuntimeError(_)
| ZeroArgumentTag { .. }
| Accessor { .. } => {}
| RecordAccessor { .. }
| TupleAccessor { .. } => {}
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {
@ -1181,7 +1182,7 @@ fn fix_values_captured_in_closure_expr(
}
Record { fields, .. }
| Update {
| RecordUpdate {
updates: fields, ..
} => {
for (_, field) in fields.iter_mut() {
@ -1193,7 +1194,17 @@ fn fix_values_captured_in_closure_expr(
}
}
Access { loc_expr, .. } => {
Tuple { elems, .. } => {
for (_var, expr) in elems.iter_mut() {
fix_values_captured_in_closure_expr(
&mut expr.value,
no_capture_symbols,
closure_captures,
);
}
}
RecordAccess { loc_expr, .. } | TupleAccess { loc_expr, .. } => {
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,

View file

@ -141,7 +141,16 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| OpaqueRef(_)
| Crash => loc_expr,
TupleAccess(_sub_expr, _paths) => todo!("Handle TupleAccess"),
TupleAccess(sub_expr, paths) => {
let region = loc_expr.region;
let loc_sub_expr = Loc {
region,
value: **sub_expr,
};
let value = TupleAccess(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths);
arena.alloc(Loc { region, value })
}
RecordAccess(sub_expr, paths) => {
let region = loc_expr.region;
let loc_sub_expr = Loc {
@ -176,9 +185,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
}
})),
}),
Tuple(_fields) => {
todo!("desugar_expr: Tuple");
}
Tuple(fields) => arena.alloc(Loc {
region: loc_expr.region,
value: Tuple(fields.map_items(arena, |field| desugar_expr(arena, field))),
}),
RecordUpdate { fields, update } => {
// NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after

View file

@ -8,8 +8,8 @@ use crate::{
abilities::AbilitiesStore,
def::{Annotation, Declaration, Def},
expr::{
self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field,
OpaqueWrapFunctionData,
self, AnnotatedMark, ClosureData, Declarations, Expr, Field, OpaqueWrapFunctionData,
RecordAccessorData, TupleAccessorData,
},
pattern::{DestructType, Pattern, RecordDestruct},
};
@ -228,17 +228,31 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
} => {
walk_record_fields(visitor, fields.iter());
}
Expr::Tuple {
tuple_var: _,
elems,
} => elems
.iter()
.for_each(|(var, elem)| visitor.visit_expr(&elem.value, elem.region, *var)),
Expr::EmptyRecord => { /* terminal */ }
Expr::Access {
Expr::RecordAccess {
field_var,
loc_expr,
field: _,
record_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
Expr::Accessor(AccessorData { .. }) => { /* terminal */ }
Expr::RecordAccessor(RecordAccessorData { .. }) => { /* terminal */ }
Expr::TupleAccess {
elem_var,
loc_expr,
index: _,
tuple_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *elem_var),
Expr::TupleAccessor(TupleAccessorData { .. }) => { /* terminal */ }
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
Expr::Update {
Expr::RecordUpdate {
record_var: _,
ext_var: _,
symbol: _,

View file

@ -279,6 +279,13 @@ where
}
}
impl<K, V> Eq for VecMap<K, V>
where
K: Eq,
V: Eq,
{
}
#[cfg(test)]
mod test_drain_filter {
use crate::VecMap;

View file

@ -16,8 +16,8 @@ use roc_can::expected::Expected::{self, *};
use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{
AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef,
ExpectLookup, Field, FunctionDef, OpaqueWrapFunctionData, WhenBranch,
AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, ExpectLookup, Field,
FunctionDef, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData, WhenBranch,
};
use roc_can::pattern::Pattern;
use roc_can::traverse::symbols_introduced_from_pattern;
@ -252,7 +252,52 @@ pub fn constrain_expr(
constraints.exists(field_vars, and_constraint)
}
}
Update {
Expr::Tuple { tuple_var, elems } => {
let mut elem_types = VecMap::with_capacity(elems.len());
let mut elem_vars = Vec::with_capacity(elems.len());
// Constraints need capacity for each elem
// + 1 for the tuple itself + 1 for tuple var
let mut tuple_constraints = Vec::with_capacity(2 + elems.len());
for (i, (elem_var, loc_expr)) in elems.iter().enumerate() {
let elem_type = constraints.push_variable(*elem_var);
let elem_expected = constraints.push_expected_type(NoExpectation(elem_type));
let elem_con = constrain_expr(
types,
constraints,
env,
loc_expr.region,
&loc_expr.value,
elem_expected,
);
elem_vars.push(*elem_var);
elem_types.insert(i, Variable(*elem_var));
tuple_constraints.push(elem_con);
}
let tuple_type = {
let typ = types.from_old_type(&Type::Tuple(elem_types, TypeExtension::Closed));
constraints.push_type(types, typ)
};
let tuple_con = constraints.equal_types_with_storage(
tuple_type,
expected,
Category::Tuple,
region,
*tuple_var,
);
tuple_constraints.push(tuple_con);
elem_vars.push(*tuple_var);
let and_constraint = constraints.and_constraint(tuple_constraints);
constraints.exists(elem_vars, and_constraint)
}
RecordUpdate {
record_var,
ext_var,
symbol,
@ -1070,7 +1115,7 @@ pub fn constrain_expr(
branch_constraints,
)
}
Access {
RecordAccess {
record_var,
ext_var,
field_var,
@ -1096,7 +1141,7 @@ pub fn constrain_expr(
};
let record_expected = constraints.push_expected_type(NoExpectation(record_type));
let category = Category::Access(field.clone());
let category = Category::RecordAccess(field.clone());
let record_con =
constraints.equal_types_var(*record_var, record_expected, category.clone(), region);
@ -1117,7 +1162,7 @@ pub fn constrain_expr(
[constraint, eq, record_con],
)
}
Accessor(AccessorData {
RecordAccessor(RecordAccessorData {
name: closure_name,
function_var,
field,
@ -1143,7 +1188,7 @@ pub fn constrain_expr(
constraints.push_type(types, typ)
};
let category = Category::Accessor(field.clone());
let category = Category::RecordAccessor(field.clone());
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
let record_con =
@ -1199,6 +1244,132 @@ pub fn constrain_expr(
cons,
)
}
TupleAccess {
tuple_var,
ext_var,
elem_var,
loc_expr,
index,
} => {
let mut tup_elem_types = VecMap::with_capacity(1);
let ext_var = *ext_var;
let ext_type = Variable(ext_var);
let elem_var = *elem_var;
let elem_type = Type::Variable(elem_var);
tup_elem_types.insert(*index, elem_type);
let tuple_type = {
let typ = types.from_old_type(&Type::Tuple(
tup_elem_types,
TypeExtension::from_non_annotation_type(ext_type),
));
constraints.push_type(types, typ)
};
let tuple_expected = constraints.push_expected_type(NoExpectation(tuple_type));
let category = Category::TupleAccess(*index);
let tuple_con =
constraints.equal_types_var(*tuple_var, tuple_expected, category.clone(), region);
let expected_tuple = constraints.push_expected_type(NoExpectation(tuple_type));
let constraint = constrain_expr(
types,
constraints,
env,
region,
&loc_expr.value,
expected_tuple,
);
let eq = constraints.equal_types_var(elem_var, expected, category, region);
constraints.exists_many([*tuple_var, elem_var, ext_var], [constraint, eq, tuple_con])
}
TupleAccessor(TupleAccessorData {
name: closure_name,
function_var,
tuple_var,
closure_var,
ext_var,
elem_var,
index,
}) => {
let ext_var = *ext_var;
let ext_type = Variable(ext_var);
let elem_var = *elem_var;
let elem_type = Variable(elem_var);
let mut elem_types = VecMap::with_capacity(1);
elem_types.insert(*index, elem_type.clone());
let record_type = Type::Tuple(
elem_types,
TypeExtension::from_non_annotation_type(ext_type),
);
let record_type_index = {
let typ = types.from_old_type(&record_type);
constraints.push_type(types, typ)
};
let category = Category::TupleAccessor(*index);
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
let record_con =
constraints.equal_types_var(*tuple_var, record_expected, category.clone(), region);
let expected_lambda_set = {
let lambda_set_ty = {
let typ = types.from_old_type(&Type::ClosureTag {
name: *closure_name,
captures: vec![],
ambient_function: *function_var,
});
constraints.push_type(types, typ)
};
constraints.push_expected_type(NoExpectation(lambda_set_ty))
};
let closure_type = Type::Variable(*closure_var);
let function_type_index = {
let typ = types.from_old_type(&Type::Function(
vec![record_type],
Box::new(closure_type),
Box::new(elem_type),
));
constraints.push_type(types, typ)
};
let cons = [
constraints.equal_types_var(
*closure_var,
expected_lambda_set,
category.clone(),
region,
),
constraints.equal_types(function_type_index, expected, category.clone(), region),
{
let store_fn_var_index = constraints.push_variable(*function_var);
let store_fn_var_expected =
constraints.push_expected_type(NoExpectation(store_fn_var_index));
constraints.equal_types(
function_type_index,
store_fn_var_expected,
category,
region,
)
},
record_con,
];
constraints.exists_many(
[*tuple_var, *function_var, *closure_var, elem_var, ext_var],
cons,
)
}
LetRec(defs, loc_ret, cycle_mark) => {
let body_con = constrain_expr(
types,
@ -3826,8 +3997,12 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
match expr {
Num(..) | Int(..) | Float(..) => return true,
Closure(_) => return true,
Accessor(_) => {
// Accessor functions `.field` are equivalent to closures `\r -> r.field`, no need to weaken them.
RecordAccessor(_) => {
// RecordAccessor functions `.field` are equivalent to closures `\r -> r.field`, no need to weaken them.
return true;
}
TupleAccessor(_) => {
// TupleAccessor functions `.0` are equivalent to closures `\r -> r.0`, no need to weaken them.
return true;
}
OpaqueWrapFunction(_) => {
@ -3852,9 +4027,11 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
| ForeignCall { .. }
| EmptyRecord
| Expr::Record { .. }
| Expr::Tuple { .. }
| Crash { .. }
| Access { .. }
| Update { .. }
| RecordAccess { .. }
| TupleAccess { .. }
| RecordUpdate { .. }
| Expect { .. }
| ExpectFx { .. }
| Dbg { .. }

View file

@ -363,7 +363,7 @@ fn decoder_record_step_field(
},
);
let updated_record = Expr::Update {
let updated_record = Expr::RecordUpdate {
record_var: state_record_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
symbol: state_arg_symbol,
@ -429,7 +429,7 @@ fn decoder_record_step_field(
// Ok val -> Ok {state & first: Ok val},
// Err err -> Err err
Expr::When {
loc_cond: Box::new(Loc::at_zero(Expr::Access {
loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess {
record_var: rec_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: rec_dot_result,
@ -458,7 +458,7 @@ fn decoder_record_step_field(
Field {
var: Variable::LIST_U8,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::Access {
loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess {
record_var: rec_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: Variable::LIST_U8,
@ -829,7 +829,7 @@ fn decoder_record_finalizer(
.zip(result_field_vars.iter().rev())
{
// when rec.first is
let cond_expr = Expr::Access {
let cond_expr = Expr::RecordAccess {
record_var: state_record_var,
ext_var: env.new_ext_var(ExtensionKind::Record),
field_var: result_field_var,

View file

@ -313,7 +313,7 @@ fn to_encoder_record(
};
// rcd.a
let field_access = Access {
let field_access = RecordAccess {
record_var,
ext_var: env.subs.fresh_unnamed_flex_var(),
field_var,

View file

@ -96,7 +96,7 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (V
let field_name = env.subs[field_name].clone();
let field_var = env.subs[field_var];
let field_access = Expr::Access {
let field_access = Expr::RecordAccess {
record_var,
field_var,
ext_var: env.subs.fresh_unnamed_flex_var(),

View file

@ -61,6 +61,9 @@ impl FlatDecodable {
Ok(Key(FlatDecodableKey::Record(field_names)))
}
FlatType::Tuple(_elems, _ext) => {
todo!()
}
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
Err(Underivable) // yet
}
@ -68,6 +71,7 @@ impl FlatDecodable {
Err(Underivable) // yet
}
FlatType::EmptyRecord => Ok(Key(FlatDecodableKey::Record(vec![]))),
FlatType::EmptyTuple => todo!(),
FlatType::EmptyTagUnion => {
Err(Underivable) // yet
}

View file

@ -66,6 +66,9 @@ impl FlatEncodable {
Ok(Key(FlatEncodableKey::Record(field_names)))
}
FlatType::Tuple(_elems, _ext) => {
todo!()
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
// The recursion var doesn't matter, because the derived implementation will only
// look on the surface of the tag union type, and more over the payloads of the
@ -104,6 +107,7 @@ impl FlatEncodable {
)))
}
FlatType::EmptyRecord => Ok(Key(FlatEncodableKey::Record(vec![]))),
FlatType::EmptyTuple => todo!(),
FlatType::EmptyTagUnion => Ok(Key(FlatEncodableKey::TagUnion(vec![]))),
//
FlatType::Func(..) => Err(Underivable),

View file

@ -65,6 +65,9 @@ impl FlatHash {
Ok(Key(FlatHashKey::Record(field_names)))
}
FlatType::Tuple(_elems, _ext) => {
todo!();
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
// The recursion var doesn't matter, because the derived implementation will only
// look on the surface of the tag union type, and more over the payloads of the
@ -101,6 +104,7 @@ impl FlatHash {
.collect(),
))),
FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))),
FlatType::EmptyTuple => todo!(),
FlatType::EmptyTagUnion => Ok(Key(FlatHashKey::TagUnion(vec![]))),
//
FlatType::Func(..) => Err(Underivable),

View file

@ -4330,6 +4330,8 @@ pub fn with_hole<'a>(
}
}
Tuple { .. } => todo!("implement tuple hole"),
Record {
record_var,
mut fields,
@ -4719,7 +4721,7 @@ pub fn with_hole<'a>(
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
Access {
RecordAccess {
record_var,
field_var,
field,
@ -4807,7 +4809,7 @@ pub fn with_hole<'a>(
stmt
}
Accessor(accessor_data) => {
RecordAccessor(accessor_data) => {
let field_var = accessor_data.field_var;
let fresh_record_symbol = env.unique_symbol();
@ -4862,6 +4864,9 @@ pub fn with_hole<'a>(
}
}
TupleAccess { .. } => todo!(),
TupleAccessor(_) => todo!(),
OpaqueWrapFunction(wrap_fn_data) => {
let opaque_var = wrap_fn_data.opaque_var;
let arg_symbol = env.unique_symbol();
@ -4919,7 +4924,7 @@ pub fn with_hole<'a>(
}
}
Update {
RecordUpdate {
record_var,
symbol: structure,
updates,

View file

@ -2078,6 +2078,13 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) {
}
stack.push((*ext, depth_any + 1, depth_lset));
}
FlatType::Tuple(elems, ext) => {
for var_index in elems.iter_variables() {
let var = subs[var_index];
stack.push((var, depth_any + 1, depth_lset));
}
stack.push((*ext, depth_any + 1, depth_lset));
}
FlatType::FunctionOrTagUnion(_, _, ext) => {
stack.push((ext.var(), depth_any + 1, depth_lset));
}
@ -2098,7 +2105,7 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) {
}
stack.push((ext.var(), depth_any + 1, depth_lset));
}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => {}
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => {}
},
Content::FlexVar(_)
| Content::RigidVar(_)
@ -3176,6 +3183,9 @@ fn layout_from_flat_type<'a>(
Cacheable(result, criteria)
}
Tuple(_elems, _ext_var) => {
todo!();
}
TagUnion(tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
@ -3205,6 +3215,7 @@ fn layout_from_flat_type<'a>(
}
EmptyTagUnion => cacheable(Ok(Layout::VOID)),
EmptyRecord => cacheable(Ok(Layout::UNIT)),
EmptyTuple => cacheable(Ok(Layout::UNIT)),
}
}

View file

@ -821,6 +821,9 @@ impl Layout {
Ok(Layout::Struct(slice))
}
FlatType::Tuple(_elems, _ext) => {
todo!();
}
FlatType::TagUnion(union_tags, ext) => {
debug_assert!(ext_var_is_empty_tag_union(subs, *ext));
@ -861,7 +864,7 @@ impl Layout {
Ok(Layout::UnionRecursive(slices))
}
FlatType::EmptyRecord => Ok(Layout::UNIT),
FlatType::EmptyRecord | FlatType::EmptyTuple => Ok(Layout::UNIT),
FlatType::EmptyTagUnion => Ok(Layout::VOID),
}
}

View file

@ -14,7 +14,7 @@ use roc_solve_problem::{
use roc_types::num::NumericRange;
use roc_types::subs::{
instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, RecordFields, Subs, SubsSlice,
Variable,
TupleElems, Variable,
};
use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory, Polarity, Types};
use roc_unify::unify::{Env, MustImplementConstraints};
@ -542,6 +542,18 @@ trait DerivableVisitor {
})
}
#[inline(always)]
fn visit_tuple(
_subs: &Subs,
var: Variable,
_elems: TupleElems,
) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
@ -574,6 +586,14 @@ trait DerivableVisitor {
})
}
#[inline(always)]
fn visit_empty_tuple(var: Variable) -> Result<(), NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_empty_tag_union(var: Variable) -> Result<(), NotDerivable> {
Err(NotDerivable {
@ -702,6 +722,18 @@ trait DerivableVisitor {
}
}
}
Tuple(elems, ext) => {
let descend = Self::visit_tuple(subs, var, elems)?;
if descend.0 {
push_var_slice!(elems.variables());
if !matches!(
subs.get_content_without_compacting(ext),
Content::FlexVar(_) | Content::RigidVar(_)
) {
stack.push(ext);
}
}
}
TagUnion(tags, ext) => {
let descend = Self::visit_tag_union(var)?;
if descend.0 {
@ -728,6 +760,7 @@ trait DerivableVisitor {
}
}
EmptyRecord => Self::visit_empty_record(var)?,
EmptyTuple => Self::visit_empty_tuple(var)?,
EmptyTagUnion => Self::visit_empty_tag_union(var)?,
},
Alias(

View file

@ -29,12 +29,13 @@ use roc_region::all::Loc;
use roc_solve_problem::TypeError;
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, LambdaSet, Mark,
OptVariable, Rank, RecordFields, Subs, SubsSlice, TagExt, UlsOfVar, UnionLabels, UnionLambdas,
UnionTags, Variable, VariableSubsSlice,
OptVariable, Rank, RecordFields, Subs, SubsSlice, TagExt, TupleElems, UlsOfVar, UnionLabels,
UnionLambdas, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::{
gather_fields_unsorted_iter, AliasKind, AliasShared, Category, ExtImplicitOpenness, OptAbleVar,
Polarity, Reason, RecordField, Type, TypeExtension, TypeTag, Types, Uls,
gather_fields_unsorted_iter, gather_tuple_elems_unsorted_iter, AliasKind, AliasShared,
Category, ExtImplicitOpenness, OptAbleVar, Polarity, Reason, RecordField, Type, TypeExtension,
TypeTag, Types, Uls,
};
use roc_unify::unify::{
unify, unify_introduced_ability_specialization, Env as UEnv, Mode, Obligated,
@ -2658,6 +2659,39 @@ fn type_to_variable<'a>(
register_with_known_var(subs, destination, rank, pools, content)
}
Tuple(elems) => {
let ext_slice = types.get_type_arguments(typ_index);
// Elems should never be empty; we don't support empty tuples
debug_assert!(!elems.is_empty() || !ext_slice.is_empty());
let mut elem_vars = Vec::with_capacity_in(elems.len(), arena);
let (indices, elem_tys) = types.tuple_elems_slices(elems);
for (index, elem_type) in indices.into_iter().zip(elem_tys.into_iter()) {
let elem_var = helper!(elem_type);
elem_vars.push((types[index], elem_var));
}
debug_assert!(ext_slice.len() <= 1);
let temp_ext_var = match ext_slice.into_iter().next() {
None => roc_types::subs::Variable::EMPTY_TUPLE,
Some(ext) => helper!(ext),
};
let (it, new_ext_var) =
gather_tuple_elems_unsorted_iter(subs, TupleElems::empty(), temp_ext_var)
.expect("Something ended up weird in this tuple type");
elem_vars.extend(it);
let tuple_elems = TupleElems::insert_into_subs(subs, elem_vars);
let content = Content::Structure(FlatType::Tuple(tuple_elems, new_ext_var));
register_with_known_var(subs, destination, rank, pools, content)
}
TagUnion(tags, ext_openness) => {
let ext_slice = types.get_type_arguments(typ_index);
@ -3728,7 +3762,7 @@ fn adjust_rank_content(
rank
}
EmptyRecord => {
EmptyRecord | EmptyTuple => {
// from elm-compiler: THEORY: an empty record never needs to get generalized
//
// But for us, that theory does not hold, because there might be type variables hidden
@ -3767,6 +3801,17 @@ fn adjust_rank_content(
rank
}
Tuple(elems, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for (_, var_index) in elems.iter_all() {
let var = subs[var_index];
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
}
rank
}
TagUnion(tags, ext_var) => {
let mut rank =
adjust_rank(subs, young_mark, visit_mark, group_rank, ext_var.var());
@ -4125,7 +4170,7 @@ fn deep_copy_var_help(
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTagUnion => same,
same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same,
Record(fields, ext_var) => {
let record_fields = {
@ -4168,6 +4213,20 @@ fn deep_copy_var_help(
Record(record_fields, work!(ext_var))
}
Tuple(elems, ext_var) => {
let tuple_elems = {
let new_variables = copy_sequence!(elems.len(), elems.iter_variables());
TupleElems {
length: elems.length,
variables_start: new_variables.start,
elem_index_start: elems.elem_index_start,
}
};
Tuple(tuple_elems, work!(ext_var))
}
TagUnion(tags, ext_var) => {
let union_tags = copy_union!(tags);

View file

@ -1469,6 +1469,34 @@ mod solve_expr {
infer_eq("{ x: 5, y : 3.14 }.x", "Num *");
}
#[test]
fn record_literal_accessor_function() {
infer_eq(".x { x: 5, y : 3.14 }", "Num *");
}
#[test]
fn tuple_literal_accessor() {
infer_eq("(5, 3.14 ).0", "Num *");
}
#[test]
fn tuple_literal_accessor_function() {
infer_eq(".0 (5, 3.14 )", "Num *");
}
#[test]
fn tuple_literal_ty() {
infer_eq("(5, 3.14 )", "( Num *, Float * )*");
}
#[test]
fn tuple_literal_accessor_ty() {
infer_eq(".0", "( a )* -> a");
infer_eq(".4", "( _, _, _, _, a )* -> a");
infer_eq(".5", "( ... 5 omitted, a )* -> a");
infer_eq(".200", "( ... 200 omitted, a )* -> a");
}
#[test]
fn record_arg() {
infer_eq("\\rec -> rec.x", "{ x : a }* -> a");

View file

@ -15,6 +15,9 @@ pub static WILDCARD: &str = "*";
static EMPTY_RECORD: &str = "{}";
static EMPTY_TAG_UNION: &str = "[]";
// TODO: since we technically don't support empty tuples at the source level, this should probably be removed
static EMPTY_TUPLE: &str = "()";
/// Requirements for parentheses.
///
/// If we're inside a function (that is, this is either an argument or a return
@ -225,6 +228,28 @@ fn find_names_needed(
find_under_alias,
);
}
Structure(Tuple(elems, ext_var)) => {
for index in elems.iter_variables() {
let var = subs[index];
find_names_needed(
var,
subs,
roots,
root_appearances,
names_taken,
find_under_alias,
);
}
find_names_needed(
*ext_var,
subs,
roots,
root_appearances,
names_taken,
find_under_alias,
);
}
Structure(TagUnion(tags, ext_var)) => {
for slice_index in tags.variables() {
let slice = subs[slice_index];
@ -372,7 +397,7 @@ fn find_names_needed(
find_under_alias,
);
}
Error | Structure(EmptyRecord) | Structure(EmptyTagUnion) => {
Error | Structure(EmptyRecord) | Structure(EmptyTuple) | Structure(EmptyTagUnion) => {
// Errors and empty records don't need names.
}
}
@ -1112,6 +1137,7 @@ fn write_flat_type<'a>(
pol,
),
EmptyRecord => buf.push_str(EMPTY_RECORD),
EmptyTuple => buf.push_str(EMPTY_TUPLE),
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
Func(args, closure, ret) => write_fn(
env,
@ -1187,6 +1213,67 @@ fn write_flat_type<'a>(
}
}
}
Tuple(elems, ext_var) => {
use crate::types::{gather_tuple_elems, TupleStructure};
// If the `ext` has concrete elems (e.g. (I64, I64)(Bool)), merge them
let TupleStructure {
elems: sorted_elems,
ext,
} = gather_tuple_elems(subs, *elems, *ext_var)
.expect("Something ended up weird in this record type");
let ext_var = ext;
buf.push_str("( ");
let mut any_written_yet = false;
let mut expected_next_index = 0;
for (index, var) in sorted_elems {
if any_written_yet {
buf.push_str(", ");
} else {
any_written_yet = true;
}
if index - expected_next_index > 4 {
// Don't write out a large number of _'s - just write out a count
buf.push_str(&format!("... {} omitted, ", index - expected_next_index));
} else if index - expected_next_index > 1 {
// Write out a bunch of _'s
for _ in expected_next_index..index {
buf.push_str("_, ");
}
}
expected_next_index = index + 1;
write_content(
env,
ctx,
subs.get_content_without_compacting(var),
subs,
buf,
Parens::Unnecessary,
pol,
);
}
buf.push_str(" )");
match subs.get_content_without_compacting(ext_var) {
Content::Structure(EmptyTuple) => {
// This is a closed tuple. We're done!
}
content => {
// This is an open tuple, so print the variable
// right after the ')'
//
// e.g. the "*" at the end of `( I64, I64 )*`
// or the "r" at the end of `( I64, I64 )r`
write_content(env, ctx, content, subs, buf, parens, pol)
}
}
}
TagUnion(tags, ext_var) => {
buf.push('[');

View file

@ -1,7 +1,7 @@
#![deny(unsafe_op_in_unsafe_fn)]
use crate::types::{
name_type_var, AbilitySet, AliasKind, ErrorType, ExtImplicitOpenness, Polarity, RecordField,
RecordFieldsError, TypeExt, Uls,
RecordFieldsError, TupleElemsError, TypeExt, Uls,
};
use roc_collections::all::{FnvMap, ImMap, ImSet, MutSet, SendMap};
use roc_collections::{VecMap, VecSet};
@ -70,6 +70,7 @@ struct SubsHeader {
tag_names: u64,
symbol_names: u64,
field_names: u64,
tuple_elem_indices: u64,
record_fields: u64,
variable_slices: u64,
unspecialized_lambda_sets: u64,
@ -85,6 +86,7 @@ impl SubsHeader {
tag_names: subs.tag_names.len() as u64,
symbol_names: subs.symbol_names.len() as u64,
field_names: subs.field_names.len() as u64,
tuple_elem_indices: subs.tuple_elem_indices.len() as u64,
record_fields: subs.record_fields.len() as u64,
variable_slices: subs.variable_slices.len() as u64,
unspecialized_lambda_sets: subs.unspecialized_lambda_sets.len() as u64,
@ -127,6 +129,7 @@ impl Subs {
written = Self::serialize_tag_names(&self.tag_names, writer, written)?;
written = bytes::serialize_slice(&self.symbol_names, writer, written)?;
written = Self::serialize_field_names(&self.field_names, writer, written)?;
written = bytes::serialize_slice(&self.tuple_elem_indices, writer, written)?;
written = bytes::serialize_slice(&self.record_fields, writer, written)?;
written = bytes::serialize_slice(&self.variable_slices, writer, written)?;
written = bytes::serialize_slice(&self.unspecialized_lambda_sets, writer, written)?;
@ -220,6 +223,8 @@ impl Subs {
bytes::deserialize_slice(bytes, header.symbol_names as usize, offset);
let (field_names, offset) =
Self::deserialize_field_names(bytes, header.field_names as usize, offset);
let (tuple_elem_indices, offset) =
bytes::deserialize_slice(bytes, header.tuple_elem_indices as usize, offset);
let (record_fields, offset) =
bytes::deserialize_slice(bytes, header.record_fields as usize, offset);
let (variable_slices, offset) =
@ -239,6 +244,7 @@ impl Subs {
tag_names: tag_names.to_vec(),
symbol_names: symbol_names.to_vec(),
field_names,
tuple_elem_indices: tuple_elem_indices.to_vec(),
record_fields: record_fields.to_vec(),
variable_slices: variable_slices.to_vec(),
unspecialized_lambda_sets: unspecialized_lambda_sets.to_vec(),
@ -368,6 +374,7 @@ impl UlsOfVar {
pub struct Subs {
utable: UnificationTable,
pub variables: Vec<Variable>,
pub tuple_elem_indices: Vec<usize>,
pub tag_names: Vec<TagName>,
pub symbol_names: Vec<Symbol>,
pub field_names: Vec<Lowercase>,
@ -445,6 +452,14 @@ impl std::ops::Index<SubsIndex<Lowercase>> for Subs {
}
}
impl std::ops::Index<SubsIndex<usize>> for Subs {
type Output = usize;
fn index(&self, index: SubsIndex<usize>) -> &Self::Output {
&self.tuple_elem_indices[index.index as usize]
}
}
impl std::ops::Index<SubsIndex<TagName>> for Subs {
type Output = TagName;
@ -742,6 +757,12 @@ impl GetSubsSlice<Uls> for Subs {
}
}
impl GetSubsSlice<usize> for Subs {
fn get_subs_slice(&self, subs_slice: SubsSlice<usize>) -> &[usize] {
subs_slice.get_slice(&self.tuple_elem_indices)
}
}
impl fmt::Debug for Subs {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@ -928,6 +949,20 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
write!(f, "}}<{:?}>", new_ext)
}
FlatType::Tuple(elems, ext) => {
write!(f, "( ")?;
let (it, new_ext) = elems.sorted_iterator_and_ext(subs, *ext);
for (_i, content) in it {
write!(
f,
"{:?}, ",
SubsFmtContent(subs.get_content_without_compacting(content), subs)
)?;
}
write!(f, ")<{:?}>", new_ext)
}
FlatType::TagUnion(tags, ext) => {
write!(f, "[")?;
@ -967,6 +1002,7 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
write!(f, "]<{:?}> as <{:?}>", new_ext, rec)
}
FlatType::EmptyRecord => write!(f, "EmptyRecord"),
FlatType::EmptyTuple => write!(f, "EmptyTuple"),
FlatType::EmptyTagUnion => write!(f, "EmptyTagUnion"),
}
}
@ -1224,6 +1260,7 @@ define_const_var! {
NULL,
:pub EMPTY_RECORD,
:pub EMPTY_TUPLE,
:pub EMPTY_TAG_UNION,
BOOL_ENUM,
@ -1714,6 +1751,7 @@ impl Subs {
symbol_names,
field_names: Vec::new(),
record_fields: Vec::new(),
tuple_elem_indices: Vec::new(),
variable_slices: vec![
// used for "TagOrFunction"
VariableSubsSlice::default(),
@ -2563,6 +2601,7 @@ pub enum FlatType {
Apply(Symbol, VariableSubsSlice),
Func(VariableSubsSlice, Variable, Variable),
Record(RecordFields, Variable),
Tuple(TupleElems, Variable),
TagUnion(UnionTags, TagExt),
/// `A` might either be a function
@ -2572,6 +2611,7 @@ pub enum FlatType {
RecursiveTagUnion(Variable, UnionTags, TagExt),
EmptyRecord,
EmptyTuple,
EmptyTagUnion,
}
@ -3068,7 +3108,8 @@ fn first<K: Ord, V>(x: &(K, V), y: &(K, V)) -> std::cmp::Ordering {
x.0.cmp(&y.0)
}
pub type SortedIterator<'a> = Box<dyn Iterator<Item = (Lowercase, RecordField<Variable>)> + 'a>;
pub type SortedFieldIterator<'a> =
Box<dyn Iterator<Item = (Lowercase, RecordField<Variable>)> + 'a>;
impl RecordFields {
pub const fn len(&self) -> usize {
@ -3190,7 +3231,7 @@ impl RecordFields {
///
/// Hopefully the inline will get rid of the Box in practice
#[inline(always)]
pub fn sorted_iterator<'a>(&'_ self, subs: &'a Subs, ext: Variable) -> SortedIterator<'a> {
pub fn sorted_iterator<'a>(&'_ self, subs: &'a Subs, ext: Variable) -> SortedFieldIterator<'a> {
self.sorted_iterator_and_ext(subs, ext).0
}
@ -3199,7 +3240,7 @@ impl RecordFields {
&'_ self,
subs: &'a Subs,
ext: Variable,
) -> (SortedIterator<'a>, Variable) {
) -> (SortedFieldIterator<'a>, Variable) {
if is_empty_record(subs, ext) {
(
Box::new(self.iter_all().map(move |(i1, i2, i3)| {
@ -3271,6 +3312,128 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
}
}
#[derive(Clone, Copy, Debug)]
pub struct TupleElems {
pub length: u16,
// TODO: make this a Result<u32, u32>, where Ok(x) means that the tuple is
// is fully sparse (the current case), and Err(x) means that the tuple is locally dense
// (meaning all the non-sparse elements are sequential) where x is the start of the
// dense section. This means we can encode both sparse tuple types generated by e.g. `.5`
// and dense tuple types, e.g. `(1, 2, 3)` without taking up any extra space.
pub elem_index_start: u32,
pub variables_start: u32,
}
impl TupleElems {
pub const fn len(&self) -> usize {
self.length as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn empty() -> Self {
Self {
length: 0,
elem_index_start: 0,
variables_start: 0,
}
}
pub const fn variables(&self) -> SubsSlice<Variable> {
SubsSlice::new(self.variables_start, self.length)
}
pub const fn elem_indices(&self) -> SubsSlice<usize> {
SubsSlice::new(self.elem_index_start, self.length)
}
pub fn iter_variables(&self) -> impl Iterator<Item = SubsIndex<Variable>> {
let slice = SubsSlice::new(self.variables_start, self.length);
slice.into_iter()
}
pub fn iter_all(&self) -> impl Iterator<Item = (SubsIndex<usize>, SubsIndex<Variable>)> {
let helper = |start| start..(start + self.length as u32);
let range1 = helper(self.elem_index_start);
let range2 = helper(self.variables_start);
let it = range1.into_iter().zip(range2.into_iter());
it.map(|(i1, i2)| (SubsIndex::new(i1), SubsIndex::new(i2)))
}
pub fn insert_into_subs<I>(subs: &mut Subs, input: I) -> Self
where
I: IntoIterator<Item = (usize, Variable)>,
{
let variables_start = subs.variables.len() as u32;
let elem_index_start = subs.tuple_elem_indices.len() as u32;
let it = input.into_iter();
let size_hint = it.size_hint().0;
subs.variables.reserve(size_hint);
subs.tuple_elem_indices.reserve(size_hint);
let mut length = 0;
for (index, var) in it {
subs.variables.push(var);
subs.tuple_elem_indices.push(index);
length += 1;
}
TupleElems {
length,
elem_index_start,
variables_start,
}
}
#[inline(always)]
pub fn unsorted_iterator<'a>(
&'a self,
subs: &'a Subs,
ext: Variable,
) -> Result<impl Iterator<Item = (usize, Variable)> + 'a, TupleElemsError> {
let (it, _) = crate::types::gather_tuple_elems_unsorted_iter(subs, *self, ext)?;
Ok(it)
}
/// get a sorted iterator over the elems of this tuple type
///
/// This involves looking at both the type itself and the ext var and unioning the results
#[inline(always)]
pub fn sorted_iterator<'a>(
&'_ self,
subs: &'a Subs,
ext: Variable,
) -> Box<dyn Iterator<Item = (usize, Variable)> + 'a> {
self.sorted_iterator_and_ext(subs, ext).0
}
#[inline(always)]
pub fn sorted_iterator_and_ext<'a>(
&'_ self,
subs: &'a Subs,
ext: Variable,
) -> (Box<dyn Iterator<Item = (usize, Variable)> + 'a>, Variable) {
let tuple_structure = crate::types::gather_tuple_elems(subs, *self, ext)
.expect("Something ended up weird in this tuple type");
(
Box::new(tuple_structure.elems.into_iter()),
tuple_structure.ext,
)
}
}
std::thread_local! {
static SCRATCHPAD_FOR_OCCURS: RefCell<Option<Vec<Variable>>> = RefCell::new(Some(Vec::with_capacity(1024)));
}
@ -3323,6 +3486,11 @@ fn occurs(
let it = once(ext).chain(subs.get_subs_slice(slice).iter());
short_circuit(subs, root_var, seen, it)
}
Tuple(vars_by_elem, ext) => {
let slice = SubsSlice::new(vars_by_elem.variables_start, vars_by_elem.length);
let it = once(ext).chain(subs.get_subs_slice(slice).iter());
short_circuit(subs, root_var, seen, it)
}
TagUnion(tags, ext) => {
occurs_union(subs, root_var, seen, tags)?;
@ -3336,7 +3504,7 @@ fn occurs(
short_circuit_help(subs, root_var, seen, ext.var())
}
EmptyRecord | EmptyTagUnion => Ok(()),
EmptyRecord | EmptyTuple | EmptyTagUnion => Ok(()),
},
Alias(_, args, real_var, _) => {
for var_index in args.into_iter() {
@ -3505,8 +3673,19 @@ fn explicit_substitute(
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext)));
}
Tuple(vars_by_elem, ext) => {
let new_ext = explicit_substitute(subs, from, to, ext, seen);
EmptyRecord | EmptyTagUnion => {}
for index in vars_by_elem.iter_variables() {
let var = subs[index];
let new_var = explicit_substitute(subs, from, to, var, seen);
subs[index] = new_var;
}
subs.set_content(in_var, Structure(Tuple(vars_by_elem, new_ext)));
}
EmptyRecord | EmptyTuple | EmptyTagUnion => {}
}
in_var
@ -3689,7 +3868,9 @@ fn get_var_names(
accum
}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => taken_names,
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => {
taken_names
}
FlatType::Record(vars_by_field, ext) => {
let mut accum = get_var_names(subs, ext, taken_names);
@ -3702,6 +3883,17 @@ fn get_var_names(
accum
}
FlatType::Tuple(vars_by_elems, ext) => {
let mut accum = get_var_names(subs, ext, taken_names);
for var_index in vars_by_elems.iter_variables() {
let arg_var = subs[var_index];
accum = get_var_names(subs, arg_var, accum)
}
accum
}
FlatType::TagUnion(tags, ext) => {
let taken_names = get_var_names(subs, ext.var(), taken_names);
get_var_names_union(subs, tags, taken_names)
@ -3938,6 +4130,55 @@ fn content_to_err_type(
}
}
fn sorted_union<T>(a: Vec<(usize, T)>, b: Vec<(usize, T)>) -> Vec<(usize, T)> {
// Asuming the two slices are sorted (by the first element), merge them into a single sorted slice
// If we see duplicates, panic.
let mut result = Vec::with_capacity(a.len() + b.len());
let mut a_iter = a.into_iter();
let mut b_iter = b.into_iter();
let mut a_next = a_iter.next();
let mut b_next = b_iter.next();
loop {
match (a_next, b_next) {
(Some((a_index, a_value)), Some((b_index, b_value))) => {
if a_index == b_index {
panic!("Duplicate index in sorted_union");
}
if a_index < b_index {
result.push((a_index, a_value));
a_next = a_iter.next();
b_next = Some((b_index, b_value));
} else {
result.push((b_index, b_value));
b_next = b_iter.next();
a_next = Some((a_index, a_value));
}
}
(Some((a_index, a_value)), None) => {
result.push((a_index, a_value));
a_next = a_iter.next();
b_next = None;
}
(None, Some((b_index, b_value))) => {
result.push((b_index, b_value));
b_next = b_iter.next();
a_next = None;
}
(None, None) => break,
}
}
result
}
fn flat_type_to_err_type(
subs: &mut Subs,
state: &mut ErrorTypeState,
@ -3975,6 +4216,7 @@ fn flat_type_to_err_type(
}
EmptyRecord => ErrorType::Record(SendMap::default(), TypeExt::Closed),
EmptyTuple => ErrorType::Tuple(Vec::default(), TypeExt::Closed),
EmptyTagUnion => ErrorType::TagUnion(SendMap::default(), TypeExt::Closed, pol),
Record(vars_by_field, ext) => {
@ -4019,6 +4261,38 @@ fn flat_type_to_err_type(
}
}
Tuple(vars_by_elems, ext) => {
let mut err_elems = Vec::default();
for (i1, i2) in vars_by_elems.iter_all() {
let index = subs[i1];
let var = subs[i2];
let error_type = var_to_err_type(subs, state, var, pol);
err_elems.push((index, error_type));
}
match var_to_err_type(subs, state, ext, pol).unwrap_structural_alias() {
ErrorType::Tuple(sub_elems, sub_ext) => {
ErrorType::Tuple(sorted_union(sub_elems, err_elems), sub_ext)
}
ErrorType::FlexVar(var) => {
ErrorType::Tuple(err_elems, TypeExt::FlexOpen(var))
}
ErrorType::RigidVar(var) => {
ErrorType::Tuple(err_elems, TypeExt::RigidOpen(var))
}
ErrorType::Error => ErrorType::Tuple(err_elems, TypeExt::Closed),
other =>
panic!("Tried to convert a record extension to an error, but the record extension had the ErrorType of {:?}", other)
}
}
TagUnion(tags, ext) => {
let err_tags = union_tags_to_err_tags(subs, state, tags, pol);
@ -4369,6 +4643,10 @@ impl StorageSubs {
Self::offset_record_fields(offsets, *record_fields),
Self::offset_variable(offsets, *ext),
),
FlatType::Tuple(tuple_elems, ext) => FlatType::Tuple(
Self::offset_tuple_elems(offsets, *tuple_elems),
Self::offset_variable(offsets, *ext),
),
FlatType::TagUnion(union_tags, ext) => FlatType::TagUnion(
Self::offset_tag_union(offsets, *union_tags),
ext.map(|v| Self::offset_variable(offsets, v)),
@ -4384,6 +4662,7 @@ impl StorageSubs {
ext.map(|v| Self::offset_variable(offsets, v)),
),
FlatType::EmptyRecord => FlatType::EmptyRecord,
FlatType::EmptyTuple => FlatType::EmptyTuple,
FlatType::EmptyTagUnion => FlatType::EmptyTagUnion,
}
}
@ -4476,6 +4755,12 @@ impl StorageSubs {
record_fields
}
fn offset_tuple_elems(offsets: &StorageSubsOffsets, mut tuple_elems: TupleElems) -> TupleElems {
tuple_elems.variables_start += offsets.variables;
tuple_elems
}
fn offset_tag_name_slice(
offsets: &StorageSubsOffsets,
mut tag_names: SubsSlice<TagName>,
@ -4670,7 +4955,7 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTagUnion => same,
same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same,
Record(fields, ext) => {
let record_fields = {
@ -4707,6 +4992,33 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
Record(record_fields, storage_copy_var_to_help(env, ext))
}
Tuple(elems, ext) => {
let tuple_elems = {
let new_variables =
VariableSubsSlice::reserve_into_subs(env.target, elems.len());
let it = (new_variables.indices()).zip(elems.iter_variables());
for (target_index, var_index) in it {
let var = env.source[var_index];
let copy_var = storage_copy_var_to_help(env, var);
env.target.variables[target_index] = copy_var;
}
let elem_index_start = env.target.tuple_elem_indices.len() as u32;
// TODO: introduce a dense variant of TupleElems, by making the indices a result
env.target.tuple_elem_indices.extend(0..elems.len());
TupleElems {
length: elems.len() as _,
variables_start: new_variables.start,
elem_index_start,
}
};
Tuple(tuple_elems, storage_copy_var_to_help(env, ext))
}
TagUnion(tags, ext) => {
let new_ext = ext.map(|v| storage_copy_var_to_help(env, v));
let union_tags = storage_copy_union(env, tags);
@ -5118,7 +5430,7 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTagUnion => same,
same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same,
Record(fields, ext) => {
let record_fields = {
@ -5155,6 +5467,33 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
Record(record_fields, copy_import_to_help(env, max_rank, ext))
}
Tuple(elems, ext) => {
let tuple_elems = {
let new_variables =
VariableSubsSlice::reserve_into_subs(env.target, elems.len());
let it = (new_variables.indices()).zip(elems.iter_variables());
for (target_index, var_index) in it {
let var = env.source[var_index];
let copy_var = copy_import_to_help(env, max_rank, var);
env.target.variables[target_index] = copy_var;
}
let elem_index_start = env.target.tuple_elem_indices.len() as u32;
// TODO: introduce a dense variant of TupleElems, by making the indices a result
env.target.tuple_elem_indices.extend(0..elems.len());
TupleElems {
length: elems.len() as _,
variables_start: new_variables.start,
elem_index_start,
}
};
Tuple(tuple_elems, copy_import_to_help(env, max_rank, ext))
}
TagUnion(tags, ext) => {
let new_ext = ext.map(|v| copy_import_to_help(env, max_rank, v));
@ -5450,8 +5789,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
stack.push(closure_var);
}
EmptyRecord => (),
EmptyTagUnion => (),
EmptyRecord | EmptyTuple | EmptyTagUnion => (),
Record(fields, ext) => {
let fields = *fields;
@ -5460,6 +5798,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
stack.push(ext);
}
Tuple(elems, ext) => {
let elems = *elems;
let ext = *ext;
stack.extend(var_slice!(elems.variables()));
stack.push(ext);
}
TagUnion(tags, ext) => {
let tags = *tags;
let ext = *ext;
@ -5570,6 +5916,10 @@ pub fn get_member_lambda_sets_at_region(subs: &Subs, var: Variable, target_regio
stack.extend(subs.get_subs_slice(fields.variables()));
stack.push(*ext);
}
FlatType::Tuple(elems, ext) => {
stack.extend(subs.get_subs_slice(elems.variables()));
stack.push(*ext);
}
FlatType::TagUnion(tags, ext) => {
stack.extend(
subs.get_subs_slice(tags.variables())
@ -5590,7 +5940,7 @@ pub fn get_member_lambda_sets_at_region(subs: &Subs, var: Variable, target_regio
);
stack.push(ext.var());
}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => {}
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => {}
},
Content::Alias(_, _, real_var, _) => {
stack.push(*real_var);
@ -5642,6 +5992,12 @@ fn is_inhabited(subs: &Subs, var: Variable) -> bool {
stack.extend(field_vars)
}
}
FlatType::Tuple(elems, ext) => {
if let Ok(iter) = elems.unsorted_iterator(subs, *ext) {
let elem_vars = iter.map(|(_, elem)| elem);
stack.extend(elem_vars)
}
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
let mut is_uninhabited = true;
// If any tag is inhabited, the union is inhabited!
@ -5658,6 +6014,7 @@ fn is_inhabited(subs: &Subs, var: Variable) -> bool {
}
FlatType::FunctionOrTagUnion(_, _, _) => {}
FlatType::EmptyRecord => {}
FlatType::EmptyTuple => {}
FlatType::EmptyTagUnion => {
return false;
}

View file

@ -1,7 +1,8 @@
use crate::num::NumericRange;
use crate::pretty_print::Parens;
use crate::subs::{
GetSubsSlice, RecordFields, Subs, TagExt, UnionTags, VarStore, Variable, VariableSubsSlice,
GetSubsSlice, RecordFields, Subs, TagExt, TupleElems, UnionTags, VarStore, Variable,
VariableSubsSlice,
};
use roc_collections::all::{HumanIndex, ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_collections::soa::{Index, Slice};
@ -436,6 +437,7 @@ pub enum TypeTag {
TagUnion(UnionTags, ExtImplicitOpenness),
RecursiveTagUnion(Variable, UnionTags, ExtImplicitOpenness),
Record(RecordFields),
Tuple(TupleElems),
}
/// Look-aside slice of types used in [Types], when the slice does not correspond to the direct
@ -485,6 +487,9 @@ pub struct Types {
field_types: Vec<RecordField<()>>,
field_names: Vec<Lowercase>,
// tuples
tuple_elem_indices: Vec<usize>,
// aliases
type_arg_abilities: Vec<AbilitySet>, // TODO: structural sharing for `AbilitySet`s themselves
aliases: Vec<AliasShared>,
@ -533,6 +538,7 @@ impl Types {
tag_names: Default::default(),
field_types: Default::default(),
field_names: Default::default(),
tuple_elem_indices: Default::default(),
type_arg_abilities: Default::default(),
aliases: Default::default(),
single_tag_union_tag_names: Default::default(),
@ -573,6 +579,19 @@ impl Types {
(names, fields, tys)
}
pub fn tuple_elems_slices(&self, elems: TupleElems) -> (Slice<usize>, Slice<TypeTag>) {
let TupleElems {
length,
variables_start,
elem_index_start,
} = elems;
let index = Slice::new(elem_index_start, length);
let tys = Slice::new(variables_start, length);
(index, tys)
}
pub fn union_tag_slices(&self, union: UnionTags) -> (Slice<TagName>, Slice<AsideTypeSlice>) {
let UnionTags {
length,
@ -853,6 +872,35 @@ impl Types {
let tag = TypeTag::Record(record_fields);
self.set_type_tag(index, tag, type_slice)
}
Type::Tuple(elems, extension) => {
let type_slice = match extension {
TypeExtension::Open(ext, _) => self.from_old_type(ext).as_slice(),
TypeExtension::Closed => Slice::default(),
};
// should we sort at this point?
let elem_type_slice = {
let slice = self.reserve_type_tags(elems.len());
for (index, (_elem_index, argument)) in slice.into_iter().zip(elems.iter()) {
self.from_old_type_at(index, argument);
}
slice
};
let elem_index_slice =
Slice::extend_new(&mut self.tuple_elem_indices, elems.iter().map(|(i, _)| *i));
let tuple_elems = TupleElems {
length: elems.len() as u16,
variables_start: elem_type_slice.start() as u32,
elem_index_start: elem_index_slice.start() as u32,
};
let tag = TypeTag::Tuple(tuple_elems);
self.set_type_tag(index, tag, type_slice)
}
Type::ClosureTag {
name,
captures,
@ -1262,6 +1310,21 @@ impl Types {
(Record(new_record_fields), new_ext_slice)
}
Tuple(elems) => {
let ext_slice = self.get_type_arguments(typ);
let (indices, tys) = self.tuple_elems_slices(elems);
let new_tys = defer_slice!(tys);
let new_ext_slice = defer_slice!(ext_slice);
let new_tuple_elems = TupleElems {
length: new_tys.len() as _,
variables_start: new_tys.start() as _,
elem_index_start: indices.start() as _,
};
(Tuple(new_tuple_elems), new_ext_slice)
}
RangedNumber(range) => (RangedNumber(range), Default::default()),
Error => (Error, Default::default()),
};
@ -1448,6 +1511,20 @@ mod debug_types {
.align(),
)
}
TypeTag::Tuple(elems) => {
let (_indices, tys) = types.tuple_elems_slices(elems);
let fmt_fields = tys.into_iter().map(|ty| typ(types, f, Free, ty));
f.text("(").append(
f.intersperse(fmt_fields, f.reflow(", "))
.append(
f.text(")")
.append(ext(types, f, types.get_type_arguments(tag))),
)
.group()
.align(),
)
}
};
group.group()
}
@ -1616,6 +1693,7 @@ impl_types_index! {
tag_names, TagName
field_types, RecordField<()>
field_names, Lowercase
tuple_elem_indices, usize
}
impl_types_index_slice! {
@ -1645,6 +1723,7 @@ pub enum Type {
/// A function. The types of its arguments, size of its closure, then the type of its return value.
Function(Vec<Type>, Box<Type>, Box<Type>),
Record(SendMap<Lowercase, RecordField<Type>>, TypeExtension),
Tuple(VecMap<usize, Type>, TypeExtension),
TagUnion(Vec<(TagName, Vec<Type>)>, TypeExtension),
FunctionOrTagUnion(TagName, Symbol, TypeExtension),
/// A function name that is used in our defunctionalization algorithm. For example in
@ -1727,6 +1806,7 @@ impl Clone for Type {
Self::Function(arg0.clone(), arg1.clone(), arg2.clone())
}
Self::Record(arg0, arg1) => Self::Record(arg0.clone(), arg1.clone()),
Self::Tuple(arg0, arg1) => Self::Tuple(arg0.clone(), arg1.clone()),
Self::TagUnion(arg0, arg1) => Self::TagUnion(arg0.clone(), arg1.clone()),
Self::FunctionOrTagUnion(arg0, arg1, arg2) => {
Self::FunctionOrTagUnion(arg0.clone(), *arg1, arg2.clone())
@ -2042,6 +2122,46 @@ impl fmt::Debug for Type {
}
}
}
Type::Tuple(elems, ext) => {
write!(f, "(")?;
if !elems.is_empty() {
write!(f, " ")?;
}
let mut any_written_yet = false;
for (_, field_type) in elems.iter() {
write!(f, "{:?}", field_type)?;
if any_written_yet {
write!(f, ", ")?;
} else {
any_written_yet = true;
}
}
if !elems.is_empty() {
write!(f, " ")?;
}
write!(f, ")")?;
match ext {
TypeExtension::Closed => {
// This is a closed record. We're done!
Ok(())
}
TypeExtension::Open(other, _) => {
// This is an open record, so print the variable
// right after the '}'
//
// e.g. the "*" at the end of `{ x: Int }*`
// or the "r" at the end of `{ x: Int }r`
other.fmt(f)
}
}
}
Type::TagUnion(tags, ext) => {
write_tags(f, tags.iter())?;
@ -2212,6 +2332,15 @@ impl Type {
stack.push(ext);
}
}
Tuple(elems, ext) => {
for (_, x) in elems.iter_mut() {
stack.push(x);
}
if let TypeExtension::Open(ext, _) = ext {
stack.push(ext);
}
}
Type::DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
@ -2344,6 +2473,14 @@ impl Type {
stack.push(ext);
}
}
Tuple(elems, ext) => {
for (_, x) in elems.iter_mut() {
stack.push(x);
}
if let TypeExtension::Open(ext, _) = ext {
stack.push(ext);
}
}
Type::DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
@ -2463,6 +2600,18 @@ impl Type {
TypeExtension::Closed => Ok(()),
}
}
Tuple(elems, ext) => {
for (_, x) in elems.iter_mut() {
x.substitute_alias(rep_symbol, rep_args, actual)?;
}
match ext {
TypeExtension::Open(ext, _) => {
ext.substitute_alias(rep_symbol, rep_args, actual)
}
TypeExtension::Closed => Ok(()),
}
}
DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables: _no_aliases_in_lambda_sets,
@ -2550,6 +2699,10 @@ impl Type {
Self::contains_symbol_ext(ext, rep_symbol)
|| fields.values().any(|arg| arg.contains_symbol(rep_symbol))
}
Tuple(elems, ext) => {
Self::contains_symbol_ext(ext, rep_symbol)
|| elems.iter().any(|(_, arg)| arg.contains_symbol(rep_symbol))
}
DelayedAlias(AliasCommon {
symbol,
type_arguments,
@ -2623,6 +2776,12 @@ impl Type {
.values()
.any(|arg| arg.contains_variable(rep_variable))
}
Tuple(elems, ext) => {
Self::contains_variable_ext(ext, rep_variable)
|| elems
.iter()
.any(|(_i, arg)| arg.contains_variable(rep_variable))
}
DelayedAlias(AliasCommon { .. }) => {
todo!()
}
@ -2758,6 +2917,27 @@ impl Type {
);
}
}
Tuple(elems, ext) => {
for (_, x) in elems.iter_mut() {
x.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
if let TypeExtension::Open(ext, _) = ext {
ext.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
@ -3111,6 +3291,10 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
stack.extend(ext);
stack.extend(fields.values().map(|field| field.as_inner()));
}
Tuple(elems, ext) => {
stack.extend(ext);
stack.extend(elems.iter().map(|(_, t)| t));
}
DelayedAlias(AliasCommon {
symbol,
type_arguments,
@ -3181,6 +3365,15 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
variables_help(ext, accum);
}
}
Tuple(elems, ext) => {
for (_, elem) in elems.iter() {
variables_help(elem, accum);
}
if let TypeExtension::Open(ext, _) = ext {
variables_help(ext, accum);
}
}
ClosureTag {
name: _,
captures,
@ -3316,6 +3509,15 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
variables_help_detailed(ext, accum);
}
}
Tuple(elems, ext) => {
for (_, elem) in elems.iter() {
variables_help_detailed(elem, accum);
}
if let TypeExtension::Open(ext, _) = ext {
variables_help_detailed(ext, accum);
}
}
ClosureTag {
name: _,
captures,
@ -3420,6 +3622,13 @@ pub struct RecordStructure {
pub ext: Variable,
}
#[derive(Debug)]
pub struct TupleStructure {
/// Invariant: these should be sorted!
pub elems: Vec<(usize, Variable)>,
pub ext: Variable,
}
#[derive(Debug)]
pub struct TagUnionStructure<'a> {
/// Invariant: these should be sorted!
@ -3566,8 +3775,11 @@ pub enum Category {
// records
Record,
Accessor(Lowercase),
Access(Lowercase),
RecordAccessor(Lowercase),
RecordAccess(Lowercase),
Tuple,
TupleAccessor(usize),
TupleAccess(usize),
DefaultValue(Lowercase), // for setting optional fields
AbilityMemberSpecialization(Symbol),
@ -3706,6 +3918,7 @@ pub enum ErrorType {
FlexAbleVar(Lowercase, AbilitySet),
RigidAbleVar(Lowercase, AbilitySet),
Record(SendMap<Lowercase, RecordField<ErrorType>>, TypeExt),
Tuple(Vec<(usize, ErrorType)>, TypeExt),
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt, Polarity),
RecursiveTagUnion(
Box<ErrorType>,
@ -3749,6 +3962,10 @@ impl ErrorType {
.for_each(|(_, t)| t.as_inner().add_names(taken));
ext.add_names(taken);
}
Tuple(elems, ext) => {
elems.iter().for_each(|(_, t)| t.add_names(taken));
ext.add_names(taken);
}
TagUnion(tags, ext, _) => {
tags.iter()
.for_each(|(_, ts)| ts.iter().for_each(|t| t.add_names(taken)));
@ -4045,6 +4262,16 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
buf.push('}');
write_type_ext(ext, buf);
}
Tuple(elems, ext) => {
buf.push('(');
for (_index, elem) in elems {
write_debug_error_type_help(elem, buf, Parens::Unnecessary);
}
buf.push(')');
write_type_ext(ext, buf);
}
TagUnion(tags, ext, _pol) => {
buf.push('[');
@ -4256,6 +4483,61 @@ pub fn gather_fields_unsorted_iter(
Ok((it, var))
}
#[derive(Debug, Copy, Clone)]
pub struct TupleElemsError;
pub fn gather_tuple_elems_unsorted_iter(
subs: &Subs,
other_elems: TupleElems,
mut var: Variable,
) -> Result<(impl Iterator<Item = (usize, Variable)> + '_, Variable), TupleElemsError> {
use crate::subs::Content::*;
use crate::subs::FlatType::*;
let mut stack = vec![other_elems];
loop {
match subs.get_content_without_compacting(var) {
Structure(Tuple(sub_elems, sub_ext)) => {
stack.push(*sub_elems);
if var == Variable::EMPTY_TUPLE {
break;
} else {
var = *sub_ext;
}
}
Alias(_, _, actual_var, _) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var;
}
FlexVar(_) | FlexAbleVar(..) => break,
// TODO investigate apparently this one pops up in the reporting tests!
RigidVar(_) | RigidAbleVar(..) => break,
// Stop on errors in the record
Error => break,
_ => return Err(TupleElemsError),
}
}
let it = stack
.into_iter()
.flat_map(|elems| elems.iter_all())
.map(move |(i1, i2)| {
let elem_index: &usize = &subs[i1];
let variable = subs[i2];
(*elem_index, variable)
});
Ok((it, var))
}
pub fn gather_fields(
subs: &Subs,
other_fields: RecordFields,
@ -4275,6 +4557,20 @@ pub fn gather_fields(
})
}
pub fn gather_tuple_elems(
subs: &Subs,
other_elems: TupleElems,
var: Variable,
) -> Result<TupleStructure, TupleElemsError> {
let (it, ext) = gather_tuple_elems_unsorted_iter(subs, other_elems, var)?;
let mut result: Vec<_> = it.collect();
result.sort_by(|(a, _), (b, _)| a.cmp(b));
Ok(TupleStructure { elems: result, ext })
}
#[derive(Debug)]
pub enum GatherTagsError {
NotATagUnion(Variable),
@ -4446,6 +4742,12 @@ fn instantiate_lambda_sets_as_unspecialized(
stack.push(x.as_inner_mut());
}
}
Type::Tuple(elems, ext) => {
stack.extend(ext.iter_mut());
for (_, x) in elems.iter_mut() {
stack.push(x);
}
}
Type::TagUnion(tags, ext) | Type::RecursiveTagUnion(_, tags, ext) => {
stack.extend(ext.iter_mut());
for (_, ts) in tags {

View file

@ -10,8 +10,8 @@ use roc_types::num::{FloatWidth, IntLitWidth, NumericRange};
use roc_types::subs::Content::{self, *};
use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, LambdaSet, Mark,
OptVariable, RecordFields, Subs, SubsIndex, SubsSlice, TagExt, UlsOfVar, UnionLabels,
UnionLambdas, UnionTags, Variable, VariableSubsSlice,
OptVariable, RecordFields, Subs, SubsIndex, SubsSlice, TagExt, TupleElems, UlsOfVar,
UnionLabels, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::{
AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, Polarity, RecordField, Uls,
@ -2029,6 +2029,118 @@ fn unify_record<M: MetaCollector>(
}
}
#[must_use]
fn unify_tuple<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
elems1: TupleElems,
ext1: Variable,
elems2: TupleElems,
ext2: Variable,
) -> Outcome<M> {
let subs = &mut env.subs;
let (separate, ext1, ext2) = separate_tuple_elems(subs, elems1, ext1, elems2, ext2);
let shared_elems = separate.in_both;
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
// these variable will be the empty tuple, but we must still unify them
let ext_outcome = unify_pool(env, pool, ext1, ext2, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_outcome =
unify_shared_tuple_elems(env, pool, ctx, shared_elems, OtherTupleElems::None, ext1);
field_outcome.union(ext_outcome);
field_outcome
} else {
let only_in_2 = TupleElems::insert_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::Tuple(only_in_2, ext2);
let sub_record = fresh(env, pool, ctx, Structure(flat_type));
let ext_outcome = unify_pool(env, pool, ext1, sub_record, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_outcome = unify_shared_tuple_elems(
env,
pool,
ctx,
shared_elems,
OtherTupleElems::None,
sub_record,
);
field_outcome.union(ext_outcome);
field_outcome
}
} else if separate.only_in_2.is_empty() {
let only_in_1 = TupleElems::insert_into_subs(subs, separate.only_in_1);
let flat_type = FlatType::Tuple(only_in_1, ext1);
let sub_record = fresh(env, pool, ctx, Structure(flat_type));
let ext_outcome = unify_pool(env, pool, sub_record, ext2, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_outcome = unify_shared_tuple_elems(
env,
pool,
ctx,
shared_elems,
OtherTupleElems::None,
sub_record,
);
field_outcome.union(ext_outcome);
field_outcome
} else {
let only_in_1 = TupleElems::insert_into_subs(subs, separate.only_in_1);
let only_in_2 = TupleElems::insert_into_subs(subs, separate.only_in_2);
let other_fields = OtherTupleElems::Other(only_in_1, only_in_2);
let ext = fresh(env, pool, ctx, Content::FlexVar(None));
let flat_type1 = FlatType::Tuple(only_in_1, ext);
let flat_type2 = FlatType::Tuple(only_in_2, ext);
let sub1 = fresh(env, pool, ctx, Structure(flat_type1));
let sub2 = fresh(env, pool, ctx, Structure(flat_type2));
let rec1_outcome = unify_pool(env, pool, ext1, sub2, ctx.mode);
if !rec1_outcome.mismatches.is_empty() {
return rec1_outcome;
}
let rec2_outcome = unify_pool(env, pool, sub1, ext2, ctx.mode);
if !rec2_outcome.mismatches.is_empty() {
return rec2_outcome;
}
let mut field_outcome =
unify_shared_tuple_elems(env, pool, ctx, shared_elems, other_fields, ext);
field_outcome
.mismatches
.reserve(rec1_outcome.mismatches.len() + rec2_outcome.mismatches.len());
field_outcome.union(rec1_outcome);
field_outcome.union(rec2_outcome);
field_outcome
}
}
enum OtherFields {
None,
Other(RecordFields, RecordFields),
@ -2172,6 +2284,89 @@ fn unify_shared_fields<M: MetaCollector>(
}
}
enum OtherTupleElems {
None,
Other(TupleElems, TupleElems),
}
type SharedTupleElems = Vec<(usize, (Variable, Variable))>;
#[must_use]
fn unify_shared_tuple_elems<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
shared_elems: SharedTupleElems,
other_elems: OtherTupleElems,
ext: Variable,
) -> Outcome<M> {
let mut matching_elems = Vec::with_capacity(shared_elems.len());
let num_shared_elems = shared_elems.len();
let mut whole_outcome = Outcome::default();
for (name, (actual, expected)) in shared_elems {
let local_outcome = unify_pool(env, pool, actual, expected, ctx.mode);
if local_outcome.mismatches.is_empty() {
let actual = choose_merged_var(env.subs, actual, expected);
matching_elems.push((name, actual));
whole_outcome.union(local_outcome);
}
}
if num_shared_elems == matching_elems.len() {
// pull elems in from the ext_var
let (ext_elems, new_ext_var) = TupleElems::empty().sorted_iterator_and_ext(env.subs, ext);
let ext_elems: Vec<_> = ext_elems.into_iter().collect();
let elems: TupleElems = match other_elems {
OtherTupleElems::None => {
if ext_elems.is_empty() {
TupleElems::insert_into_subs(env.subs, matching_elems)
} else {
let all_elems = merge_sorted(matching_elems, ext_elems);
TupleElems::insert_into_subs(env.subs, all_elems)
}
}
OtherTupleElems::Other(other1, other2) => {
let mut all_elems = merge_sorted(matching_elems, ext_elems);
all_elems = merge_sorted(
all_elems,
other1.iter_all().map(|(i1, i2)| {
let elem_index: usize = env.subs[i1];
let variable = env.subs[i2];
(elem_index, variable)
}),
);
all_elems = merge_sorted(
all_elems,
other2.iter_all().map(|(i1, i2)| {
let elem_index: usize = env.subs[i1];
let variable = env.subs[i2];
(elem_index, variable)
}),
);
TupleElems::insert_into_subs(env.subs, all_elems)
}
};
let flat_type = FlatType::Tuple(elems, new_ext_var);
let merge_outcome = merge(env, ctx, Structure(flat_type));
whole_outcome.union(merge_outcome);
whole_outcome
} else {
mismatch!("in unify_shared_tuple_elems")
}
}
fn separate_record_fields(
subs: &Subs,
fields1: RecordFields,
@ -2192,6 +2387,22 @@ fn separate_record_fields(
(separate(it1, it2), new_ext1, new_ext2)
}
fn separate_tuple_elems(
subs: &Subs,
elems1: TupleElems,
ext1: Variable,
elems2: TupleElems,
ext2: Variable,
) -> (Separate<usize, Variable>, Variable, Variable) {
let (it1, new_ext1) = elems1.sorted_iterator_and_ext(subs, ext1);
let (it2, new_ext2) = elems2.sorted_iterator_and_ext(subs, ext2);
let it1 = it1.collect::<Vec<_>>();
let it2 = it2.collect::<Vec<_>>();
(separate(it1, it2), new_ext1, new_ext2)
}
// TODO: consider combining with `merge_sorted_help` with a `by_key` predicate.
// But that might not get inlined!
fn merge_sorted_keys<K, I1, I2>(input1: I1, input2: I2) -> Vec<K>
@ -3020,6 +3231,10 @@ fn unify_flat_type<M: MetaCollector>(
unify_record(env, pool, ctx, *fields1, *ext1, *fields2, *ext2)
}
(Tuple(elems1, ext1), Tuple(elems2, ext2)) => {
unify_tuple(env, pool, ctx, *elems1, *ext1, *elems2, *ext2)
}
(EmptyTagUnion, EmptyTagUnion) => merge(env, ctx, Structure(*left)),
(TagUnion(tags, ext), EmptyTagUnion) if tags.is_empty() => {