implement mono / lowering for tuples

This commit is contained in:
Joshua Warner 2023-01-24 20:23:17 -08:00
parent 65f8bb3d0d
commit 5a6be05ead
No known key found for this signature in database
GPG Key ID: 89AD497003F93FDD
42 changed files with 1773 additions and 290 deletions

View File

@ -9,7 +9,7 @@ use roc_module::{
use roc_region::all::Region;
use roc_types::{
subs::Variable,
types::{self, AnnotationSource, PReason, PatternCategory},
types::{self, AnnotationSource, IndexOrField, PReason, PatternCategory},
types::{Category, Reason},
};
@ -375,7 +375,7 @@ pub fn constrain_expr<'a>(
env.pool.add(ext_type),
);
let category = Category::RecordAccessor(field.as_str(env.pool).into());
let category = Category::Accessor(IndexOrField::Field(field.as_str(env.pool).into()));
let record_expected = Expected::NoExpectation(record_type.shallow_clone());
let record_con = Eq(

View File

@ -6,6 +6,7 @@ use roc_can::num::{
use roc_can::operator::desugar_expr;
use roc_collections::all::MutSet;
use roc_module::symbol::Symbol;
use roc_parse::ident::Accessor;
use roc_parse::{ast::Expr, pattern::PatternType};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
@ -295,7 +296,7 @@ pub fn expr_to_expr2<'a>(
)
}
RecordAccessorFunction(field) => (
AccessorFunction(Accessor::RecordField(field)) => (
Expr2::Accessor {
function_var: env.var_store.fresh(),
record_var: env.var_store.fresh(),

View File

@ -405,7 +405,7 @@ pub fn to_type2<'a>(
Type2::Variable(var)
}
Tuple { fields: _, ext: _ } => {
Tuple { elems: _, ext: _ } => {
todo!("tuple type");
}
Record { fields, ext, .. } => {

View File

@ -448,7 +448,7 @@ pub fn find_type_def_symbols(
As(actual, _, _) => {
stack.push(&actual.value);
}
Tuple { fields: _, ext: _ } => {
Tuple { elems: _, ext: _ } => {
todo!("find_type_def_symbols: Tuple");
}
Record { fields, ext } => {
@ -872,8 +872,41 @@ fn can_annotation_help(
}
}
Tuple { fields: _, ext: _ } => {
todo!("tuple");
Tuple { elems, ext } => {
let (ext_type, is_implicit_openness) = can_extension_type(
env,
pol,
scope,
var_store,
introduced_variables,
local_aliases,
references,
ext,
roc_problem::can::ExtensionTypeKind::Record,
);
debug_assert!(
matches!(is_implicit_openness, ExtImplicitOpenness::No),
"tuples should never be implicitly inferred open"
);
debug_assert!(!elems.is_empty()); // We don't allow empty tuples
let elem_types = can_assigned_tuple_elems(
env,
pol,
&elems.items,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
Type::Tuple(
elem_types,
TypeExtension::from_type(ext_type, is_implicit_openness),
)
}
Record { fields, ext } => {
let (ext_type, is_implicit_openness) = can_extension_type(
@ -1440,6 +1473,39 @@ fn can_assigned_fields<'a>(
field_types
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_assigned_tuple_elems<'a>(
env: &mut Env,
pol: CanPolarity,
elems: &&[Loc<TypeAnnotation<'a>>],
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
) -> VecMap<usize, Type> {
let mut elem_types = VecMap::with_capacity(elems.len());
for (index, loc_elem) in elems.iter().enumerate() {
let elem_type = can_annotation_help(
env,
pol,
&loc_elem.value,
loc_elem.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
elem_types.insert(index, elem_type);
}
elem_types
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_tags<'a>(

View File

@ -1,10 +1,9 @@
use crate::{
def::Def,
expr::{
ClosureData, Expr, Field, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData,
WhenBranchPattern,
ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranchPattern,
},
pattern::{DestructType, ListPatterns, Pattern, RecordDestruct},
pattern::{DestructType, ListPatterns, Pattern, RecordDestruct, TupleDestruct},
};
use roc_module::{
ident::{Lowercase, TagName},
@ -513,7 +512,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
field: field.clone(),
},
RecordAccessor(RecordAccessorData {
RecordAccessor(StructAccessorData {
name,
function_var,
record_var,
@ -521,7 +520,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
ext_var,
field_var,
field,
}) => RecordAccessor(RecordAccessorData {
}) => RecordAccessor(StructAccessorData {
name: *name,
function_var: sub!(*function_var),
record_var: sub!(*record_var),
@ -545,24 +544,6 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
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,
@ -794,6 +775,30 @@ fn deep_copy_pattern_help<C: CopyEnv>(
})
.collect(),
},
TupleDestructure {
whole_var,
ext_var,
destructs,
} => TupleDestructure {
whole_var: sub!(*whole_var),
ext_var: sub!(*ext_var),
destructs: destructs
.iter()
.map(|lrd| {
lrd.map(
|TupleDestruct {
destruct_index: index,
var,
typ: (tyvar, pat),
}: &crate::pattern::TupleDestruct| TupleDestruct {
destruct_index: *index,
var: sub!(*var),
typ: (sub!(*tyvar), pat.map(|p| go_help!(p))),
},
)
})
.collect(),
},
List {
list_var,
elem_var,

View File

@ -5,7 +5,7 @@ use crate::expr::Expr::{self, *};
use crate::expr::{
ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData, WhenBranch,
};
use crate::pattern::{Pattern, RecordDestruct};
use crate::pattern::{Pattern, RecordDestruct, TupleDestruct};
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -335,7 +335,6 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
f.text(format!("@{}", opaque_name.as_str(c.interns)))
}
RecordAccessor(_) => todo!(),
TupleAccessor(_) => todo!(),
RecordUpdate {
symbol, updates, ..
} => f
@ -505,6 +504,19 @@ fn pattern<'a>(
)
.append(f.text("}"))
.group(),
TupleDestructure { destructs, .. } => f
.text("(")
.append(
f.intersperse(
destructs
.iter()
.map(|l| &l.value)
.map(|TupleDestruct { typ: (_, p), .. }| pattern(c, Free, f, &p.value)),
f.text(", "),
),
)
.append(f.text(")"))
.group(),
List { .. } => todo!(),
NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => {
f.text(&**n)

View File

@ -15,7 +15,7 @@ use crate::expr::AnnotatedMark;
use crate::expr::ClosureData;
use crate::expr::Declarations;
use crate::expr::Expr::{self, *};
use crate::expr::RecordAccessorData;
use crate::expr::StructAccessorData;
use crate::expr::{canonicalize_expr, Output, Recursive};
use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern};
use crate::procedure::References;
@ -36,6 +36,7 @@ use roc_parse::ast::AssignedField;
use roc_parse::ast::Defs;
use roc_parse::ast::ExtractSpaces;
use roc_parse::ast::TypeHeader;
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
@ -45,6 +46,7 @@ use roc_types::subs::{VarStore, Variable};
use roc_types::types::AliasCommon;
use roc_types::types::AliasKind;
use roc_types::types::AliasVar;
use roc_types::types::IndexOrField;
use roc_types::types::LambdaSet;
use roc_types::types::MemberImpl;
use roc_types::types::OptAbleType;
@ -1995,6 +1997,16 @@ fn pattern_to_vars_by_symbol(
vars_by_symbol.insert(*opaque, expr_var);
}
TupleDestructure { destructs, .. } => {
for destruct in destructs {
pattern_to_vars_by_symbol(
vars_by_symbol,
&destruct.value.typ.1.value,
destruct.value.typ.0,
);
}
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
@ -2316,19 +2328,23 @@ fn canonicalize_pending_body<'a>(
ident: defined_symbol,
..
},
ast::Expr::RecordAccessorFunction(field),
ast::Expr::AccessorFunction(field),
) => {
let field = match field {
Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
};
let (loc_can_expr, can_output) = (
Loc::at(
loc_expr.region,
RecordAccessor(RecordAccessorData {
RecordAccessor(StructAccessorData {
name: *defined_symbol,
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
field,
}),
),
Output::default(),

View File

@ -81,6 +81,8 @@ enum IndexCtor<'a> {
Opaque,
/// Index a record type. The arguments are the types of the record fields.
Record(&'a [Lowercase]),
/// Index a tuple type.
Tuple,
/// Index a guard constructor. The arguments are a faux guard pattern, and then the real
/// pattern being guarded. E.g. `A B if g` becomes Guard { [True, (A B)] }.
Guard,
@ -113,6 +115,7 @@ impl<'a> IndexCtor<'a> {
}
RenderAs::Opaque => Self::Opaque,
RenderAs::Record(fields) => Self::Record(fields),
RenderAs::Tuple => Self::Tuple,
RenderAs::Guard => Self::Guard,
}
}
@ -366,6 +369,30 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
SP::KnownCtor(union, tag_id, patterns)
}
TupleDestructure { destructs, .. } => {
let tag_id = TagId(0);
let mut patterns = std::vec::Vec::with_capacity(destructs.len());
for Loc {
value: destruct,
region: _,
} in destructs
{
patterns.push(sketch_pattern(&destruct.typ.1.value));
}
let union = Union {
render_as: RenderAs::Tuple,
alternatives: vec![Ctor {
name: CtorName::Tag(TagName("#Record".into())),
tag_id,
arity: destructs.len(),
}],
};
SP::KnownCtor(union, tag_id, patterns)
}
List {
patterns,
list_var: _,

View File

@ -19,12 +19,13 @@ use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, Defs, StrLiteral};
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::num::SingleQuoteBound;
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display};
use std::{char, u32};
@ -186,8 +187,8 @@ pub enum Expr {
field: Lowercase,
},
/// field accessor as a function, e.g. (.foo) expr
RecordAccessor(RecordAccessorData),
/// tuple or field accessor as a function, e.g. (.foo) expr or (.1) expr
RecordAccessor(StructAccessorData),
TupleAccess {
tuple_var: Variable,
@ -197,9 +198,6 @@ pub enum Expr {
index: usize,
},
/// tuple accessor as a function, e.g. (.1) expr
TupleAccessor(TupleAccessorData),
RecordUpdate {
record_var: Variable,
ext_var: Variable,
@ -315,9 +313,8 @@ impl Expr {
Self::Record { .. } => Category::Record,
Self::EmptyRecord => Category::Record,
Self::RecordAccess { field, .. } => Category::RecordAccess(field.clone()),
Self::RecordAccessor(data) => Category::RecordAccessor(data.field.clone()),
Self::RecordAccessor(data) => Category::Accessor(data.field.clone()),
Self::TupleAccess { index, .. } => Category::TupleAccess(*index),
Self::TupleAccessor(data) => Category::TupleAccessor(data.index),
Self::RecordUpdate { .. } => Category::Record,
Self::Tag {
name, arguments, ..
@ -383,43 +380,30 @@ pub struct ClosureData {
pub loc_body: Box<Loc<Expr>>,
}
/// A tuple accessor like `.2`, which is equivalent to `\x -> x.2`
/// TupleAccessors are desugared to closures; they need to have a name
/// A record or tuple accessor like `.foo` or `.0`, which is equivalent to `\r -> r.foo`
/// Struct accessors 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 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 struct StructAccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
pub closure_var: Variable,
pub ext_var: Variable,
pub field_var: Variable,
pub field: Lowercase,
/// Note that the `field` field is an `IndexOrField` in order to represent both
/// record and tuple accessors. This is different from `TupleAccess` and
/// `RecordAccess` (and RecordFields/TupleElems), which share less of their implementation.
pub field: IndexOrField,
}
impl RecordAccessorData {
impl StructAccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
let RecordAccessorData {
let StructAccessorData {
name,
function_var,
record_var,
@ -436,12 +420,21 @@ impl RecordAccessorData {
// into
//
// (\r -> r.foo)
let body = Expr::RecordAccess {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
field,
let body = match field {
IndexOrField::Index(index) => Expr::TupleAccess {
tuple_var: record_var,
ext_var,
elem_var: field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
index,
},
IndexOrField::Field(field) => Expr::RecordAccess {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
field,
},
};
let loc_body = Loc::at_zero(body);
@ -1080,15 +1073,18 @@ pub fn canonicalize_expr<'a>(
output,
)
}
ast::Expr::RecordAccessorFunction(field) => (
RecordAccessor(RecordAccessorData {
ast::Expr::AccessorFunction(field) => (
RecordAccessor(StructAccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
field: match field {
Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
},
}),
Output::default(),
),
@ -1106,18 +1102,6 @@ pub fn canonicalize_expr<'a>(
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();
@ -1874,7 +1858,6 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ RecordAccessor { .. }
| other @ TupleAccessor { .. }
| other @ RecordUpdate { .. }
| other @ Var(..)
| other @ AbilityMember(..)
@ -3004,7 +2987,6 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
| Expr::Str(_)
| Expr::ZeroArgumentTag { .. }
| Expr::RecordAccessor(_)
| Expr::TupleAccessor(_)
| Expr::SingleQuote(..)
| Expr::EmptyRecord
| Expr::TypedHole(_)

View File

@ -918,6 +918,15 @@ fn fix_values_captured_in_closure_pattern(
}
}
}
TupleDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_destruct.value.typ.1.value,
no_capture_symbols,
closure_captures,
)
}
}
List { patterns, .. } => {
for loc_pat in patterns.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(
@ -1087,8 +1096,7 @@ fn fix_values_captured_in_closure_expr(
| TypedHole { .. }
| RuntimeError(_)
| ZeroArgumentTag { .. }
| RecordAccessor { .. }
| TupleAccessor { .. } => {}
| RecordAccessor { .. } => {}
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {

View File

@ -130,8 +130,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| RecordAccessorFunction(_)
| TupleAccessorFunction(_)
| AccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)

View File

@ -58,6 +58,11 @@ pub enum Pattern {
ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>,
},
TupleDestructure {
whole_var: Variable,
ext_var: Variable,
destructs: Vec<Loc<TupleDestruct>>,
},
List {
list_var: Variable,
elem_var: Variable,
@ -100,6 +105,7 @@ impl Pattern {
AppliedTag { whole_var, .. } => Some(*whole_var),
UnwrappedOpaque { whole_var, .. } => Some(*whole_var),
RecordDestructure { whole_var, .. } => Some(*whole_var),
TupleDestructure { whole_var, .. } => Some(*whole_var),
List {
list_var: whole_var,
..
@ -130,7 +136,21 @@ impl Pattern {
| UnsupportedPattern(..)
| MalformedPattern(..)
| AbilityMemberSpecialization { .. } => true,
RecordDestructure { destructs, .. } => destructs.is_empty(),
RecordDestructure { destructs, .. } => {
// If all destructs are surely exhaustive, then this is surely exhaustive.
destructs.iter().all(|d| match &d.value.typ {
DestructType::Required | DestructType::Optional(_, _) => false,
DestructType::Guard(_, pat) => pat.value.surely_exhaustive(),
})
}
TupleDestructure { destructs, .. } => {
// If all destructs are surely exhaustive, then this is surely exhaustive.
destructs
.iter()
.all(|d| d.value.typ.1.value.surely_exhaustive())
}
As(pattern, _identifier) => pattern.value.surely_exhaustive(),
List { patterns, .. } => patterns.surely_exhaustive(),
AppliedTag { .. }
@ -160,6 +180,7 @@ impl Pattern {
UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque),
RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord,
RecordDestructure { .. } => C::Record,
TupleDestructure { .. } => C::Tuple,
List { .. } => C::List,
NumLiteral(..) => C::Num,
IntLiteral(..) => C::Int,
@ -215,6 +236,13 @@ pub struct RecordDestruct {
pub typ: DestructType,
}
#[derive(Clone, Debug)]
pub struct TupleDestruct {
pub var: Variable,
pub destruct_index: usize,
pub typ: (Variable, Loc<Pattern>),
}
#[derive(Clone, Debug)]
pub enum DestructType {
Required,
@ -554,8 +582,38 @@ pub fn canonicalize_pattern<'a>(
)
}
Tuple(_patterns) => {
todo!("canonicalize_pattern: Tuple")
Tuple(patterns) => {
let ext_var = var_store.fresh();
let whole_var = var_store.fresh();
let mut destructs = Vec::with_capacity(patterns.len());
for (i, loc_pattern) in patterns.iter().enumerate() {
let can_guard = canonicalize_pattern(
env,
var_store,
scope,
output,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
permit_shadows,
);
destructs.push(Loc {
region: loc_pattern.region,
value: TupleDestruct {
destruct_index: i,
var: var_store.fresh(),
typ: (var_store.fresh(), can_guard),
},
});
}
Pattern::TupleDestructure {
whole_var,
ext_var,
destructs,
}
}
RecordDestructure(patterns) => {
@ -861,7 +919,8 @@ pub enum BindingsFromPattern<'a> {
pub enum BindingsFromPatternWork<'a> {
Pattern(&'a Loc<Pattern>),
Destruct(&'a Loc<RecordDestruct>),
RecordDestruct(&'a Loc<RecordDestruct>),
TupleDestruct(&'a Loc<TupleDestruct>),
}
impl<'a> BindingsFromPattern<'a> {
@ -911,8 +970,12 @@ impl<'a> BindingsFromPattern<'a> {
let (_, loc_arg) = &**argument;
stack.push(Pattern(loc_arg));
}
TupleDestructure { destructs, .. } => {
let it = destructs.iter().rev().map(TupleDestruct);
stack.extend(it);
}
RecordDestructure { destructs, .. } => {
let it = destructs.iter().rev().map(Destruct);
let it = destructs.iter().rev().map(RecordDestruct);
stack.extend(it);
}
NumLiteral(..)
@ -930,7 +993,7 @@ impl<'a> BindingsFromPattern<'a> {
}
}
}
BindingsFromPatternWork::Destruct(loc_destruct) => {
BindingsFromPatternWork::RecordDestruct(loc_destruct) => {
match &loc_destruct.value.typ {
DestructType::Required | DestructType::Optional(_, _) => {
return Some((loc_destruct.value.symbol, loc_destruct.region));
@ -941,6 +1004,10 @@ impl<'a> BindingsFromPattern<'a> {
}
}
}
BindingsFromPatternWork::TupleDestruct(loc_destruct) => {
let inner = &loc_destruct.value.typ.1;
stack.push(BindingsFromPatternWork::Pattern(inner))
}
}
}

View File

@ -9,9 +9,9 @@ use crate::{
def::{Annotation, Declaration, Def},
expr::{
self, AnnotatedMark, ClosureData, Declarations, Expr, Field, OpaqueWrapFunctionData,
RecordAccessorData, TupleAccessorData,
StructAccessorData,
},
pattern::{DestructType, Pattern, RecordDestruct},
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
macro_rules! visit_list {
@ -242,7 +242,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
record_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
Expr::RecordAccessor(RecordAccessorData { .. }) => { /* terminal */ }
Expr::RecordAccessor(StructAccessorData { .. }) => { /* terminal */ }
Expr::TupleAccess {
elem_var,
loc_expr,
@ -250,7 +250,6 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
tuple_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *elem_var),
Expr::TupleAccessor(TupleAccessorData { .. }) => { /* terminal */ }
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
Expr::RecordUpdate {
record_var: _,
@ -483,6 +482,16 @@ pub trait Visitor: Sized {
walk_record_destruct(self, destruct);
}
}
fn visit_tuple_destruct(&mut self, destruct: &TupleDestruct, region: Region) {
if self.should_visit(region) {
self.visit_pattern(
&destruct.typ.1.value,
destruct.typ.1.region,
Some(destruct.typ.0),
)
}
}
}
pub fn walk_pattern<V: Visitor>(visitor: &mut V, pattern: &Pattern) {
@ -503,6 +512,9 @@ pub fn walk_pattern<V: Visitor>(visitor: &mut V, pattern: &Pattern) {
RecordDestructure { destructs, .. } => destructs
.iter()
.for_each(|d| visitor.visit_record_destruct(&d.value, d.region)),
TupleDestructure { destructs, .. } => destructs
.iter()
.for_each(|d| visitor.visit_tuple_destruct(&d.value, d.region)),
List {
patterns, elem_var, ..
} => patterns

View File

@ -17,7 +17,7 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{
AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, ExpectLookup, Field,
FunctionDef, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData, WhenBranch,
FunctionDef, OpaqueWrapFunctionData, StructAccessorData, WhenBranch,
};
use roc_can::pattern::Pattern;
use roc_can::traverse::symbols_introduced_from_pattern;
@ -30,7 +30,7 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{IllegalCycleMark, Variable};
use roc_types::types::Type::{self, *};
use roc_types::types::{
AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField,
AliasKind, AnnotationSource, Category, IndexOrField, OptAbleType, PReason, Reason, RecordField,
TypeExtension, TypeTag, Types,
};
@ -1162,7 +1162,7 @@ pub fn constrain_expr(
[constraint, eq, record_con],
)
}
RecordAccessor(RecordAccessorData {
RecordAccessor(StructAccessorData {
name: closure_name,
function_var,
field,
@ -1176,19 +1176,32 @@ pub fn constrain_expr(
let field_var = *field_var;
let field_type = Variable(field_var);
let mut field_types = SendMap::default();
let label = field.clone();
field_types.insert(label, RecordField::Demanded(field_type.clone()));
let record_type = Type::Record(
field_types,
TypeExtension::from_non_annotation_type(ext_type),
);
let record_type = match field {
IndexOrField::Field(field) => {
let mut field_types = SendMap::default();
let label = field.clone();
field_types.insert(label, RecordField::Demanded(field_type.clone()));
Type::Record(
field_types,
TypeExtension::from_non_annotation_type(ext_type),
)
}
IndexOrField::Index(index) => {
let mut field_types = VecMap::with_capacity(1);
field_types.insert(*index, field_type.clone());
Type::Tuple(
field_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::RecordAccessor(field.clone());
let category = Category::Accessor(field.clone());
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
let record_con =
@ -1288,88 +1301,6 @@ pub fn constrain_expr(
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,
@ -4001,10 +3932,6 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
// 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(_) => {
// Opaque wrapper functions `@Q` are equivalent to closures `\x -> @Q x`, no need to weaken them.
return true;

View File

@ -3,7 +3,7 @@ use crate::expr::{constrain_expr, Env};
use roc_can::constraint::{Constraint, Constraints, PExpectedTypeIndex, TypeOrVar};
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct};
use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct, TupleDestruct};
use roc_collections::all::{HumanIndex, SendMap};
use roc_collections::VecMap;
use roc_module::ident::Lowercase;
@ -125,6 +125,10 @@ fn headers_from_annotation_help(
_ => false,
},
TupleDestructure { destructs: _, .. } => {
todo!();
}
List { patterns, .. } => {
if let Some((_, Some(rest))) = patterns.opt_rest {
let annotation_index = {
@ -465,6 +469,96 @@ pub fn constrain_pattern(
));
}
TupleDestructure {
whole_var,
ext_var,
destructs,
} => {
state.vars.push(*whole_var);
state.vars.push(*ext_var);
let ext_type = Type::Variable(*ext_var);
let mut elem_types: VecMap<usize, Type> = VecMap::default();
for Loc {
value:
TupleDestruct {
destruct_index: index,
var,
typ,
},
..
} in destructs.iter()
{
let pat_type = Type::Variable(*var);
let pat_type_index = constraints.push_variable(*var);
let expected =
constraints.push_pat_expected_type(PExpected::NoExpectation(pat_type_index));
let (guard_var, loc_guard) = typ;
let elem_type = {
let guard_type = constraints.push_variable(*guard_var);
let expected_pat = constraints.push_pat_expected_type(PExpected::ForReason(
PReason::PatternGuard,
pat_type_index,
loc_guard.region,
));
state.constraints.push(constraints.pattern_presence(
guard_type,
expected_pat,
PatternCategory::PatternGuard,
region,
));
state.vars.push(*guard_var);
constrain_pattern(
types,
constraints,
env,
&loc_guard.value,
loc_guard.region,
expected,
state,
);
pat_type
};
elem_types.insert(*index, elem_type);
state.vars.push(*var);
}
let tuple_type = {
let typ = types.from_old_type(&Type::Tuple(
elem_types,
TypeExtension::from_non_annotation_type(ext_type),
));
constraints.push_type(types, typ)
};
let whole_var_index = constraints.push_variable(*whole_var);
let expected_record =
constraints.push_expected_type(Expected::NoExpectation(tuple_type));
let whole_con = constraints.equal_types(
whole_var_index,
expected_record,
Category::Storage(std::file!(), std::line!()),
region,
);
let record_con = constraints.pattern_presence(
whole_var_index,
expected,
PatternCategory::Record,
region,
);
state.constraints.push(whole_con);
state.constraints.push(record_con);
}
RecordDestructure {
whole_var,
ext_var,

View File

@ -38,6 +38,7 @@ pub enum RenderAs {
Tag,
Opaque,
Record(Vec<Lowercase>),
Tuple,
Guard,
}

View File

@ -177,7 +177,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
}
Tuple { fields, ext } => {
Tuple { elems: fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
@ -343,7 +343,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
}
Tuple { fields, ext } => {
Tuple { elems: fields, ext } => {
fmt_collection(buf, indent, Braces::Round, *fields, newlines);
if let Some(loc_ext_ann) = *ext {

View File

@ -12,6 +12,7 @@ use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch,
};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::ident::Accessor;
use roc_region::all::Loc;
impl<'a> Formattable for Expr<'a> {
@ -35,9 +36,8 @@ impl<'a> Formattable for Expr<'a> {
| NonBase10Int { .. }
| SingleQuote(_)
| RecordAccess(_, _)
| RecordAccessorFunction(_)
| AccessorFunction(_)
| TupleAccess(_, _)
| TupleAccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
@ -434,21 +434,19 @@ impl<'a> Formattable for Expr<'a> {
sub_expr.format_with_options(buf, Parens::InApply, newlines, indent);
}
RecordAccessorFunction(key) => {
AccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
buf.push_str(key);
match key {
Accessor::RecordField(key) => buf.push_str(key),
Accessor::TupleIndex(key) => buf.push_str(key),
}
}
RecordAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
buf.push_str(key);
}
TupleAccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
buf.push_str(key);
}
TupleAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');

View File

@ -656,9 +656,8 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::RecordAccessorFunction(a) => Expr::RecordAccessorFunction(a),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::TupleAccessorFunction(a) => Expr::TupleAccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
@ -813,8 +812,8 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
vars: vars.remove_spaces(arena),
},
),
TypeAnnotation::Tuple { fields, ext } => TypeAnnotation::Tuple {
fields: fields.remove_spaces(arena),
TypeAnnotation::Tuple { elems: fields, ext } => TypeAnnotation::Tuple {
elems: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {

View File

@ -403,7 +403,7 @@ fn contains_unexposed_type(
false
}
Tuple { fields, ext } => {
Tuple { elems: fields, ext } => {
if let Some(loc_ext) = ext {
if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) {
return true;

View File

@ -19,6 +19,7 @@ use roc_module::symbol::Symbol;
type Label = u64;
const RECORD_TAG_NAME: &str = "#Record";
const TUPLE_TAG_NAME: &str = "#Tuple";
/// Users of this module will mainly interact with this function. It takes
/// some normal branches and gives out a decision tree that has "labels" at all
@ -572,6 +573,31 @@ fn test_for_pattern<'a>(pattern: &Pattern<'a>) -> Option<Test<'a>> {
}
}
TupleDestructure(destructs, _) => {
// not rendered, so pick the easiest
let union = Union {
render_as: RenderAs::Tag,
alternatives: vec![Ctor {
tag_id: TagId(0),
name: CtorName::Tag(TagName(TUPLE_TAG_NAME.into())),
arity: destructs.len(),
}],
};
let mut arguments = std::vec::Vec::new();
for destruct in destructs {
arguments.push((destruct.pat.clone(), destruct.layout));
}
IsCtor {
tag_id: 0,
ctor_name: CtorName::Tag(TagName(TUPLE_TAG_NAME.into())),
union,
arguments,
}
}
NewtypeDestructure {
tag_name,
arguments,
@ -790,6 +816,42 @@ fn to_relevant_branch_help<'a>(
_ => None,
},
TupleDestructure(destructs, _) => match test {
IsCtor {
ctor_name: test_name,
tag_id,
..
} => {
debug_assert!(test_name == &CtorName::Tag(TagName(TUPLE_TAG_NAME.into())));
let destructs_len = destructs.len();
let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| {
let pattern = destruct.pat.clone();
let mut new_path = path.to_vec();
let next_instr = if destructs_len == 1 {
PathInstruction::NewType
} else {
PathInstruction::TagIndex {
index: index as u64,
tag_id: *tag_id,
}
};
new_path.push(next_instr);
(new_path, pattern)
});
start.extend(sub_positions);
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
_ => None,
},
OpaqueUnwrap { opaque, argument } => match test {
IsCtor {
ctor_name: test_opaque_tag_name,
@ -1126,6 +1188,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
NewtypeDestructure { .. }
| RecordDestructure(..)
| TupleDestructure(..)
| AppliedTag { .. }
| OpaqueUnwrap { .. }
| BitLiteral { .. }

View File

@ -1104,6 +1104,7 @@ impl<'a> Procs<'a> {
let needs_suspended_specialization =
self.symbol_needs_suspended_specialization(name.name());
match (
&mut self.pending_specializations,
needs_suspended_specialization,
@ -2813,7 +2814,10 @@ fn pattern_to_when<'a>(
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
AppliedTag { .. } | RecordDestructure { .. } | UnwrappedOpaque { .. } => {
AppliedTag { .. }
| RecordDestructure { .. }
| TupleDestructure { .. }
| UnwrappedOpaque { .. } => {
let symbol = env.unique_symbol();
let wrapped_body = When {
@ -4245,7 +4249,111 @@ pub fn with_hole<'a>(
}
}
Tuple { .. } => todo!("implement tuple hole"),
Tuple {
tuple_var, elems, ..
} => {
let sorted_elems_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
layout::sort_tuple_elems(&mut layout_env, tuple_var)
};
let sorted_elems = match sorted_elems_result {
Ok(elems) => elems,
Err(_) => return runtime_error(env, "Can't create tuple with improper layout"),
};
let mut elem_symbols = Vec::with_capacity_in(elems.len(), env.arena);
let mut can_elems = Vec::with_capacity_in(elems.len(), env.arena);
#[allow(clippy::enum_variant_names)]
enum Field {
// TODO: rename this since it can handle unspecialized expressions now too
FunctionOrUnspecialized(Symbol, Variable),
ValueSymbol,
Field(Variable, Loc<roc_can::expr::Expr>),
}
// Hacky way to let us remove the owned elements from the vector, possibly out-of-order.
let mut elems = Vec::from_iter_in(elems.into_iter().map(Some), env.arena);
for (index, variable, _) in sorted_elems.into_iter() {
// TODO how should function pointers be handled here?
use ReuseSymbol::*;
let (var, loc_expr) = elems[index].take().unwrap();
match can_reuse_symbol(env, procs, &loc_expr.value, var) {
Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => {
elem_symbols.push(symbol);
can_elems.push(Field::FunctionOrUnspecialized(symbol, variable));
}
Value(symbol) => {
let reusable = procs.get_or_insert_symbol_specialization(
env,
layout_cache,
symbol,
var,
);
elem_symbols.push(reusable);
can_elems.push(Field::ValueSymbol);
}
NotASymbol => {
elem_symbols.push(env.unique_symbol());
can_elems.push(Field::Field(var, *loc_expr));
}
}
}
// creating a record from the var will unpack it if it's just a single field.
let layout = match layout_cache.from_var(env.arena, tuple_var, env.subs) {
Ok(layout) => layout,
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let elem_symbols = elem_symbols.into_bump_slice();
let mut stmt = if let [only_field] = elem_symbols {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
hole
} else {
Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole)
};
for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_symbols.iter().rev()) {
match opt_field {
Field::ValueSymbol => {
// this symbol is already defined; nothing to do
}
Field::FunctionOrUnspecialized(symbol, variable) => {
stmt = specialize_symbol(
env,
procs,
layout_cache,
Some(variable),
symbol,
env.arena.alloc(stmt),
symbol,
);
}
Field::Field(var, loc_expr) => {
stmt = with_hole(
env,
loc_expr.value,
var,
procs,
layout_cache,
*symbol,
env.arena.alloc(stmt),
);
}
}
}
stmt
}
Record {
record_var,
@ -4779,8 +4887,82 @@ pub fn with_hole<'a>(
}
}
TupleAccess { .. } => todo!(),
TupleAccessor(_) => todo!(),
TupleAccess {
tuple_var,
elem_var,
index: accessed_index,
loc_expr,
..
} => {
let sorted_elems_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
layout::sort_tuple_elems(&mut layout_env, tuple_var)
};
let sorted_elems = match sorted_elems_result {
Ok(fields) => fields,
Err(_) => return runtime_error(env, "Can't access tuple with improper layout"),
};
let mut final_index = None;
let mut elem_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena);
for (current, (index, _, elem_layout)) in sorted_elems.into_iter().enumerate() {
elem_layouts.push(elem_layout);
if index == accessed_index {
final_index = Some(current);
}
}
let tuple_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_expr.value,
tuple_var,
);
let mut stmt = match elem_layouts.as_slice() {
[_] => {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, tuple_symbol);
hole
}
_ => {
let expr = Expr::StructAtIndex {
index: final_index.expect("field not in its own type") as u64,
field_layouts: elem_layouts.into_bump_slice(),
structure: tuple_symbol,
};
let layout = layout_cache
.from_var(env.arena, elem_var, env.subs)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
Stmt::Let(assigned, expr, layout, hole)
}
};
stmt = assign_to_symbol(
env,
procs,
layout_cache,
tuple_var,
*loc_expr,
tuple_symbol,
stmt,
);
stmt
}
OpaqueWrapFunction(wrap_fn_data) => {
let opaque_var = wrap_fn_data.opaque_var;
@ -7467,6 +7649,46 @@ fn store_pattern_help<'a>(
return StorePattern::NotProductive(stmt);
}
}
TupleDestructure(destructs, [_single_field]) => {
if let Some(destruct) = destructs.first() {
return store_pattern_help(
env,
procs,
layout_cache,
&destruct.pat,
outer_symbol,
stmt,
);
}
}
TupleDestructure(destructs, sorted_fields) => {
let mut is_productive = false;
for (index, destruct) in destructs.iter().enumerate().rev() {
match store_tuple_destruct(
env,
procs,
layout_cache,
destruct,
index as u64,
outer_symbol,
sorted_fields,
stmt,
) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
}
StorePattern::NotProductive(new) => {
stmt = new;
}
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
}
}
}
StorePattern::Productive(stmt)
@ -7799,6 +8021,71 @@ fn store_newtype_pattern<'a>(
}
}
#[allow(clippy::too_many_arguments)]
fn store_tuple_destruct<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
destruct: &TupleDestruct<'a>,
index: u64,
outer_symbol: Symbol,
sorted_fields: &'a [InLayout<'a>],
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
use Pattern::*;
let load = Expr::StructAtIndex {
index,
field_layouts: sorted_fields,
structure: outer_symbol,
};
match &destruct.pat {
Identifier(symbol) => {
stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt));
}
Underscore => {
// important that this is special-cased to do nothing: mono record patterns will extract all the
// fields, but those not bound in the source code are guarded with the underscore
// pattern. So given some record `{ x : a, y : b }`, a match
//
// { x } -> ...
//
// is actually
//
// { x, y: _ } -> ...
//
// internally. But `y` is never used, so we must make sure it't not stored/loaded.
//
// This also happens with tuples, so when matching a tuple `(a, b, c)`,
// a pattern like `(x, y)` will be internally rewritten to `(x, y, _)`.
return StorePattern::NotProductive(stmt);
}
IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
_ => {
let symbol = env.unique_symbol();
match store_pattern_help(env, procs, layout_cache, &destruct.pat, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = new;
stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
}
StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
}
}
}
StorePattern::Productive(stmt)
}
#[allow(clippy::too_many_arguments)]
fn store_record_destruct<'a>(
env: &mut Env<'a, '_>,
@ -9081,6 +9368,7 @@ pub enum Pattern<'a> {
StrLiteral(Box<str>),
RecordDestructure(Vec<'a, RecordDestruct<'a>>, &'a [InLayout<'a>]),
TupleDestructure(Vec<'a, TupleDestruct<'a>>, &'a [InLayout<'a>]),
NewtypeDestructure {
tag_name: TagName,
arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>,
@ -9133,6 +9421,11 @@ impl<'a> Pattern<'a> {
}
}
}
Pattern::TupleDestructure(destructs, _) => {
for destruct in destructs {
stack.push(&destruct.pat);
}
}
Pattern::NewtypeDestructure { arguments, .. } => {
stack.extend(arguments.iter().map(|(t, _)| t))
}
@ -9157,6 +9450,14 @@ pub struct RecordDestruct<'a> {
pub typ: DestructType<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TupleDestruct<'a> {
pub index: usize,
pub variable: Variable,
pub layout: InLayout<'a>,
pub pat: Pattern<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum DestructType<'a> {
Required(Symbol),
@ -9752,6 +10053,62 @@ fn from_can_pattern_help<'a>(
})
}
TupleDestructure {
whole_var,
destructs,
..
} => {
// sorted fields based on the type
let sorted_elems = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
crate::layout::sort_tuple_elems(&mut layout_env, *whole_var)
.map_err(RuntimeError::from)?
};
// sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
let mut destructs_by_index = Vec::with_capacity_in(destructs.len(), env.arena);
destructs_by_index.extend(destructs.iter().map(Some));
let mut elem_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena);
for (index, variable, res_layout) in sorted_elems.into_iter() {
if index < destructs.len() {
// this elem is destructured by the pattern
mono_destructs.push(from_can_tuple_destruct(
env,
procs,
layout_cache,
&destructs[index].value,
res_layout,
assignments,
)?);
} else {
// this elem is not destructured by the pattern
// put in an underscore
mono_destructs.push(TupleDestruct {
index,
variable,
layout: res_layout,
pat: Pattern::Underscore,
});
}
// the layout of this field is part of the layout of the record
elem_layouts.push(res_layout);
}
Ok(Pattern::TupleDestructure(
mono_destructs,
elem_layouts.into_bump_slice(),
))
}
RecordDestructure {
whole_var,
destructs,
@ -9930,6 +10287,22 @@ fn from_can_record_destruct<'a>(
})
}
fn from_can_tuple_destruct<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_rd: &roc_can::pattern::TupleDestruct,
field_layout: InLayout<'a>,
assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
) -> Result<TupleDestruct<'a>, RuntimeError> {
Ok(TupleDestruct {
index: can_rd.destruct_index,
variable: can_rd.var,
layout: field_layout,
pat: from_can_pattern_help(env, procs, layout_cache, &can_rd.typ.1.value, assignments)?,
})
}
#[derive(Debug, Clone, Copy)]
enum IntOrFloatValue {
Int(IntValue),

View File

@ -13,9 +13,12 @@ use roc_target::{PtrWidth, TargetInfo};
use roc_types::num::NumericRange;
use roc_types::subs::{
self, Content, FlatType, GetSubsSlice, Label, OptVariable, RecordFields, Subs, TagExt,
UnsortedUnionLabels, Variable, VariableSubsSlice,
TupleElems, UnsortedUnionLabels, Variable, VariableSubsSlice,
};
use roc_types::types::{
gather_fields_unsorted_iter, gather_tuple_elems_unsorted_iter, RecordField, RecordFieldsError,
TupleElemsError,
};
use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError};
use std::cmp::Ordering;
use std::collections::hash_map::{DefaultHasher, Entry};
use std::collections::HashMap;
@ -660,6 +663,17 @@ impl FieldOrderHash {
fields.iter().for_each(|field| field.hash(&mut hasher));
Self(hasher.finish())
}
pub fn from_ordered_tuple_elems(elems: &[usize]) -> Self {
if elems.is_empty() {
// HACK: we must make sure this is always equivalent to a `ZERO_FIELD_HASH`.
return Self::ZERO_FIELD_HASH;
}
let mut hasher = DefaultHasher::new();
elems.iter().for_each(|elem| elem.hash(&mut hasher));
Self(hasher.finish())
}
}
/// Types for code gen must be monomorphic. No type variables allowed!
@ -3155,8 +3169,52 @@ fn layout_from_flat_type<'a>(
Cacheable(result, criteria)
}
Tuple(_elems, _ext_var) => {
todo!();
Tuple(elems, ext_var) => {
let mut criteria = CACHEABLE;
// extract any values from the ext_var
let mut sortables = Vec::with_capacity_in(elems.len(), arena);
let it = match elems.unsorted_iterator(subs, ext_var) {
Ok(it) => it,
Err(TupleElemsError) => return Cacheable(Err(LayoutProblem::Erroneous), criteria),
};
for (index, elem) in it {
let elem_layout = cached!(Layout::from_var(env, elem), criteria);
sortables.push((index, elem_layout));
}
sortables.sort_by(|(index1, layout1), (index2, layout2)| {
cmp_fields(
&env.cache.interner,
index1,
*layout1,
index2,
*layout2,
target_info,
)
});
let ordered_field_names =
Vec::from_iter_in(sortables.iter().map(|(index, _)| *index), arena);
let field_order_hash =
FieldOrderHash::from_ordered_tuple_elems(ordered_field_names.as_slice());
let result = if sortables.len() == 1 {
// If the tuple has only one field that isn't zero-sized,
// unwrap it.
Ok(sortables.pop().unwrap().1)
} else {
let layouts = Vec::from_iter_in(sortables.into_iter().map(|t| t.1), arena);
let struct_layout = Layout::Struct {
field_order_hash,
field_layouts: layouts.into_bump_slice(),
};
Ok(env.cache.put_in(struct_layout))
};
Cacheable(result, criteria)
}
TagUnion(tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
@ -3191,6 +3249,48 @@ fn layout_from_flat_type<'a>(
}
}
pub type SortedTupleElem<'a> = (usize, Variable, InLayout<'a>);
pub fn sort_tuple_elems<'a>(
env: &mut Env<'a, '_>,
var: Variable,
) -> Result<Vec<'a, SortedTupleElem<'a>>, LayoutProblem> {
let (it, _) = match gather_tuple_elems_unsorted_iter(env.subs, TupleElems::empty(), var) {
Ok(it) => it,
Err(_) => return Err(LayoutProblem::Erroneous),
};
sort_tuple_elems_help(env, it)
}
fn sort_tuple_elems_help<'a>(
env: &mut Env<'a, '_>,
elems_map: impl Iterator<Item = (usize, Variable)>,
) -> Result<Vec<'a, SortedTupleElem<'a>>, LayoutProblem> {
let target_info = env.target_info;
let mut sorted_elems = Vec::with_capacity_in(elems_map.size_hint().0, env.arena);
for (index, elem) in elems_map {
let Cacheable(layout, _) = Layout::from_var(env, elem);
let layout = layout?;
sorted_elems.push((index, elem, layout));
}
sorted_elems.sort_by(|(index1, _, res_layout1), (index2, _, res_layout2)| {
cmp_fields(
&env.cache.interner,
index1,
*res_layout1,
index2,
*res_layout2,
target_info,
)
});
Ok(sorted_elems)
}
pub type SortedField<'a> = (Lowercase, Variable, Result<InLayout<'a>, InLayout<'a>>);
pub fn sort_record_fields<'a>(

View File

@ -1,6 +1,7 @@
use std::fmt::Debug;
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader};
use crate::ident::Accessor;
use crate::parser::ESingleQuote;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
@ -243,13 +244,12 @@ pub enum Expr<'a> {
/// Look up exactly one field on a record, e.g. `x.foo`.
RecordAccess(&'a Expr<'a>, &'a str),
/// e.g. `.foo`
RecordAccessorFunction(&'a str),
/// e.g. `.foo` or `.0`
AccessorFunction(Accessor<'a>),
/// Look up exactly one field on a tuple, e.g. `(x, y).1`.
TupleAccess(&'a Expr<'a>, &'a str),
/// e.g. `.1`
TupleAccessorFunction(&'a str),
// Collection Literals
List(Collection<'a, &'a Loc<Expr<'a>>>),
@ -612,7 +612,7 @@ pub enum TypeAnnotation<'a> {
},
Tuple {
fields: Collection<'a, Loc<TypeAnnotation<'a>>>,
elems: Collection<'a, Loc<TypeAnnotation<'a>>>,
/// The row type variable in an open tuple, e.g. the `r` in `( Str, Str )r`.
/// This is None if it's a closed tuple annotation like `( Str, Str )`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
@ -1459,8 +1459,7 @@ impl<'a> Malformed for Expr<'a> {
Float(_) |
Num(_) |
NonBase10Int { .. } |
TupleAccessorFunction(_) |
RecordAccessorFunction(_) |
AccessorFunction(_) |
Var { .. } |
Underscore(_) |
Tag(_) |
@ -1729,7 +1728,7 @@ impl<'a> Malformed for TypeAnnotation<'a> {
fields.iter().any(|field| field.is_malformed())
|| ext.map(|ext| ext.is_malformed()).unwrap_or_default()
}
TypeAnnotation::Tuple { fields, ext } => {
TypeAnnotation::Tuple { elems: fields, ext } => {
fields.iter().any(|field| field.is_malformed())
|| ext.map(|ext| ext.is_malformed()).unwrap_or_default()
}

View File

@ -1872,9 +1872,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
is_negative: *is_negative,
}),
// These would not have parsed as patterns
Expr::RecordAccessorFunction(_)
Expr::AccessorFunction(_)
| Expr::RecordAccess(_, _)
| Expr::TupleAccessorFunction(_)
| Expr::TupleAccess(_, _)
| Expr::List { .. }
| Expr::Closure(_, _)
@ -2459,8 +2458,7 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
answer
}
Ident::RecordAccessorFunction(string) => Expr::RecordAccessorFunction(string),
Ident::TupleAccessorFunction(string) => Expr::TupleAccessorFunction(string),
Ident::AccessorFunction(string) => Expr::AccessorFunction(string),
Ident::Malformed(string, problem) => Expr::MalformedIdent(string, problem),
}
}

View File

@ -43,10 +43,8 @@ pub enum Ident<'a> {
module_name: &'a str,
parts: &'a [Accessor<'a>],
},
/// .foo { foo: 42 }
RecordAccessorFunction(&'a str),
/// .1 (1, 2, 3)
TupleAccessorFunction(&'a str),
/// `.foo { foo: 42 }` or `.1 (1, 2, 3)`
AccessorFunction(Accessor<'a>),
/// .Foo or foo. or something like foo.Bar
Malformed(&'a str, BadIdent),
}
@ -71,8 +69,7 @@ impl<'a> Ident<'a> {
len - 1
}
RecordAccessorFunction(string) => string.len(),
TupleAccessorFunction(string) => string.len(),
AccessorFunction(string) => string.len(),
Malformed(string, _) => string.len(),
}
}
@ -339,7 +336,7 @@ where
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Accessor<'a> {
RecordField(&'a str),
TupleIndex(&'a str),
@ -436,13 +433,9 @@ fn chomp_identifier_chain<'a>(
match char::from_utf8_slice_start(&buffer[chomped..]) {
Ok((ch, width)) => match ch {
'.' => match chomp_accessor(&buffer[1..], pos) {
Ok(Accessor::RecordField(accessor)) => {
Ok(accessor) => {
let bytes_parsed = 1 + accessor.len();
return Ok((bytes_parsed as u32, Ident::RecordAccessorFunction(accessor)));
}
Ok(Accessor::TupleIndex(accessor)) => {
let bytes_parsed = 1 + accessor.len();
return Ok((bytes_parsed as u32, Ident::TupleAccessorFunction(accessor)));
return Ok((bytes_parsed as u32, Ident::AccessorFunction(accessor)));
}
Err(fail) => return Err((1, fail)),
},

View File

@ -536,8 +536,7 @@ pub enum EPattern<'a> {
IndentEnd(Position),
AsIndentStart(Position),
RecordAccessorFunction(Position),
TupleAccessorFunction(Position),
AccessorFunction(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -417,13 +417,9 @@ fn loc_ident_pattern_help<'a>(
state,
))
}
Ident::RecordAccessorFunction(_string) => Err((
Ident::AccessorFunction(_string) => Err((
MadeProgress,
EPattern::RecordAccessorFunction(loc_ident.region.start()),
)),
Ident::TupleAccessorFunction(_string) => Err((
MadeProgress,
EPattern::TupleAccessorFunction(loc_ident.region.start()),
EPattern::AccessorFunction(loc_ident.region.start()),
)),
Ident::Malformed(malformed, problem) => {
debug_assert!(!malformed.is_empty());

View File

@ -250,7 +250,7 @@ fn loc_type_in_parens<'a>(
if fields.len() > 1 || ext.is_some() {
Ok((
MadeProgress,
Loc::at(region, TypeAnnotation::Tuple { fields, ext }),
Loc::at(region, TypeAnnotation::Tuple { elems: fields, ext }),
state,
))
} else if fields.len() == 1 {

View File

@ -1497,6 +1497,20 @@ mod solve_expr {
infer_eq(".200", "( ... 200 omitted, a )* -> a");
}
#[test]
fn tuple_accessor_generalization() {
infer_eq(
indoc!(
r#"
get0 = .0
{ a: get0 (1, 2), b: get0 ("a", "b", "c") }
"#
),
"{ a : Num *, b : Str }",
);
}
#[test]
fn record_arg() {
infer_eq("\\rec -> rec.x", "{ x : a }* -> a");

View File

@ -0,0 +1,620 @@
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
// use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn basic_tuple() {
assert_evals_to!(
indoc!(
r#"
( 15, 17, 19 ).0
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
( 15, 17, 19 ).1
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
( 15, 17, 19 ).2
"#
),
19,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn f64_tuple() {
assert_evals_to!(
indoc!(
r#"
tup = (17.2, 15.1, 19.3)
tup.0
"#
),
17.2,
f64
);
assert_evals_to!(
indoc!(
r#"
tup = (17.2, 15.1, 19.3)
tup.1
"#
),
15.1,
f64
);
assert_evals_to!(
indoc!(
r#"
tup = (17.2, 15.1, 19.3)
tup.2
"#
),
19.3,
f64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn fn_tuple() {
assert_evals_to!(
indoc!(
r#"
getRec = \x -> ("foo", x, 19)
(getRec 15).1
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = (15, 17, 19)
rec.2 + rec.0
"#
),
34,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn int_tuple() {
assert_evals_to!(
indoc!(
r#"
rec = (15, 17, 19)
rec.0
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = (15, 17, 19)
rec.1
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = (15, 17, 19)
rec.2
"#
),
19,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn when_on_tuple() {
assert_evals_to!(
indoc!(
r#"
when (0x2, 0x3) is
(x, y) -> x + y
"#
),
5,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn when_tuple_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when (0x2, 1.23) is
(var, _) -> var + 3
"#
),
5,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn let_with_tuple_pattern() {
assert_evals_to!(
indoc!(
r#"
(x, _ ) = (0x2, 1.23)
x
"#
),
2,
i64
);
assert_evals_to!(
indoc!(
r#"
(_, y) = (0x2, 0x3)
y
"#
),
3,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn tuple_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when (0x2, 1.23) is
(0x4, _) -> 5
(x, _) -> x + 4
"#
),
6,
i64
);
assert_evals_to!(
indoc!(
r#"
when (0x2, 0x3) is
(_, 0x4) -> 5
(_, x) -> x + 4
"#
),
7,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn twice_tuple_access() {
assert_evals_to!(
indoc!(
r#"
x = (0x2, 0x3)
x.0 + x.1
"#
),
5,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn i64_tuple2_literal() {
assert_evals_to!(
indoc!(
r#"
(3, 5)
"#
),
(3, 5),
(i64, i64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn i64_tuple3_literal() {
assert_evals_to!(
indoc!(
r#"
(3, 5, 17)
"#
),
(3, 5, 17),
(i64, i64, i64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn f64_tuple2_literal() {
assert_evals_to!(
indoc!(
r#"
(3.1, 5.1)
"#
),
(3.1, 5.1),
(f64, f64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn bool_tuple4_literal() {
assert_evals_to!(
indoc!(
r#"
tuple : (Bool, Bool, Bool, Bool)
tuple = (Bool.true, Bool.false, Bool.false, Bool.true)
tuple
"#
),
(true, false, false, true),
(bool, bool, bool, bool)
);
}
// Not supported by wasm because of the size of the tuple:
// FromWasm32Memory is only implemented for tuples of up to 4 elements
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn i64_tuple9_literal() {
assert_evals_to!(
indoc!(
r#"
( 3, 5, 17, 1, 9, 12, 13, 14, 15 )
"#
),
[3, 5, 17, 1, 9, 12, 13, 14, 15],
[i64; 9]
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_tuple() {
assert_evals_to!(
indoc!(
r#"
x = 4
y = 3
(x, y)
"#
),
(4, 3),
(i64, i64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_tuple_2() {
assert_evals_to!(
indoc!(
r#"
(3, 5)
"#
),
[3, 5],
[i64; 2]
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_tuple_3() {
assert_evals_to!(
indoc!(
r#"
( 3, 5, 4 )
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_tuple_4() {
assert_evals_to!(
indoc!(
r#"
( 3, 5, 4, 2 )
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
// Not supported by wasm because of the size of the tuple:
// FromWasm32Memory is only implemented for tuples of up to 4 elements
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn return_tuple_5() {
assert_evals_to!(
indoc!(
r#"
( 3, 5, 4, 2, 1 )
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
// Not supported by wasm because of the size of the tuple:
// FromWasm32Memory is only implemented for tuples of up to 4 elements
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn return_tuple_6() {
assert_evals_to!(
indoc!(
r#"
( 3, 5, 4, 2, 1, 7 )
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
// Not supported by wasm because of the size of the tuple:
// FromWasm32Memory is only implemented for tuples of up to 4 elements
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn return_tuple_7() {
assert_evals_to!(
indoc!(
r#"
( 3, 5, 4, 2, 1, 7, 8 )
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn return_tuple_float_int() {
assert_evals_to!(
indoc!(
r#"
(1.23, 0x1)
"#
),
(1.23, 0x1),
(f64, i64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn return_tuple_int_float() {
assert_evals_to!(
indoc!(
r#"
( 0x1, 1.23 )
"#
),
(0x1, 1.23),
(i64, f64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn return_tuple_float_float() {
assert_evals_to!(
indoc!(
r#"
( 2.46, 1.23 )
"#
),
(2.46, 1.23),
(f64, f64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn return_tuple_float_float_float() {
assert_evals_to!(
indoc!(
r#"
( 2.46, 1.23, 0.1 )
"#
),
(2.46, 1.23, 0.1),
(f64, f64, f64)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_nested_tuple() {
assert_evals_to!(
indoc!(
r#"
(0x0, (2.46, 1.23, 0.1))
"#
),
(0x0, (2.46, 1.23, 0.1)),
(i64, (f64, f64, f64))
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn nested_tuple_load() {
assert_evals_to!(
indoc!(
r#"
x = (0, (0x2, 0x5, 0x6))
y = x.1
y.2
"#
),
6,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn tuple_accessor_twice() {
assert_evals_to!(".0 (4, 5) + .1 ( 2.46, 3 ) ", 7, i64);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn tuple_accessor_multi_element_tuple() {
assert_evals_to!(
indoc!(
r#"
.0 (4, "foo")
"#
),
4,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn booleans_in_tuple() {
assert_evals_to!(indoc!("(1 == 1, 1 == 1)"), (true, true), (bool, bool));
assert_evals_to!(indoc!("(1 != 1, 1 == 1)"), (false, true), (bool, bool));
assert_evals_to!(indoc!("(1 == 1, 1 != 1)"), (true, false), (bool, bool));
assert_evals_to!(indoc!("(1 != 1, 1 != 1)"), (false, false), (bool, bool));
}
// TODO: this test fails for mysterious reasons
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alignment_in_tuple() {
assert_evals_to!(
indoc!("(32, 1 == 1, 78u16)"),
(32i64, 78u16, true),
(i64, u16, bool)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn tuple_length_polymorphism() {
assert_evals_to!(
indoc!(
r#"
a = (42, 43)
b = (1, 2, 44)
f : (I64, I64)a, (I64, I64)b -> I64
f = \(x1, x2), (x3, x4) -> x1 + x2 + x3 + x4
f a b
"#
),
88,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn generalized_tuple_accessor() {
assert_evals_to!(
indoc!(
r#"
return0 = .0
return0 ("foo", 1)
"#
),
RocStr::from("foo"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn generalized_explicit_tuple_accessor() {
assert_evals_to!(
indoc!(
r#"
return0 = \x -> x.0
return0 ("foo", 1)
"#
),
RocStr::from("foo"),
RocStr
);
}

View File

@ -17,6 +17,7 @@ pub mod gen_result;
pub mod gen_set;
pub mod gen_str;
pub mod gen_tags;
pub mod gen_tuples;
mod helpers;
pub mod wasm_str;

View File

@ -0,0 +1,29 @@
procedure Test.0 ():
let Test.15 : I64 = 1i64;
let Test.16 : I64 = 2i64;
let Test.1 : {I64, I64} = Struct {Test.15, Test.16};
joinpoint Test.5:
let Test.2 : Str = "A";
ret Test.2;
in
let Test.12 : I64 = StructAtIndex 1 Test.1;
let Test.13 : I64 = 2i64;
let Test.14 : Int1 = lowlevel Eq Test.13 Test.12;
if Test.14 then
let Test.6 : I64 = StructAtIndex 0 Test.1;
let Test.7 : I64 = 1i64;
let Test.8 : Int1 = lowlevel Eq Test.7 Test.6;
if Test.8 then
jump Test.5;
else
let Test.3 : Str = "B";
ret Test.3;
else
let Test.9 : I64 = StructAtIndex 0 Test.1;
let Test.10 : I64 = 1i64;
let Test.11 : Int1 = lowlevel Eq Test.10 Test.9;
if Test.11 then
jump Test.5;
else
let Test.4 : Str = "C";
ret Test.4;

View File

@ -2192,6 +2192,20 @@ fn list_one_vs_one_spread_issue_4685() {
)
}
#[mono_test]
fn tuple_pattern_match() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main = when (1, 2) is
(1, _) -> "A"
(_, 2) -> "B"
(_, _) -> "C"
"#
)
}
#[mono_test(mode = "test")]
fn issue_4705() {
indoc!(

View File

@ -22,7 +22,7 @@ Defs(
@4-20 Function(
[
@4-10 Tuple {
fields: [
elems: [
@5-8 Apply(
"",
"Str",
@ -37,7 +37,7 @@ Defs(
},
],
@14-20 Tuple {
fields: [
elems: [
@15-18 Apply(
"",
"Str",
@ -59,7 +59,7 @@ Defs(
ann_type: @4-20 Function(
[
@4-10 Tuple {
fields: [
elems: [
@5-8 Apply(
"",
"Str",
@ -74,7 +74,7 @@ Defs(
},
],
@14-20 Tuple {
fields: [
elems: [
@15-18 Apply(
"",
"Str",

View File

@ -28,7 +28,7 @@ Defs(
),
],
@11-21 Tuple {
fields: [
elems: [
@12-15 Apply(
"",
"I64",
@ -57,7 +57,7 @@ Defs(
),
],
@11-21 Tuple {
fields: [
elems: [
@12-15 Apply(
"",
"I64",

View File

@ -1,6 +1,8 @@
Apply(
@0-2 TupleAccessorFunction(
"1",
@0-2 AccessorFunction(
TupleIndex(
"1",
),
),
[
@3-12 Tuple(

View File

@ -22,7 +22,7 @@ Defs(
@3-27 Function(
[
@3-13 Tuple {
fields: [
elems: [
@4-7 Apply(
"",
"Str",
@ -38,7 +38,7 @@ Defs(
},
],
@17-27 Tuple {
fields: [
elems: [
@18-21 Apply(
"",
"Str",
@ -61,7 +61,7 @@ Defs(
ann_type: @3-27 Function(
[
@3-13 Tuple {
fields: [
elems: [
@4-7 Apply(
"",
"Str",
@ -77,7 +77,7 @@ Defs(
},
],
@17-27 Tuple {
fields: [
elems: [
@18-21 Apply(
"",
"Str",

View File

@ -22,7 +22,7 @@ Defs(
@3-29 Function(
[
@3-14 Tuple {
fields: [
elems: [
@4-7 Apply(
"",
"Str",
@ -42,7 +42,7 @@ Defs(
},
],
@18-29 Tuple {
fields: [
elems: [
@19-22 Apply(
"",
"Str",
@ -69,7 +69,7 @@ Defs(
ann_type: @3-29 Function(
[
@3-14 Tuple {
fields: [
elems: [
@4-7 Apply(
"",
"Str",
@ -89,7 +89,7 @@ Defs(
},
],
@18-29 Tuple {
fields: [
elems: [
@19-22 Apply(
"",
"Str",

View File

@ -3617,6 +3617,13 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
}
}
/// Either a field name for a record or an index into a tuple
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IndexOrField {
Field(Lowercase),
Index(usize),
}
#[derive(Debug)]
pub struct RecordStructure {
/// Invariant: these should be sorted!
@ -3777,10 +3784,9 @@ pub enum Category {
// records
Record,
RecordAccessor(Lowercase),
Accessor(IndexOrField),
RecordAccess(Lowercase),
Tuple,
TupleAccessor(usize),
TupleAccess(usize),
DefaultValue(Lowercase), // for setting optional fields
@ -3796,6 +3802,7 @@ pub enum Category {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatternCategory {
Record,
Tuple,
List,
EmptyRecord,
PatternGuard,

View File

@ -19,8 +19,8 @@ use roc_solve_problem::{
use roc_std::RocDec;
use roc_types::pretty_print::{Parens, WILDCARD};
use roc_types::types::{
AbilitySet, AliasKind, Category, ErrorType, PatternCategory, Polarity, Reason, RecordField,
TypeExt,
AbilitySet, AliasKind, Category, ErrorType, IndexOrField, PatternCategory, Polarity, Reason,
RecordField, TypeExt,
};
use std::path::PathBuf;
use ven_pretty::DocAllocator;
@ -1705,10 +1705,13 @@ fn format_category<'b>(
alloc.text(" of type:"),
),
RecordAccessor(field) => (
Accessor(field) => (
alloc.concat([
alloc.text(format!("{}his ", t)),
alloc.record_field(field.to_owned()),
match field {
IndexOrField::Index(index) => alloc.tuple_field(*index),
IndexOrField::Field(field) => alloc.record_field(field.to_owned()),
},
alloc.text(" value"),
]),
alloc.text(" is a:"),
@ -1726,14 +1729,6 @@ fn format_category<'b>(
alloc.text(" of type:"),
),
TupleAccessor(index) => (
alloc.concat([
alloc.text(format!("{}his ", t)),
alloc.tuple_field(*index),
alloc.text(" value"),
]),
alloc.text(" is a:"),
),
TupleAccess(index) => (
alloc.concat([
alloc.text(format!("{}he value at ", t)),
@ -2046,6 +2041,7 @@ fn add_pattern_category<'b>(
let rest = match category {
Record => alloc.reflow(" record values of type:"),
Tuple => alloc.reflow(" tuple values of type:"),
EmptyRecord => alloc.reflow(" an empty record:"),
PatternGuard => alloc.reflow(" a pattern guard of type:"),
PatternDefault => alloc.reflow(" an optional field of type:"),
@ -4853,6 +4849,18 @@ fn pattern_to_doc_help<'b>(
.append(alloc.intersperse(arg_docs, alloc.reflow(", ")))
.append(" }")
}
RenderAs::Tuple => {
let mut arg_docs = Vec::with_capacity(args.len());
for v in args.into_iter() {
arg_docs.push(pattern_to_doc_help(alloc, v, false));
}
alloc
.text("( ")
.append(alloc.intersperse(arg_docs, alloc.reflow(", ")))
.append(" )")
}
RenderAs::Tag | RenderAs::Opaque => {
let ctor = &union.alternatives[tag_id.0 as usize];
match &ctor.name {

View File

@ -957,6 +957,59 @@ mod test_reporting {
"###
);
test_report!(
tuple_exhaustiveness_bad,
indoc!(
r#"
Color : [Red, Blue]
value : (Color, Color)
value = (Red, Red)
when value is
(Blue, Blue) -> "foo"
(Red, Blue) -> "foo"
(Blue, Red) -> "foo"
#(Red, Red) -> "foo"
"#
),
@r###"
UNSAFE PATTERN /code/proj/Main.roc
This `when` does not cover all the possibilities:
9> when value is
10> (Blue, Blue) -> "foo"
11> (Red, Blue) -> "foo"
12> (Blue, Red) -> "foo"
Other possibilities include:
( Red, Red )
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
tuple_exhaustiveness_good,
indoc!(
r#"
Color : [Red, Blue]
value : (Color, Color)
value = (Red, Red)
when value is
(Blue, Blue) -> "foo"
(Red, Blue) -> "foo"
(Blue, Red) -> "foo"
(Red, Red) -> "foo"
"#
),
@"" // No error
);
test_report!(
elem_in_list,
indoc!(