From 5a6be05ead752c5adea601af86f6b56c177b6e7c Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Tue, 24 Jan 2023 20:23:17 -0800 Subject: [PATCH] implement mono / lowering for tuples --- crates/ast/src/constrain.rs | 4 +- .../ast/src/lang/core/expr/expr_to_expr2.rs | 3 +- crates/ast/src/lang/core/types.rs | 2 +- crates/compiler/can/src/annotation.rs | 72 +- crates/compiler/can/src/copy.rs | 51 +- crates/compiler/can/src/debug/pretty_print.rs | 16 +- crates/compiler/can/src/def.rs | 24 +- crates/compiler/can/src/exhaustive.rs | 27 + crates/compiler/can/src/expr.rs | 90 +-- crates/compiler/can/src/module.rs | 12 +- crates/compiler/can/src/operator.rs | 3 +- crates/compiler/can/src/pattern.rs | 79 ++- crates/compiler/can/src/traverse.rs | 20 +- crates/compiler/constrain/src/expr.rs | 121 +--- crates/compiler/constrain/src/pattern.rs | 96 ++- crates/compiler/exhaustive/src/lib.rs | 1 + crates/compiler/fmt/src/annotation.rs | 4 +- crates/compiler/fmt/src/expr.rs | 16 +- crates/compiler/fmt/src/spaces.rs | 7 +- crates/compiler/load_internal/src/docs.rs | 2 +- crates/compiler/mono/src/decision_tree.rs | 63 ++ crates/compiler/mono/src/ir.rs | 381 ++++++++++- crates/compiler/mono/src/layout.rs | 108 ++- crates/compiler/parse/src/ast.rs | 15 +- crates/compiler/parse/src/expr.rs | 6 +- crates/compiler/parse/src/ident.rs | 19 +- crates/compiler/parse/src/parser.rs | 3 +- crates/compiler/parse/src/pattern.rs | 8 +- crates/compiler/parse/src/type_annotation.rs | 2 +- crates/compiler/solve/tests/solve_expr.rs | 14 + crates/compiler/test_gen/src/gen_tuples.rs | 620 ++++++++++++++++++ crates/compiler/test_gen/src/tests.rs | 1 + .../generated/tuple_pattern_match.txt | 29 + crates/compiler/test_mono/src/tests.rs | 14 + ...nction_with_tuple_ext_type.expr.result-ast | 8 +- .../function_with_tuple_type.expr.result-ast | 4 +- .../tuple_accessor_function.expr.result-ast | 6 +- .../snapshots/pass/tuple_type.expr.result-ast | 8 +- .../pass/tuple_type_ext.expr.result-ast | 8 +- crates/compiler/types/src/types.rs | 11 +- crates/reporting/src/error/type.rs | 32 +- crates/reporting/tests/test_reporting.rs | 53 ++ 42 files changed, 1773 insertions(+), 290 deletions(-) create mode 100644 crates/compiler/test_gen/src/gen_tuples.rs create mode 100644 crates/compiler/test_mono/generated/tuple_pattern_match.txt diff --git a/crates/ast/src/constrain.rs b/crates/ast/src/constrain.rs index c6ea36e795..eb2d8080df 100644 --- a/crates/ast/src/constrain.rs +++ b/crates/ast/src/constrain.rs @@ -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( diff --git a/crates/ast/src/lang/core/expr/expr_to_expr2.rs b/crates/ast/src/lang/core/expr/expr_to_expr2.rs index 9a551a4109..cdbc12a054 100644 --- a/crates/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/crates/ast/src/lang/core/expr/expr_to_expr2.rs @@ -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(), diff --git a/crates/ast/src/lang/core/types.rs b/crates/ast/src/lang/core/types.rs index 7ef0b52e51..75ace432c8 100644 --- a/crates/ast/src/lang/core/types.rs +++ b/crates/ast/src/lang/core/types.rs @@ -405,7 +405,7 @@ pub fn to_type2<'a>( Type2::Variable(var) } - Tuple { fields: _, ext: _ } => { + Tuple { elems: _, ext: _ } => { todo!("tuple type"); } Record { fields, ext, .. } => { diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index fbfd6fc3cc..e028b750b8 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -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>], + scope: &mut Scope, + var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, + local_aliases: &mut VecMap, + references: &mut VecSet, +) -> VecMap { + 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>( diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index 307c435425..7da4816a45 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -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(env: &mut C, copied: &mut Vec, expr field: field.clone(), }, - RecordAccessor(RecordAccessorData { + RecordAccessor(StructAccessorData { name, function_var, record_var, @@ -521,7 +520,7 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, 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(env: &mut C, copied: &mut Vec, 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( }) .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, diff --git a/crates/compiler/can/src/debug/pretty_print.rs b/crates/compiler/can/src/debug/pretty_print.rs index 0f8de5070d..fa981db290 100644 --- a/crates/compiler/can/src/debug/pretty_print.rs +++ b/crates/compiler/can/src/debug/pretty_print.rs @@ -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) diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 5e4e95e3bc..2910e2498a 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -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(), diff --git a/crates/compiler/can/src/exhaustive.rs b/crates/compiler/can/src/exhaustive.rs index defac744c1..1960ee2aab 100644 --- a/crates/compiler/can/src/exhaustive.rs +++ b/crates/compiler/can/src/exhaustive.rs @@ -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: _, diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 1a8ceb303b..888d0222b6 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -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>, } -/// 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 { | Expr::Str(_) | Expr::ZeroArgumentTag { .. } | Expr::RecordAccessor(_) - | Expr::TupleAccessor(_) | Expr::SingleQuote(..) | Expr::EmptyRecord | Expr::TypedHole(_) diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 07958d457e..51f0e874ba 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -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() { diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs index bbcc84f89c..013a87d6d8 100644 --- a/crates/compiler/can/src/operator.rs +++ b/crates/compiler/can/src/operator.rs @@ -130,8 +130,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc | NonBase10Int { .. } | Str(_) | SingleQuote(_) - | RecordAccessorFunction(_) - | TupleAccessorFunction(_) + | AccessorFunction(_) | Var { .. } | Underscore { .. } | MalformedIdent(_, _) diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index 9d6a366cf9..fbbee0ab0e 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -58,6 +58,11 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, + TupleDestructure { + whole_var: Variable, + ext_var: Variable, + destructs: Vec>, + }, 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), +} + #[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), - Destruct(&'a Loc), + RecordDestruct(&'a Loc), + TupleDestruct(&'a Loc), } 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)) + } } } diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index ceaa46fd6a..c8b31ec715 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -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(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(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(visitor: &mut V, pattern: &Pattern) { @@ -503,6 +512,9 @@ pub fn walk_pattern(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 diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 659c202e65..96baf89b22 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -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; diff --git a/crates/compiler/constrain/src/pattern.rs b/crates/compiler/constrain/src/pattern.rs index d67c0c95a4..f05c218125 100644 --- a/crates/compiler/constrain/src/pattern.rs +++ b/crates/compiler/constrain/src/pattern.rs @@ -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 = 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, diff --git a/crates/compiler/exhaustive/src/lib.rs b/crates/compiler/exhaustive/src/lib.rs index 561564f7fd..2b71f3e6cf 100644 --- a/crates/compiler/exhaustive/src/lib.rs +++ b/crates/compiler/exhaustive/src/lib.rs @@ -38,6 +38,7 @@ pub enum RenderAs { Tag, Opaque, Record(Vec), + Tuple, Guard, } diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index 73ed08a605..6ae31b9487 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -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 { diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 1fc7a90032..c12b484654 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -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('.'); diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index b91fcad716..629ce05184 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -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 { diff --git a/crates/compiler/load_internal/src/docs.rs b/crates/compiler/load_internal/src/docs.rs index eb8b7ed514..27dee8c8a0 100644 --- a/crates/compiler/load_internal/src/docs.rs +++ b/crates/compiler/load_internal/src/docs.rs @@ -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; diff --git a/crates/compiler/mono/src/decision_tree.rs b/crates/compiler/mono/src/decision_tree.rs index 63fdc394ed..444565e5c3 100644 --- a/crates/compiler/mono/src/decision_tree.rs +++ b/crates/compiler/mono/src/decision_tree.rs @@ -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> { } } + 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 { .. } diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 6c6c3d3df1..a55373ba33 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -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), + } + + // 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), 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, 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), diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 17366c54b7..408cff6183 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -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>, 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, +) -> Result>, 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>>); pub fn sort_record_fields<'a>( diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index edd85b4310..677902474c 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -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>>), @@ -612,7 +612,7 @@ pub enum TypeAnnotation<'a> { }, Tuple { - fields: Collection<'a, Loc>>, + elems: Collection<'a, Loc>>, /// 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>>, @@ -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() } diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index c86cb688b3..32b70dce9a 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -1872,9 +1872,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result(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), } } diff --git a/crates/compiler/parse/src/ident.rs b/crates/compiler/parse/src/ident.rs index 0bb2596590..62546e0a0f 100644 --- a/crates/compiler/parse/src/ident.rs +++ b/crates/compiler/parse/src/ident.rs @@ -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)), }, diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 9dc715d74f..e7e5a3ec02 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -536,8 +536,7 @@ pub enum EPattern<'a> { IndentEnd(Position), AsIndentStart(Position), - RecordAccessorFunction(Position), - TupleAccessorFunction(Position), + AccessorFunction(Position), } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/compiler/parse/src/pattern.rs b/crates/compiler/parse/src/pattern.rs index 723631cc48..0854eb04be 100644 --- a/crates/compiler/parse/src/pattern.rs +++ b/crates/compiler/parse/src/pattern.rs @@ -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()); diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index d3228c4276..48d665e61d 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -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 { diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index ea09b3f2fb..937c7aadcb 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -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"); diff --git a/crates/compiler/test_gen/src/gen_tuples.rs b/crates/compiler/test_gen/src/gen_tuples.rs new file mode 100644 index 0000000000..d74fbc9f54 --- /dev/null +++ b/crates/compiler/test_gen/src/gen_tuples.rs @@ -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 + ); +} diff --git a/crates/compiler/test_gen/src/tests.rs b/crates/compiler/test_gen/src/tests.rs index a1cbff449d..b7d87f148a 100644 --- a/crates/compiler/test_gen/src/tests.rs +++ b/crates/compiler/test_gen/src/tests.rs @@ -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; diff --git a/crates/compiler/test_mono/generated/tuple_pattern_match.txt b/crates/compiler/test_mono/generated/tuple_pattern_match.txt new file mode 100644 index 0000000000..c96c80b04e --- /dev/null +++ b/crates/compiler/test_mono/generated/tuple_pattern_match.txt @@ -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; diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 0c538f4bf5..8458a44c90 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -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!( diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast index e37c89546d..9cda2a8e5a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast @@ -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", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast index 50ccc54f6f..c6c9bb71c5 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast @@ -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", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast index a54857c683..122d168422 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast @@ -1,6 +1,8 @@ Apply( - @0-2 TupleAccessorFunction( - "1", + @0-2 AccessorFunction( + TupleIndex( + "1", + ), ), [ @3-12 Tuple( diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast index 3aae783860..50167155f7 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast @@ -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", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast index 3d9bab17da..78a9811c47 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast @@ -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", diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index 82cfa9909e..6c196ce27a 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -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, diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index e5463b84f2..c9340a4f50 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -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 { diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 428b20d8e2..77612096b0 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -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!(