mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-10 10:02:38 +03:00
implement mono / lowering for tuples
This commit is contained in:
parent
65f8bb3d0d
commit
5a6be05ead
@ -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(
|
||||
|
@ -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(),
|
||||
|
@ -405,7 +405,7 @@ pub fn to_type2<'a>(
|
||||
|
||||
Type2::Variable(var)
|
||||
}
|
||||
Tuple { fields: _, ext: _ } => {
|
||||
Tuple { elems: _, ext: _ } => {
|
||||
todo!("tuple type");
|
||||
}
|
||||
Record { fields, ext, .. } => {
|
||||
|
@ -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>(
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
|
@ -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: _,
|
||||
|
@ -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(_)
|
||||
|
@ -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() {
|
||||
|
@ -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(_, _)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -38,6 +38,7 @@ pub enum RenderAs {
|
||||
Tag,
|
||||
Opaque,
|
||||
Record(Vec<Lowercase>),
|
||||
Tuple,
|
||||
Guard,
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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('.');
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 { .. }
|
||||
|
@ -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),
|
||||
|
@ -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>(
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
},
|
||||
|
@ -536,8 +536,7 @@ pub enum EPattern<'a> {
|
||||
IndentEnd(Position),
|
||||
AsIndentStart(Position),
|
||||
|
||||
RecordAccessorFunction(Position),
|
||||
TupleAccessorFunction(Position),
|
||||
AccessorFunction(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -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());
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
620
crates/compiler/test_gen/src/gen_tuples.rs
Normal file
620
crates/compiler/test_gen/src/gen_tuples.rs
Normal 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
|
||||
);
|
||||
}
|
@ -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;
|
||||
|
||||
|
29
crates/compiler/test_mono/generated/tuple_pattern_match.txt
Normal file
29
crates/compiler/test_mono/generated/tuple_pattern_match.txt
Normal 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;
|
@ -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!(
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -1,6 +1,8 @@
|
||||
Apply(
|
||||
@0-2 TupleAccessorFunction(
|
||||
"1",
|
||||
@0-2 AccessorFunction(
|
||||
TupleIndex(
|
||||
"1",
|
||||
),
|
||||
),
|
||||
[
|
||||
@3-12 Tuple(
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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!(
|
||||
|
Loading…
Reference in New Issue
Block a user