From 82bb341e9ca8687736c103d39739c9fa6319ed31 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 1 Dec 2021 19:26:01 +0100 Subject: [PATCH 001/150] add constraint soa form --- Cargo.lock | 1 + compiler/can/Cargo.toml | 1 + compiler/can/src/constraint_soa.rs | 194 +++++++++++++++++++++++++++++ compiler/can/src/lib.rs | 1 + 4 files changed, 197 insertions(+) create mode 100644 compiler/can/src/constraint_soa.rs diff --git a/Cargo.lock b/Cargo.lock index 3cd24a5c1f..ee75ebfb7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3208,6 +3208,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_types", + "static_assertions", "ven_graph", ] diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 2e8bd639dd..edec9d23ff 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -15,6 +15,7 @@ roc_types = { path = "../types" } roc_builtins = { path = "../builtins" } ven_graph = { path = "../../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } +static_assertions = "1.1.0" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs new file mode 100644 index 0000000000..a25c630755 --- /dev/null +++ b/compiler/can/src/constraint_soa.rs @@ -0,0 +1,194 @@ +use crate::expected::{Expected, PExpected}; +use roc_collections::all::{MutSet, SendMap}; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::types::{Category, PatternCategory, Type}; +use roc_types::{subs::Variable, types::VariableDetail}; + +pub struct Constraints { + constraints: Vec, + types: Vec, + variables: Vec, + def_types: Vec<(Symbol, Located>)>, + let_constraints: Vec, + categories: Vec, + pattern_categories: Vec, + expectations: Vec>, + pattern_expectations: Vec>, +} + +impl Constraints { + pub fn equal_types( + &mut self, + typ: Type, + expected: Expected, + category: Category, + region: Region, + ) -> Constraint { + let type_index = Index::new(self.types.len() as _); + let expected_index = Index::new(self.expectations.len() as _); + let category_index = Index::new(self.categories.len() as _); + + self.types.push(typ); + self.expectations.push(expected); + self.categories.push(category); + + Constraint::Eq(type_index, expected_index, category_index, region) + } + + pub fn equal_pattern_types( + &mut self, + typ: Type, + expected: PExpected, + category: PatternCategory, + region: Region, + ) -> Constraint { + let type_index = Index::new(self.types.len() as _); + let expected_index = Index::new(self.pattern_expectations.len() as _); + let category_index = Index::new(self.pattern_categories.len() as _); + + self.types.push(typ); + self.pattern_expectations.push(expected); + self.pattern_categories.push(category); + + Constraint::Pattern(type_index, expected_index, category_index, region) + } + + fn variable_slice(&mut self, it: I) -> Slice + where + I: IntoIterator, + { + let start = self.variables.len(); + self.variables.extend(it); + let length = self.variables.len() - start; + + Slice::new(start as _, length as _) + } + + fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Located)> + where + I: IntoIterator)>, + { + let start = self.def_types.len(); + + for (symbol, loc_type) in it { + let type_index = Index::new(self.types.len() as _); + let Located { region, value } = loc_type; + self.types.push(value); + + self.def_types + .push((symbol, Located::at(region, type_index))); + } + + let length = self.def_types.len() - start; + + Slice::new(start as _, length as _) + } + + pub fn let_contraint( + &mut self, + rigid_vars: I1, + flex_vars: I2, + def_types: I3, + defs_constraint: Constraint, + ret_constraint: Constraint, + ) -> Constraint + where + I1: IntoIterator, + I2: IntoIterator, + I3: IntoIterator)>, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.constraints.push(defs_constraint); + self.constraints.push(ret_constraint); + + let let_contraint = LetConstraint { + rigid_vars: self.variable_slice(rigid_vars), + flex_vars: self.variable_slice(flex_vars), + def_types: self.def_types_slice(def_types), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } +} + +static_assertions::assert_eq_size!([u8; 4 * 8], Constraint); +static_assertions::assert_eq_size!([u8; 3 * 8 + 4], LetConstraint); + +#[derive(Debug, Clone, PartialEq)] +pub enum Constraint { + Eq(Index, Index>, Index, Region), + Store(Index, Variable, &'static str, u32), + Lookup(Symbol, Index>, Region), + Pattern( + Index, + Index>, + Index, + Region, + ), + True, // Used for things that always unify, e.g. blanks and runtime errors + SaveTheEnvironment, + Let(Index), + And(Slice), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LetConstraint { + pub rigid_vars: Slice, + pub flex_vars: Slice, + pub def_types: Slice<(Symbol, Located)>, + pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Index { + index: u32, + _marker: std::marker::PhantomData, +} + +impl Index { + pub const fn new(index: u32) -> Self { + Self { + index, + _marker: std::marker::PhantomData, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Slice { + start: u32, + length: u16, + _marker: std::marker::PhantomData, +} + +impl Slice { + pub const fn new(start: u32, length: u16) -> Self { + Self { + start, + length, + _marker: std::marker::PhantomData, + } + } + + pub const fn len(&self) -> usize { + self.length as _ + } + + pub const fn is_empty(&self) -> bool { + self.length == 0 + } + + pub const fn indices(&self) -> std::ops::Range { + self.start as usize..(self.start as usize + self.length as usize) + } + + pub fn into_iter(&self) -> impl Iterator> { + self.indices().map(|i| Index::new(i as _)) + } +} diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index 7230ab1a36..5cd16be75a 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -4,6 +4,7 @@ pub mod annotation; pub mod builtins; pub mod constraint; +pub mod constraint_soa; pub mod def; pub mod env; pub mod expected; From a23b76d35f922e4982de4d20d6ecd0462edca045 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 1 Dec 2021 20:01:48 +0100 Subject: [PATCH 002/150] more helpers --- compiler/can/src/constraint_soa.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index a25c630755..05e1652340 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -85,6 +85,28 @@ impl Constraints { Slice::new(start as _, length as _) } + pub fn exists(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint + where + I: IntoIterator, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.constraints.push(defs_constraint); + self.constraints.push(Constraint::True); + + let let_contraint = LetConstraint { + rigid_vars: Slice::default(), + flex_vars: self.variable_slice(flex_vars), + def_types: Slice::default(), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + pub fn let_contraint( &mut self, rigid_vars: I1, @@ -167,6 +189,12 @@ pub struct Slice { _marker: std::marker::PhantomData, } +impl Default for Slice { + fn default() -> Self { + Self::new(0, 0) + } +} + impl Slice { pub const fn new(start: u32, length: u16) -> Self { Self { From dcc68fbc849322117f1a0bd3fc9b9d654b0e032b Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 13:15:51 -0800 Subject: [PATCH 003/150] enable passing lists to functions --- compiler/gen_dev/src/generic64/x86_64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 70ac3f2c47..2545d3fb54 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -326,7 +326,7 @@ impl CallConv for X86_64Syste tmp_stack_offset += 8; } } - Layout::Builtin(Builtin::Str) => { + Layout::Builtin(Builtin::Str | Builtin::List(_)) => { if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { let (base_offset, _size) = storage_manager.stack_offset_and_size(sym); debug_assert_eq!(base_offset % 8, 0); @@ -669,7 +669,7 @@ impl CallConv for X86_64Windo tmp_stack_offset += 8; } } - Layout::Builtin(Builtin::Str) => { + Layout::Builtin(Builtin::Str | Builtin::List(_)) => { // I think this just needs to be passed on the stack, so not a huge deal. todo!("Passing str args with Windows fast call"); } From 582413a5bb5ad2e49fc821e0e9d6424005360a9e Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 13:18:17 -0800 Subject: [PATCH 004/150] add List.get wrapper --- compiler/gen_dev/src/lib.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 2971d30ddb..f4d378ddfe 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -277,13 +277,7 @@ trait Backend<'a> { self.load_literal_symbols(arguments); self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) } else { - self.build_inline_builtin( - sym, - *func_sym, - arguments, - arg_layouts, - ret_layout, - ) + self.build_builtin(sym, *func_sym, arguments, arg_layouts, ret_layout) } } @@ -558,8 +552,9 @@ trait Backend<'a> { } } - // inlines simple builtin functions that do not map directly to a low level - fn build_inline_builtin( + /// Builds a builtin functions that do not map directly to a low level + /// If the builtin is simple enough, it will be inlined. + fn build_builtin( &mut self, sym: &Symbol, func_sym: Symbol, @@ -585,6 +580,14 @@ trait Backend<'a> { self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.free_symbol(&Symbol::DEV_TMP) } + Symbol::LIST_GET => { + // TODO: This is probably simple enough to be worth inlining. + let layout_id = LayoutIds::default().get(func_sym, ret_layout); + let fn_name = self.symbol_to_string(func_sym, layout_id); + // Now that the arguments are needed, load them if they are literals. + self.load_literal_symbols(args); + self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) + } _ => todo!("the function, {:?}", func_sym), } } From 05959b06220e0f83ac654cad2c4ae118645b2b5c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 13:23:13 -0800 Subject: [PATCH 005/150] enable returning small complex types --- compiler/gen_dev/src/generic64/x86_64.rs | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 2545d3fb54..74f5e780cc 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -381,6 +381,32 @@ impl CallConv for X86_64Syste ); } x if x.stack_size(TARGET_INFO) == 0 => {} + x if !Self::returns_via_arg_pointer(x) => { + let (base_offset, size) = storage_manager.stack_offset_and_size(sym); + debug_assert_eq!(base_offset % 8, 0); + if size <= 8 { + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[0], + base_offset, + ); + } else if size <= 16 { + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[0], + base_offset, + ); + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[1], + base_offset + 8, + ); + } else { + internal_error!( + "types that don't return via arg pointer must be less than 16 bytes" + ); + } + } x => todo!("returning complex type, {:?}", x), } } @@ -406,6 +432,24 @@ impl CallConv for X86_64Syste X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]); } + x if !Self::returns_via_arg_pointer(x) => { + let size = layout.stack_size(TARGET_INFO); + let offset = storage_manager.claim_stack_area(sym, size); + if size <= 8 { + X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); + } else if size <= 16 { + X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); + X86_64Assembler::mov_base32_reg64( + buf, + offset + 8, + Self::GENERAL_RETURN_REGS[1], + ); + } else { + internal_error!( + "types that don't return via arg pointer must be less than 16 bytes" + ); + } + } x if x.stack_size(TARGET_INFO) == 0 => {} x => todo!("receiving complex return type, {:?}", x), } From 2307be8f0a67564e66c5da6171ead510366c8eda Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 14:09:55 -0800 Subject: [PATCH 006/150] add loading tag for non-recursive union --- compiler/gen_dev/src/generic64/mod.rs | 7 ++- compiler/gen_dev/src/generic64/storage.rs | 71 ++++++++++++++++++----- compiler/gen_dev/src/lib.rs | 11 +++- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 6639d27d6f..cd4338e05e 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -6,7 +6,7 @@ use roc_error_macros::internal_error; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; -use roc_mono::layout::{Builtin, Layout}; +use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_target::TargetInfo; use std::marker::PhantomData; @@ -870,6 +870,11 @@ impl< .load_field_at_index(sym, structure, index, field_layouts); } + fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { + self.storage_manager + .load_union_tag_id(&mut self.buf, sym, structure, union_layout); + } + fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) { match (lit, layout) { ( diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 93c3734073..0b8a790fdc 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -9,7 +9,7 @@ use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::{ ir::{JoinPointId, Param}, - layout::{Builtin, Layout}, + layout::{Builtin, Layout, UnionLayout}, }; use roc_target::TargetInfo; use std::cmp::max; @@ -522,11 +522,7 @@ impl< ) { debug_assert!(index < field_layouts.len() as u64); // This must be removed and reinserted for ownership and mutability reasons. - let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) { - owned_data - } else { - internal_error!("Unknown symbol: {}", structure); - }; + let owned_data = self.remove_allocation_for_sym(structure); self.allocation_map .insert(*structure, Rc::clone(&owned_data)); match self.get_storage_for_sym(structure) { @@ -538,9 +534,9 @@ impl< data_offset += field_size as i32; } debug_assert!(data_offset < base_offset + size as i32); - self.allocation_map.insert(*sym, owned_data); let layout = field_layouts[index as usize]; let size = layout.stack_size(self.target_info); + self.allocation_map.insert(*sym, owned_data); self.symbol_storage_map.insert( *sym, Stack(if is_primitive(&layout) { @@ -565,6 +561,40 @@ impl< } } + pub fn load_union_tag_id( + &mut self, + _buf: &mut Vec<'a, u8>, + sym: &Symbol, + structure: &Symbol, + union_layout: &UnionLayout<'a>, + ) { + // This must be removed and reinserted for ownership and mutability reasons. + let owned_data = self.remove_allocation_for_sym(structure); + self.allocation_map + .insert(*structure, Rc::clone(&owned_data)); + match union_layout { + UnionLayout::NonRecursive(_) => { + let (union_offset, _) = self.stack_offset_and_size(structure); + + let (data_size, data_alignment) = + union_layout.data_size_and_alignment(self.target_info); + let id_offset = data_size - data_alignment; + let id_builtin = union_layout.tag_id_builtin(); + + let size = id_builtin.stack_size(self.target_info); + self.allocation_map.insert(*sym, owned_data); + self.symbol_storage_map.insert( + *sym, + Stack(ReferencedPrimitive { + base_offset: union_offset + id_offset as i32, + size, + }), + ); + } + x => todo!("getting tag id of union with layout ({:?})", x), + } + } + /// Creates a struct on the stack, moving the data in fields into the struct. pub fn create_struct( &mut self, @@ -973,11 +1003,7 @@ impl< /// Frees an reference and release an allocation if it is no longer used. fn free_reference(&mut self, sym: &Symbol) { - let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { - owned_data - } else { - internal_error!("Unknown symbol: {:?}", sym); - }; + let owned_data = self.remove_allocation_for_sym(sym); if Rc::strong_count(&owned_data) == 1 { self.free_stack_chunk(owned_data.0, owned_data.1); } @@ -1060,7 +1086,26 @@ impl< } } - /// Gets a value from storage. They index symbol must be defined. + #[allow(dead_code)] + /// Gets the allocated area for a symbol. The index symbol must be defined. + fn get_allocation_for_sym(&self, sym: &Symbol) -> &Rc<(i32, u32)> { + if let Some(allocation) = self.allocation_map.get(sym) { + allocation + } else { + internal_error!("Unknown symbol: {:?}", sym); + } + } + + /// Removes and returns the allocated area for a symbol. They index symbol must be defined. + fn remove_allocation_for_sym(&mut self, sym: &Symbol) -> Rc<(i32, u32)> { + if let Some(allocation) = self.allocation_map.remove(sym) { + allocation + } else { + internal_error!("Unknown symbol: {:?}", sym); + } + } + + /// Gets a value from storage. The index symbol must be defined. fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage { if let Some(storage) = self.symbol_storage_map.get(sym) { storage diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index f4d378ddfe..d85de00c47 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -14,7 +14,7 @@ use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt, }; -use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds}; +use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, UnionLayout}; mod generic64; mod object_builder; @@ -315,6 +315,12 @@ trait Backend<'a> { } => { self.load_struct_at_index(sym, structure, *index, field_layouts); } + Expr::GetTagId { + structure, + union_layout, + } => { + self.get_tag_id(sym, structure, union_layout); + } x => todo!("the expression, {:?}", x), } } @@ -680,6 +686,9 @@ trait Backend<'a> { field_layouts: &'a [Layout<'a>], ); + /// get_tag_id loads the tag id from a the union. + fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>); + /// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function. fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>); From f55f82119c05c8a2a41f25de0856582afa82c8d2 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 14:16:06 -0800 Subject: [PATCH 007/150] wider int support for equality --- compiler/gen_dev/src/generic64/mod.rs | 39 ++++++++++++++--------- compiler/gen_dev/src/generic64/storage.rs | 3 +- compiler/gen_dev/src/generic64/x86_64.rs | 3 +- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index cd4338e05e..75cca2b4ed 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1,4 +1,7 @@ -use crate::{single_register_floats, single_register_integers, Backend, Env, Relocation}; +use crate::{ + single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env, + Relocation, +}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; @@ -16,6 +19,8 @@ pub(crate) mod x86_64; use storage::StorageManager; +// TODO: on all number functions double check and deal with over/underflow. + pub trait CallConv>: Sized { @@ -716,7 +721,7 @@ impl< fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) { match arg_layout { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + Layout::Builtin(single_register_int_builtins!()) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let src1_reg = self .storage_manager @@ -998,22 +1003,26 @@ impl< } } +#[macro_export] +macro_rules! single_register_int_builtins { + () => { + Builtin::Int( + IntWidth::I8 + | IntWidth::I16 + | IntWidth::I32 + | IntWidth::I64 + | IntWidth::U8 + | IntWidth::U16 + | IntWidth::U32 + | IntWidth::U64, + ) + }; +} + #[macro_export] macro_rules! single_register_integers { () => { - Layout::Builtin( - Builtin::Bool - | Builtin::Int( - IntWidth::I8 - | IntWidth::I16 - | IntWidth::I32 - | IntWidth::I64 - | IntWidth::U8 - | IntWidth::U16 - | IntWidth::U32 - | IntWidth::U64, - ), - ) | Layout::RecursivePointer + Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer }; } diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 0b8a790fdc..5266f3d1ec 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,6 +1,7 @@ use crate::{ generic64::{Assembler, CallConv, RegTrait}, - single_register_floats, single_register_integers, single_register_layouts, Env, + single_register_floats, single_register_int_builtins, single_register_integers, + single_register_layouts, Env, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 74f5e780cc..5ac4456ca3 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,6 +1,7 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::{ - single_register_floats, single_register_integers, single_register_layouts, Relocation, + single_register_floats, single_register_int_builtins, single_register_integers, + single_register_layouts, Relocation, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; From bed779a290a1f9205e359f238ef6262e97dbecb7 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:12:50 -0800 Subject: [PATCH 008/150] add zero and sign extension --- compiler/gen_dev/src/generic64/aarch64.rs | 31 +++++++++ compiler/gen_dev/src/generic64/mod.rs | 14 ++++ compiler/gen_dev/src/generic64/storage.rs | 57 ++++++++++------ compiler/gen_dev/src/generic64/x86_64.rs | 83 ++++++++++++++++++++++- 4 files changed, 162 insertions(+), 23 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 302db9b596..bd01061158 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -480,6 +480,37 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) { + debug_assert!(size <= 8); + if size == 8 { + Self::mov_reg64_base32(buf, dst, offset); + } else if size == 4 { + todo!("sign extending 4 byte values"); + } else if size == 2 { + todo!("sign extending 2 byte values"); + } else if size == 1 { + todo!("sign extending 1 byte values"); + } else { + internal_error!("Invalid size for sign extension: {}", size); + } + } + #[inline(always)] + fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) { + debug_assert!(size <= 8); + if size == 8 { + Self::mov_reg64_base32(buf, dst, offset); + } else if size == 4 { + todo!("zero extending 4 byte values"); + } else if size == 2 { + todo!("zero extending 2 byte values"); + } else if size == 1 { + todo!("zero extending 1 byte values"); + } else { + internal_error!("Invalid size for zero extension: {}", size); + } + } + #[inline(always)] fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) { todo!("loading floating point reg from stack for AArch64"); diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 75cca2b4ed..d70a5b8669 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -172,6 +172,13 @@ pub trait Assembler: Sized { fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + /// Sign extends the data at `offset` with `size` as it copies it to `dst` + /// size must be less than or equal to 8. + fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8); + /// Zero extends the data at `offset` with `size` as it copies it to `dst` + /// size must be less than or equal to 8. + fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8); + fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); @@ -1003,6 +1010,13 @@ impl< } } +#[macro_export] +macro_rules! sign_extended_builtins { + () => { + Builtin::Int(IntWidth::I8 | IntWidth::I16 | IntWidth::I32 | IntWidth::I64 | IntWidth::I128) + }; +} + #[macro_export] macro_rules! single_register_int_builtins { () => { diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 5266f3d1ec..a3f266f81b 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,7 +1,7 @@ use crate::{ generic64::{Assembler, CallConv, RegTrait}, - single_register_floats, single_register_int_builtins, single_register_integers, - single_register_layouts, Env, + sign_extended_builtins, single_register_floats, single_register_int_builtins, + single_register_integers, single_register_layouts, Env, }; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; @@ -49,6 +49,9 @@ enum StackStorage { base_offset: i32, // Size on the stack in bytes. size: u32, + // Whether or not the data is need to be sign extended on load. + // If not, it must be zero extended. + sign_extend: bool, }, /// Complex data (lists, unions, structs, str) stored on the stack. /// Note, this is also used for referencing a value within a struct/union. @@ -324,20 +327,22 @@ impl< ); reg } - Stack(ReferencedPrimitive { base_offset, size }) - if base_offset % 8 == 0 && size == 8 => - { - // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. + Stack(ReferencedPrimitive { + base_offset, + size, + sign_extend, + }) => { let reg = self.get_general_reg(buf); - ASM::mov_reg64_base32(buf, reg, base_offset); + if sign_extend { + ASM::movsx_reg64_base32(buf, reg, base_offset, size as u8); + } else { + ASM::movzx_reg64_base32(buf, reg, base_offset, size as u8); + } self.general_used_regs.push((reg, *sym)); self.symbol_storage_map.insert(*sym, Reg(General(reg))); self.free_reference(sym); reg } - Stack(ReferencedPrimitive { .. }) => { - todo!("loading referenced primitives") - } Stack(Complex { .. }) => { internal_error!("Cannot load large values into general registers: {}", sym) } @@ -386,9 +391,9 @@ impl< ); reg } - Stack(ReferencedPrimitive { base_offset, size }) - if base_offset % 8 == 0 && size == 8 => - { + Stack(ReferencedPrimitive { + base_offset, size, .. + }) if base_offset % 8 == 0 && size == 8 => { // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. let reg = self.get_float_reg(buf); ASM::mov_freg64_base32(buf, reg, base_offset); @@ -445,9 +450,9 @@ impl< debug_assert_eq!(base_offset % 8, 0); ASM::mov_reg64_base32(buf, reg, *base_offset); } - Stack(ReferencedPrimitive { base_offset, size }) - if base_offset % 8 == 0 && *size == 8 => - { + Stack(ReferencedPrimitive { + base_offset, size, .. + }) if base_offset % 8 == 0 && *size == 8 => { // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. ASM::mov_reg64_base32(buf, reg, *base_offset); } @@ -494,9 +499,9 @@ impl< debug_assert_eq!(base_offset % 8, 0); ASM::mov_freg64_base32(buf, reg, *base_offset); } - Stack(ReferencedPrimitive { base_offset, size }) - if base_offset % 8 == 0 && *size == 8 => - { + Stack(ReferencedPrimitive { + base_offset, size, .. + }) if base_offset % 8 == 0 && *size == 8 => { // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. ASM::mov_freg64_base32(buf, reg, *base_offset); } @@ -544,6 +549,10 @@ impl< ReferencedPrimitive { base_offset: data_offset, size, + sign_extend: matches!( + layout, + Layout::Builtin(sign_extended_builtins!()) + ), } } else { Complex { @@ -589,6 +598,7 @@ impl< Stack(ReferencedPrimitive { base_offset: union_offset + id_offset as i32, size, + sign_extend: matches!(id_builtin, sign_extended_builtins!()), }), ); } @@ -770,9 +780,12 @@ impl< pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) { match self.get_storage_for_sym(sym) { Stack(Primitive { base_offset, .. }) => (*base_offset, 8), - Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => { - (*base_offset, *size) - } + Stack( + ReferencedPrimitive { + base_offset, size, .. + } + | Complex { base_offset, size }, + ) => (*base_offset, *size), storage => { internal_error!( "Data not on the stack for sym ({}) with storage ({:?})", diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 5ac4456ca3..015fbba92e 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -433,6 +433,7 @@ impl CallConv for X86_64Syste X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]); } + x if x.stack_size(TARGET_INFO) == 0 => {} x if !Self::returns_via_arg_pointer(x) => { let size = layout.stack_size(TARGET_INFO); let offset = storage_manager.claim_stack_area(sym, size); @@ -451,7 +452,6 @@ impl CallConv for X86_64Syste ); } } - x if x.stack_size(TARGET_INFO) == 0 => {} x => todo!("receiving complex return type, {:?}", x), } } @@ -1033,6 +1033,37 @@ impl Assembler for X86_64Assembler { mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src) } + #[inline(always)] + fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { + debug_assert!(size <= 8); + if size == 8 { + Self::mov_reg64_base32(buf, dst, offset); + } else if size == 4 { + todo!("sign extending 4 byte values"); + } else if size == 2 { + todo!("sign extending 2 byte values"); + } else if size == 1 { + todo!("sign extending 1 byte values"); + } else { + internal_error!("Invalid size for sign extension: {}", size); + } + } + #[inline(always)] + fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { + debug_assert!(size <= 8); + if size == 8 { + Self::mov_reg64_base32(buf, dst, offset); + } else if size == 4 { + todo!("zero extending 4 byte values"); + } else if size == 2 { + todo!("zero extending 2 byte values"); + } else if size == 1 { + movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset); + } else { + internal_error!("Invalid size for zero extension: {}", size); + } + } + #[inline(always)] fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) { movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset) @@ -1423,6 +1454,27 @@ fn mov_reg64_base64_offset32( buf.extend(&offset.to_le_bytes()); } +/// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset. +#[inline(always)] +fn movzx_reg64_base8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + let rex = add_rm_extension(base, REX_W); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(9); + buf.extend(&[rex, 0x0F, 0xB6, 0x80 + dst_mod + base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(&offset.to_le_bytes()); +} + /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register. #[inline(always)] fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { @@ -2126,6 +2178,35 @@ mod tests { } } + #[test] + fn test_movzx_reg64_base8_offset32() { + let arena = bumpalo::Bump::new(); + let mut buf = bumpalo::vec![in &arena]; + for ((dst, src, offset), expected) in &[ + ( + (X86_64GeneralReg::RAX, X86_64GeneralReg::RBP, TEST_I32), + vec![0x48, 0x0F, 0xB6, 0x85], + ), + ( + (X86_64GeneralReg::R15, X86_64GeneralReg::RBP, TEST_I32), + vec![0x4C, 0x0F, 0xB6, 0xBD], + ), + ( + (X86_64GeneralReg::RAX, X86_64GeneralReg::RSP, TEST_I32), + vec![0x48, 0x0F, 0xB6, 0x84, 0x24], + ), + ( + (X86_64GeneralReg::R15, X86_64GeneralReg::RSP, TEST_I32), + vec![0x4C, 0x0F, 0xB6, 0xBC, 0x24], + ), + ] { + buf.clear(); + movzx_reg64_base8_offset32(&mut buf, *dst, *src, *offset); + assert_eq!(expected, &buf[..expected.len()]); + assert_eq!(TEST_I32.to_le_bytes(), &buf[expected.len()..]); + } + } + #[test] fn test_mov_reg64_stack32() { let arena = bumpalo::Bump::new(); From a86f1193c6c9fcf2b34330bec1a1182c69520158 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:15:08 -0800 Subject: [PATCH 009/150] skip branch info in switch --- compiler/gen_dev/src/generic64/mod.rs | 41 +++++++++++++-------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index d70a5b8669..0dd31e5aa9 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -537,32 +537,29 @@ impl< let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena]; - for (val, branch_info, stmt) in branches.iter() { + for (val, _branch_info, stmt) in branches.iter() { + // TODO: look inot branch info and if it matters here. tmp.clear(); - if let BranchInfo::None = branch_info { - // Create jump to next branch if not cond_sym not equal to value. - // Since we don't know the offset yet, set it to 0 and overwrite later. - let jne_location = self.buf.len(); - let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); + // Create jump to next branch if not cond_sym not equal to value. + // Since we don't know the offset yet, set it to 0 and overwrite later. + let jne_location = self.buf.len(); + let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); - // Build all statements in this branch. - self.build_stmt(stmt, ret_layout); + // Build all statements in this branch. + self.build_stmt(stmt, ret_layout); - // Build unconditional jump to the end of this switch. - // Since we don't know the offset yet, set it to 0 and overwrite later. - let jmp_location = self.buf.len(); - let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); - ret_jumps.push((jmp_location, jmp_offset)); + // Build unconditional jump to the end of this switch. + // Since we don't know the offset yet, set it to 0 and overwrite later. + let jmp_location = self.buf.len(); + let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); + ret_jumps.push((jmp_location, jmp_offset)); - // Overwrite the original jne with the correct offset. - let end_offset = self.buf.len(); - let jne_offset = end_offset - start_offset; - ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32); - for (i, byte) in tmp.iter().enumerate() { - self.buf[jne_location + i] = *byte; - } - } else { - todo!("Switch: branch info, {:?}", branch_info); + // Overwrite the original jne with the correct offset. + let end_offset = self.buf.len(); + let jne_offset = end_offset - start_offset; + ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32); + for (i, byte) in tmp.iter().enumerate() { + self.buf[jne_location + i] = *byte; } } let (branch_info, stmt) = default_branch; From f2e5b63ec3ce15ac18cc8cd9b2b2781c17685859 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:24:25 -0800 Subject: [PATCH 010/150] load union at index for recursive and nonrecursive unions --- compiler/gen_dev/src/generic64/mod.rs | 23 ++++++++++++++++++++++- compiler/gen_dev/src/lib.rs | 20 +++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 0dd31e5aa9..a2e5915856 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -9,7 +9,7 @@ use roc_error_macros::internal_error; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; -use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_mono::layout::{Builtin, Layout, TagIdIntType, UnionLayout}; use roc_target::TargetInfo; use std::marker::PhantomData; @@ -879,6 +879,27 @@ impl< .load_field_at_index(sym, structure, index, field_layouts); } + fn load_union_at_index( + &mut self, + sym: &Symbol, + structure: &Symbol, + tag_id: TagIdIntType, + index: u64, + union_layout: &UnionLayout<'a>, + ) { + match union_layout { + UnionLayout::NonRecursive(tag_layouts) | UnionLayout::Recursive(tag_layouts) => { + self.storage_manager.load_field_at_index( + sym, + structure, + index, + tag_layouts[tag_id as usize], + ); + } + x => todo!("loading from union type: {:?}", x), + } + } + fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { self.storage_manager .load_union_tag_id(&mut self.buf, sym, structure, union_layout); diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index d85de00c47..9a5e81abbd 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -14,7 +14,7 @@ use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt, }; -use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, UnionLayout}; +use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout}; mod generic64; mod object_builder; @@ -315,6 +315,14 @@ trait Backend<'a> { } => { self.load_struct_at_index(sym, structure, *index, field_layouts); } + Expr::UnionAtIndex { + structure, + tag_id, + union_layout, + index, + } => { + self.load_union_at_index(sym, structure, *tag_id, *index, union_layout); + } Expr::GetTagId { structure, union_layout, @@ -686,6 +694,16 @@ trait Backend<'a> { field_layouts: &'a [Layout<'a>], ); + /// load_union_at_index loads into `sym` the value at `index` for `tag_id`. + fn load_union_at_index( + &mut self, + sym: &Symbol, + structure: &Symbol, + tag_id: TagIdIntType, + index: u64, + union_layout: &UnionLayout<'a>, + ); + /// get_tag_id loads the tag id from a the union. fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>); From c0b0fe3ba1349030e638bc640c03b55f1b4de68f Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:29:52 -0800 Subject: [PATCH 011/150] add Num.Lte --- compiler/gen_dev/src/generic64/aarch64.rs | 10 ++++++++ compiler/gen_dev/src/generic64/mod.rs | 31 ++++++++++++++++++++++- compiler/gen_dev/src/generic64/x86_64.rs | 17 +++++++++++++ compiler/gen_dev/src/lib.rs | 26 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index bd01061158..45c5bd514b 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -637,6 +637,16 @@ impl Assembler for AArch64Assembler { todo!("registers to float for AArch64"); } + #[inline(always)] + fn lte_reg64_reg64_reg64( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) { + todo!("registers less than or equal for AArch64"); + } + #[inline(always)] fn gte_reg64_reg64_reg64( _buf: &mut Vec<'_, u8>, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index a2e5915856..b3924c4da4 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -229,6 +229,13 @@ pub trait Assembler: Sized { fn to_float_freg64_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); + fn lte_reg64_reg64_reg64( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ); + fn gte_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, @@ -832,6 +839,28 @@ impl< } } + fn build_num_lte( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + arg_layout: &Layout<'a>, + ) { + match arg_layout { + Layout::Builtin(single_register_int_builtins!()) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); + ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); + } + x => todo!("NumLte: layout, {:?}", x), + } + } + fn build_num_gte( &mut self, dst: &Symbol, @@ -840,7 +869,7 @@ impl< arg_layout: &Layout<'a>, ) { match arg_layout { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + Layout::Builtin(single_register_int_builtins!()) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let src1_reg = self .storage_manager diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 015fbba92e..b7ac7737bc 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1167,6 +1167,17 @@ impl Assembler for X86_64Assembler { cvtsi2sd_freg64_reg64(buf, dst, src); } + #[inline(always)] + fn lte_reg64_reg64_reg64( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, + ) { + cmp_reg64_reg64(buf, src1, src2); + setle_reg64(buf, dst); + } + #[inline(always)] fn gte_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, @@ -1682,6 +1693,12 @@ fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { set_reg64_help(0x9c, buf, reg); } +/// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF). +#[inline(always)] +fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { + set_reg64_help(0x9e, buf, reg); +} + /// `SETGE r/m64` -> Set byte if greater or equal (SF=OF). #[inline(always)] fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 9a5e81abbd..1e99ef1300 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -509,6 +509,23 @@ trait Backend<'a> { ); self.build_num_to_float(sym, &args[0], &arg_layouts[0], ret_layout) } + LowLevel::NumLte => { + debug_assert_eq!( + 2, + args.len(), + "NumLte: expected to have exactly two argument" + ); + debug_assert_eq!( + arg_layouts[0], arg_layouts[1], + "NumLte: expected all arguments of to have the same layout" + ); + debug_assert_eq!( + Layout::Builtin(Builtin::Bool), + *ret_layout, + "NumLte: expected to have return layout of type Bool" + ); + self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0]) + } LowLevel::NumGte => { debug_assert_eq!( 2, @@ -650,6 +667,15 @@ trait Backend<'a> { ret_layout: &Layout<'a>, ); + /// build_num_lte stores the result of `src1 <= src2` into dst. + fn build_num_lte( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + arg_layout: &Layout<'a>, + ); + /// build_num_gte stores the result of `src1 >= src2` into dst. fn build_num_gte( &mut self, From bbee753e21d24c1144bf0d2069f7e4755422b10d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:33:39 -0800 Subject: [PATCH 012/150] copying list and str to stack offset --- compiler/gen_dev/src/generic64/storage.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index a3f266f81b..750a85ee52 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -657,6 +657,15 @@ impl< let reg = self.load_to_float_reg(buf, sym); ASM::mov_base32_freg64(buf, to_offset, reg); } + Layout::Builtin(Builtin::Str | Builtin::List(_)) => { + let (from_offset, _) = self.stack_offset_and_size(sym); + self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { + ASM::mov_reg64_base32(buf, reg, from_offset); + ASM::mov_base32_reg64(buf, to_offset, reg); + ASM::mov_reg64_base32(buf, reg, from_offset + 8); + ASM::mov_base32_reg64(buf, to_offset + 8, reg); + }); + } // Layout::Struct(_) if layout.safe_to_memcpy() => { // // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) // // if let Some(SymbolStorage::Base { From 39b9ab44a7a3804bb4e3c6811a01429977e3751b Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:34:27 -0800 Subject: [PATCH 013/150] more ignoring of branch info --- compiler/gen_dev/src/generic64/mod.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index b3924c4da4..fc916352f9 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -569,22 +569,18 @@ impl< self.buf[jne_location + i] = *byte; } } - let (branch_info, stmt) = default_branch; - if let BranchInfo::None = branch_info { - self.build_stmt(stmt, ret_layout); + let (_branch_info, stmt) = default_branch; + self.build_stmt(stmt, ret_layout); - // Update all return jumps to jump past the default case. - let ret_offset = self.buf.len(); - for (jmp_location, start_offset) in ret_jumps.into_iter() { - self.update_jmp_imm32_offset( - &mut tmp, - jmp_location as u64, - start_offset as u64, - ret_offset as u64, - ); - } - } else { - todo!("Switch: branch info, {:?}", branch_info); + // Update all return jumps to jump past the default case. + let ret_offset = self.buf.len(); + for (jmp_location, start_offset) in ret_jumps.into_iter() { + self.update_jmp_imm32_offset( + &mut tmp, + jmp_location as u64, + start_offset as u64, + ret_offset as u64, + ); } } From a39f610395eabc96e7e18d5bcb24b2dbb77d883d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 15:53:31 -0800 Subject: [PATCH 014/150] add copying to a return pointer --- compiler/gen_dev/src/generic64/aarch64.rs | 33 +++++++++++++++++++++++ compiler/gen_dev/src/generic64/mod.rs | 13 +++++++++ compiler/gen_dev/src/generic64/storage.rs | 19 +++++++++++++ compiler/gen_dev/src/generic64/x86_64.rs | 24 ++++++++++++++++- 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 45c5bd514b..35657e2983 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -480,6 +480,39 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn mov_reg64_mem64_offset32( + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + src: AArch64GeneralReg, + offset: i32, + ) { + if offset < 0 { + todo!("negative mem offsets for AArch64"); + } else if offset < (0xFFF << 8) { + debug_assert!(offset % 8 == 0); + ldr_reg64_imm12(buf, dst, src, (offset as u16) >> 3); + } else { + todo!("mem offsets over 32k for AArch64"); + } + } + #[inline(always)] + fn mov_mem64_offset32_reg64( + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + offset: i32, + src: AArch64GeneralReg, + ) { + if offset < 0 { + todo!("negative mem offsets for AArch64"); + } else if offset < (0xFFF << 8) { + debug_assert!(offset % 8 == 0); + str_reg64_imm12(buf, src, dst, (offset as u16) >> 3); + } else { + todo!("mem offsets over 32k for AArch64"); + } + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index fc916352f9..386978fd04 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -172,6 +172,19 @@ pub trait Assembler: Sized { fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_reg64_mem64_offset32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src: GeneralReg, + offset: i32, + ); + fn mov_mem64_offset32_reg64( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + offset: i32, + src: GeneralReg, + ); + /// Sign extends the data at `offset` with `size` as it copies it to `dst` /// size must be less than or equal to 8. fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8); diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 750a85ee52..fbeeeafd61 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -635,6 +635,25 @@ impl< } } + /// Copies a complex symbol ot the stack to the arg pointer. + pub fn copy_symbol_to_arg_pionter( + &mut self, + buf: &mut Vec<'a, u8>, + sym: &Symbol, + _layout: &Layout<'a>, + ) { + let ret_reg = self.load_to_general_reg(buf, &Symbol::RET_POINTER); + let (base_offset, size) = self.stack_offset_and_size(sym); + debug_assert!(base_offset % 8 == 0); + debug_assert!(size % 8 == 0); + self.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| { + for i in (0..size as i32).step_by(8) { + ASM::mov_reg64_base32(buf, tmp_reg, base_offset + i); + ASM::mov_mem64_offset32_reg64(buf, ret_reg, i, tmp_reg); + } + }); + } + /// Copies a symbol to the specified stack offset. This is used for things like filling structs. /// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan. /// This means that, for example 2 I32s might be back to back on the stack. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index b7ac7737bc..856fa2be68 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -408,7 +408,10 @@ impl CallConv for X86_64Syste ); } } - x => todo!("returning complex type, {:?}", x), + _ => { + // This is a large type returned via the arg pointer. + storage_manager.copy_symbol_to_arg_pionter(buf, sym, layout); + } } } @@ -1033,6 +1036,25 @@ impl Assembler for X86_64Assembler { mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src) } + #[inline(always)] + fn mov_reg64_mem64_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg64_base64_offset32(buf, dst, src, offset) + } + #[inline(always)] + fn mov_mem64_offset32_reg64( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base64_offset32_reg64(buf, dst, offset, src) + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); From cf8d294ec16343eeff4ce8195bc0d9463528feba Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 16:01:59 -0800 Subject: [PATCH 015/150] add List.len --- compiler/gen_dev/src/generic64/mod.rs | 4 ++++ compiler/gen_dev/src/generic64/storage.rs | 16 ++++++++++++++++ compiler/gen_dev/src/lib.rs | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 386978fd04..adc337cbbf 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -892,6 +892,10 @@ impl< } } + fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) { + self.storage_manager.list_len(&mut self.buf, dst, list); + } + fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) { // We may not strictly need an instruction here. // What's important is to load the value, and for src and dest to have different Layouts. diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index fbeeeafd61..9bc2dac2c8 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -606,6 +606,22 @@ impl< } } + // Loads the dst to be the later 64 bits of a list (its length). + pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) { + let owned_data = self.remove_allocation_for_sym(list); + self.allocation_map.insert(*list, Rc::clone(&owned_data)); + self.allocation_map.insert(*dst, owned_data); + let (list_offset, _) = self.stack_offset_and_size(list); + self.symbol_storage_map.insert( + *dst, + Stack(ReferencedPrimitive { + base_offset: list_offset + 8, + size: 8, + sign_extend: false, + }), + ); + } + /// Creates a struct on the stack, moving the data in fields into the struct. pub fn create_struct( &mut self, diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 1e99ef1300..a92917fc56 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -550,6 +550,14 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::ListLen => { + debug_assert_eq!( + 1, + args.len(), + "ListLen: expected to have exactly one argument" + ); + self.build_list_len(sym, &args[0]) + } LowLevel::StrConcat => self.build_fn_call( sym, bitcode::STR_CONCAT.to_string(), @@ -685,6 +693,9 @@ trait Backend<'a> { arg_layout: &Layout<'a>, ); + /// build_list_len returns the length of a list. + fn build_list_len(&mut self, dst: &Symbol, list: &Symbol); + /// build_refcount_getptr loads the pointer to the reference count of src into dst. fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol); From b5a10695ac139a28e05de143c0e40f771abb7678 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 16:36:28 -0800 Subject: [PATCH 016/150] update ret pointer returning --- compiler/gen_dev/src/generic64/aarch64.rs | 1 + compiler/gen_dev/src/generic64/mod.rs | 2 ++ compiler/gen_dev/src/generic64/x86_64.rs | 37 +++++++++++++++++------ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 35657e2983..e09cb2aa42 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -281,6 +281,7 @@ impl CallConv for AArch64C AArch64Assembler, AArch64Call, >, + _dst: &Symbol, _args: &'a [Symbol], _arg_layouts: &[Layout<'a>], _ret_layout: &Layout<'a>, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index adc337cbbf..26bab5dd30 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -77,6 +77,7 @@ pub trait CallConv( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, + dst: &Symbol, args: &'a [Symbol], arg_layouts: &[Layout<'a>], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. @@ -511,6 +512,7 @@ impl< CC::store_args( &mut self.buf, &mut self.storage_manager, + dst, args, arg_layouts, ret_layout, diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 856fa2be68..0dd50f3e5c 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -266,19 +266,28 @@ impl CallConv for X86_64Syste X86_64Assembler, X86_64SystemV, >, + dst: &Symbol, args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ) { let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; - if Self::returns_via_arg_pointer(ret_layout) { - // Save space on the stack for the arg we will return. - storage_manager - .claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO)); - todo!("claim first parama reg for the address"); - } let mut general_i = 0; let mut float_i = 0; + if Self::returns_via_arg_pointer(ret_layout) { + // Save space on the stack for the result we will be return. + let base_offset = + storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO)); + // Set the first reg to the address base + offset. + let ret_reg = Self::GENERAL_PARAM_REGS[general_i]; + general_i += 1; + X86_64Assembler::add_reg64_reg64_imm32( + buf, + ret_reg, + X86_64GeneralReg::RBP, + base_offset, + ); + } for (sym, layout) in args.iter().zip(arg_layouts.iter()) { match layout { single_register_integers!() => { @@ -411,6 +420,12 @@ impl CallConv for X86_64Syste _ => { // This is a large type returned via the arg pointer. storage_manager.copy_symbol_to_arg_pionter(buf, sym, layout); + // Also set the return reg to the arg pointer. + storage_manager.load_to_specified_general_reg( + buf, + &Symbol::RET_POINTER, + Self::GENERAL_RETURN_REGS[0], + ); } } } @@ -455,7 +470,11 @@ impl CallConv for X86_64Syste ); } } - x => todo!("receiving complex return type, {:?}", x), + _ => { + // This should have been recieved via an arg pointer. + // That means the value is already loaded onto the stack area we allocated before the call. + // Nothing to do. + } } } } @@ -660,6 +679,7 @@ impl CallConv for X86_64Windo X86_64Assembler, X86_64WindowsFastcall, >, + dst: &Symbol, args: &'a [Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, @@ -667,8 +687,7 @@ impl CallConv for X86_64Windo let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; if Self::returns_via_arg_pointer(ret_layout) { // Save space on the stack for the arg we will return. - storage_manager - .claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO)); + storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO)); todo!("claim first parama reg for the address"); } for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { From 4bc59cdb61511f0884242cd302da31428b9a9c98 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 16:42:03 -0800 Subject: [PATCH 017/150] add storing large args for passing --- compiler/gen_dev/src/generic64/x86_64.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 0dd50f3e5c..f92c4c246e 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -356,6 +356,19 @@ impl CallConv for X86_64Syste } } x if x.stack_size(TARGET_INFO) == 0 => {} + x if x.stack_size(TARGET_INFO) > 16 => { + // TODO: Double check this. + // Just copy onto the stack. + let (base_offset, size) = storage_manager.stack_offset_and_size(sym); + debug_assert_eq!(base_offset % 8, 0); + storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { + for i in (0..size as i32).step_by(8) { + X86_64Assembler::mov_reg64_base32(buf, reg, base_offset + i); + X86_64Assembler::mov_stack32_reg64(buf, tmp_stack_offset + i, reg); + } + }); + tmp_stack_offset += size as i32; + } x => { todo!("calling with arg type, {:?}", x); } From 1ef7df19b4c6870ac397001f0464528d83ffb872 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 16:43:24 -0800 Subject: [PATCH 018/150] add List.set --- compiler/gen_dev/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index a92917fc56..0989a47088 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -558,6 +558,13 @@ trait Backend<'a> { ); self.build_list_len(sym, &args[0]) } + LowLevel::ListSet => self.build_fn_call( + sym, + bitcode::LIST_SET.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::StrConcat => self.build_fn_call( sym, bitcode::STR_CONCAT.to_string(), From c47209c54af51a77b3e650fac59e8ed108562f2c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 16:48:27 -0800 Subject: [PATCH 019/150] add copying any memcpy-able symbols to a stack offset --- compiler/gen_dev/src/generic64/storage.rs | 34 ++++++++--------------- compiler/gen_dev/src/lib.rs | 2 +- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 9bc2dac2c8..8f4f075ec5 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -701,28 +701,18 @@ impl< ASM::mov_base32_reg64(buf, to_offset + 8, reg); }); } - // Layout::Struct(_) if layout.safe_to_memcpy() => { - // // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) - // // if let Some(SymbolStorage::Base { - // // offset: from_offset, - // // size, - // // .. - // // }) = self.symbol_storage_map.get(sym) - // // { - // // debug_assert_eq!( - // // *size, - // // layout.stack_size(self.target_info), - // // "expected struct to have same size as data being stored in it" - // // ); - // // for i in 0..layout.stack_size(self.target_info) as i32 { - // // ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); - // // ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); - // // } - // todo!() - // } else { - // internal_error!("unknown struct: {:?}", sym); - // } - // } + _ if layout.safe_to_memcpy() => { + let (from_offset, size) = self.stack_offset_and_size(sym); + debug_assert!(from_offset % 8 == 0); + debug_assert!(size % 8 == 0); + debug_assert_eq!(size, layout.stack_size(self.target_info)); + self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { + for i in (0..size as i32).step_by(8) { + ASM::mov_reg64_base32(buf, reg, from_offset + i); + ASM::mov_base32_reg64(buf, to_offset + i, reg); + } + }); + } x => todo!("copying data to the stack with layout, {:?}", x), } } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 0989a47088..a67236d294 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -626,7 +626,7 @@ trait Backend<'a> { self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.free_symbol(&Symbol::DEV_TMP) } - Symbol::LIST_GET => { + Symbol::LIST_GET | Symbol::LIST_SET => { // TODO: This is probably simple enough to be worth inlining. let layout_id = LayoutIds::default().get(func_sym, ret_layout); let fn_name = self.symbol_to_string(func_sym, layout_id); From f8e12da809975ab0595d0081d75635783b6e4a37 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 17:07:53 -0800 Subject: [PATCH 020/150] add List.getUnsafe --- compiler/gen_dev/src/generic64/mod.rs | 42 ++++++++++++++++++++++- compiler/gen_dev/src/generic64/storage.rs | 10 ++++-- compiler/gen_dev/src/lib.rs | 16 +++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 26bab5dd30..f65f5f9f9a 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -897,6 +897,39 @@ impl< fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) { self.storage_manager.list_len(&mut self.buf, dst, list); } + fn build_list_get_unsafe( + &mut self, + dst: &Symbol, + list: &Symbol, + index: &Symbol, + ret_layout: &Layout<'a>, + ) { + let (base_offset, _) = self.storage_manager.stack_offset_and_size(list); + let index_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, index); + let ret_stack_size = ret_layout.stack_size(self.storage_manager.target_info()); + // TODO: This can be optimized with smarter instructions. + // Also can probably be moved into storage manager at least partly. + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, list_ptr| { + ASM::mov_reg64_base32(buf, list_ptr, base_offset as i32); + storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp| { + ASM::mov_reg64_imm64(buf, tmp, ret_stack_size as i64); + ASM::imul_reg64_reg64_reg64(buf, tmp, tmp, index_reg); + ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr); + match ret_layout { + single_register_integers!() if ret_stack_size == 8 => { + let dst_reg = storage_manager.claim_general_reg(buf, dst); + ASM::mov_reg64_mem64_offset32(buf, dst_reg, list_ptr, 0); + } + x => internal_error!("Loading list element with layout: {:?}", x), + } + }); + }, + ); + } fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) { // We may not strictly need an instruction here. @@ -1073,12 +1106,19 @@ impl< } #[macro_export] -macro_rules! sign_extended_builtins { +macro_rules! sign_extended_int_builtins { () => { Builtin::Int(IntWidth::I8 | IntWidth::I16 | IntWidth::I32 | IntWidth::I64 | IntWidth::I128) }; } +#[macro_export] +macro_rules! zero_extended_int_builtins { + () => { + Builtin::Int(IntWidth::U8 | IntWidth::U16 | IntWidth::U32 | IntWidth::U64 | IntWidth::U128) + }; +} + #[macro_export] macro_rules! single_register_int_builtins { () => { diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 8f4f075ec5..40cb7d1a44 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1,6 +1,6 @@ use crate::{ generic64::{Assembler, CallConv, RegTrait}, - sign_extended_builtins, single_register_floats, single_register_int_builtins, + sign_extended_int_builtins, single_register_floats, single_register_int_builtins, single_register_integers, single_register_layouts, Env, }; use bumpalo::collections::Vec; @@ -181,6 +181,10 @@ impl< self.fn_call_stack_size = 0; } + pub fn target_info(&self) -> TargetInfo { + self.target_info + } + pub fn stack_size(&self) -> u32 { self.stack_size } @@ -551,7 +555,7 @@ impl< size, sign_extend: matches!( layout, - Layout::Builtin(sign_extended_builtins!()) + Layout::Builtin(sign_extended_int_builtins!()) ), } } else { @@ -598,7 +602,7 @@ impl< Stack(ReferencedPrimitive { base_offset: union_offset + id_offset as i32, size, - sign_extend: matches!(id_builtin, sign_extended_builtins!()), + sign_extend: matches!(id_builtin, sign_extended_int_builtins!()), }), ); } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index a67236d294..1c93476a38 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -558,6 +558,14 @@ trait Backend<'a> { ); self.build_list_len(sym, &args[0]) } + LowLevel::ListGetUnsafe => { + debug_assert_eq!( + 2, + args.len(), + "ListLen: expected to have exactly two arguments" + ); + self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout) + } LowLevel::ListSet => self.build_fn_call( sym, bitcode::LIST_SET.to_string(), @@ -703,6 +711,14 @@ trait Backend<'a> { /// build_list_len returns the length of a list. fn build_list_len(&mut self, dst: &Symbol, list: &Symbol); + /// build_list_get_unsafe loads the element from the list at the index. + fn build_list_get_unsafe( + &mut self, + dst: &Symbol, + list: &Symbol, + index: &Symbol, + ret_layout: &Layout<'a>, + ); /// build_refcount_getptr loads the pointer to the reference count of src into dst. fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol); From 28ec18c564a7f6bf4256ab10a07b3ffb07acb7c1 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 19:14:09 -0800 Subject: [PATCH 021/150] add new union creation --- compiler/gen_dev/src/generic64/mod.rs | 11 ++++++ compiler/gen_dev/src/generic64/storage.rs | 41 +++++++++++++++++++++-- compiler/gen_dev/src/lib.rs | 17 ++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index f65f5f9f9a..809974b24c 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -982,6 +982,17 @@ impl< .load_union_tag_id(&mut self.buf, sym, structure, union_layout); } + fn tag( + &mut self, + sym: &Symbol, + fields: &'a [Symbol], + union_layout: &UnionLayout<'a>, + tag_id: TagIdIntType, + ) { + self.storage_manager + .create_union(&mut self.buf, sym, union_layout, fields, tag_id) + } + fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) { match (lit, layout) { ( diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 40cb7d1a44..70911e77d2 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -10,7 +10,7 @@ use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::{ ir::{JoinPointId, Param}, - layout::{Builtin, Layout, UnionLayout}, + layout::{Builtin, Layout, TagIdIntType, UnionLayout}, }; use roc_target::TargetInfo; use std::cmp::max; @@ -655,6 +655,42 @@ impl< } } + /// Creates a union on the stack, moving the data in fields into the union and tagging it. + pub fn create_union( + &mut self, + buf: &mut Vec<'a, u8>, + sym: &Symbol, + union_layout: &UnionLayout<'a>, + fields: &'a [Symbol], + tag_id: TagIdIntType, + ) { + match union_layout { + UnionLayout::NonRecursive(field_layouts) => { + let (data_size, data_alignment) = + union_layout.data_size_and_alignment(self.target_info); + let id_offset = data_size - data_alignment; + if data_alignment < 8 || data_alignment % 8 != 0 { + todo!("small/unaligned tagging"); + } + let base_offset = self.claim_stack_area(sym, data_size); + let mut current_offset = base_offset; + for (field, field_layout) in + fields.iter().zip(field_layouts[tag_id as usize].iter()) + { + self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout); + let field_size = field_layout.stack_size(self.target_info); + current_offset += field_size as i32; + } + self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| { + ASM::mov_reg64_imm64(buf, reg, tag_id as i64); + debug_assert!((base_offset + id_offset as i32) % 8 == 0); + ASM::mov_base32_reg64(buf, base_offset + id_offset as i32, reg); + }); + } + x => todo!("creating unions with layout: {:?}", x), + } + } + /// Copies a complex symbol ot the stack to the arg pointer. pub fn copy_symbol_to_arg_pionter( &mut self, @@ -678,7 +714,7 @@ impl< /// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan. /// This means that, for example 2 I32s might be back to back on the stack. /// Always interact with the stack using aligned 64bit movement. - fn copy_symbol_to_stack_offset( + pub fn copy_symbol_to_stack_offset( &mut self, buf: &mut Vec<'a, u8>, to_offset: i32, @@ -705,6 +741,7 @@ impl< ASM::mov_base32_reg64(buf, to_offset + 8, reg); }); } + _ if layout.stack_size(self.target_info) == 0 => {} _ if layout.safe_to_memcpy() => { let (from_offset, size) = self.stack_offset_and_size(sym); debug_assert!(from_offset % 8 == 0); diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 1c93476a38..4822315460 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -329,6 +329,14 @@ trait Backend<'a> { } => { self.get_tag_id(sym, structure, union_layout); } + Expr::Tag { + tag_layout, + tag_id, + arguments, + .. + } => { + self.tag(sym, &arguments, tag_layout, *tag_id); + } x => todo!("the expression, {:?}", x), } } @@ -767,6 +775,15 @@ trait Backend<'a> { /// get_tag_id loads the tag id from a the union. fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>); + /// tag sets the tag for a union. + fn tag( + &mut self, + sym: &Symbol, + args: &'a [Symbol], + tag_layout: &UnionLayout<'a>, + tag_id: TagIdIntType, + ); + /// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function. fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>); From c6f1edbf47bd37f3743105c59ce13c1aee27b6cc Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 19:24:37 -0800 Subject: [PATCH 022/150] load literals that are union args --- compiler/gen_dev/src/lib.rs | 1 + compiler/test_gen/src/gen_tags.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 4822315460..f72963cd5a 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -335,6 +335,7 @@ trait Backend<'a> { arguments, .. } => { + self.load_literal_symbols(arguments); self.tag(sym, &arguments, tag_layout, *tag_id); } x => todo!("the expression, {:?}", x), diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index c1643ecc9a..759242c91d 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1,8 +1,8 @@ #[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-dev")] +use crate::helpers::dev::assert_evals_to; #[cfg(feature = "gen-wasm")] use crate::helpers::wasm::assert_evals_to; @@ -29,7 +29,7 @@ fn width_and_alignment_u8_u8() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn applied_tag_nothing_ir() { assert_evals_to!( indoc!( @@ -49,7 +49,7 @@ fn applied_tag_nothing_ir() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn applied_tag_nothing() { assert_evals_to!( indoc!( @@ -69,7 +69,7 @@ fn applied_tag_nothing() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn applied_tag_just() { assert_evals_to!( indoc!( @@ -88,7 +88,7 @@ fn applied_tag_just() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn applied_tag_just_ir() { assert_evals_to!( indoc!( From 2154e910d86d056bd4bd82d183e9376e0fe90dca Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 19 Feb 2022 19:39:44 -0800 Subject: [PATCH 023/150] expand test coverage --- compiler/gen_dev/src/generic64/storage.rs | 2 ++ compiler/test_gen/src/gen_list.rs | 2 +- compiler/test_gen/src/gen_records.rs | 20 ++++++++++---------- compiler/test_gen/src/gen_tags.rs | 14 +++++++------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 70911e77d2..ece5ea3d1c 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -900,6 +900,8 @@ impl< pub fn ret_pointer_arg(&mut self, reg: GeneralReg) { self.symbol_storage_map .insert(Symbol::RET_POINTER, Reg(General(reg))); + self.general_free_regs.retain(|x| *x != reg); + self.general_used_regs.push((reg, Symbol::RET_POINTER)); } /// updates the function call stack size to the max of its current value and the size need for this call. diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 4fbe60537d..d7e238605f 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -19,7 +19,7 @@ use indoc::indoc; use roc_std::{RocList, RocStr}; #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn roc_list_construction() { let list = RocList::from_slice(&[1i64; 23]); assert_eq!(&list, &list); diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index 5377da5afc..baa3675acd 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -272,7 +272,7 @@ fn empty_record() { ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn i64_record2_literal() { assert_evals_to!( indoc!( @@ -299,7 +299,7 @@ fn i64_record2_literal() { // ); // } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn f64_record2_literal() { assert_evals_to!( indoc!( @@ -401,7 +401,7 @@ fn bool_literal() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record() { assert_evals_to!( indoc!( @@ -652,7 +652,7 @@ fn optional_field_empty_record() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record_2() { assert_evals_to!( indoc!( @@ -666,7 +666,7 @@ fn return_record_2() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record_3() { assert_evals_to!( indoc!( @@ -680,7 +680,7 @@ fn return_record_3() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record_4() { assert_evals_to!( indoc!( @@ -694,7 +694,7 @@ fn return_record_4() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record_5() { assert_evals_to!( indoc!( @@ -708,7 +708,7 @@ fn return_record_5() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record_6() { assert_evals_to!( indoc!( @@ -722,7 +722,7 @@ fn return_record_6() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_record_7() { assert_evals_to!( indoc!( @@ -792,7 +792,7 @@ fn return_record_float_float_float() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn return_nested_record() { assert_evals_to!( indoc!( diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 759242c91d..7d54bd9cbb 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -314,7 +314,7 @@ fn gen_if_float() { ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn when_on_nothing() { assert_evals_to!( indoc!( @@ -333,7 +333,7 @@ fn when_on_nothing() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn when_on_just() { assert_evals_to!( indoc!( @@ -352,7 +352,7 @@ fn when_on_just() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn when_on_result() { assert_evals_to!( indoc!( @@ -371,7 +371,7 @@ fn when_on_result() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn when_on_these() { assert_evals_to!( indoc!( @@ -393,7 +393,7 @@ fn when_on_these() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn match_on_two_values() { // this will produce a Chain internally assert_evals_to!( @@ -410,7 +410,7 @@ fn match_on_two_values() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn pair_with_underscore() { assert_evals_to!( indoc!( @@ -427,7 +427,7 @@ fn pair_with_underscore() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn result_with_underscore() { // This test revealed an issue with hashing Test values assert_evals_to!( From a34c4f7c44866b1803eefad52bdccaba57894b09 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 21 Feb 2022 19:19:39 -0800 Subject: [PATCH 024/150] stop printing to stdout before timing completes --- cli/tests/fixtures/multi-dep-str/platform/host.zig | 8 ++++---- cli/tests/fixtures/multi-dep-thunk/platform/host.zig | 8 ++++---- examples/fib/platform/host.zig | 4 ++-- examples/hello-zig/platform/host.zig | 8 ++++---- examples/quicksort/platform/host.zig | 8 ++++---- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/tests/fixtures/multi-dep-str/platform/host.zig b/cli/tests/fixtures/multi-dep-str/platform/host.zig index f72ded7353..b4d54a6d02 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -61,15 +61,15 @@ pub export fn main() i32 { // actually call roc to populate the callresult const callresult = roc__mainForHost_1_exposed(); + // end time + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; callresult.deinit(); - // end time - var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; - const delta = to_seconds(ts2) - to_seconds(ts1); stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index 4c8ee671cf..6bc8b61dce 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -60,15 +60,15 @@ pub export fn main() i32 { // actually call roc to populate the callresult const callresult = roc__mainForHost_1_exposed(); + // end time + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; callresult.deinit(); - // end time - var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; - const delta = to_seconds(ts2) - to_seconds(ts1); stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; diff --git a/examples/fib/platform/host.zig b/examples/fib/platform/host.zig index 111a84c27f..d31cbad4d9 100644 --- a/examples/fib/platform/host.zig +++ b/examples/fib/platform/host.zig @@ -79,12 +79,12 @@ pub export fn main() u8 { const result = roc__mainForHost_1_exposed(10); - stdout.print("{d}\n", .{result}) catch unreachable; - // end time var ts2: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + stdout.print("{d}\n", .{result}) catch unreachable; + const delta = to_seconds(ts2) - to_seconds(ts1); stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; diff --git a/examples/hello-zig/platform/host.zig b/examples/hello-zig/platform/host.zig index f0b17ec4d4..a2e4df7ac5 100644 --- a/examples/hello-zig/platform/host.zig +++ b/examples/hello-zig/platform/host.zig @@ -92,15 +92,15 @@ pub fn main() u8 { // actually call roc to populate the callresult var callresult = roc__mainForHost_1_exposed(); + // end time + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; callresult.deinit(); - // end time - var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; - const delta = to_seconds(ts2) - to_seconds(ts1); stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; diff --git a/examples/quicksort/platform/host.zig b/examples/quicksort/platform/host.zig index fe8e90e490..de77c84fdc 100644 --- a/examples/quicksort/platform/host.zig +++ b/examples/quicksort/platform/host.zig @@ -112,6 +112,10 @@ pub export fn main() u8 { const length = std.math.min(20, callresult.length); var result = callresult.elements[0..length]; + // end time + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + for (result) |x, i| { if (i == 0) { stdout.print("[{}, ", .{x}) catch unreachable; @@ -122,10 +126,6 @@ pub export fn main() u8 { } } - // end time - var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; - const delta = to_seconds(ts2) - to_seconds(ts1); stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; From 069361a07ecc40fa0eee69c171cd5138299529ba Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sun, 27 Feb 2022 22:02:47 -0800 Subject: [PATCH 025/150] first attempt at List.replace in the dev backend --- compiler/gen_dev/src/generic64/aarch64.rs | 2 +- compiler/gen_dev/src/generic64/mod.rs | 132 +++++++++++++++++++++- compiler/gen_dev/src/generic64/storage.rs | 24 +++- compiler/gen_dev/src/generic64/x86_64.rs | 4 +- compiler/gen_dev/src/lib.rs | 33 ++++-- compiler/module/src/symbol.rs | 4 + 6 files changed, 180 insertions(+), 19 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index e09cb2aa42..970e531968 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -282,7 +282,7 @@ impl CallConv for AArch64C AArch64Call, >, _dst: &Symbol, - _args: &'a [Symbol], + _args: &[Symbol], _arg_layouts: &[Layout<'a>], _ret_layout: &Layout<'a>, ) { diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 809974b24c..4e4875fe18 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -3,7 +3,7 @@ use crate::{ Relocation, }; use bumpalo::collections::Vec; -use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::{Interns, Symbol}; @@ -78,7 +78,7 @@ pub trait CallConv, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, dst: &Symbol, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. ret_layout: &Layout<'a>, @@ -495,7 +495,7 @@ impl< &mut self, dst: &Symbol, fn_name: String, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ) { @@ -636,7 +636,7 @@ impl< fn build_jump( &mut self, id: &JoinPointId, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], _ret_layout: &Layout<'a>, ) { @@ -897,6 +897,7 @@ impl< fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) { self.storage_manager.list_len(&mut self.buf, dst, list); } + fn build_list_get_unsafe( &mut self, dst: &Symbol, @@ -931,6 +932,129 @@ impl< ); } + fn build_list_replace_unsafe( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[Layout<'a>], + ret_layout: &Layout<'a>, + ) { + // We want to delegate to the zig builtin, but it takes some extra parameters. + // Firstly, it takes the alignment of the list. + // Secondly, it takes the stack size of an element. + // Thirdly, it takes a pointer that it will write the output element to. + let list = args[0]; + let list_layout = arg_layouts[0]; + let index = args[1]; + let index_layout = arg_layouts[1]; + let elem = args[2]; + let elem_layout = arg_layouts[2]; + + let u32_layout = &Layout::Builtin(Builtin::Int(IntWidth::U32)); + let list_alignment = list_layout.alignment_bytes(self.storage_manager.target_info()); + self.load_literal( + &Symbol::DEV_TMP, + &u32_layout, + &Literal::Int(list_alignment as i128), + ); + + // Have to pass the input element by pointer, so put it on the stack and load it's address. + self.storage_manager + .ensure_symbol_on_stack(&mut self.buf, &elem); + let u64_layout = &Layout::Builtin(Builtin::Int(IntWidth::U64)); + let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem); + // Load address of output element into register. + let reg = self + .storage_manager + .claim_general_reg(&mut self.buf, &Symbol::DEV_TMP2); + ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset); + + // Load the elements size. + let elem_stack_size = elem_layout.stack_size(self.storage_manager.target_info()); + self.load_literal( + &Symbol::DEV_TMP3, + &u64_layout, + &Literal::Int(elem_stack_size as i128), + ); + + // Setup the return location. + let base_offset = self.storage_manager.claim_stack_area( + &dst, + ret_layout.stack_size(self.storage_manager.target_info()), + ); + + let ret_fields = if let Layout::Struct { field_layouts, .. } = ret_layout { + field_layouts + } else { + internal_error!( + "Expected replace to return a struct instead found: {:?}", + ret_layout + ) + }; + + // Only return list and old element. + debug_assert_eq!(ret_fields.len(), 2); + + let (out_list_offset, out_elem_offset) = if ret_fields[0] == elem_layout { + ( + base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32, + base_offset, + ) + } else { + ( + base_offset, + base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32, + ) + }; + + // Load address of output element into register. + let reg = self + .storage_manager + .claim_general_reg(&mut self.buf, &Symbol::DEV_TMP4); + ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, out_elem_offset); + + let lowlevel_args = bumpalo::vec![ + in self.env.arena; + list, + Symbol::DEV_TMP, + index, + Symbol::DEV_TMP2, + Symbol::DEV_TMP3, + Symbol::DEV_TMP4, + ]; + let lowlevel_arg_layouts = bumpalo::vec![ + in self.env.arena; + list_layout, + *u32_layout, + index_layout, + *u64_layout, + *u64_layout, + *u64_layout, + ]; + + self.build_fn_call( + &Symbol::DEV_TMP5, + bitcode::LIST_REPLACE.to_string(), + &lowlevel_args, + &lowlevel_arg_layouts, + &list_layout, + ); + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + self.free_symbol(&Symbol::DEV_TMP3); + self.free_symbol(&Symbol::DEV_TMP4); + + // Copy from list to the output record. + self.storage_manager.copy_symbol_to_stack_offset( + &mut self.buf, + out_list_offset, + &Symbol::DEV_TMP5, + &list_layout, + ); + + self.free_symbol(&Symbol::DEV_TMP5); + } + fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) { // We may not strictly need an instruction here. // What's important is to load the value, and for src and dest to have different Layouts. diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 3f2b18866a..418b658c78 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -806,6 +806,28 @@ impl< } } + pub fn ensure_symbol_on_stack(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) { + match self.remove_storage_for_sym(sym) { + Reg(reg_storage) => { + let base_offset = self.claim_stack_size(8); + match reg_storage { + General(reg) => ASM::mov_base32_reg64(buf, base_offset, reg), + Float(reg) => ASM::mov_base32_freg64(buf, base_offset, reg), + } + self.symbol_storage_map.insert( + *sym, + Stack(Primitive { + base_offset, + reg: Some(reg_storage), + }), + ); + } + x => { + self.symbol_storage_map.insert(*sym, x); + } + } + } + /// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. /// Note, used and free regs are expected to be updated outside of this function. fn free_to_stack( @@ -960,7 +982,7 @@ impl< &mut self, buf: &mut Vec<'a, u8>, id: &JoinPointId, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], ) { // TODO: remove was use here and for current_storage to deal with borrow checker. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index f92c4c246e..c93f41432a 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -267,7 +267,7 @@ impl CallConv for X86_64Syste X86_64SystemV, >, dst: &Symbol, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ) { @@ -693,7 +693,7 @@ impl CallConv for X86_64Windo X86_64WindowsFastcall, >, dst: &Symbol, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ) { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index f72963cd5a..419b3cf9e8 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -233,7 +233,7 @@ trait Backend<'a> { fn build_jump( &mut self, id: &JoinPointId, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ); @@ -571,17 +571,18 @@ trait Backend<'a> { debug_assert_eq!( 2, args.len(), - "ListLen: expected to have exactly two arguments" + "ListGetUnsafe: expected to have exactly two arguments" ); self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout) } - LowLevel::ListSet => self.build_fn_call( - sym, - bitcode::LIST_SET.to_string(), - args, - arg_layouts, - ret_layout, - ), + LowLevel::ListReplaceUnsafe => { + debug_assert_eq!( + 3, + args.len(), + "ListReplaceUnsafe: expected to have exactly three arguments" + ); + self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout) + } LowLevel::StrConcat => self.build_fn_call( sym, bitcode::STR_CONCAT.to_string(), @@ -643,7 +644,7 @@ trait Backend<'a> { self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.free_symbol(&Symbol::DEV_TMP) } - Symbol::LIST_GET | Symbol::LIST_SET => { + Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => { // TODO: This is probably simple enough to be worth inlining. let layout_id = LayoutIds::default().get(func_sym, ret_layout); let fn_name = self.symbol_to_string(func_sym, layout_id); @@ -661,7 +662,7 @@ trait Backend<'a> { &mut self, dst: &Symbol, fn_name: String, - args: &'a [Symbol], + args: &[Symbol], arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ); @@ -728,6 +729,16 @@ trait Backend<'a> { index: &Symbol, ret_layout: &Layout<'a>, ); + + /// build_list_replace_unsafe returns the old element and new list with the list having the new element inserted. + fn build_list_replace_unsafe( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[Layout<'a>], + ret_layout: &Layout<'a>, + ); + /// build_refcount_getptr loads the pointer to the reference count of src into dst. fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 80d4bd235c..b9b76a662d 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -883,6 +883,10 @@ define_builtins! { // used in dev backend 26 DEV_TMP: "#dev_tmp" + 27 DEV_TMP2: "#dev_tmp2" + 28 DEV_TMP3: "#dev_tmp3" + 29 DEV_TMP4: "#dev_tmp4" + 30 DEV_TMP5: "#dev_tmp5" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias From 06c3376577e5b66d815f2c7ebbc0b4fa3507d127 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 00:05:12 -0800 Subject: [PATCH 026/150] fix some storage issues around jumps --- compiler/gen_dev/src/generic64/mod.rs | 15 +++++++ compiler/gen_dev/src/generic64/storage.rs | 51 +++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 4e4875fe18..498a97f2b6 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -550,6 +550,10 @@ impl< default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), ret_layout: &Layout<'a>, ) { + // Free everything to the stack to make sure they don't get messed with in the branch. + // TODO: look into a nicer solution. + self.storage_manager.free_all_to_stack(&mut self.buf); + // Switches are a little complex due to keeping track of jumps. // In general I am trying to not have to loop over things multiple times or waste memory. // The basic plan is to make jumps to nowhere and then correct them once we know the correct address. @@ -557,6 +561,7 @@ impl< .storage_manager .load_to_general_reg(&mut self.buf, cond_symbol); + let mut base_storage = self.storage_manager.clone(); let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena]; for (val, _branch_info, stmt) in branches.iter() { @@ -568,6 +573,7 @@ impl< let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); // Build all statements in this branch. + self.storage_manager = base_storage.clone(); self.build_stmt(stmt, ret_layout); // Build unconditional jump to the end of this switch. @@ -583,7 +589,10 @@ impl< for (i, byte) in tmp.iter().enumerate() { self.buf[jne_location + i] = *byte; } + + base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size()); } + self.storage_manager = base_storage; let (_branch_info, stmt) = default_branch; self.build_stmt(stmt, ret_layout); @@ -607,6 +616,10 @@ impl< remainder: &'a Stmt<'a>, ret_layout: &Layout<'a>, ) { + // Free everything to the stack to make sure they don't get messed with in the branch. + // TODO: look into a nicer solution. + self.storage_manager.free_all_to_stack(&mut self.buf); + // Ensure all the joinpoint parameters have storage locations. // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. self.storage_manager @@ -617,8 +630,10 @@ impl< let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); // Build all statements in body. + let mut base_storage = self.storage_manager.clone(); self.join_map.insert(*id, self.buf.len() as u64); self.build_stmt(body, ret_layout); + base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size()); // Overwrite the original jump with the correct offset. let mut tmp = bumpalo::vec![in self.env.arena]; diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 418b658c78..41cc0c85a8 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -181,6 +181,27 @@ impl< self.fn_call_stack_size = 0; } + pub fn clone(&self) -> Self { + Self { + phantom_asm: PhantomData, + phantom_cc: PhantomData, + env: self.env, + target_info: self.target_info, + symbol_storage_map: self.symbol_storage_map.clone(), + allocation_map: self.allocation_map.clone(), + join_param_map: self.join_param_map.clone(), + general_free_regs: self.general_free_regs.clone(), + general_used_regs: self.general_used_regs.clone(), + general_used_callee_saved_regs: self.general_used_callee_saved_regs.clone(), + float_free_regs: self.float_free_regs.clone(), + float_used_regs: self.float_used_regs.clone(), + float_used_callee_saved_regs: self.float_used_callee_saved_regs.clone(), + free_stack_chunks: self.free_stack_chunks.clone(), + stack_size: self.stack_size, + fn_call_stack_size: self.fn_call_stack_size, + } + } + pub fn target_info(&self) -> TargetInfo { self.target_info } @@ -828,6 +849,36 @@ impl< } } + /// Frees all symbols to the stack setuping up a clean slate. + pub fn free_all_to_stack(&mut self, buf: &mut Vec<'a, u8>) { + let mut free_list = bumpalo::vec![in self.env.arena]; + for (sym, storage) in self.symbol_storage_map.iter() { + match storage { + Reg(reg_storage) + | Stack(Primitive { + reg: Some(reg_storage), + .. + }) => { + free_list.push((*sym, *reg_storage)); + } + _ => {} + } + } + for (sym, reg_storage) in free_list { + match reg_storage { + General(reg) => { + self.general_free_regs.push(reg); + self.general_used_regs.retain(|(r, _)| *r != reg); + } + Float(reg) => { + self.float_free_regs.push(reg); + self.float_used_regs.retain(|(r, _)| *r != reg); + } + } + self.free_to_stack(buf, &sym, reg_storage); + } + } + /// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. /// Note, used and free regs are expected to be updated outside of this function. fn free_to_stack( From 151b5c9336b0cc2cfdd7f9dfcae5ac453b858d2d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 00:49:28 -0800 Subject: [PATCH 027/150] generate nested rc procs --- compiler/gen_dev/src/generic64/mod.rs | 2 +- compiler/gen_dev/src/object_builder.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 498a97f2b6..83defd48c5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -361,7 +361,7 @@ impl< self.join_map.clear(); self.free_map.clear(); self.buf.clear(); - self.helper_proc_symbols.clear(); + // self.helper_proc_symbols.clear(); self.storage_manager.reset(); } diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index eec99ad63f..92c01583d1 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -267,6 +267,27 @@ fn build_object<'a, B: Backend<'a>>( helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc)); continue; } + } else { + // The symbol isn't defined yet and will just be used by other rc procs. + let section_id = output.add_section( + output.segment_name(StandardSegment::Text).to_vec(), + format!(".text.{:x}", sym.as_u64()).as_bytes().to_vec(), + SectionKind::Text, + ); + + let rc_symbol = Symbol { + name: fn_name.as_bytes().to_vec(), + value: 0, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Linkage, + weak: false, + section: SymbolSection::Section(section_id), + flags: SymbolFlags::None, + }; + let proc_id = output.add_symbol(rc_symbol); + helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc)); + continue; } internal_error!("failed to create rc fn for symbol {:?}", sym); } From 20e792c17472fe34374e1eade9877e30c1ae5125 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 00:53:57 -0800 Subject: [PATCH 028/150] fix pointer cast --- compiler/gen_dev/src/generic64/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 83defd48c5..15f410ff87 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1071,12 +1071,11 @@ impl< } fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) { - // We may not strictly need an instruction here. - // What's important is to load the value, and for src and dest to have different Layouts. - // This is used for pointer math in refcounting and for pointer equality let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); - ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg); + self.storage_manager + .ensure_symbol_on_stack(&mut self.buf, src); + let (offset, _) = self.storage_manager.stack_offset_and_size(src); + ASM::add_reg64_reg64_imm32(&mut self.buf, dst_reg, CC::BASE_PTR_REG, offset); } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { From 442d0e3bff09f9c09f9647e235b55c2c3ca69931 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 01:03:31 -0800 Subject: [PATCH 029/150] expand arg support --- compiler/gen_dev/src/generic64/storage.rs | 14 ++++++++++++++ compiler/gen_dev/src/generic64/x86_64.rs | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 41cc0c85a8..2378550acc 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -967,6 +967,20 @@ impl< reg: None, }), ); + self.allocation_map.insert(*sym, Rc::new((base_offset, 8))); + } + + /// Specifies a complex is loaded at the specific base offset. + pub fn complex_stack_arg(&mut self, sym: &Symbol, base_offset: i32, size: u32) { + self.symbol_storage_map + .insert(*sym, Stack(Complex { base_offset, size })); + self.allocation_map + .insert(*sym, Rc::new((base_offset, size))); + } + + /// Specifies a no data exists. + pub fn no_data_arg(&mut self, sym: &Symbol) { + self.symbol_storage_map.insert(*sym, NoData); } /// Loads the arg pointer symbol to the specified general reg. diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index c93f41432a..d2a174923f 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -216,6 +216,7 @@ impl CallConv for X86_64Syste general_i += 1; } for (layout, sym) in args.iter() { + let stack_size = layout.stack_size(TARGET_INFO); match layout { single_register_integers!() => { if general_i < Self::GENERAL_PARAM_REGS.len() { @@ -248,7 +249,14 @@ impl CallConv for X86_64Syste todo!("loading lists and strings args on the stack"); } } - x if x.stack_size(TARGET_INFO) == 0 => {} + _ if stack_size == 0 => { + storage_manager.no_data_arg(sym); + } + _ if stack_size > 16 => { + // TODO: Double check this. + storage_manager.complex_stack_arg(sym, arg_offset, stack_size); + arg_offset += stack_size as i32; + } x => { todo!("Loading args with layout {:?}", x); } From 90e44003fc50de279b39668f12f819829aeaa4ab Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 08:17:34 -0800 Subject: [PATCH 030/150] fix infinite looping --- compiler/gen_dev/src/generic64/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 15f410ff87..f9b592b57a 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -645,6 +645,7 @@ impl< ); // Build remainder of function. + self.storage_manager = base_storage; self.build_stmt(remainder, ret_layout) } From 3b77f5c23526280cd0778bca9eead27e91b2e072 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 08:41:21 -0800 Subject: [PATCH 031/150] fix stack size being too small in branches --- compiler/gen_dev/src/generic64/mod.rs | 2 ++ compiler/gen_dev/src/generic64/storage.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index f9b592b57a..1f47174fe4 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -590,6 +590,7 @@ impl< self.buf[jne_location + i] = *byte; } + base_storage.update_stack_size(self.storage_manager.stack_size()); base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size()); } self.storage_manager = base_storage; @@ -633,6 +634,7 @@ impl< let mut base_storage = self.storage_manager.clone(); self.join_map.insert(*id, self.buf.len() as u64); self.build_stmt(body, ret_layout); + base_storage.update_stack_size(self.storage_manager.stack_size()); base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size()); // Overwrite the original jump with the correct offset. diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 2378550acc..b3ab75bee9 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -991,6 +991,11 @@ impl< self.general_used_regs.push((reg, Symbol::RET_POINTER)); } + /// updates the stack size to the max of its current value and the tmp size needed. + pub fn update_stack_size(&mut self, tmp_size: u32) { + self.stack_size = max(self.stack_size, tmp_size); + } + /// updates the function call stack size to the max of its current value and the size need for this call. pub fn update_fn_call_stack_size(&mut self, tmp_size: u32) { self.fn_call_stack_size = max(self.fn_call_stack_size, tmp_size); From b53a53adbd21d10a62c17d3eeebbf2501221090d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 09:11:36 -0800 Subject: [PATCH 032/150] fix list get unsafe --- compiler/gen_dev/src/generic64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 1f47174fe4..62666245a5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -941,7 +941,7 @@ impl< match ret_layout { single_register_integers!() if ret_stack_size == 8 => { let dst_reg = storage_manager.claim_general_reg(buf, dst); - ASM::mov_reg64_mem64_offset32(buf, dst_reg, list_ptr, 0); + ASM::mov_reg64_mem64_offset32(buf, dst_reg, tmp, 0); } x => internal_error!("Loading list element with layout: {:?}", x), } From 72cc0e4f38072c3431a0c40e24eaaa2123390066 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 10:38:38 -0800 Subject: [PATCH 033/150] cleanup join points --- compiler/gen_dev/src/generic64/mod.rs | 53 +++++++++-------------- compiler/gen_dev/src/generic64/storage.rs | 35 +++++++-------- compiler/gen_dev/src/lib.rs | 8 ++-- 3 files changed, 42 insertions(+), 54 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 62666245a5..c1d1630526 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -289,7 +289,7 @@ pub struct Backend64Bit< free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, literal_map: MutMap, *const Layout<'a>)>, - join_map: MutMap, + join_map: MutMap>, storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>, } @@ -550,10 +550,6 @@ impl< default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), ret_layout: &Layout<'a>, ) { - // Free everything to the stack to make sure they don't get messed with in the branch. - // TODO: look into a nicer solution. - self.storage_manager.free_all_to_stack(&mut self.buf); - // Switches are a little complex due to keeping track of jumps. // In general I am trying to not have to loop over things multiple times or waste memory. // The basic plan is to make jumps to nowhere and then correct them once we know the correct address. @@ -619,36 +615,34 @@ impl< ) { // Free everything to the stack to make sure they don't get messed with in the branch. // TODO: look into a nicer solution. - self.storage_manager.free_all_to_stack(&mut self.buf); + // self.storage_manager.free_all_to_stack(&mut self.buf); // Ensure all the joinpoint parameters have storage locations. // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. self.storage_manager .setup_joinpoint(&mut self.buf, id, parameters); - // Create jump to remaining. - let jmp_location = self.buf.len(); - let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); + self.join_map.insert(*id, bumpalo::vec![in self.env.arena]); + + // Build remainder of function first. It is what gets run and jumps to join. + // self.storage_manager = base_storage; + self.build_stmt(remainder, ret_layout); + + let join_location = self.buf.len() as u64; // Build all statements in body. - let mut base_storage = self.storage_manager.clone(); - self.join_map.insert(*id, self.buf.len() as u64); self.build_stmt(body, ret_layout); - base_storage.update_stack_size(self.storage_manager.stack_size()); - base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size()); - // Overwrite the original jump with the correct offset. + // Overwrite the all jumps to the joinpoint with the correct offset. let mut tmp = bumpalo::vec![in self.env.arena]; - self.update_jmp_imm32_offset( - &mut tmp, - jmp_location as u64, - start_offset as u64, - self.buf.len() as u64, - ); - - // Build remainder of function. - self.storage_manager = base_storage; - self.build_stmt(remainder, ret_layout) + for (jmp_location, start_offset) in self + .join_map + .remove(id) + .unwrap_or_else(|| internal_error!("join point not defined")) + { + tmp.clear(); + self.update_jmp_imm32_offset(&mut tmp, jmp_location, start_offset, join_location); + } } fn build_jump( @@ -664,15 +658,8 @@ impl< let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); - if let Some(offset) = self.join_map.get(id) { - let offset = *offset; - let mut tmp = bumpalo::vec![in self.env.arena]; - self.update_jmp_imm32_offset( - &mut tmp, - jmp_location as u64, - start_offset as u64, - offset, - ); + if let Some(vec) = self.join_map.get_mut(id) { + vec.push((jmp_location as u64, start_offset as u64)) } else { internal_error!("Jump: unknown point specified to jump to: {:?}", id); } diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index b3ab75bee9..f57697e89e 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -1025,12 +1025,20 @@ impl< todo!("joinpoints with borrowed parameters"); } // Claim a location for every join point parameter to be loaded at. + // Put everything on the stack for simplicity. match layout { - single_register_integers!() => { - self.claim_general_reg(buf, symbol); - } - single_register_floats!() => { - self.claim_float_reg(buf, symbol); + single_register_layouts!() => { + let base_offset = self.claim_stack_size(8); + self.symbol_storage_map.insert( + *symbol, + Stack(ReferencedPrimitive { + base_offset, + size: 8, + sign_extend: false, + }), + ); + self.allocation_map + .insert(*symbol, Rc::new((base_offset, 8))); } _ => { let stack_size = layout.stack_size(self.target_info); @@ -1069,17 +1077,8 @@ impl< continue; } match wanted_storage { - Reg(General(reg)) => { - // Ensure the reg is free, if not free it. - self.ensure_reg_free(buf, General(*reg)); - // Copy the value over to the reg. - self.load_to_specified_general_reg(buf, sym, *reg) - } - Reg(Float(reg)) => { - // Ensure the reg is free, if not free it. - self.ensure_reg_free(buf, Float(*reg)); - // Copy the value over to the reg. - self.load_to_specified_float_reg(buf, sym, *reg) + Reg(_) => { + internal_error!("Register storage is not allowed for jumping to joinpoint") } Stack(ReferencedPrimitive { base_offset, .. } | Complex { base_offset, .. }) => { // TODO: This might be better not to call. @@ -1090,7 +1089,9 @@ impl< } NoData => {} Stack(Primitive { .. }) => { - internal_error!("Primitive stack storage is not allowed for jumping") + internal_error!( + "Primitive stack storage is not allowed for jumping to joinpoint" + ) } } } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 419b3cf9e8..c4ed9edf83 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -950,15 +950,16 @@ trait Backend<'a> { parameters, body: continuation, remainder, - id, + id: JoinPointId(sym), .. } => { - join_map.insert(*id, parameters); + self.set_last_seen(*sym, stmt); + join_map.insert(JoinPointId(*sym), parameters); for param in *parameters { self.set_last_seen(param.symbol, stmt); } - self.scan_ast(continuation); self.scan_ast(remainder); + self.scan_ast(continuation); } Stmt::Jump(JoinPointId(sym), symbols) => { if let Some(parameters) = join_map.get(&JoinPointId(*sym)) { @@ -967,7 +968,6 @@ trait Backend<'a> { self.set_last_seen(param.symbol, stmt); } } - self.set_last_seen(*sym, stmt); for sym in *symbols { self.set_last_seen(*sym, stmt); } From facfe9cffc69864fd39cacc0725a1c065b7bd1c1 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 11:05:16 -0800 Subject: [PATCH 034/150] re-enable freeing all to stack --- compiler/gen_dev/src/generic64/mod.rs | 2 +- compiler/gen_dev/src/generic64/storage.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index c1d1630526..e0c53d9c0b 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -615,7 +615,7 @@ impl< ) { // Free everything to the stack to make sure they don't get messed with in the branch. // TODO: look into a nicer solution. - // self.storage_manager.free_all_to_stack(&mut self.buf); + self.storage_manager.free_all_to_stack(&mut self.buf); // Ensure all the joinpoint parameters have storage locations. // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index f57697e89e..39760c59d8 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -712,7 +712,7 @@ impl< } } - /// Copies a complex symbol ot the stack to the arg pointer. + /// Copies a complex symbol on the stack to the arg pointer. pub fn copy_symbol_to_arg_pionter( &mut self, buf: &mut Vec<'a, u8>, From 2801699ffce667f637a725b6773749394dfbc3d7 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 28 Feb 2022 17:10:14 -0800 Subject: [PATCH 035/150] fix warnings --- compiler/gen_dev/src/generic64/storage.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 39760c59d8..7bd3c79222 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -779,6 +779,7 @@ impl< } } + #[allow(dead_code)] /// Ensures that a register is free. If it is not free, data will be moved to make it free. fn ensure_reg_free( &mut self, @@ -1007,7 +1008,7 @@ impl< /// Later jumps to the join point can overwrite the stored locations to pass parameters. pub fn setup_joinpoint( &mut self, - buf: &mut Vec<'a, u8>, + _buf: &mut Vec<'a, u8>, id: &JoinPointId, params: &'a [Param<'a>], ) { From 63ea556055a1f15cd44ed8848fd4bf46435ea2fe Mon Sep 17 00:00:00 2001 From: Jose Quesada Date: Tue, 1 Mar 2022 09:21:27 -0600 Subject: [PATCH 036/150] fix: allow editor to launch even when requesting device fails --- editor/src/editor/main.rs | 73 ++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index e94412d628..b0bc5e471d 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -21,6 +21,7 @@ use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; +use futures::TryFutureExt; use pipelines::RectResources; use roc_ast::lang::env::Env; use roc_ast::mem_pool::pool::Pool; @@ -73,28 +74,25 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> { + if force_fallback_adapter { + log::error!("Falling back to software renderer. GPU acceleration has been disabled."); + } + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference, + compatible_surface: Some(&surface), + force_fallback_adapter, + }) + .await + .expect(r#"Request adapter + If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor + "#); + + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await +} + fn draw_rects( all_rects: &[Rect], encoder: &mut CommandEncoder, From fd8dfd284d0ddc273ee60271658c89b8203cdcc6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 1 Mar 2022 22:41:46 +0100 Subject: [PATCH 037/150] add Index and Slice to collections --- compiler/collections/src/soa.rs | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 compiler/collections/src/soa.rs diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs new file mode 100644 index 0000000000..6c9247b020 --- /dev/null +++ b/compiler/collections/src/soa.rs @@ -0,0 +1,47 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Index { + index: u32, + _marker: std::marker::PhantomData, +} + +impl Index { + pub const fn new(index: u32) -> Self { + Self { + index, + _marker: std::marker::PhantomData, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Slice { + start: u32, + length: u16, + _marker: std::marker::PhantomData, +} + +impl Slice { + pub const fn new(start: u32, length: u16) -> Self { + Self { + start, + length, + _marker: std::marker::PhantomData, + } + } + + pub const fn len(&self) -> usize { + self.length as _ + } + + pub const fn is_empty(&self) -> bool { + self.length == 0 + } + + pub const fn indices(&self) -> std::ops::Range { + self.start as usize..(self.start as usize + self.length as usize) + } + + pub fn into_iter(&self) -> impl Iterator> { + self.indices().map(|i| Index::new(i as _)) + } +} From beeff5ca726e2862c482d9d20d1e509a05697ce9 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 1 Mar 2022 15:52:34 -0800 Subject: [PATCH 038/150] misc cleanup --- compiler/gen_dev/src/generic64/mod.rs | 22 +++++++++++++--------- compiler/gen_dev/src/generic64/storage.rs | 2 +- compiler/gen_dev/src/generic64/x86_64.rs | 6 +++--- compiler/gen_dev/src/lib.rs | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index e0c53d9c0b..1bc07d0954 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -558,17 +558,18 @@ impl< .load_to_general_reg(&mut self.buf, cond_symbol); let mut base_storage = self.storage_manager.clone(); + let mut max_branch_stack_size = 0; let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena]; for (val, _branch_info, stmt) in branches.iter() { - // TODO: look inot branch info and if it matters here. + // TODO: look into branch info and if it matters here. tmp.clear(); - // Create jump to next branch if not cond_sym not equal to value. + // Create jump to next branch if cond_sym not equal to value. // Since we don't know the offset yet, set it to 0 and overwrite later. let jne_location = self.buf.len(); let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); - // Build all statements in this branch. + // Build all statements in this branch. Using storage as from before any branch. self.storage_manager = base_storage.clone(); self.build_stmt(stmt, ret_layout); @@ -586,10 +587,14 @@ impl< self.buf[jne_location + i] = *byte; } - base_storage.update_stack_size(self.storage_manager.stack_size()); + // Update important storage information to avoid overwrites. + max_branch_stack_size = + std::cmp::max(max_branch_stack_size, self.storage_manager.stack_size()); base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size()); } self.storage_manager = base_storage; + self.storage_manager + .update_stack_size(max_branch_stack_size); let (_branch_info, stmt) = default_branch; self.build_stmt(stmt, ret_layout); @@ -613,7 +618,7 @@ impl< remainder: &'a Stmt<'a>, ret_layout: &Layout<'a>, ) { - // Free everything to the stack to make sure they don't get messed with in the branch. + // Free everything to the stack to make sure they don't get messed up when looping back to this point. // TODO: look into a nicer solution. self.storage_manager.free_all_to_stack(&mut self.buf); @@ -625,7 +630,6 @@ impl< self.join_map.insert(*id, bumpalo::vec![in self.env.arena]); // Build remainder of function first. It is what gets run and jumps to join. - // self.storage_manager = base_storage; self.build_stmt(remainder, ret_layout); let join_location = self.buf.len() as u64; @@ -959,7 +963,7 @@ impl< let list_alignment = list_layout.alignment_bytes(self.storage_manager.target_info()); self.load_literal( &Symbol::DEV_TMP, - &u32_layout, + u32_layout, &Literal::Int(list_alignment as i128), ); @@ -978,13 +982,13 @@ impl< let elem_stack_size = elem_layout.stack_size(self.storage_manager.target_info()); self.load_literal( &Symbol::DEV_TMP3, - &u64_layout, + u64_layout, &Literal::Int(elem_stack_size as i128), ); // Setup the return location. let base_offset = self.storage_manager.claim_stack_area( - &dst, + dst, ret_layout.stack_size(self.storage_manager.target_info()), ); diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 7bd3c79222..be5eeaea8a 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -713,7 +713,7 @@ impl< } /// Copies a complex symbol on the stack to the arg pointer. - pub fn copy_symbol_to_arg_pionter( + pub fn copy_symbol_to_arg_pointer( &mut self, buf: &mut Vec<'a, u8>, sym: &Symbol, diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index d2a174923f..2eff83ecd2 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -440,7 +440,7 @@ impl CallConv for X86_64Syste } _ => { // This is a large type returned via the arg pointer. - storage_manager.copy_symbol_to_arg_pionter(buf, sym, layout); + storage_manager.copy_symbol_to_arg_pointer(buf, sym, layout); // Also set the return reg to the arg pointer. storage_manager.load_to_specified_general_reg( buf, @@ -1599,7 +1599,7 @@ fn movsd_freg64_rip_offset32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: buf.extend(&offset.to_le_bytes()); } -/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pionter. +/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pointer. #[inline(always)] fn movsd_base64_offset32_freg64( buf: &mut Vec<'_, u8>, @@ -1622,7 +1622,7 @@ fn movsd_base64_offset32_freg64( buf.extend(&offset.to_le_bytes()); } -/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pionter. +/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pointer. #[inline(always)] fn movsd_freg64_base64_offset32( buf: &mut Vec<'_, u8>, diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index c4ed9edf83..5daa75e0b4 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -336,7 +336,7 @@ trait Backend<'a> { .. } => { self.load_literal_symbols(arguments); - self.tag(sym, &arguments, tag_layout, *tag_id); + self.tag(sym, arguments, tag_layout, *tag_id); } x => todo!("the expression, {:?}", x), } From ec099bbdec5fc61809a8b76e869e09c143fd59a6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 14:46:30 +0100 Subject: [PATCH 039/150] WIP --- ast/src/constrain.rs | 26 ++++----- compiler/can/src/constraint_soa.rs | 93 ++++++++++-------------------- compiler/collections/src/all.rs | 10 ++-- compiler/collections/src/lib.rs | 1 + compiler/collections/src/soa.rs | 18 ++++++ compiler/constrain/src/expr.rs | 30 +++++----- compiler/constrain/src/lib.rs | 1 + compiler/constrain/src/pattern.rs | 4 +- compiler/exhaustive/src/lib.rs | 4 +- compiler/mono/src/exhaustive.rs | 4 +- compiler/types/src/types.rs | 24 ++++---- reporting/src/error/type.rs | 6 +- 12 files changed, 103 insertions(+), 118 deletions(-) diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 6c5af07eaf..bd00bbbda5 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1,7 +1,7 @@ use bumpalo::{collections::Vec as BumpVec, Bump}; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; +use roc_collections::all::{BumpMap, BumpMapDefault, HumanIndex, SendMap}; use roc_module::{ ident::{Lowercase, TagName}, symbol::Symbol, @@ -163,7 +163,7 @@ pub fn constrain_expr<'a>( let elem_expected = Expected::ForReason( Reason::ElemInList { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, list_elem_type.shallow_clone(), region, @@ -339,7 +339,7 @@ pub fn constrain_expr<'a>( let reason = Reason::FnArg { name: opt_symbol, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); @@ -538,7 +538,7 @@ pub fn constrain_expr<'a>( name.clone(), arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), num_branches, region: ann_source.region(), }, @@ -559,7 +559,7 @@ pub fn constrain_expr<'a>( name, arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), num_branches, region: ann_source.region(), }, @@ -596,7 +596,7 @@ pub fn constrain_expr<'a>( body, Expected::ForReason( Reason::IfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), total_branches: branches.len(), }, Type2::Variable(*expr_var), @@ -616,7 +616,7 @@ pub fn constrain_expr<'a>( final_else_expr, Expected::ForReason( Reason::IfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), total_branches: branches.len() + 1, }, Type2::Variable(*expr_var), @@ -691,7 +691,7 @@ pub fn constrain_expr<'a>( when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.shallow_clone(), pattern_region, @@ -700,7 +700,7 @@ pub fn constrain_expr<'a>( name.clone(), *arity, AnnotationSource::TypedWhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), region: ann_source.region(), }, typ.shallow_clone(), @@ -733,14 +733,14 @@ pub fn constrain_expr<'a>( when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.shallow_clone(), pattern_region, ), Expected::ForReason( Reason::WhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, branch_type.shallow_clone(), // TODO: when_branch.value.region, @@ -1065,7 +1065,7 @@ pub fn constrain_expr<'a>( let reason = Reason::LowLevelOpArg { op: *op, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); @@ -1681,7 +1681,7 @@ fn constrain_tag_pattern<'a>( let expected = PExpected::ForReason( PReason::TagArg { tag_name: tag_name.clone(), - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, pattern_type, region, diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 05e1652340..7edde7e960 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -1,7 +1,7 @@ use crate::expected::{Expected, PExpected}; -use roc_collections::all::{MutSet, SendMap}; +use roc_collections::soa::{Index, Slice}; use roc_module::symbol::Symbol; -use roc_region::all::{Located, Region}; +use roc_region::all::{Loc, Region}; use roc_types::types::{Category, PatternCategory, Type}; use roc_types::{subs::Variable, types::VariableDetail}; @@ -9,7 +9,7 @@ pub struct Constraints { constraints: Vec, types: Vec, variables: Vec, - def_types: Vec<(Symbol, Located>)>, + def_types: Vec<(Symbol, Loc>)>, let_constraints: Vec, categories: Vec, pattern_categories: Vec, @@ -18,6 +18,26 @@ pub struct Constraints { } impl Constraints { + pub const EMPTY_RECORD: Index = Index::new(0); + pub const EMPTY_TAG_UNION: Index = Index::new(1); + + pub const CATEGORY_RECORD: Index = Index::new(0); + + pub fn push_type(&mut self, typ: Type) -> Index { + match typ { + Type::EmptyRec => Self::EMPTY_RECORD, + Type::EmptyTagUnion => Self::EMPTY_TAG_UNION, + other => Index::push_new(&mut self.types, other), + } + } + + pub fn push_category(&mut self, category: Category) -> Index { + match category { + Category::Record => Self::CATEGORY_RECORD, + other => Index::push_new(&mut self.categories, other), + } + } + pub fn equal_types( &mut self, typ: Type, @@ -65,19 +85,18 @@ impl Constraints { Slice::new(start as _, length as _) } - fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Located)> + fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Loc)> where - I: IntoIterator)>, + I: IntoIterator)>, { let start = self.def_types.len(); for (symbol, loc_type) in it { let type_index = Index::new(self.types.len() as _); - let Located { region, value } = loc_type; + let Loc { region, value } = loc_type; self.types.push(value); - self.def_types - .push((symbol, Located::at(region, type_index))); + self.def_types.push((symbol, Loc::at(region, type_index))); } let length = self.def_types.len() - start; @@ -118,7 +137,7 @@ impl Constraints { where I1: IntoIterator, I2: IntoIterator, - I3: IntoIterator)>, + I3: IntoIterator)>, { let defs_and_ret_constraint = Index::new(self.constraints.len() as _); @@ -163,60 +182,6 @@ pub enum Constraint { pub struct LetConstraint { pub rigid_vars: Slice, pub flex_vars: Slice, - pub def_types: Slice<(Symbol, Located)>, + pub def_types: Slice<(Symbol, Loc)>, pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Index { - index: u32, - _marker: std::marker::PhantomData, -} - -impl Index { - pub const fn new(index: u32) -> Self { - Self { - index, - _marker: std::marker::PhantomData, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Slice { - start: u32, - length: u16, - _marker: std::marker::PhantomData, -} - -impl Default for Slice { - fn default() -> Self { - Self::new(0, 0) - } -} - -impl Slice { - pub const fn new(start: u32, length: u16) -> Self { - Self { - start, - length, - _marker: std::marker::PhantomData, - } - } - - pub const fn len(&self) -> usize { - self.length as _ - } - - pub const fn is_empty(&self) -> bool { - self.length == 0 - } - - pub const fn indices(&self) -> std::ops::Range { - self.start as usize..(self.start as usize + self.length as usize) - } - - pub fn into_iter(&self) -> impl Iterator> { - self.indices().map(|i| Index::new(i as _)) - } -} diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index efd74751bb..b5874be5bb 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -161,13 +161,13 @@ where } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Index(usize); +pub struct HumanIndex(usize); -impl Index { - pub const FIRST: Self = Index(0); +impl HumanIndex { + pub const FIRST: Self = HumanIndex(0); pub fn zero_based(i: usize) -> Self { - Index(i) + HumanIndex(i) } pub fn to_zero_based(self) -> usize { @@ -175,7 +175,7 @@ impl Index { } pub fn one_based(i: usize) -> Self { - Index(i - 1) + HumanIndex(i - 1) } pub fn ordinal(self) -> std::string::String { diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index 885d50b458..16f8d165dc 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -3,3 +3,4 @@ #![allow(clippy::large_enum_variant)] pub mod all; +pub mod soa; diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs index 6c9247b020..ad5bae89d0 100644 --- a/compiler/collections/src/soa.rs +++ b/compiler/collections/src/soa.rs @@ -11,6 +11,14 @@ impl Index { _marker: std::marker::PhantomData, } } + + pub fn push_new(vector: &mut Vec, value: T) -> Index { + let index = Self::new(vector.len() as _); + + vector.push(value); + + index + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -20,6 +28,16 @@ pub struct Slice { _marker: std::marker::PhantomData, } +impl Default for Slice { + fn default() -> Self { + Self { + start: Default::default(), + length: Default::default(), + _marker: Default::default(), + } + } +} + impl Slice { pub const fn new(start: u32, length: u16) -> Self { Self { diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index da99c6c50e..cbced89851 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -11,7 +11,7 @@ use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; -use roc_collections::all::{ImMap, Index, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, ImMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; @@ -229,7 +229,7 @@ pub fn constrain_expr( for (index, loc_elem) in loc_elems.iter().enumerate() { let elem_expected = ForReason( Reason::ElemInList { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, list_elem_type.clone(), loc_elem.region, @@ -293,7 +293,7 @@ pub fn constrain_expr( let reason = Reason::FnArg { name: opt_symbol, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), region); let arg_con = constrain_expr(env, loc_arg.region, &loc_arg.value, expected_arg); @@ -462,7 +462,7 @@ pub fn constrain_expr( name.clone(), arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), num_branches, region: ann_source.region(), }, @@ -482,7 +482,7 @@ pub fn constrain_expr( name, arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), num_branches, region: ann_source.region(), }, @@ -517,7 +517,7 @@ pub fn constrain_expr( &loc_body.value, ForReason( Reason::IfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), total_branches: branches.len(), }, Type::Variable(*branch_var), @@ -534,7 +534,7 @@ pub fn constrain_expr( &final_else.value, ForReason( Reason::IfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), total_branches: branches.len() + 1, }, Type::Variable(*branch_var), @@ -592,7 +592,7 @@ pub fn constrain_expr( when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.clone(), pattern_region, @@ -601,7 +601,7 @@ pub fn constrain_expr( name.clone(), *arity, AnnotationSource::TypedWhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), region: ann_source.region(), }, typ.clone(), @@ -629,14 +629,14 @@ pub fn constrain_expr( when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.clone(), pattern_region, ), ForReason( Reason::WhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, branch_type.clone(), when_branch.value.region, @@ -1007,7 +1007,7 @@ pub fn constrain_expr( let mut add_arg = |index, arg_type: Type, arg| { let reason = Reason::LowLevelOpArg { op: *op, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); @@ -1053,7 +1053,7 @@ pub fn constrain_expr( let mut add_arg = |index, arg_type: Type, arg| { let reason = Reason::ForeignCallArg { foreign_symbol: foreign_symbol.clone(), - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); @@ -1320,7 +1320,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let pattern_expected = PExpected::ForReason( PReason::TypedArg { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), opt_name: opt_label, }, pattern_type.clone(), @@ -1687,7 +1687,7 @@ pub fn rec_defs_help( let pattern_expected = PExpected::ForReason( PReason::TypedArg { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), opt_name: opt_label, }, pattern_type.clone(), diff --git a/compiler/constrain/src/lib.rs b/compiler/constrain/src/lib.rs index 94067ab076..b95a36ee6d 100644 --- a/compiler/constrain/src/lib.rs +++ b/compiler/constrain/src/lib.rs @@ -5,3 +5,4 @@ pub mod builtins; pub mod expr; pub mod module; pub mod pattern; +pub mod soa_expr; diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 200f38dc74..e7a3f0f7fc 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -4,7 +4,7 @@ use roc_can::constraint::{Constraint, PresenceConstraint}; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::{DestructType, RecordDestruct}; -use roc_collections::all::{Index, SendMap}; +use roc_collections::all::{HumanIndex, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -412,7 +412,7 @@ pub fn constrain_pattern( let expected = PExpected::ForReason( PReason::TagArg { tag_name: tag_name.clone(), - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, pattern_type, region, diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs index c80b9b01d0..8ca1c78298 100644 --- a/compiler/exhaustive/src/lib.rs +++ b/compiler/exhaustive/src/lib.rs @@ -1,4 +1,4 @@ -use roc_collections::all::{Index, MutMap}; +use roc_collections::all::{HumanIndex, MutMap}; use roc_module::ident::{Lowercase, TagIdIntType, TagName}; use roc_region::all::Region; use roc_std::RocDec; @@ -70,7 +70,7 @@ pub enum Error { Redundant { overall_region: Region, branch_region: Region, - index: Index, + index: HumanIndex, }, } diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index cafbf8bbf9..ceaea825c6 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -1,5 +1,5 @@ use crate::ir::DestructType; -use roc_collections::all::Index; +use roc_collections::all::HumanIndex; use roc_exhaustive::{ is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, }; @@ -189,7 +189,7 @@ fn to_nonredundant_rows( return Err(Error::Redundant { overall_region, branch_region: region, - index: Index::zero_based(checked_rows.len()), + index: HumanIndex::zero_based(checked_rows.len()), }); } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 298fd32be8..6bc9b7ed9e 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -2,7 +2,7 @@ use crate::pretty_print::Parens; use crate::subs::{ GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, }; -use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, ImMap, ImSet, MutSet, SendMap}; use roc_error_macros::internal_error; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; @@ -1203,14 +1203,14 @@ pub struct TagUnionStructure<'a> { pub enum PReason { TypedArg { opt_name: Option, - index: Index, + index: HumanIndex, }, WhenMatch { - index: Index, + index: HumanIndex, }, TagArg { tag_name: TagName, - index: Index, + index: HumanIndex, }, PatternGuard, OptionalField, @@ -1219,12 +1219,12 @@ pub enum PReason { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnnotationSource { TypedIfBranch { - index: Index, + index: HumanIndex, num_branches: usize, region: Region, }, TypedWhenBranch { - index: Index, + index: HumanIndex, region: Region, }, TypedBody { @@ -1246,7 +1246,7 @@ impl AnnotationSource { pub enum Reason { FnArg { name: Option, - arg_index: Index, + arg_index: HumanIndex, }, FnCall { name: Option, @@ -1254,28 +1254,28 @@ pub enum Reason { }, LowLevelOpArg { op: LowLevel, - arg_index: Index, + arg_index: HumanIndex, }, ForeignCallArg { foreign_symbol: ForeignSymbol, - arg_index: Index, + arg_index: HumanIndex, }, FloatLiteral, IntLiteral, NumLiteral, StrInterpolation, WhenBranch { - index: Index, + index: HumanIndex, }, WhenGuard, ExpectCondition, IfCondition, IfBranch { - index: Index, + index: HumanIndex, total_branches: usize, }, ElemInList { - index: Index, + index: HumanIndex, }, RecordUpdateValue(Lowercase), RecordUpdateKeys(Symbol, SendMap), diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index ec7597f0aa..5f181c24d4 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1,5 +1,5 @@ use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{Index, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, MutSet, SendMap}; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -350,7 +350,7 @@ fn to_expr_report<'b>( num_branches, .. } if num_branches == 2 => alloc.concat(vec![ - alloc.keyword(if index == Index::FIRST { + alloc.keyword(if index == HumanIndex::FIRST { "then" } else { "else" @@ -1384,7 +1384,7 @@ fn to_pattern_report<'b>( found, expected_type, add_pattern_category( - alloc, + HumanIndexlloc, alloc.text("The first pattern is trying to match"), &category, ), From b8fd6992a2b2df18ab23eff0f5e9a23935e9ccb3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 15:29:45 +0100 Subject: [PATCH 040/150] More wip --- compiler/can/src/constraint_soa.rs | 22 +++++++++++++ compiler/constrain/src/builtins.rs | 50 ++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 7edde7e960..8a70610948 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -23,6 +23,7 @@ impl Constraints { pub const CATEGORY_RECORD: Index = Index::new(0); + #[inline(always)] pub fn push_type(&mut self, typ: Type) -> Index { match typ { Type::EmptyRec => Self::EMPTY_RECORD, @@ -31,6 +32,12 @@ impl Constraints { } } + #[inline(always)] + pub fn push_expected_type(&mut self, expected: Expected) -> Index> { + Index::push_new(&mut self.expectations, expected) + } + + #[inline(always)] pub fn push_category(&mut self, category: Category) -> Index { match category { Category::Record => Self::CATEGORY_RECORD, @@ -156,6 +163,21 @@ impl Constraints { Constraint::Let(let_index) } + + pub fn and_contraint(&mut self, constraints: I) -> Constraint + where + I: IntoIterator, + { + let start = self.constraints.len() as u32; + + self.constraints.extend(constraints); + + let end = self.constraints.len() as u32; + + let slice = Slice::new(start, (end - start) as u16); + + Constraint::And(slice) + } } static_assertions::assert_eq_size!([u8; 4 * 8], Constraint); diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index cf1a907845..d0683a6507 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,5 +1,6 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; +use roc_can::constraint_soa::Constraints; use roc_can::expected::Expected::{self, *}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; use roc_collections::all::SendMap; @@ -82,6 +83,55 @@ pub fn float_literal( ) -> Constraint { let reason = Reason::FloatLiteral; + let value_is_float_literal = Eq( + num_type.clone(), + ForReason(reason, num_float(Type::Variable(precision_var)), region), + Category::Float, + region, + ); + + let expected_float = Eq(num_type, expected, Category::Float, region); + + let mut constrs = Vec::with_capacity(3); + let num_type = { + let constrs: &mut Vec = &mut constrs; + let num_type = Variable(num_var); + let category = Category::Float; + let range = bound.bounded_range(); + + let total_num_type = num_type; + + match range.len() { + 0 => total_num_type, + 1 => { + let actual_type = Variable(range[0]); + constrs.push(Eq( + total_num_type.clone(), + Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), + category, + region, + )); + total_num_type + } + _ => RangedNumber(Box::new(total_num_type), range), + } + }; + constrs.extend(vec![]); + + exists(vec![num_var, precision_var], And(constrs)) +} + +#[inline(always)] +pub fn float_literal_soa( + constraints: &mut Constraints, + num_var: Variable, + precision_var: Variable, + expected: Expected, + region: Region, + bound: FloatBound, +) -> Constraint { + let reason = Reason::FloatLiteral; + let mut constrs = Vec::with_capacity(3); let num_type = add_numeric_bound_constr( &mut constrs, From 01a7fe77d494dda1bb12a57df77201c47e3e37e7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 17:32:50 +0100 Subject: [PATCH 041/150] even more wip --- Cargo.lock | 1 + compiler/can/src/annotation.rs | 3 +- compiler/can/src/constraint_soa.rs | 34 +- compiler/constrain/Cargo.toml | 1 + compiler/constrain/src/builtins.rs | 170 ++- compiler/constrain/src/lib.rs | 1 + compiler/constrain/src/soa_expr.rs | 2017 +++++++++++++++++++++++++ compiler/constrain/src/soa_pattern.rs | 521 +++++++ linker/src/lib.rs | 24 +- reporting/src/error/type.rs | 2 +- 10 files changed, 2710 insertions(+), 64 deletions(-) create mode 100644 compiler/constrain/src/soa_expr.rs create mode 100644 compiler/constrain/src/soa_pattern.rs diff --git a/Cargo.lock b/Cargo.lock index f56718aa57..143666db8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3416,6 +3416,7 @@ dependencies = [ name = "roc_constrain" version = "0.1.0" dependencies = [ + "arrayvec 0.7.2", "roc_builtins", "roc_can", "roc_collections", diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 89f0a6f38b..de0812c4a2 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -360,8 +360,7 @@ fn can_annotation_help( As( loc_inner, _spaces, - alias_header - @ TypeHeader { + alias_header @ TypeHeader { name, vars: loc_vars, }, diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 8a70610948..9d24540cf1 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -133,7 +133,7 @@ impl Constraints { Constraint::Let(let_index) } - pub fn let_contraint( + pub fn let_constraint( &mut self, rigid_vars: I1, flex_vars: I2, @@ -164,7 +164,7 @@ impl Constraints { Constraint::Let(let_index) } - pub fn and_contraint(&mut self, constraints: I) -> Constraint + pub fn and_constraint(&mut self, constraints: I) -> Constraint where I: IntoIterator, { @@ -178,6 +178,36 @@ impl Constraints { Constraint::And(slice) } + + pub fn lookup( + &mut self, + symbol: Symbol, + expected: Expected, + region: Region, + ) -> Constraint { + Constraint::Lookup( + symbol, + Index::push_new(&mut self.expectations, expected), + region, + ) + } + pub fn contains_save_the_environment(&self, constraint: Constraint) -> bool { + todo!() + } + + pub fn store( + &mut self, + typ: Type, + variable: Variable, + filename: &'static str, + line_number: u32, + ) -> Constraint { + let type_index = Index::new(self.types.len() as _); + + self.types.push(typ); + + Constraint::Store(type_index, variable, filename, line_number) + } } static_assertions::assert_eq_size!([u8; 4 * 8], Constraint); diff --git a/compiler/constrain/Cargo.toml b/compiler/constrain/Cargo.toml index 82e7426c0d..d5c4059c63 100644 --- a/compiler/constrain/Cargo.toml +++ b/compiler/constrain/Cargo.toml @@ -14,3 +14,4 @@ roc_parse = { path = "../parse" } roc_types = { path = "../types" } roc_can = { path = "../can" } roc_builtins = { path = "../builtins" } +arrayvec = "0.7.2" diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index d0683a6507..f036400799 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,5 +1,7 @@ +use arrayvec::ArrayVec; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; +use roc_can::constraint_soa; use roc_can::constraint_soa::Constraints; use roc_can::expected::Expected::{self, *}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; @@ -40,6 +42,35 @@ pub fn add_numeric_bound_constr( } } +#[must_use] +#[inline(always)] +pub fn add_numeric_bound_constr_soa( + constraints: &mut Constraints, + num_constraints: &mut impl Extend, + num_type: Type, + bound: impl TypedNumericBound, + region: Region, + category: Category, +) -> Type { + let range = bound.bounded_range(); + + let total_num_type = num_type; + + match range.len() { + 0 => total_num_type, + 1 => { + let actual_type = Variable(range[0]); + let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); + let because_suffix = constraints.equal_types(actual_type, expected, category, region); + + num_constraints.extend([because_suffix]); + + total_num_type + } + _ => RangedNumber(Box::new(total_num_type), range), + } +} + #[inline(always)] pub fn int_literal( num_var: Variable, @@ -83,15 +114,6 @@ pub fn float_literal( ) -> Constraint { let reason = Reason::FloatLiteral; - let value_is_float_literal = Eq( - num_type.clone(), - ForReason(reason, num_float(Type::Variable(precision_var)), region), - Category::Float, - region, - ); - - let expected_float = Eq(num_type, expected, Category::Float, region); - let mut constrs = Vec::with_capacity(3); let num_type = { let constrs: &mut Vec = &mut constrs; @@ -121,38 +143,6 @@ pub fn float_literal( exists(vec![num_var, precision_var], And(constrs)) } -#[inline(always)] -pub fn float_literal_soa( - constraints: &mut Constraints, - num_var: Variable, - precision_var: Variable, - expected: Expected, - region: Region, - bound: FloatBound, -) -> Constraint { - let reason = Reason::FloatLiteral; - - let mut constrs = Vec::with_capacity(3); - let num_type = add_numeric_bound_constr( - &mut constrs, - Variable(num_var), - bound, - region, - Category::Float, - ); - constrs.extend(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_float(Type::Variable(precision_var)), region), - Category::Float, - region, - ), - Eq(num_type, expected, Category::Float, region), - ]); - - exists(vec![num_var, precision_var], And(constrs)) -} - #[inline(always)] pub fn num_literal( num_var: Variable, @@ -170,6 +160,104 @@ pub fn num_literal( exists(vec![num_var], And(constrs)) } +#[inline(always)] +pub fn int_literal_soa( + constraints: &mut Constraints, + num_var: Variable, + precision_var: Variable, + expected: Expected, + region: Region, + bound: IntBound, +) -> constraint_soa::Constraint { + let reason = Reason::IntLiteral; + + // Always add the bound first; this improves the resolved type quality in case it's an alias like "U8". + let mut constrs = ArrayVec::<_, 3>::new(); + let num_type = add_numeric_bound_constr_soa( + constraints, + &mut constrs, + Variable(num_var), + bound, + region, + Category::Num, + ); + + constrs.extend([ + constraints.equal_types( + num_type.clone(), + ForReason(reason, num_int(Type::Variable(precision_var)), region), + Category::Int, + region, + ), + constraints.equal_types(num_type, expected, Category::Int, region), + ]); + + // TODO the precision_var is not part of the exists here; for float it is. Which is correct? + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var], and_constraint) +} + +#[inline(always)] +pub fn float_literal_soa( + constraints: &mut Constraints, + num_var: Variable, + precision_var: Variable, + expected: Expected, + region: Region, + bound: FloatBound, +) -> constraint_soa::Constraint { + let reason = Reason::FloatLiteral; + + let mut constrs = ArrayVec::<_, 3>::new(); + let num_type = add_numeric_bound_constr_soa( + constraints, + &mut constrs, + Variable(num_var), + bound, + region, + Category::Float, + ); + + constrs.extend([ + constraints.equal_types( + num_type.clone(), + ForReason(reason, num_float(Type::Variable(precision_var)), region), + Category::Float, + region, + ), + constraints.equal_types(num_type, expected, Category::Float, region), + ]); + + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var, precision_var], and_constraint) +} + +#[inline(always)] +pub fn num_literal_soa( + constraints: &mut Constraints, + num_var: Variable, + expected: Expected, + region: Region, + bound: NumericBound, +) -> constraint_soa::Constraint { + let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); + + let mut constrs = ArrayVec::<_, 2>::new(); + let num_type = add_numeric_bound_constr_soa( + constraints, + &mut constrs, + open_number_type, + bound, + region, + Category::Num, + ); + + constrs.extend([constraints.equal_types(num_type, expected, Category::Num, region)]); + + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var], and_constraint) +} + #[inline(always)] pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { Let(Box::new(LetConstraint { diff --git a/compiler/constrain/src/lib.rs b/compiler/constrain/src/lib.rs index b95a36ee6d..67edd169ed 100644 --- a/compiler/constrain/src/lib.rs +++ b/compiler/constrain/src/lib.rs @@ -6,3 +6,4 @@ pub mod expr; pub mod module; pub mod pattern; pub mod soa_expr; +pub mod soa_pattern; diff --git a/compiler/constrain/src/soa_expr.rs b/compiler/constrain/src/soa_expr.rs new file mode 100644 index 0000000000..7be8bdeaf8 --- /dev/null +++ b/compiler/constrain/src/soa_expr.rs @@ -0,0 +1,2017 @@ +use crate::builtins::{ + empty_list_type, float_literal_soa, int_literal_soa, list_type, num_literal_soa, num_u32, + str_type, +}; +use crate::soa_pattern::{constrain_pattern, PatternState}; +use roc_can::annotation::IntroducedVariables; +// use roc_can::constraint::Constraint::{self, *}; +// use roc_can::constraint::LetConstraint; +use roc_can::constraint_soa::{Constraint, Constraints}; +use roc_can::def::{Declaration, Def}; +use roc_can::expected::Expected::{self, *}; +use roc_can::expected::PExpected; +use roc_can::expr::Expr::{self, *}; +use roc_can::expr::{ClosureData, Field, WhenBranch}; +use roc_can::pattern::Pattern; +use roc_collections::all::{HumanIndex, ImMap, MutSet, SendMap}; +use roc_module::ident::{Lowercase, TagName}; +use roc_module::symbol::{ModuleId, Symbol}; +use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; +use roc_types::types::Type::{self, *}; +use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField}; + +/// This is for constraining Defs +#[derive(Default, Debug)] +pub struct Info { + pub vars: Vec, + pub constraints: Vec, + pub def_types: SendMap>, +} + +impl Info { + pub fn with_capacity(capacity: usize) -> Self { + Info { + vars: Vec::with_capacity(capacity), + constraints: Vec::with_capacity(capacity), + def_types: SendMap::default(), + } + } +} + +pub struct Env { + /// Whenever we encounter a user-defined type variable (a "rigid" var for short), + /// for example `a` in the annotation `identity : a -> a`, we add it to this + /// map so that expressions within that annotation can share these vars. + pub rigids: ImMap, + pub home: ModuleId, +} + +fn constrain_untyped_args( + constraints: &mut Constraints, + env: &Env, + arguments: &[(Variable, Loc)], + closure_type: Type, + return_type: Type, +) -> (Vec, PatternState, Type) { + let mut vars = Vec::with_capacity(arguments.len()); + let mut pattern_types = Vec::with_capacity(arguments.len()); + + let mut pattern_state = PatternState::default(); + + for (pattern_var, loc_pattern) in arguments { + let pattern_type = Type::Variable(*pattern_var); + let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); + + pattern_types.push(pattern_type); + + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut pattern_state, + ); + + vars.push(*pattern_var); + } + + let function_type = + Type::Function(pattern_types, Box::new(closure_type), Box::new(return_type)); + + (vars, pattern_state, function_type) +} + +pub fn constrain_expr( + constraints: &mut Constraints, + env: &Env, + region: Region, + expr: &Expr, + expected: Expected, +) -> Constraint { + match expr { + &Int(var, precision, _, _, bound) => { + int_literal_soa(constraints, var, precision, expected, region, bound) + } + &Num(var, _, _, bound) => num_literal_soa(constraints, var, expected, region, bound), + &Float(var, precision, _, _, bound) => { + float_literal_soa(constraints, var, precision, expected, region, bound) + } + EmptyRecord => constrain_empty_record(constraints, region, expected), + Expr::Record { record_var, fields } => { + if fields.is_empty() { + constrain_empty_record(constraints, region, expected) + } else { + let mut field_exprs = SendMap::default(); + let mut field_types = SendMap::default(); + let mut field_vars = Vec::with_capacity(fields.len()); + + // Constraints need capacity for each field + // + 1 for the record itself + 1 for record var + let mut rec_constraints = Vec::with_capacity(2 + fields.len()); + + for (label, field) in fields { + let field_var = field.var; + let loc_field_expr = &field.loc_expr; + let (field_type, field_con) = + constrain_field(constraints, env, field_var, &*loc_field_expr); + + field_vars.push(field_var); + field_exprs.insert(label.clone(), loc_field_expr); + field_types.insert(label.clone(), RecordField::Required(field_type)); + + rec_constraints.push(field_con); + } + + let record_type = Type::Record( + field_types, + // TODO can we avoid doing Box::new on every single one of these? + // We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a + // lifetime parameter on `Type` + Box::new(Type::EmptyRec), + ); + let record_con = constraints.equal_types( + record_type, + expected.clone(), + Category::Record, + region, + ); + + rec_constraints.push(record_con); + + // variable to store in the AST + let stored_con = constraints.equal_types( + Type::Variable(*record_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + field_vars.push(*record_var); + rec_constraints.push(stored_con); + + let and_constraint = constraints.and_constraint(rec_constraints); + constraints.exists(field_vars, and_constraint) + } + } + Update { + record_var, + ext_var, + symbol, + updates, + } => { + let mut fields: SendMap> = SendMap::default(); + let mut vars = Vec::with_capacity(updates.len() + 2); + let mut cons = Vec::with_capacity(updates.len() + 1); + for (field_name, Field { var, loc_expr, .. }) in updates.clone() { + let (var, tipe, con) = constrain_field_update( + constraints, + env, + var, + loc_expr.region, + field_name.clone(), + &loc_expr, + ); + fields.insert(field_name, RecordField::Required(tipe)); + vars.push(var); + cons.push(con); + } + + let fields_type = Type::Record(fields, Box::new(Type::Variable(*ext_var))); + let record_type = Type::Variable(*record_var); + + // NOTE from elm compiler: fields_type is separate so that Error propagates better + let fields_con = constraints.equal_types( + record_type.clone(), + NoExpectation(fields_type), + Category::Record, + region, + ); + let record_con = + constraints.equal_types(record_type.clone(), expected, Category::Record, region); + + vars.push(*record_var); + vars.push(*ext_var); + + let con = constraints.lookup( + *symbol, + ForReason( + Reason::RecordUpdateKeys( + *symbol, + updates + .iter() + .map(|(key, field)| (key.clone(), field.region)) + .collect(), + ), + record_type, + region, + ), + region, + ); + + // ensure constraints are solved in this order, gives better errors + cons.insert(0, fields_con); + cons.insert(1, con); + cons.insert(2, record_con); + + let and_constraint = constraints.and_constraint(cons); + constraints.exists(vars, and_constraint) + } + Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region), + SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region), + List { + elem_var, + loc_elems, + } => { + if loc_elems.is_empty() { + let eq = constraints.equal_types( + empty_list_type(*elem_var), + expected, + Category::List, + region, + ); + constraints.exists(vec![*elem_var], eq) + } else { + let list_elem_type = Type::Variable(*elem_var); + let mut list_constraints = Vec::with_capacity(1 + loc_elems.len()); + + for (index, loc_elem) in loc_elems.iter().enumerate() { + let elem_expected = ForReason( + Reason::ElemInList { + index: HumanIndex::zero_based(index), + }, + list_elem_type.clone(), + loc_elem.region, + ); + let constraint = constrain_expr( + constraints, + env, + loc_elem.region, + &loc_elem.value, + elem_expected, + ); + + list_constraints.push(constraint); + } + + list_constraints.push(constraints.equal_types( + list_type(list_elem_type), + expected, + Category::List, + region, + )); + + let and_constraint = constraints.and_constraint(list_constraints); + constraints.exists([*elem_var], and_constraint) + } + } + Call(boxed, loc_args, called_via) => { + let (fn_var, loc_fn, closure_var, ret_var) = &**boxed; + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let opt_symbol = if let Var(symbol) = loc_fn.value { + Some(symbol) + } else { + None + }; + + let fn_type = Variable(*fn_var); + let fn_region = loc_fn.region; + let fn_expected = NoExpectation(fn_type.clone()); + + let fn_reason = Reason::FnCall { + name: opt_symbol, + arity: loc_args.len() as u8, + }; + + let fn_con = + constrain_expr(constraints, env, loc_fn.region, &loc_fn.value, fn_expected); + + // The function's return type + let ret_type = Variable(*ret_var); + + // type of values captured in the closure + let closure_type = Variable(*closure_var); + + // This will be used in the occurs check + let mut vars = Vec::with_capacity(2 + loc_args.len()); + + vars.push(*fn_var); + vars.push(*ret_var); + vars.push(*closure_var); + + let mut arg_types = Vec::with_capacity(loc_args.len()); + let mut arg_cons = Vec::with_capacity(loc_args.len()); + + for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() { + let region = loc_arg.region; + let arg_type = Variable(*arg_var); + + let reason = Reason::FnArg { + name: opt_symbol, + arg_index: HumanIndex::zero_based(index), + }; + let expected_arg = ForReason(reason, arg_type.clone(), region); + let arg_con = constrain_expr( + constraints, + env, + loc_arg.region, + &loc_arg.value, + expected_arg, + ); + + vars.push(*arg_var); + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let expected_fn_type = ForReason( + fn_reason, + Function( + arg_types, + Box::new(closure_type), + Box::new(ret_type.clone()), + ), + region, + ); + + let category = Category::CallResult(opt_symbol, *called_via); + + let and_cons = [ + fn_con, + constraints.equal_types(fn_type, expected_fn_type, category.clone(), fn_region), + constraints.and_constraint(arg_cons), + constraints.equal_types(ret_type, expected, category, region), + ]; + + let and_constraint = constraints.and_constraint(and_cons); + constraints.exists(vars, and_constraint) + } + Var(symbol) => { + // make lookup constraint to lookup this symbol's type in the environment + constraints.lookup(*symbol, expected, region) + } + Closure(ClosureData { + function_type: fn_var, + closure_type: closure_var, + closure_ext_var, + return_type: ret_var, + arguments, + loc_body: boxed, + captured_symbols, + name, + .. + }) => { + // NOTE defs are treated somewhere else! + let loc_body_expr = &**boxed; + + let ret_var = *ret_var; + let closure_var = *closure_var; + let closure_ext_var = *closure_ext_var; + + let closure_type = Type::Variable(closure_var); + let return_type = Type::Variable(ret_var); + let (mut vars, pattern_state, function_type) = constrain_untyped_args( + constraints, + env, + arguments, + closure_type, + return_type.clone(), + ); + + vars.push(ret_var); + vars.push(closure_var); + vars.push(closure_ext_var); + vars.push(*fn_var); + + let body_type = NoExpectation(return_type); + let ret_constraint = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); + + // make sure the captured symbols are sorted! + debug_assert_eq!(captured_symbols.clone(), { + let mut copy = captured_symbols.clone(); + copy.sort(); + copy + }); + + let closure_constraint = constrain_closure_size( + constraints, + *name, + region, + captured_symbols, + closure_var, + closure_ext_var, + &mut vars, + ); + + constraints.exists( + vars, + constraints.and_constraint([ + constraints.let_constraint( + Vec::new(), + pattern_state.vars, + pattern_state.headers, + constraints.and_constraint(pattern_state.constraints), + ret_constraint, + ), + // "the closure's type is equal to expected type" + constraints.equal_types( + function_type.clone(), + expected, + Category::Lambda, + region, + ), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + constraints.equal_types( + Type::Variable(*fn_var), + NoExpectation(function_type), + Category::Storage(std::file!(), std::line!()), + region, + ), + closure_constraint, + ]), + ) + } + + Expect(loc_cond, continuation) => { + let expect_bool = |region| { + let bool_type = Type::Variable(Variable::BOOL); + Expected::ForReason(Reason::ExpectCondition, bool_type, region) + }; + + let cond_con = constrain_expr( + constraints, + env, + loc_cond.region, + &loc_cond.value, + expect_bool(loc_cond.region), + ); + + let continuation_con = constrain_expr( + constraints, + env, + continuation.region, + &continuation.value, + expected, + ); + + constraints.exists([], constraints.and_constraint([cond_con, continuation_con])) + } + + If { + cond_var, + branch_var, + branches, + final_else, + } => { + let expect_bool = |region| { + let bool_type = Type::Variable(Variable::BOOL); + Expected::ForReason(Reason::IfCondition, bool_type, region) + }; + let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3); + + // TODO why does this cond var exist? is it for error messages? + let first_cond_region = branches[0].0.region; + let cond_var_is_bool_con = constraints.equal_types( + Type::Variable(*cond_var), + expect_bool(first_cond_region), + Category::If, + first_cond_region, + ); + + branch_cons.push(cond_var_is_bool_con); + + match expected { + FromAnnotation(name, arity, ann_source, tipe) => { + let num_branches = branches.len() + 1; + for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { + let cond_con = constrain_expr( + constraints, + env, + loc_cond.region, + &loc_cond.value, + expect_bool(loc_cond.region), + ); + + let then_con = constrain_expr( + constraints, + env, + loc_body.region, + &loc_body.value, + FromAnnotation( + name.clone(), + arity, + AnnotationSource::TypedIfBranch { + index: HumanIndex::zero_based(index), + num_branches, + region: ann_source.region(), + }, + tipe.clone(), + ), + ); + + branch_cons.push(cond_con); + branch_cons.push(then_con); + } + + let else_con = constrain_expr( + constraints, + env, + final_else.region, + &final_else.value, + FromAnnotation( + name, + arity, + AnnotationSource::TypedIfBranch { + index: HumanIndex::zero_based(branches.len()), + num_branches, + region: ann_source.region(), + }, + tipe.clone(), + ), + ); + + let ast_con = constraints.equal_types( + Type::Variable(*branch_var), + NoExpectation(tipe), + Category::Storage(std::file!(), std::line!()), + region, + ); + + branch_cons.push(ast_con); + branch_cons.push(else_con); + + constraints.exists( + [*cond_var, *branch_var], + constraints.and_constraint(branch_cons), + ) + } + _ => { + for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { + let cond_con = constrain_expr( + constraints, + env, + loc_cond.region, + &loc_cond.value, + expect_bool(loc_cond.region), + ); + + let then_con = constrain_expr( + constraints, + env, + loc_body.region, + &loc_body.value, + ForReason( + Reason::IfBranch { + index: HumanIndex::zero_based(index), + total_branches: branches.len(), + }, + Type::Variable(*branch_var), + loc_body.region, + ), + ); + + branch_cons.push(cond_con); + branch_cons.push(then_con); + } + let else_con = constrain_expr( + constraints, + env, + final_else.region, + &final_else.value, + ForReason( + Reason::IfBranch { + index: HumanIndex::zero_based(branches.len()), + total_branches: branches.len() + 1, + }, + Type::Variable(*branch_var), + final_else.region, + ), + ); + + branch_cons.push(constraints.equal_types( + Type::Variable(*branch_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + )); + branch_cons.push(else_con); + + constraints.exists( + [*cond_var, *branch_var], + constraints.and_constraint(branch_cons), + ) + } + } + } + When { + cond_var, + expr_var, + loc_cond, + branches, + .. + } => { + // Infer the condition expression's type. + let cond_var = *cond_var; + let cond_type = Variable(cond_var); + let expr_con = constrain_expr( + constraints, + env, + region, + &loc_cond.value, + NoExpectation(cond_type.clone()), + ); + + let mut branch_constraints = Vec::with_capacity(branches.len() + 1); + branch_constraints.push(expr_con); + + match &expected { + FromAnnotation(name, arity, ann_source, _typ) => { + // NOTE deviation from elm. + // + // in elm, `_typ` is used, but because we have this `expr_var` too + // and need to constrain it, this is what works and gives better error messages + let typ = Type::Variable(*expr_var); + + for (index, when_branch) in branches.iter().enumerate() { + let pattern_region = + Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + + let branch_con = constrain_when_branch( + constraints, + env, + when_branch.value.region, + when_branch, + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + }, + cond_type.clone(), + pattern_region, + ), + FromAnnotation( + name.clone(), + *arity, + AnnotationSource::TypedWhenBranch { + index: HumanIndex::zero_based(index), + region: ann_source.region(), + }, + typ.clone(), + ), + ); + + branch_constraints.push(branch_con); + } + + branch_constraints.push(constraints.equal_types( + typ, + expected, + Category::When, + region, + )); + + return constraints.exists( + [cond_var, *expr_var], + constraints.and_constraint(branch_constraints), + ); + } + + _ => { + let branch_type = Variable(*expr_var); + let mut branch_cons = Vec::with_capacity(branches.len()); + + for (index, when_branch) in branches.iter().enumerate() { + let pattern_region = + Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + let branch_con = constrain_when_branch( + env, + region, + when_branch, + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + }, + cond_type.clone(), + pattern_region, + ), + ForReason( + Reason::WhenBranch { + index: HumanIndex::zero_based(index), + }, + branch_type.clone(), + when_branch.value.region, + ), + ); + + branch_cons.push(branch_con); + } + + branch_constraints.push(constraints.and_constraint([ + // Record the original conditional expression's constraint. + // Each branch's pattern must have the same type + // as the condition expression did. + constraints.and_constraint(branch_cons), + // The return type of each branch must equal + // the return type of the entire when-expression. + constraints.equal_types(branch_type, expected, Category::When, region), + ])); + } + } + + // exhautiveness checking happens when converting to mono::Expr + constraints.exists( + [cond_var, *expr_var], + constraints.and_constraint(branch_constraints), + ) + } + Access { + record_var, + ext_var, + field_var, + loc_expr, + field, + } => { + let ext_var = *ext_var; + let ext_type = Type::Variable(ext_var); + let field_var = *field_var; + let field_type = Type::Variable(field_var); + + let mut rec_field_types = SendMap::default(); + + let label = field.clone(); + rec_field_types.insert(label, RecordField::Demanded(field_type.clone())); + + let record_type = Type::Record(rec_field_types, Box::new(ext_type)); + let record_expected = Expected::NoExpectation(record_type); + + let category = Category::Access(field.clone()); + + let record_con = constraints.equal_types( + Type::Variable(*record_var), + record_expected.clone(), + category.clone(), + region, + ); + + let constraint = constrain_expr( + constraints, + &Env { + home: env.home, + rigids: ImMap::default(), + }, + region, + &loc_expr.value, + record_expected, + ); + + constraints.exists( + [*record_var, field_var, ext_var], + constraints.and_constraint([ + constraint, + constraints.equal_types(field_type, expected, category, region), + record_con, + ]), + ) + } + Accessor { + name: closure_name, + function_var, + field, + record_var, + closure_ext_var: closure_var, + ext_var, + field_var, + } => { + let ext_var = *ext_var; + let ext_type = Variable(ext_var); + 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, Box::new(ext_type)); + + let category = Category::Accessor(field.clone()); + + let record_expected = Expected::NoExpectation(record_type.clone()); + let record_con = constraints.equal_types( + Type::Variable(*record_var), + record_expected, + category.clone(), + region, + ); + + let lambda_set = Type::ClosureTag { + name: *closure_name, + ext: *closure_var, + }; + + let function_type = Type::Function( + vec![record_type], + Box::new(lambda_set), + Box::new(field_type), + ); + + constraints.exists( + [*record_var, *function_var, *closure_var, field_var, ext_var], + constraints.and_constraint([ + constraints.equal_types( + function_type.clone(), + expected, + category.clone(), + region, + ), + constraints.equal_types( + function_type, + NoExpectation(Variable(*function_var)), + category, + region, + ), + record_con, + ]), + ) + } + LetRec(defs, loc_ret, var) => { + let body_con = constrain_expr( + constraints, + env, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); + + constraints.exists( + [*var], + constraints.and_constraint([ + constrain_recursive_defs(env, defs, body_con), + // Record the type of tne entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected, + Category::Storage(std::file!(), std::line!()), + loc_ret.region, + ), + ]), + ) + } + LetNonRec(def, loc_ret, var) => { + let mut stack = Vec::with_capacity(1); + + let mut loc_ret = loc_ret; + + stack.push((def, var, loc_ret.region)); + + while let LetNonRec(def, new_loc_ret, var) = &loc_ret.value { + stack.push((def, var, new_loc_ret.region)); + loc_ret = new_loc_ret; + } + + let mut body_con = constrain_expr( + constraints, + env, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); + + while let Some((def, var, ret_region)) = stack.pop() { + body_con = constraints.exists( + [*var], + constraints.and_constraint([ + constrain_def(env, def, body_con), + // Record the type of the entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected.clone(), + Category::Storage(std::file!(), std::line!()), + ret_region, + ), + ]), + ) + } + + body_con + } + Tag { + variant_var, + ext_var, + name, + arguments, + } => { + let mut vars = Vec::with_capacity(arguments.len()); + let mut types = Vec::with_capacity(arguments.len()); + let mut arg_cons = Vec::with_capacity(arguments.len()); + + for (var, loc_expr) in arguments { + let arg_con = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + Expected::NoExpectation(Type::Variable(*var)), + ); + + arg_cons.push(arg_con); + vars.push(*var); + types.push(Type::Variable(*var)); + } + + let union_con = constraints.equal_types( + Type::TagUnion( + vec![(name.clone(), types)], + Box::new(Type::Variable(*ext_var)), + ), + expected.clone(), + Category::TagApply { + tag_name: name.clone(), + args_count: arguments.len(), + }, + region, + ); + let ast_con = constraints.equal_types( + Type::Variable(*variant_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + vars.push(*variant_var); + vars.push(*ext_var); + arg_cons.push(union_con); + arg_cons.push(ast_con); + + constraints.exists(vars, constraints.and_constraint(arg_cons)) + } + ZeroArgumentTag { + variant_var, + ext_var, + name, + arguments, + closure_name, + } => { + let mut vars = Vec::with_capacity(arguments.len()); + let mut types = Vec::with_capacity(arguments.len()); + let mut arg_cons = Vec::with_capacity(arguments.len()); + + for (var, loc_expr) in arguments { + let arg_con = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + Expected::NoExpectation(Type::Variable(*var)), + ); + + arg_cons.push(arg_con); + vars.push(*var); + types.push(Type::Variable(*var)); + } + + let union_con = constraints.equal_types( + Type::FunctionOrTagUnion( + name.clone(), + *closure_name, + Box::new(Type::Variable(*ext_var)), + ), + expected.clone(), + Category::TagApply { + tag_name: name.clone(), + args_count: arguments.len(), + }, + region, + ); + let ast_con = constraints.equal_types( + Type::Variable(*variant_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + vars.push(*variant_var); + vars.push(*ext_var); + arg_cons.push(union_con); + arg_cons.push(ast_con); + + constraints.exists(vars, constraints.and_constraint(arg_cons)) + } + + OpaqueRef { + opaque_var, + name, + argument, + specialized_def_type, + type_arguments, + lambda_set_variables, + } => { + let (arg_var, arg_loc_expr) = &**argument; + let arg_type = Type::Variable(*arg_var); + + let opaque_type = Type::Alias { + symbol: *name, + type_arguments: type_arguments.clone(), + lambda_set_variables: lambda_set_variables.clone(), + actual: Box::new(arg_type.clone()), + kind: AliasKind::Opaque, + }; + + // Constrain the argument + let arg_con = constrain_expr( + constraints, + env, + arg_loc_expr.region, + &arg_loc_expr.value, + Expected::NoExpectation(arg_type.clone()), + ); + + // Link the entire wrapped opaque type (with the now-constrained argument) to the + // expected type + let opaque_con = constraints.equal_types( + opaque_type, + expected.clone(), + Category::OpaqueWrap(*name), + region, + ); + + // Link the entire wrapped opaque type (with the now-constrained argument) to the type + // variables of the opaque type + // TODO: better expectation here + let link_type_variables_con = constraints.equal_types( + arg_type, + Expected::NoExpectation((**specialized_def_type).clone()), + Category::OpaqueArg, + arg_loc_expr.region, + ); + + // Store the entire wrapped opaque type in `opaque_var` + let storage_con = constraints.equal_types( + Type::Variable(*opaque_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + let mut vars = vec![*arg_var, *opaque_var]; + // Also add the fresh variables we created for the type argument and lambda sets + vars.extend(type_arguments.iter().map(|(_, t)| { + t.expect_variable("all type arguments should be fresh variables here") + })); + vars.extend(lambda_set_variables.iter().map(|v| { + v.0.expect_variable("all lambda sets should be fresh variables here") + })); + + constraints.exists( + vars, + constraints.and([arg_con, opaque_con, link_type_variables_con, storage_con]), + ) + } + + RunLowLevel { args, ret_var, op } => { + // This is a modified version of what we do for function calls. + + // The operation's return type + let ret_type = Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = Vec::with_capacity(1 + args.len()); + + vars.push(*ret_var); + + let mut arg_types = Vec::with_capacity(args.len()); + let mut arg_cons = Vec::with_capacity(args.len()); + + let mut add_arg = |index, arg_type: Type, arg| { + let reason = Reason::LowLevelOpArg { + op: *op, + arg_index: HumanIndex::zero_based(index), + }; + let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); + let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); + + arg_types.push(arg_type); + arg_cons.push(arg_con); + }; + + for (index, (arg_var, arg)) in args.iter().enumerate() { + vars.push(*arg_var); + + add_arg(index, Variable(*arg_var), arg); + } + + let category = Category::LowLevelOpResult(*op); + + constraints.exists( + vars, + constraints.and_constraint(arg_cons.into_iter().chain(std::iter::once( + constraints.equal_types(ret_type, expected, category, region), + ))), + ) + } + ForeignCall { + args, + ret_var, + foreign_symbol, + } => { + // This is a modified version of what we do for function calls. + + // The operation's return type + let ret_type = Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = Vec::with_capacity(1 + args.len()); + + vars.push(*ret_var); + + let mut arg_types = Vec::with_capacity(args.len()); + let mut arg_cons = Vec::with_capacity(args.len()); + + let mut add_arg = |index, arg_type: Type, arg| { + let reason = Reason::ForeignCallArg { + foreign_symbol: foreign_symbol.clone(), + arg_index: HumanIndex::zero_based(index), + }; + let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); + let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); + + arg_types.push(arg_type); + arg_cons.push(arg_con); + }; + + for (index, (arg_var, arg)) in args.iter().enumerate() { + vars.push(*arg_var); + + add_arg(index, Variable(*arg_var), arg); + } + + let category = Category::ForeignCall; + + constraints.exists( + vars, + constraints.and_constraint(arg_cons.into_iter().chain(std::iter::once( + constraints.equal_types(ret_type, expected, category, region), + ))), + ) + } + RuntimeError(_) => { + // Runtime Errors have no constraints because they're going to crash. + Constraint::True + } + } +} + +#[inline(always)] +fn constrain_when_branch( + constraints: &mut Constraints, + env: &Env, + region: Region, + when_branch: &WhenBranch, + pattern_expected: PExpected, + expr_expected: Expected, +) -> Constraint { + let ret_constraint = constrain_expr( + constraints, + env, + region, + &when_branch.value.value, + expr_expected, + ); + + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(1), + constraints: Vec::with_capacity(1), + }; + + // TODO investigate for error messages, is it better to unify all branches with a variable, + // then unify that variable with the expectation? + for loc_pattern in &when_branch.patterns { + constrain_pattern( + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected.clone(), + &mut state, + ); + } + + if let Some(loc_guard) = &when_branch.guard { + let guard_constraint = constrain_expr( + constraints, + env, + region, + &loc_guard.value, + Expected::ForReason( + Reason::WhenGuard, + Type::Variable(Variable::BOOL), + loc_guard.region, + ), + ); + + // must introduce the headers from the pattern before constraining the guard + constraints.let_constraint( + [], + state.vars, + state.headers, + constraints.and_constraint(state.constraints), + constraints.let_constraint( + [], + [], + SendMap::default(), + guard_constraint, + ret_constraint, + ), + ) + } else { + constraints.let_constraint( + [], + state.vars, + state.headers, + constraints.and_constraint(state.constraints), + ret_constraint, + ) + } +} + +fn constrain_field( + constraints: &mut Constraints, + env: &Env, + field_var: Variable, + loc_expr: &Loc, +) -> (Type, Constraint) { + let field_type = Variable(field_var); + let field_expected = NoExpectation(field_type.clone()); + let constraint = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + field_expected, + ); + + (field_type, constraint) +} + +#[inline(always)] +fn constrain_empty_record( + constraints: &mut Constraints, + region: Region, + expected: Expected, +) -> Constraint { + let expected_index = constraints.push_expected_type(expected); + + Constraint::Eq( + Constraints::EMPTY_RECORD, + expected_index, + Constraints::CATEGORY_RECORD, + region, + ) +} + +/// Constrain top-level module declarations +#[inline(always)] +pub fn constrain_decls( + constraints: &mut Constraints, + home: ModuleId, + decls: &[Declaration], +) -> Constraint { + let mut constraint = Constraint::SaveTheEnvironment; + + let mut env = Env { + home, + rigids: ImMap::default(), + }; + + for decl in decls.iter().rev() { + // Clear the rigids from the previous iteration. + // rigids are not shared between top-level definitions + env.rigids.clear(); + + match decl { + Declaration::Declare(def) | Declaration::Builtin(def) => { + constraint = constrain_def(constraints, &env, def, constraint); + } + Declaration::DeclareRec(defs) => { + constraint = constrain_recursive_defs(&env, defs, constraint); + } + Declaration::InvalidCycle(_) => { + // invalid cycles give a canonicalization error. we skip them here. + continue; + } + } + } + + // this assert make the "root" of the constraint wasn't dropped + debug_assert!(constraints.contains_save_the_environment(constraint)); + + constraint +} + +fn constrain_def_pattern( + constraints: &mut Constraints, + env: &Env, + loc_pattern: &Loc, + expr_type: Type, +) -> PatternState { + let pattern_expected = PExpected::NoExpectation(expr_type); + + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(1), + constraints: Vec::with_capacity(1), + }; + + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + + state +} + +fn constrain_def( + constraints: &mut Constraints, + env: &Env, + def: &Def, + body_con: Constraint, +) -> Constraint { + let expr_var = def.expr_var; + let expr_type = Type::Variable(expr_var); + + let mut def_pattern_state = + constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); + + def_pattern_state.vars.push(expr_var); + + let mut new_rigids = Vec::new(); + + let expr_con = match &def.annotation { + Some(annotation) => { + let arity = annotation.signature.arity(); + let rigids = &env.rigids; + let mut ftv = rigids.clone(); + + let signature = instantiate_rigids( + &annotation.signature, + &annotation.introduced_variables, + &mut new_rigids, + &mut ftv, + &def.loc_pattern, + &mut def_pattern_state.headers, + ); + + let env = &Env { + home: env.home, + rigids: ftv, + }; + + let annotation_expected = FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature.clone(), + ); + + def_pattern_state.constraints.push(constraints.equal_types( + expr_type, + annotation_expected.clone(), + Category::Storage(std::file!(), std::line!()), + Region::span_across(&annotation.region, &def.loc_expr.region), + )); + + // when a def is annotated, and it's body is a closure, treat this + // as a named function (in elm terms) for error messages. + // + // This means we get errors like "the first argument of `f` is weird" + // instead of the more generic "something is wrong with the body of `f`" + match (&def.loc_expr.value, &signature) { + ( + Closure(ClosureData { + function_type: fn_var, + closure_type: closure_var, + closure_ext_var, + return_type: ret_var, + captured_symbols, + arguments, + loc_body, + name, + .. + }), + Type::Function(arg_types, signature_closure_type, ret_type), + ) => { + // NOTE if we ever have problems with the closure, the ignored `_closure_type` + // is probably a good place to start the investigation! + + let region = def.loc_expr.region; + + let loc_body_expr = &**loc_body; + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(arguments.len()), + constraints: Vec::with_capacity(1), + }; + let mut vars = Vec::with_capacity(state.vars.capacity() + 1); + let mut pattern_types = Vec::with_capacity(state.vars.capacity()); + let ret_var = *ret_var; + let closure_var = *closure_var; + let closure_ext_var = *closure_ext_var; + let ret_type = *ret_type.clone(); + + vars.push(ret_var); + vars.push(closure_var); + vars.push(closure_ext_var); + + let it = arguments.iter().zip(arg_types.iter()).enumerate(); + for (index, ((pattern_var, loc_pattern), loc_ann)) in it { + { + // ensure type matches the one in the annotation + let opt_label = + if let Pattern::Identifier(label) = def.loc_pattern.value { + Some(label) + } else { + None + }; + let pattern_type: &Type = loc_ann; + + let pattern_expected = PExpected::ForReason( + PReason::TypedArg { + index: HumanIndex::zero_based(index), + opt_name: opt_label, + }, + pattern_type.clone(), + loc_pattern.region, + ); + + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + } + + { + // NOTE: because we perform an equality with part of the signature + // this constraint must be to the def_pattern_state's constraints + def_pattern_state.vars.push(*pattern_var); + pattern_types.push(Type::Variable(*pattern_var)); + + let pattern_con = constraints.equal_types( + Type::Variable(*pattern_var), + Expected::NoExpectation(loc_ann.clone()), + Category::Storage(std::file!(), std::line!()), + loc_pattern.region, + ); + + def_pattern_state.constraints.push(pattern_con); + } + } + + let closure_constraint = constrain_closure_size( + *name, + region, + captured_symbols, + closure_var, + closure_ext_var, + &mut vars, + ); + + let body_type = FromAnnotation( + def.loc_pattern.clone(), + arguments.len(), + AnnotationSource::TypedBody { + region: annotation.region, + }, + ret_type.clone(), + ); + + let ret_constraint = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); + + vars.push(*fn_var); + let defs_constraint = constraints.and_constraint(state.constraints); + + constraints.exists( + vars, + constraints.and_constraint([ + constraints.let_constraint( + [], + state.vars, + state.headers, + defs_constraint, + ret_constraint, + ), + constraints.equal_types( + Type::Variable(closure_var), + Expected::FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + *signature_closure_type.clone(), + ), + Category::ClosureSize, + region, + ), + constraints.store( + signature.clone(), + *fn_var, + std::file!(), + std::line!(), + ), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]), + ) + } + + _ => { + let expected = annotation_expected; + + let ret_constraint = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + expected, + ); + + constraints.and_constraint([ + constraints.let_constraint( + [], + [], + SendMap::default(), + Constraint::True, + ret_constraint, + ), + // Store type into AST vars. We use Store so errors aren't reported twice + constraints.store(signature, expr_var, std::file!(), std::line!()), + ]) + } + } + } + None => { + // no annotation, so no extra work with rigids + + constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + NoExpectation(expr_type), + ) + } + }; + + constraints.let_constraint( + new_rigids, + def_pattern_state.vars, + def_pattern_state.headers, + constraints.let_constraint( + [], + [], + SendMap::default(), // empty, because our functions have no arguments! + constraints.and_constraint(def_pattern_state.constraints), + expr_con, + ), + body_con, + ) +} + +fn constrain_closure_size( + constraints: &mut Constraints, + name: Symbol, + region: Region, + captured_symbols: &[(Symbol, Variable)], + closure_var: Variable, + closure_ext_var: Variable, + variables: &mut Vec, +) -> Constraint { + debug_assert!(variables.iter().any(|s| *s == closure_var)); + debug_assert!(variables.iter().any(|s| *s == closure_ext_var)); + + let mut tag_arguments = Vec::with_capacity(captured_symbols.len()); + let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len()); + + for (symbol, var) in captured_symbols { + // make sure the variable is registered + variables.push(*var); + + // this symbol is captured, so it must be part of the closure type + tag_arguments.push(Type::Variable(*var)); + + // make the variable equal to the looked-up type of symbol + captured_symbols_constraints.push(constraints.lookup( + *symbol, + Expected::NoExpectation(Type::Variable(*var)), + Region::zero(), + )); + } + + // pick a more efficient representation if we don't actually capture anything + let closure_type = if tag_arguments.is_empty() { + Type::ClosureTag { + name, + ext: closure_ext_var, + } + } else { + let tag_name = TagName::Closure(name); + Type::TagUnion( + vec![(tag_name, tag_arguments)], + Box::new(Type::Variable(closure_ext_var)), + ) + }; + + let finalizer = constraints.equal_types( + Type::Variable(closure_var), + NoExpectation(closure_type), + Category::ClosureSize, + region, + ); + + captured_symbols_constraints.push(finalizer); + + constraints.and_constraint(captured_symbols_constraints) +} + +fn instantiate_rigids( + annotation: &Type, + introduced_vars: &IntroducedVariables, + new_rigids: &mut Vec, + ftv: &mut ImMap, // rigids defined before the current annotation + loc_pattern: &Loc, + headers: &mut SendMap>, +) -> Type { + let mut annotation = annotation.clone(); + let mut rigid_substitution: ImMap = ImMap::default(); + + let outside_rigids: MutSet = ftv.values().copied().collect(); + + for (name, var) in introduced_vars.var_by_name.iter() { + if let Some(existing_rigid) = ftv.get(name) { + rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); + } else { + // It's possible to use this rigid in nested defs + ftv.insert(name.clone(), *var); + } + } + + // Instantiate rigid variables + if !rigid_substitution.is_empty() { + annotation.substitute(&rigid_substitution); + } + + if let Some(new_headers) = crate::pattern::headers_from_annotation( + &loc_pattern.value, + &Loc::at(loc_pattern.region, annotation.clone()), + ) { + for (symbol, loc_type) in new_headers { + for var in loc_type.value.variables() { + // a rigid is only new if this annotation is the first occurrence of this rigid + if !outside_rigids.contains(&var) { + new_rigids.push(var); + } + } + headers.insert(symbol, loc_type); + } + } + + for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { + ftv.insert(format!("*{}", i).into(), *wildcard); + } + + annotation +} + +fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Constraint { + rec_defs_help( + env, + defs, + body_con, + Info::with_capacity(defs.len()), + Info::with_capacity(defs.len()), + ) +} + +pub fn rec_defs_help( + constraints: &mut Constraints, + env: &Env, + defs: &[Def], + body_con: Constraint, + mut rigid_info: Info, + mut flex_info: Info, +) -> Constraint { + for def in defs { + let expr_var = def.expr_var; + let expr_type = Type::Variable(expr_var); + + let mut def_pattern_state = + constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); + + def_pattern_state.vars.push(expr_var); + + let mut new_rigids = Vec::new(); + match &def.annotation { + None => { + let expr_con = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + NoExpectation(expr_type), + ); + + // TODO investigate if this let can be safely removed + let def_con = constraints.let_constraint( + [], + [], // empty because Roc function defs have no args + SendMap::default(), // empty because Roc function defs have no args + Constraint::True, // I think this is correct, once again because there are no args + expr_con, + ); + + flex_info.vars = def_pattern_state.vars; + flex_info.constraints.push(def_con); + flex_info.def_types.extend(def_pattern_state.headers); + } + + Some(annotation) => { + let arity = annotation.signature.arity(); + let mut ftv = env.rigids.clone(); + + let signature = instantiate_rigids( + &annotation.signature, + &annotation.introduced_variables, + &mut new_rigids, + &mut ftv, + &def.loc_pattern, + &mut def_pattern_state.headers, + ); + + let annotation_expected = FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature.clone(), + ); + + // when a def is annotated, and it's body is a closure, treat this + // as a named function (in elm terms) for error messages. + // + // This means we get errors like "the first argument of `f` is weird" + // instead of the more generic "something is wrong with the body of `f`" + match (&def.loc_expr.value, &signature) { + ( + Closure(ClosureData { + function_type: fn_var, + closure_type: closure_var, + closure_ext_var, + return_type: ret_var, + captured_symbols, + arguments, + loc_body, + name, + .. + }), + Type::Function(arg_types, _closure_type, ret_type), + ) => { + // NOTE if we ever have trouble with closure type unification, the ignored + // `_closure_type` here is a good place to start investigating + + let expected = annotation_expected; + let region = def.loc_expr.region; + + let loc_body_expr = &**loc_body; + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(arguments.len()), + constraints: Vec::with_capacity(1), + }; + let mut vars = Vec::with_capacity(state.vars.capacity() + 1); + let mut pattern_types = Vec::with_capacity(state.vars.capacity()); + let ret_var = *ret_var; + let closure_var = *closure_var; + let closure_ext_var = *closure_ext_var; + let ret_type = *ret_type.clone(); + + vars.push(ret_var); + vars.push(closure_var); + vars.push(closure_ext_var); + + let it = arguments.iter().zip(arg_types.iter()).enumerate(); + for (index, ((pattern_var, loc_pattern), loc_ann)) in it { + { + // ensure type matches the one in the annotation + let opt_label = + if let Pattern::Identifier(label) = def.loc_pattern.value { + Some(label) + } else { + None + }; + let pattern_type: &Type = loc_ann; + + let pattern_expected = PExpected::ForReason( + PReason::TypedArg { + index: HumanIndex::zero_based(index), + opt_name: opt_label, + }, + pattern_type.clone(), + loc_pattern.region, + ); + + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + } + + { + // NOTE: because we perform an equality with part of the signature + // this constraint must be to the def_pattern_state's constraints + def_pattern_state.vars.push(*pattern_var); + pattern_types.push(Type::Variable(*pattern_var)); + + let pattern_con = constraints.equal_types( + Type::Variable(*pattern_var), + Expected::NoExpectation(loc_ann.clone()), + Category::Storage(std::file!(), std::line!()), + loc_pattern.region, + ); + + def_pattern_state.constraints.push(pattern_con); + } + } + + let closure_constraint = constrain_closure_size( + constraints, + *name, + region, + captured_symbols, + closure_var, + closure_ext_var, + &mut vars, + ); + + let fn_type = Type::Function( + pattern_types, + Box::new(Type::Variable(closure_var)), + Box::new(ret_type.clone()), + ); + let body_type = NoExpectation(ret_type.clone()); + let expr_con = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); + + vars.push(*fn_var); + + let def_con = constraints.exists( + vars, + constraints.and_constraint([ + constraints.let_constraint( + [], + state.vars, + state.headers, + constraints.and_constraint(state.constraints), + expr_con, + ), + constraints.equal_types( + fn_type.clone(), + expected.clone(), + Category::Lambda, + region, + ), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + // Store type into AST vars. We use Store so errors aren't reported twice + constraints.store( + signature.clone(), + *fn_var, + std::file!(), + std::line!(), + ), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]), + ); + + rigid_info.vars.extend(&new_rigids); + + rigid_info.constraints.push(constraints.let_constraint( + new_rigids, + def_pattern_state.vars, + SendMap::default(), // no headers introduced (at this level) + def_con, + Constraint::True, + )); + rigid_info.def_types.extend(def_pattern_state.headers); + } + _ => { + let expected = annotation_expected; + + let ret_constraint = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + expected, + ); + + let def_con = constraints.and_constraint([ + constraints.let_constraint( + [], + [], + SendMap::default(), + Constraint::True, + ret_constraint, + ), + // Store type into AST vars. We use Store so errors aren't reported twice + constraints.store(signature, expr_var, std::file!(), std::line!()), + ]); + + rigid_info.vars.extend(&new_rigids); + + rigid_info.constraints.push(constraints.let_constraint( + new_rigids, + def_pattern_state.vars, + SendMap::default(), // no headers introduced (at this level) + def_con, + Constraint::True, + )); + rigid_info.def_types.extend(def_pattern_state.headers); + } + } + } + } + } + + constraints.let_constraint( + rigid_info.vars, + [], + rigid_info.def_types, + Constraint::True, + constraints.let_constraint( + [], + flex_info.vars, + flex_info.def_types.clone(), + constraints.let_constraint( + [], + [], + flex_info.def_types, + Constraint::True, + constraints.and_constraint(flex_info.constraints), + ), + constraints + .and_constraint([constraints.and_constraint(rigid_info.constraints), body_con]), + ), + ) +} + +#[inline(always)] +fn constrain_field_update( + constraints: &mut Constraints, + env: &Env, + var: Variable, + region: Region, + field: Lowercase, + loc_expr: &Loc, +) -> (Variable, Type, Constraint) { + let field_type = Type::Variable(var); + let reason = Reason::RecordUpdateValue(field); + let expected = ForReason(reason, field_type.clone(), region); + let con = constrain_expr(constraints, env, loc_expr.region, &loc_expr.value, expected); + + (var, field_type, con) +} diff --git a/compiler/constrain/src/soa_pattern.rs b/compiler/constrain/src/soa_pattern.rs new file mode 100644 index 0000000000..adbdc3d68a --- /dev/null +++ b/compiler/constrain/src/soa_pattern.rs @@ -0,0 +1,521 @@ +use crate::builtins; +use crate::soa_expr::{constrain_expr, Env}; +use roc_can::constraint_soa::{Constraint, Constraints, PresenceConstraint}; +use roc_can::expected::{Expected, PExpected}; +use roc_can::pattern::Pattern::{self, *}; +use roc_can::pattern::{DestructType, RecordDestruct}; +use roc_collections::all::{HumanIndex, SendMap}; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; +use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type}; + +#[derive(Default)] +pub struct PatternState { + pub headers: SendMap>, + pub vars: Vec, + pub constraints: Vec, +} + +/// If there is a type annotation, the pattern state headers can be optimized by putting the +/// annotation in the headers. Normally +/// +/// x = 4 +/// +/// Would add `x => <42>` to the headers (i.e., symbol points to a type variable). If the +/// definition has an annotation, we instead now add `x => Int`. +pub fn headers_from_annotation( + pattern: &Pattern, + annotation: &Loc, +) -> Option>> { + let mut headers = SendMap::default(); + // Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int` + // in such incorrect cases we don't put the full annotation in headers, just a variable, and let + // inference generate a proper error. + let is_structurally_valid = headers_from_annotation_help(pattern, annotation, &mut headers); + + if is_structurally_valid { + Some(headers) + } else { + None + } +} + +fn headers_from_annotation_help( + pattern: &Pattern, + annotation: &Loc, + headers: &mut SendMap>, +) -> bool { + match pattern { + Identifier(symbol) | Shadowed(_, _, symbol) => { + headers.insert(*symbol, annotation.clone()); + true + } + Underscore + | MalformedPattern(_, _) + | UnsupportedPattern(_) + | OpaqueNotInScope(..) + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) + | SingleQuote(_) + | StrLiteral(_) => true, + + RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { + Type::Record(fields, _) => { + for loc_destruct in destructs { + let destruct = &loc_destruct.value; + + // NOTE: We ignore both Guard and optionality when + // determining the type of the assigned def (which is what + // gets added to the header here). + // + // For example, no matter whether it's `{ x } = rec` or + // `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases + // the type of `x` within the binding itself is the same. + if let Some(field_type) = fields.get(&destruct.label) { + headers.insert( + destruct.symbol, + Loc::at(annotation.region, field_type.clone().into_inner()), + ); + } else { + return false; + } + } + true + } + Type::EmptyRec => destructs.is_empty(), + _ => false, + }, + + AppliedTag { + tag_name, + arguments, + .. + } => match annotation.value.shallow_dealias() { + Type::TagUnion(tags, _) => { + if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) { + if !arguments.len() == arg_types.len() { + return false; + } + + arguments + .iter() + .zip(arg_types.iter()) + .all(|(arg_pattern, arg_type)| { + headers_from_annotation_help( + &arg_pattern.1.value, + &Loc::at(annotation.region, arg_type.clone()), + headers, + ) + }) + } else { + false + } + } + _ => false, + }, + + UnwrappedOpaque { + whole_var: _, + opaque, + argument, + specialized_def_type: _, + type_arguments: pat_type_arguments, + lambda_set_variables: pat_lambda_set_variables, + } => match &annotation.value { + Type::Alias { + symbol, + kind: AliasKind::Opaque, + actual, + type_arguments, + lambda_set_variables, + } if symbol == opaque + && type_arguments.len() == pat_type_arguments.len() + && lambda_set_variables.len() == pat_lambda_set_variables.len() => + { + headers.insert(*opaque, annotation.clone()); + + let (_, argument_pat) = &**argument; + headers_from_annotation_help( + &argument_pat.value, + &Loc::at(annotation.region, (**actual).clone()), + headers, + ) + } + _ => false, + }, + } +} + +/// This accepts PatternState (rather than returning it) so that the caller can +/// initialize the Vecs in PatternState using with_capacity +/// based on its knowledge of their lengths. +pub fn constrain_pattern( + constraints: &mut Constraints, + env: &Env, + pattern: &Pattern, + region: Region, + expected: PExpected, + state: &mut PatternState, +) { + match pattern { + Underscore => { + // This is an underscore in a position where we destruct a variable, + // like a when expression: + // when x is + // A -> "" + // _ -> "" + // so, we know that "x" (in this case, a tag union) must be open. + state.constraints.push(Constraint::Present( + expected.get_type(), + PresenceConstraint::IsOpen, + )); + } + UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => { + // Erroneous patterns don't add any constraints. + } + + Identifier(symbol) | Shadowed(_, _, symbol) => { + state.constraints.push(Constraint::Present( + expected.get_type_ref().clone(), + PresenceConstraint::IsOpen, + )); + state.headers.insert( + *symbol, + Loc { + region, + value: expected.get_type(), + }, + ); + } + + &NumLiteral(var, _, _, bound) => { + state.vars.push(var); + + let num_type = builtins::num_num(Type::Variable(var)); + + let num_type = builtins::add_numeric_bound_constr_soa( + constraints, + &mut state.constraints, + num_type, + bound, + region, + Category::Num, + ); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Num, + num_type, + expected, + )); + } + + &IntLiteral(num_var, precision_var, _, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + let num_type = builtins::add_numeric_bound_constr( + &mut state.constraints, + Type::Variable(num_var), + bound, + region, + Category::Int, + ); + + // Link the free num var with the int var and our expectation. + let int_type = builtins::num_int(Type::Variable(precision_var)); + + state.constraints.push(constraints.equal_types( + num_type, // TODO check me if something breaks! + Expected::NoExpectation(int_type), + Category::Int, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. + state.constraints.push(constraints.equal_pattern_types( + Type::Variable(num_var), + expected, + PatternCategory::Int, + region, + )); + } + + &FloatLiteral(num_var, precision_var, _, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + let num_type = builtins::add_numeric_bound_constr_soa( + constraints, + &mut state.constraints, + Type::Variable(num_var), + bound, + region, + Category::Float, + ); + + // Link the free num var with the float var and our expectation. + let float_type = builtins::num_float(Type::Variable(precision_var)); + + state.constraints.push(constraints.equal_types( + num_type.clone(), // TODO check me if something breaks! + Expected::NoExpectation(float_type), + Category::Float, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Float, + num_type, // TODO check me if something breaks! + expected, + )); + } + + StrLiteral(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Str, + builtins::str_type(), + expected, + )); + } + + SingleQuote(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Character, + builtins::num_u32(), + expected, + )); + } + + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { + state.vars.push(*whole_var); + state.vars.push(*ext_var); + let ext_type = Type::Variable(*ext_var); + + let mut field_types: SendMap> = SendMap::default(); + + for Loc { + value: + RecordDestruct { + var, + label, + symbol, + typ, + }, + .. + } in destructs + { + let pat_type = Type::Variable(*var); + let expected = PExpected::NoExpectation(pat_type.clone()); + + if !state.headers.contains_key(symbol) { + state + .headers + .insert(*symbol, Loc::at(region, pat_type.clone())); + } + + let field_type = match typ { + DestructType::Guard(guard_var, loc_guard) => { + state.constraints.push(Constraint::Present( + Type::Variable(*guard_var), + PresenceConstraint::Pattern( + region, + PatternCategory::PatternGuard, + PExpected::ForReason( + PReason::PatternGuard, + pat_type.clone(), + loc_guard.region, + ), + ), + )); + state.vars.push(*guard_var); + + constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state); + + RecordField::Demanded(pat_type) + } + DestructType::Optional(expr_var, loc_expr) => { + state.constraints.push(Constraint::Present( + Type::Variable(*expr_var), + PresenceConstraint::Pattern( + region, + PatternCategory::PatternDefault, + PExpected::ForReason( + PReason::OptionalField, + pat_type.clone(), + loc_expr.region, + ), + ), + )); + + state.vars.push(*expr_var); + + let expr_expected = Expected::ForReason( + Reason::RecordDefaultField(label.clone()), + pat_type.clone(), + loc_expr.region, + ); + + let expr_con = + constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + state.constraints.push(expr_con); + + RecordField::Optional(pat_type) + } + DestructType::Required => { + // No extra constraints necessary. + RecordField::Demanded(pat_type) + } + }; + + field_types.insert(label.clone(), field_type); + + state.vars.push(*var); + } + + let record_type = Type::Record(field_types, Box::new(ext_type)); + + let whole_con = constraints.equal_types( + Type::Variable(*whole_var), + Expected::NoExpectation(record_type), + Category::Storage(std::file!(), std::line!()), + region, + ); + + let record_con = Constraint::Present( + Type::Variable(*whole_var), + PresenceConstraint::Pattern(region, PatternCategory::Record, expected), + ); + + state.constraints.push(whole_con); + state.constraints.push(record_con); + } + AppliedTag { + whole_var, + ext_var, + tag_name, + arguments, + } => { + let mut argument_types = Vec::with_capacity(arguments.len()); + for (index, (pattern_var, loc_pattern)) in arguments.iter().enumerate() { + state.vars.push(*pattern_var); + + let pattern_type = Type::Variable(*pattern_var); + argument_types.push(pattern_type.clone()); + + let expected = PExpected::ForReason( + PReason::TagArg { + tag_name: tag_name.clone(), + index: HumanIndex::zero_based(index), + }, + pattern_type, + region, + ); + constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); + } + + let pat_category = PatternCategory::Ctor(tag_name.clone()); + + let whole_con = Constraint::Present( + expected.clone().get_type(), + PresenceConstraint::IncludesTag( + tag_name.clone(), + argument_types.clone(), + region, + pat_category.clone(), + ), + ); + + let tag_con = Constraint::Present( + Type::Variable(*whole_var), + PresenceConstraint::Pattern(region, pat_category, expected), + ); + + state.vars.push(*whole_var); + state.vars.push(*ext_var); + state.constraints.push(whole_con); + state.constraints.push(tag_con); + } + + UnwrappedOpaque { + whole_var, + opaque, + argument, + specialized_def_type, + type_arguments, + lambda_set_variables, + } => { + // Suppose we are constraining the pattern \@Id who, where Id n := [ Id U64 n ] + let (arg_pattern_var, loc_arg_pattern) = &**argument; + let arg_pattern_type = Type::Variable(*arg_pattern_var); + + let opaque_type = Type::Alias { + symbol: *opaque, + type_arguments: type_arguments.clone(), + lambda_set_variables: lambda_set_variables.clone(), + actual: Box::new(arg_pattern_type.clone()), + kind: AliasKind::Opaque, + }; + + // First, add a constraint for the argument "who" + let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone()); + constrain_pattern( + env, + &loc_arg_pattern.value, + loc_arg_pattern.region, + arg_pattern_expected, + state, + ); + + // Next, link `whole_var` to the opaque type of "@Id who" + let whole_con = constraints.equal_types( + Type::Variable(*whole_var), + Expected::NoExpectation(opaque_type), + Category::Storage(std::file!(), std::line!()), + region, + ); + + // Link the entire wrapped opaque type (with the now-constrained argument) to the type + // variables of the opaque type + // TODO: better expectation here + let link_type_variables_con = constraints.equal_types( + (**specialized_def_type).clone(), + Expected::NoExpectation(arg_pattern_type), + Category::OpaqueWrap(*opaque), + loc_arg_pattern.region, + ); + + // Next, link `whole_var` (the type of "@Id who") to the expected type + let opaque_pattern_con = Constraint::Present( + Type::Variable(*whole_var), + PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected), + ); + + state + .vars + .extend_from_slice(&[*arg_pattern_var, *whole_var]); + // Also add the fresh variables we created for the type argument and lambda sets + state.vars.extend(type_arguments.iter().map(|(_, t)| { + t.expect_variable("all type arguments should be fresh variables here") + })); + state.vars.extend(lambda_set_variables.iter().map(|v| { + v.0.expect_variable("all lambda sets should be fresh variables here") + })); + + state.constraints.extend_from_slice(&[ + whole_con, + link_type_variables_con, + opaque_pattern_con, + ]); + } + } +} diff --git a/linker/src/lib.rs b/linker/src/lib.rs index b9d6ee8519..3bf6fa80ff 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -367,9 +367,7 @@ fn preprocess_impl( Some(section) => { let file_offset = match section.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -494,9 +492,7 @@ fn preprocess_impl( for sec in text_sections { let (file_offset, compressed) = match sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -626,9 +622,7 @@ fn preprocess_impl( }; let dyn_offset = match dyn_sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -714,9 +708,7 @@ fn preprocess_impl( }; let symtab_offset = match symtab_sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -738,9 +730,7 @@ fn preprocess_impl( }; let dynsym_offset = match dynsym_sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -759,9 +749,7 @@ fn preprocess_impl( { match sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 5f181c24d4..d633d5e6c4 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1384,7 +1384,7 @@ fn to_pattern_report<'b>( found, expected_type, add_pattern_category( - HumanIndexlloc, + HumanIndexlloc, alloc.text("The first pattern is trying to match"), &category, ), From a4889fd571009dc87f03e22fb826944c5881f789 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 18:48:34 +0100 Subject: [PATCH 042/150] we are compiling --- compiler/can/src/constraint_soa.rs | 113 ++++++- compiler/collections/src/soa.rs | 13 + compiler/constrain/src/builtins.rs | 3 +- compiler/constrain/src/soa_expr.rs | 437 +++++++++++++------------- compiler/constrain/src/soa_pattern.rs | 133 ++++---- reporting/src/error/type.rs | 4 +- 6 files changed, 406 insertions(+), 297 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 9d24540cf1..e79ac4fae0 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -1,5 +1,6 @@ use crate::expected::{Expected, PExpected}; use roc_collections::soa::{Index, Slice}; +use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::types::{Category, PatternCategory, Type}; @@ -15,6 +16,7 @@ pub struct Constraints { pattern_categories: Vec, expectations: Vec>, pattern_expectations: Vec>, + includes_tags: Vec, } impl Constraints { @@ -52,13 +54,9 @@ impl Constraints { category: Category, region: Region, ) -> Constraint { - let type_index = Index::new(self.types.len() as _); - let expected_index = Index::new(self.expectations.len() as _); - let category_index = Index::new(self.categories.len() as _); - - self.types.push(typ); - self.expectations.push(expected); - self.categories.push(category); + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.expectations, expected); + let category_index = Index::push_new(&mut self.categories, category); Constraint::Eq(type_index, expected_index, category_index, region) } @@ -70,17 +68,61 @@ impl Constraints { category: PatternCategory, region: Region, ) -> Constraint { - let type_index = Index::new(self.types.len() as _); - let expected_index = Index::new(self.pattern_expectations.len() as _); - let category_index = Index::new(self.pattern_categories.len() as _); - - self.types.push(typ); - self.pattern_expectations.push(expected); - self.pattern_categories.push(category); + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.pattern_expectations, expected); + let category_index = Index::push_new(&mut self.pattern_categories, category); Constraint::Pattern(type_index, expected_index, category_index, region) } + pub fn pattern_presence( + &mut self, + typ: Type, + expected: PExpected, + category: PatternCategory, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.pattern_expectations, expected); + let category_index = Index::push_new(&mut self.pattern_categories, category); + + Constraint::PatternPresence(type_index, expected_index, category_index, region) + } + + pub fn is_open_type(&mut self, typ: Type) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + + Constraint::IsOpenType(type_index) + } + + pub fn includes_tag( + &mut self, + typ: Type, + tag_name: TagName, + types: I, + category: PatternCategory, + region: Region, + ) -> Constraint + where + I: IntoIterator, + { + let type_index = Index::push_new(&mut self.types, typ); + let category_index = Index::push_new(&mut self.pattern_categories, category); + let types_slice = Slice::extend_new(&mut self.types, types); + + let includes_tag = IncludesTag { + type_index, + tag_name, + types: types_slice, + pattern_category: category_index, + region, + }; + + let includes_tag_index = Index::push_new(&mut self.includes_tags, includes_tag); + + Constraint::IncludesTag(includes_tag_index) + } + fn variable_slice(&mut self, it: I) -> Slice where I: IntoIterator, @@ -133,6 +175,29 @@ impl Constraints { Constraint::Let(let_index) } + pub fn exists_many(&mut self, flex_vars: I, defs_constraint: C) -> Constraint + where + I: IntoIterator, + C: IntoIterator, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.and_constraint(defs_constraint); + self.constraints.push(Constraint::True); + + let let_contraint = LetConstraint { + rigid_vars: Slice::default(), + flex_vars: self.variable_slice(flex_vars), + def_types: Slice::default(), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + pub fn let_constraint( &mut self, rigid_vars: I1, @@ -191,7 +256,7 @@ impl Constraints { region, ) } - pub fn contains_save_the_environment(&self, constraint: Constraint) -> bool { + pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool { todo!() } @@ -228,6 +293,15 @@ pub enum Constraint { SaveTheEnvironment, Let(Index), And(Slice), + /// Presence constraints + IsOpenType(Index), // Theory; always applied to a variable? if yes the use that + IncludesTag(Index), + PatternPresence( + Index, + Index>, + Index, + Region, + ), } #[derive(Debug, Clone, PartialEq)] @@ -237,3 +311,12 @@ pub struct LetConstraint { pub def_types: Slice<(Symbol, Loc)>, pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } + +#[derive(Debug, Clone, PartialEq)] +pub struct IncludesTag { + pub type_index: Index, + pub tag_name: TagName, + pub types: Slice, + pub pattern_category: Index, + pub region: Region, +} diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs index ad5bae89d0..7c5b7e4d61 100644 --- a/compiler/collections/src/soa.rs +++ b/compiler/collections/src/soa.rs @@ -47,6 +47,19 @@ impl Slice { } } + pub fn extend_new(vector: &mut Vec, values: I) -> Slice + where + I: IntoIterator, + { + let start = vector.len() as u32; + + vector.extend(values); + + let end = vector.len() as u32; + + Self::new(start, (end - start) as u16) + } + pub const fn len(&self) -> usize { self.length as _ } diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index f036400799..44ae773491 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -61,7 +61,8 @@ pub fn add_numeric_bound_constr_soa( 1 => { let actual_type = Variable(range[0]); let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); - let because_suffix = constraints.equal_types(actual_type, expected, category, region); + let because_suffix = + constraints.equal_types(total_num_type.clone(), expected, category, region); num_constraints.extend([because_suffix]); diff --git a/compiler/constrain/src/soa_expr.rs b/compiler/constrain/src/soa_expr.rs index 7be8bdeaf8..9d8bd17b19 100644 --- a/compiler/constrain/src/soa_expr.rs +++ b/compiler/constrain/src/soa_expr.rs @@ -411,33 +411,28 @@ pub fn constrain_expr( &mut vars, ); - constraints.exists( - vars, - constraints.and_constraint([ - constraints.let_constraint( - Vec::new(), - pattern_state.vars, - pattern_state.headers, - constraints.and_constraint(pattern_state.constraints), - ret_constraint, - ), - // "the closure's type is equal to expected type" - constraints.equal_types( - function_type.clone(), - expected, - Category::Lambda, - region, - ), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - constraints.equal_types( - Type::Variable(*fn_var), - NoExpectation(function_type), - Category::Storage(std::file!(), std::line!()), - region, - ), - closure_constraint, - ]), - ) + let pattern_state_constraints = constraints.and_constraint(pattern_state.constraints); + let cons = [ + constraints.let_constraint( + [], + pattern_state.vars, + pattern_state.headers, + pattern_state_constraints, + ret_constraint, + ), + // "the closure's type is equal to expected type" + constraints.equal_types(function_type.clone(), expected, Category::Lambda, region), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + constraints.equal_types( + Type::Variable(*fn_var), + NoExpectation(function_type), + Category::Storage(std::file!(), std::line!()), + region, + ), + closure_constraint, + ]; + + constraints.exists_many(vars, cons) } Expect(loc_cond, continuation) => { @@ -462,7 +457,7 @@ pub fn constrain_expr( expected, ); - constraints.exists([], constraints.and_constraint([cond_con, continuation_con])) + constraints.exists_many([], [cond_con, continuation_con]) } If { @@ -548,10 +543,7 @@ pub fn constrain_expr( branch_cons.push(ast_con); branch_cons.push(else_con); - constraints.exists( - [*cond_var, *branch_var], - constraints.and_constraint(branch_cons), - ) + constraints.exists_many([*cond_var, *branch_var], branch_cons) } _ => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { @@ -604,10 +596,7 @@ pub fn constrain_expr( )); branch_cons.push(else_con); - constraints.exists( - [*cond_var, *branch_var], - constraints.and_constraint(branch_cons), - ) + constraints.exists_many([*cond_var, *branch_var], branch_cons) } } } @@ -677,10 +666,7 @@ pub fn constrain_expr( region, )); - return constraints.exists( - [cond_var, *expr_var], - constraints.and_constraint(branch_constraints), - ); + return constraints.exists_many([cond_var, *expr_var], branch_constraints); } _ => { @@ -691,6 +677,7 @@ pub fn constrain_expr( let pattern_region = Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( + constraints, env, region, when_branch, @@ -713,23 +700,26 @@ pub fn constrain_expr( branch_cons.push(branch_con); } - branch_constraints.push(constraints.and_constraint([ - // Record the original conditional expression's constraint. - // Each branch's pattern must have the same type - // as the condition expression did. - constraints.and_constraint(branch_cons), - // The return type of each branch must equal - // the return type of the entire when-expression. - constraints.equal_types(branch_type, expected, Category::When, region), - ])); + // Deviation: elm adds another layer of And nesting + // + // Record the original conditional expression's constraint. + // Each branch's pattern must have the same type + // as the condition expression did. + // + // The return type of each branch must equal the return type of + // the entire when-expression. + branch_cons.push(constraints.equal_types( + branch_type, + expected, + Category::When, + region, + )); + branch_constraints.push(constraints.and_constraint(branch_cons)); } } // exhautiveness checking happens when converting to mono::Expr - constraints.exists( - [cond_var, *expr_var], - constraints.and_constraint(branch_constraints), - ) + constraints.exists_many([cond_var, *expr_var], branch_constraints) } Access { record_var, @@ -771,13 +761,10 @@ pub fn constrain_expr( record_expected, ); - constraints.exists( + let eq = constraints.equal_types(field_type, expected, category, region); + constraints.exists_many( [*record_var, field_var, ext_var], - constraints.and_constraint([ - constraint, - constraints.equal_types(field_type, expected, category, region), - record_con, - ]), + [constraint, eq, record_con], ) } Accessor { @@ -820,23 +807,20 @@ pub fn constrain_expr( Box::new(field_type), ); - constraints.exists( + let cons = [ + constraints.equal_types(function_type.clone(), expected, category.clone(), region), + constraints.equal_types( + function_type, + NoExpectation(Variable(*function_var)), + category, + region, + ), + record_con, + ]; + + constraints.exists_many( [*record_var, *function_var, *closure_var, field_var, ext_var], - constraints.and_constraint([ - constraints.equal_types( - function_type.clone(), - expected, - category.clone(), - region, - ), - constraints.equal_types( - function_type, - NoExpectation(Variable(*function_var)), - category, - region, - ), - record_con, - ]), + cons, ) } LetRec(defs, loc_ret, var) => { @@ -848,20 +832,19 @@ pub fn constrain_expr( expected.clone(), ); - constraints.exists( - [*var], - constraints.and_constraint([ - constrain_recursive_defs(env, defs, body_con), - // Record the type of tne entire def-expression in the variable. - // Code gen will need that later! - constraints.equal_types( - Type::Variable(*var), - expected, - Category::Storage(std::file!(), std::line!()), - loc_ret.region, - ), - ]), - ) + let cons = [ + constrain_recursive_defs(constraints, env, defs, body_con), + // Record the type of tne entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected, + Category::Storage(std::file!(), std::line!()), + loc_ret.region, + ), + ]; + + constraints.exists_many([*var], cons) } LetNonRec(def, loc_ret, var) => { let mut stack = Vec::with_capacity(1); @@ -884,20 +867,19 @@ pub fn constrain_expr( ); while let Some((def, var, ret_region)) = stack.pop() { - body_con = constraints.exists( - [*var], - constraints.and_constraint([ - constrain_def(env, def, body_con), - // Record the type of the entire def-expression in the variable. - // Code gen will need that later! - constraints.equal_types( - Type::Variable(*var), - expected.clone(), - Category::Storage(std::file!(), std::line!()), - ret_region, - ), - ]), - ) + let cons = [ + constrain_def(constraints, env, def, body_con), + // Record the type of the entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected.clone(), + Category::Storage(std::file!(), std::line!()), + ret_region, + ), + ]; + + body_con = constraints.exists_many([*var], cons) } body_con @@ -950,7 +932,7 @@ pub fn constrain_expr( arg_cons.push(union_con); arg_cons.push(ast_con); - constraints.exists(vars, constraints.and_constraint(arg_cons)) + constraints.exists_many(vars, arg_cons) } ZeroArgumentTag { variant_var, @@ -1002,7 +984,7 @@ pub fn constrain_expr( arg_cons.push(union_con); arg_cons.push(ast_con); - constraints.exists(vars, constraints.and_constraint(arg_cons)) + constraints.exists_many(vars, arg_cons) } OpaqueRef { @@ -1069,9 +1051,9 @@ pub fn constrain_expr( v.0.expect_variable("all lambda sets should be fresh variables here") })); - constraints.exists( + constraints.exists_many( vars, - constraints.and([arg_con, opaque_con, link_type_variables_con, storage_con]), + [arg_con, opaque_con, link_type_variables_con, storage_con], ) } @@ -1109,12 +1091,8 @@ pub fn constrain_expr( let category = Category::LowLevelOpResult(*op); - constraints.exists( - vars, - constraints.and_constraint(arg_cons.into_iter().chain(std::iter::once( - constraints.equal_types(ret_type, expected, category, region), - ))), - ) + let eq = constraints.equal_types(ret_type, expected, category, region); + constraints.exists_many(vars, arg_cons.into_iter().chain(std::iter::once(eq))) } ForeignCall { args, @@ -1154,12 +1132,8 @@ pub fn constrain_expr( let category = Category::ForeignCall; - constraints.exists( - vars, - constraints.and_constraint(arg_cons.into_iter().chain(std::iter::once( - constraints.equal_types(ret_type, expected, category, region), - ))), - ) + let eq = constraints.equal_types(ret_type, expected, category, region); + constraints.exists_many(vars, arg_cons.into_iter().chain(std::iter::once(eq))) } RuntimeError(_) => { // Runtime Errors have no constraints because they're going to crash. @@ -1195,6 +1169,7 @@ fn constrain_when_branch( // then unify that variable with the expectation? for loc_pattern in &when_branch.patterns { constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1217,25 +1192,23 @@ fn constrain_when_branch( ); // must introduce the headers from the pattern before constraining the guard - constraints.let_constraint( + let state_constraints = constraints.and_constraint(state.constraints); + let inner = constraints.let_constraint( [], - state.vars, - state.headers, - constraints.and_constraint(state.constraints), - constraints.let_constraint( - [], - [], - SendMap::default(), - guard_constraint, - ret_constraint, - ), - ) + [], + SendMap::default(), + guard_constraint, + ret_constraint, + ); + + constraints.let_constraint([], state.vars, state.headers, state_constraints, inner) } else { + let state_constraints = constraints.and_constraint(state.constraints); constraints.let_constraint( [], state.vars, state.headers, - constraints.and_constraint(state.constraints), + state_constraints, ret_constraint, ) } @@ -1300,7 +1273,7 @@ pub fn constrain_decls( constraint = constrain_def(constraints, &env, def, constraint); } Declaration::DeclareRec(defs) => { - constraint = constrain_recursive_defs(&env, defs, constraint); + constraint = constrain_recursive_defs(constraints, &env, defs, constraint); } Declaration::InvalidCycle(_) => { // invalid cycles give a canonicalization error. we skip them here. @@ -1310,7 +1283,7 @@ pub fn constrain_decls( } // this assert make the "root" of the constraint wasn't dropped - debug_assert!(constraints.contains_save_the_environment(constraint)); + debug_assert!(constraints.contains_save_the_environment(&constraint)); constraint } @@ -1484,6 +1457,7 @@ fn constrain_def( } let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -1512,40 +1486,35 @@ fn constrain_def( vars.push(*fn_var); let defs_constraint = constraints.and_constraint(state.constraints); - constraints.exists( - vars, - constraints.and_constraint([ - constraints.let_constraint( - [], - state.vars, - state.headers, - defs_constraint, - ret_constraint, + let cons = [ + constraints.let_constraint( + [], + state.vars, + state.headers, + defs_constraint, + ret_constraint, + ), + constraints.equal_types( + Type::Variable(closure_var), + Expected::FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + *signature_closure_type.clone(), ), - constraints.equal_types( - Type::Variable(closure_var), - Expected::FromAnnotation( - def.loc_pattern.clone(), - arity, - AnnotationSource::TypedBody { - region: annotation.region, - }, - *signature_closure_type.clone(), - ), - Category::ClosureSize, - region, - ), - constraints.store( - signature.clone(), - *fn_var, - std::file!(), - std::line!(), - ), - constraints.store(signature, expr_var, std::file!(), std::line!()), - constraints.store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]), - ) + Category::ClosureSize, + region, + ), + constraints.store(signature.clone(), *fn_var, std::file!(), std::line!()), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]; + + let and_constraint = constraints.and_constraint(cons); + constraints.exists(vars, and_constraint) } _ => { @@ -1559,7 +1528,7 @@ fn constrain_def( expected, ); - constraints.and_constraint([ + let cons = [ constraints.let_constraint( [], [], @@ -1569,7 +1538,8 @@ fn constrain_def( ), // Store type into AST vars. We use Store so errors aren't reported twice constraints.store(signature, expr_var, std::file!(), std::line!()), - ]) + ]; + constraints.and_constraint(cons) } } } @@ -1586,17 +1556,21 @@ fn constrain_def( } }; + let and_constraint = constraints.and_constraint(def_pattern_state.constraints); + + let def_con = constraints.let_constraint( + [], + [], + SendMap::default(), // empty, because our functions have no arguments! + and_constraint, + expr_con, + ); + constraints.let_constraint( new_rigids, def_pattern_state.vars, def_pattern_state.headers, - constraints.let_constraint( - [], - [], - SendMap::default(), // empty, because our functions have no arguments! - constraints.and_constraint(def_pattern_state.constraints), - expr_con, - ), + def_con, body_con, ) } @@ -1706,8 +1680,14 @@ fn instantiate_rigids( annotation } -fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Constraint { +fn constrain_recursive_defs( + constraints: &mut Constraints, + env: &Env, + defs: &[Def], + body_con: Constraint, +) -> Constraint { rec_defs_help( + constraints, env, defs, body_con, @@ -1897,35 +1877,36 @@ pub fn rec_defs_help( vars.push(*fn_var); - let def_con = constraints.exists( - vars, - constraints.and_constraint([ - constraints.let_constraint( - [], - state.vars, - state.headers, - constraints.and_constraint(state.constraints), - expr_con, - ), - constraints.equal_types( - fn_type.clone(), - expected.clone(), - Category::Lambda, - region, - ), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - // Store type into AST vars. We use Store so errors aren't reported twice - constraints.store( - signature.clone(), - *fn_var, - std::file!(), - std::line!(), - ), - constraints.store(signature, expr_var, std::file!(), std::line!()), - constraints.store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]), - ); + let state_constraints = constraints.and_constraint(state.constraints); + let cons = [ + constraints.let_constraint( + [], + state.vars, + state.headers, + state_constraints, + expr_con, + ), + constraints.equal_types( + fn_type.clone(), + expected.clone(), + Category::Lambda, + region, + ), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + // Store type into AST vars. We use Store so errors aren't reported twice + constraints.store( + signature.clone(), + *fn_var, + std::file!(), + std::line!(), + ), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]; + + let and_constraint = constraints.and_constraint(cons); + let def_con = constraints.exists(vars, and_constraint); rigid_info.vars.extend(&new_rigids); @@ -1949,7 +1930,7 @@ pub fn rec_defs_help( expected, ); - let def_con = constraints.and_constraint([ + let cons = [ constraints.let_constraint( [], [], @@ -1959,7 +1940,8 @@ pub fn rec_defs_help( ), // Store type into AST vars. We use Store so errors aren't reported twice constraints.store(signature, expr_var, std::file!(), std::line!()), - ]); + ]; + let def_con = constraints.and_constraint(cons); rigid_info.vars.extend(&new_rigids); @@ -1977,25 +1959,36 @@ pub fn rec_defs_help( } } + let flex_constraints = constraints.and_constraint(flex_info.constraints); + let inner_inner = constraints.let_constraint( + [], + [], + flex_info.def_types.clone(), + Constraint::True, + flex_constraints, + ); + + let rigid_constraints = { + let mut temp = rigid_info.constraints; + temp.push(body_con); + + constraints.and_constraint(temp) + }; + + let inner = constraints.let_constraint( + [], + flex_info.vars, + flex_info.def_types, + inner_inner, + rigid_constraints, + ); + constraints.let_constraint( rigid_info.vars, [], rigid_info.def_types, Constraint::True, - constraints.let_constraint( - [], - flex_info.vars, - flex_info.def_types.clone(), - constraints.let_constraint( - [], - [], - flex_info.def_types, - Constraint::True, - constraints.and_constraint(flex_info.constraints), - ), - constraints - .and_constraint([constraints.and_constraint(rigid_info.constraints), body_con]), - ), + inner, ) } diff --git a/compiler/constrain/src/soa_pattern.rs b/compiler/constrain/src/soa_pattern.rs index adbdc3d68a..721a1488d7 100644 --- a/compiler/constrain/src/soa_pattern.rs +++ b/compiler/constrain/src/soa_pattern.rs @@ -1,6 +1,6 @@ use crate::builtins; use crate::soa_expr::{constrain_expr, Env}; -use roc_can::constraint_soa::{Constraint, Constraints, PresenceConstraint}; +use roc_can::constraint_soa::{Constraint, Constraints}; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::{DestructType, RecordDestruct}; @@ -168,20 +168,18 @@ pub fn constrain_pattern( // A -> "" // _ -> "" // so, we know that "x" (in this case, a tag union) must be open. - state.constraints.push(Constraint::Present( - expected.get_type(), - PresenceConstraint::IsOpen, - )); + state + .constraints + .push(constraints.is_open_type(expected.get_type())); } UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => { // Erroneous patterns don't add any constraints. } Identifier(symbol) | Shadowed(_, _, symbol) => { - state.constraints.push(Constraint::Present( - expected.get_type_ref().clone(), - PresenceConstraint::IsOpen, - )); + state + .constraints + .push(constraints.is_open_type(expected.get_type_ref().clone())); state.headers.insert( *symbol, Loc { @@ -205,18 +203,19 @@ pub fn constrain_pattern( Category::Num, ); - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Num, + state.constraints.push(constraints.equal_pattern_types( num_type, expected, + PatternCategory::Num, + region, )); } &IntLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr_soa( + constraints, &mut state.constraints, Type::Variable(num_var), bound, @@ -266,29 +265,29 @@ pub fn constrain_pattern( )); // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Float, + state.constraints.push(constraints.equal_pattern_types( num_type, // TODO check me if something breaks! expected, + PatternCategory::Float, + region, )); } StrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, + state.constraints.push(constraints.equal_pattern_types( builtins::str_type(), expected, + PatternCategory::Str, + region, )); } SingleQuote(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Character, + state.constraints.push(constraints.equal_pattern_types( builtins::num_u32(), expected, + PatternCategory::Character, + region, )); } @@ -325,36 +324,39 @@ pub fn constrain_pattern( let field_type = match typ { DestructType::Guard(guard_var, loc_guard) => { - state.constraints.push(Constraint::Present( + state.constraints.push(constraints.pattern_presence( Type::Variable(*guard_var), - PresenceConstraint::Pattern( - region, - PatternCategory::PatternGuard, - PExpected::ForReason( - PReason::PatternGuard, - pat_type.clone(), - loc_guard.region, - ), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.clone(), + loc_guard.region, ), + PatternCategory::PatternGuard, + region, )); state.vars.push(*guard_var); - constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state); + constrain_pattern( + constraints, + env, + &loc_guard.value, + loc_guard.region, + expected, + state, + ); RecordField::Demanded(pat_type) } DestructType::Optional(expr_var, loc_expr) => { - state.constraints.push(Constraint::Present( + state.constraints.push(constraints.pattern_presence( Type::Variable(*expr_var), - PresenceConstraint::Pattern( - region, - PatternCategory::PatternDefault, - PExpected::ForReason( - PReason::OptionalField, - pat_type.clone(), - loc_expr.region, - ), + PExpected::ForReason( + PReason::OptionalField, + pat_type.clone(), + loc_expr.region, ), + PatternCategory::PatternDefault, + region, )); state.vars.push(*expr_var); @@ -365,8 +367,13 @@ pub fn constrain_pattern( loc_expr.region, ); - let expr_con = - constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + let expr_con = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + expr_expected, + ); state.constraints.push(expr_con); RecordField::Optional(pat_type) @@ -391,9 +398,11 @@ pub fn constrain_pattern( region, ); - let record_con = Constraint::Present( + let record_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, PatternCategory::Record, expected), + expected, + PatternCategory::Record, + region, ); state.constraints.push(whole_con); @@ -420,24 +429,31 @@ pub fn constrain_pattern( pattern_type, region, ); - constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + expected, + state, + ); } let pat_category = PatternCategory::Ctor(tag_name.clone()); - let whole_con = Constraint::Present( + let whole_con = constraints.includes_tag( expected.clone().get_type(), - PresenceConstraint::IncludesTag( - tag_name.clone(), - argument_types.clone(), - region, - pat_category.clone(), - ), + tag_name.clone(), + argument_types.clone(), + pat_category.clone(), + region, ); - let tag_con = Constraint::Present( + let tag_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, pat_category, expected), + expected, + pat_category, + region, ); state.vars.push(*whole_var); @@ -469,6 +485,7 @@ pub fn constrain_pattern( // First, add a constraint for the argument "who" let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone()); constrain_pattern( + constraints, env, &loc_arg_pattern.value, loc_arg_pattern.region, @@ -495,9 +512,11 @@ pub fn constrain_pattern( ); // Next, link `whole_var` (the type of "@Id who") to the expected type - let opaque_pattern_con = Constraint::Present( + let opaque_pattern_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected), + expected, + PatternCategory::Opaque(*opaque), + region, ); state diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index d633d5e6c4..65a0936977 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1372,7 +1372,7 @@ fn to_pattern_report<'b>( } } PReason::WhenMatch { index } => { - if index == Index::FIRST { + if index == HumanIndex::FIRST { let doc = alloc.stack(vec![ alloc .text("The 1st pattern in this ") @@ -1384,7 +1384,7 @@ fn to_pattern_report<'b>( found, expected_type, add_pattern_category( - HumanIndexlloc, + alloc, alloc.text("The first pattern is trying to match"), &category, ), From 0be23e23da7433b67ee0eb5f8ab6748741ec9e74 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 18:59:08 +0100 Subject: [PATCH 043/150] implement save the environment check --- compiler/can/src/constraint_soa.rs | 31 ++++++++++++++++++++++++++++-- compiler/collections/src/soa.rs | 6 ++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index e79ac4fae0..10959f9806 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -3,8 +3,8 @@ use roc_collections::soa::{Index, Slice}; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; use roc_types::types::{Category, PatternCategory, Type}; -use roc_types::{subs::Variable, types::VariableDetail}; pub struct Constraints { constraints: Vec, @@ -257,7 +257,34 @@ impl Constraints { ) } pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool { - todo!() + match constraint { + Constraint::Eq(..) => false, + Constraint::Store(..) => false, + Constraint::Lookup(..) => false, + Constraint::Pattern(..) => false, + Constraint::True => false, + Constraint::SaveTheEnvironment => true, + Constraint::Let(index) => { + let let_constraint = &self.let_constraints[index.usize()]; + + let offset = let_constraint.defs_and_ret_constraint.usize(); + let defs_constraint = &self.constraints[offset]; + let ret_constraint = &self.constraints[offset + 1]; + + self.contains_save_the_environment(defs_constraint) + || self.contains_save_the_environment(ret_constraint) + } + Constraint::And(slice) => { + let constraints = &self.constraints[slice.indices()]; + + constraints + .iter() + .any(|c| self.contains_save_the_environment(c)) + } + Constraint::IsOpenType(_) => false, + Constraint::IncludesTag(_) => false, + Constraint::PatternPresence(_, _, _, _) => false, + } } pub fn store( diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs index 7c5b7e4d61..1c3afa18d0 100644 --- a/compiler/collections/src/soa.rs +++ b/compiler/collections/src/soa.rs @@ -1,3 +1,5 @@ +use std::usize; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Index { index: u32, @@ -12,6 +14,10 @@ impl Index { } } + pub const fn usize(&self) -> usize { + self.index as usize + } + pub fn push_new(vector: &mut Vec, value: T) -> Index { let index = Self::new(vector.len() as _); From 36c2b78c306265335fce5b465e78490a91ce7ac0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 19:08:42 +0100 Subject: [PATCH 044/150] fix copy paste mistake --- compiler/constrain/src/builtins.rs | 40 ++++++++++++------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 44ae773491..df7c0e9bb9 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -116,30 +116,22 @@ pub fn float_literal( let reason = Reason::FloatLiteral; let mut constrs = Vec::with_capacity(3); - let num_type = { - let constrs: &mut Vec = &mut constrs; - let num_type = Variable(num_var); - let category = Category::Float; - let range = bound.bounded_range(); - - let total_num_type = num_type; - - match range.len() { - 0 => total_num_type, - 1 => { - let actual_type = Variable(range[0]); - constrs.push(Eq( - total_num_type.clone(), - Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), - category, - region, - )); - total_num_type - } - _ => RangedNumber(Box::new(total_num_type), range), - } - }; - constrs.extend(vec![]); + let num_type = add_numeric_bound_constr( + &mut constrs, + Variable(num_var), + bound, + region, + Category::Float, + ); + constrs.extend(vec![ + Eq( + num_type.clone(), + ForReason(reason, num_float(Type::Variable(precision_var)), region), + Category::Float, + region, + ), + Eq(num_type, expected, Category::Float, region), + ]); exists(vec![num_var, precision_var], And(constrs)) } From fe48bdf5b1b083dcf95053f8d91ba772f3c71adf Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 19:43:03 +0100 Subject: [PATCH 045/150] make solve that uses new constraint --- compiler/can/src/constraint_soa.rs | 24 +- compiler/collections/src/soa.rs | 2 +- compiler/solve/src/solve.rs | 567 ++++++++++++++++++++++++++++- 3 files changed, 577 insertions(+), 16 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 10959f9806..46ad6d2fc7 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -7,16 +7,16 @@ use roc_types::subs::Variable; use roc_types::types::{Category, PatternCategory, Type}; pub struct Constraints { - constraints: Vec, - types: Vec, - variables: Vec, - def_types: Vec<(Symbol, Loc>)>, - let_constraints: Vec, - categories: Vec, - pattern_categories: Vec, - expectations: Vec>, - pattern_expectations: Vec>, - includes_tags: Vec, + pub constraints: Vec, + pub types: Vec, + pub variables: Vec, + pub def_types: Vec<(Symbol, Loc>)>, + pub let_constraints: Vec, + pub categories: Vec, + pub pattern_categories: Vec, + pub expectations: Vec>, + pub pattern_expectations: Vec>, + pub includes_tags: Vec, } impl Constraints { @@ -265,9 +265,9 @@ impl Constraints { Constraint::True => false, Constraint::SaveTheEnvironment => true, Constraint::Let(index) => { - let let_constraint = &self.let_constraints[index.usize()]; + let let_constraint = &self.let_constraints[index.index()]; - let offset = let_constraint.defs_and_ret_constraint.usize(); + let offset = let_constraint.defs_and_ret_constraint.index(); let defs_constraint = &self.constraints[offset]; let ret_constraint = &self.constraints[offset + 1]; diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs index 1c3afa18d0..47f93ba7c2 100644 --- a/compiler/collections/src/soa.rs +++ b/compiler/collections/src/soa.rs @@ -14,7 +14,7 @@ impl Index { } } - pub const fn usize(&self) -> usize { + pub const fn index(&self) -> usize { self.index as usize } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c7ce79b7a6..f62f15413e 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,6 +1,5 @@ use bumpalo::Bump; -use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::PresenceConstraint; +use roc_can::constraint::Constraint; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; use roc_module::ident::TagName; @@ -226,6 +225,16 @@ enum Work<'a> { After(After), } +enum WorkSoa<'a> { + Constraint { + env: &'a Env, + rank: Rank, + constraint: &'a roc_can::constraint_soa::Constraint, + }, + /// Something to be done after a constraint and all its dependencies are fully solved. + After(After), +} + #[allow(clippy::too_many_arguments)] fn solve( arena: &Bump, @@ -236,8 +245,11 @@ fn solve( problems: &mut Vec, cached_aliases: &mut MutMap, subs: &mut Subs, - constraint: &Constraint, + constraint: &roc_can::constraint::Constraint, ) -> State { + use roc_can::constraint::PresenceConstraint; + use Constraint::*; + let mut stack = vec![Work::Constraint { env, rank, @@ -745,6 +757,555 @@ fn solve( state } +#[allow(clippy::too_many_arguments)] +fn solve_soa( + arena: &Bump, + constraints: &roc_can::constraint_soa::Constraints, + env: &Env, + mut state: State, + rank: Rank, + pools: &mut Pools, + problems: &mut Vec, + cached_aliases: &mut MutMap, + subs: &mut Subs, + constraint: &roc_can::constraint_soa::Constraint, +) -> State { + use roc_can::constraint_soa::Constraint::*; + + let initial = WorkSoa::Constraint { + env, + rank, + constraint, + }; + let mut stack = vec![initial]; + + while let Some(work_item) = stack.pop() { + let (env, rank, constraint) = match work_item { + WorkSoa::After(After::CheckForInfiniteTypes(def_vars)) => { + for (symbol, loc_var) in def_vars.iter() { + check_for_infinite_type(subs, problems, *symbol, *loc_var); + } + // No constraint to be solved + continue; + } + WorkSoa::Constraint { + env, + rank, + constraint, + } => (env, rank, constraint), + }; + + state = match constraint { + True => state, + SaveTheEnvironment => { + // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment + let mut copy = state; + + copy.env = env.clone(); + + copy + } + Eq(type_index, expectation_index, category_index, region) => { + let typ = &constraints.types[type_index.index()]; + let expectation = &constraints.expectations[expectation_index.index()]; + let category = &constraints.categories[category_index.index()]; + + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected, Mode::EQ) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + category.clone(), + actual_type, + expectation.clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + Store(source_index, target, _filename, _linenr) => { + let source = &constraints.types[source_index.index()]; + + // a special version of Eq that is used to store types in the AST. + // IT DOES NOT REPORT ERRORS! + let actual = type_to_var(subs, rank, pools, cached_aliases, source); + let target = *target; + + match unify(subs, actual, target, Mode::EQ) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, _actual_type, _expected_type) => { + introduce(subs, rank, pools, &vars); + + // ERROR NOT REPORTED + + state + } + BadType(vars, _) => { + introduce(subs, rank, pools, &vars); + + // ERROR NOT REPORTED + + state + } + } + } + Lookup(symbol, expectation_index, region) => { + match env.get_var_by_symbol(symbol) { + Some(var) => { + // Deep copy the vars associated with this symbol before unifying them. + // Otherwise, suppose we have this: + // + // identity = \a -> a + // + // x = identity 5 + // + // When we call (identity 5), it's important that we not unify + // on identity's original vars. If we do, the type of `identity` will be + // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; + // the type of `identity` is more general than that! + // + // Instead, we want to unify on a *copy* of its vars. If the copy unifies + // successfully (in this case, to `Int -> Int`), we can use that to + // infer the type of this lookup (in this case, `Int`) without ever + // having mutated the original. + // + // If this Lookup is targeting a value in another module, + // then we copy from that module's Subs into our own. If the value + // is being looked up in this module, then we use our Subs as both + // the source and destination. + let actual = deep_copy_var(subs, rank, pools, var); + + let expectation = &constraints.expectations[expectation_index.index()]; + let expected = type_to_var( + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected, Mode::EQ) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + Category::Lookup(*symbol), + actual_type, + expectation.clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + None => { + problems.push(TypeError::UnexposedLookup(*symbol)); + + state + } + } + } + And(slice) => { + let it = constraints.constraints[slice.indices()].iter().rev(); + for sub_constraint in it { + stack.push(WorkSoa::Constraint { + env, + rank, + constraint: sub_constraint, + }) + } + + state + } + Pattern(type_index, expectation_index, category_index, region) + | PatternPresence(type_index, expectation_index, category_index, region) => { + let typ = &constraints.types[type_index.index()]; + let expectation = &constraints.pattern_expectations[expectation_index.index()]; + let category = &constraints.pattern_categories[category_index.index()]; + + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + let mode = match constraint { + PatternPresence(..) => Mode::PRESENT, + _ => Mode::EQ, + }; + + match unify(subs, actual, expected, mode) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadPattern( + *region, + category.clone(), + actual_type, + expectation.clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + Let(index) => { + let let_con = &constraints.let_constraints[index.index()]; + + let offset = let_con.defs_and_ret_constraint.index(); + let defs_constraint = &constraints.constraints[offset]; + let ret_constraint = &constraints.constraints[offset + 1]; + + let flex_vars = &constraints.variables[let_con.flex_vars.indices()]; + let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + + let def_types = &constraints.def_types[let_con.def_types.indices()]; + + match &ret_constraint { + True if let_con.rigid_vars.is_empty() => { + introduce(subs, rank, pools, flex_vars); + + // If the return expression is guaranteed to solve, + // solve the assignments themselves and move on. + stack.push(WorkSoa::Constraint { + env, + rank, + constraint: defs_constraint, + }); + state + } + ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { + // TODO: make into `WorkItem` with `After` + let state = solve_soa( + arena, + constraints, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + defs_constraint, + ); + + // Add a variable for each def to new_vars_by_env. + let mut local_def_vars = + LocalDefVarsVec::with_length(let_con.def_types.len()); + + for (symbol, loc_type_index) in def_types.iter() { + let typ = &constraints.types[loc_type_index.value.index()]; + let var = type_to_var(subs, rank, pools, cached_aliases, typ); + + local_def_vars.push(( + *symbol, + Loc { + value: var, + region: loc_type_index.region, + }, + )); + } + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + } + + stack.push(WorkSoa::After(After::CheckForInfiniteTypes(local_def_vars))); + stack.push(WorkSoa::Constraint { + env: arena.alloc(new_env), + rank, + constraint: ret_con, + }); + + state + } + ret_con => { + // work in the next pool to localize header + let next_rank = rank.next(); + + // introduce variables + for &var in rigid_vars.iter().chain(flex_vars.iter()) { + subs.set_rank(var, next_rank); + } + + // determine the next pool + if next_rank.into_usize() < pools.len() { + // Nothing to do, we already accounted for the next rank, no need to + // adjust the pools + } else { + // we should be off by one at this point + debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + pools.extend_to(next_rank.into_usize()); + } + + let pool: &mut Vec = pools.get_mut(next_rank); + + // Replace the contents of this pool with rigid_vars and flex_vars + pool.clear(); + pool.reserve(rigid_vars.len() + flex_vars.len()); + pool.extend(rigid_vars.iter()); + pool.extend(flex_vars.iter()); + + // run solver in next pool + + // Add a variable for each def to local_def_vars. + let mut local_def_vars = + LocalDefVarsVec::with_length(let_con.def_types.len()); + + for (symbol, loc_type) in def_types.iter() { + let def_type = &constraints.types[loc_type.value.index()]; + + let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type); + + local_def_vars.push(( + *symbol, + Loc { + value: var, + region: loc_type.region, + }, + )); + } + + // Solve the assignments' constraints first. + // TODO: make into `WorkItem` with `After` + let State { + env: saved_env, + mark, + } = solve_soa( + arena, + constraints, + env, + state, + next_rank, + pools, + problems, + cached_aliases, + subs, + defs_constraint, + ); + + let young_mark = mark; + let visit_mark = young_mark.next(); + let final_mark = visit_mark.next(); + + debug_assert_eq!( + { + let offenders = pools + .get(next_rank) + .iter() + .filter(|var| { + let current_rank = + subs.get_rank(roc_types::subs::Variable::clone(var)); + + current_rank.into_usize() > next_rank.into_usize() + }) + .collect::>(); + + let result = offenders.len(); + + if result > 0 { + dbg!(&subs, &offenders, &let_con.def_types); + } + + result + }, + 0 + ); + + // pop pool + generalize(subs, young_mark, visit_mark, next_rank, pools); + + pools.get_mut(next_rank).clear(); + + // check that things went well + debug_assert!({ + // NOTE the `subs.redundant` check is added for the uniqueness + // inference, and does not come from elm. It's unclear whether this is + // a bug with uniqueness inference (something is redundant that + // shouldn't be) or that it just never came up in elm. + let failing: Vec<_> = rigid_vars + .iter() + .filter(|&var| { + !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE + }) + .collect(); + + if !failing.is_empty() { + println!("Rigids {:?}", &rigid_vars); + println!("Failing {:?}", failing); + } + + failing.is_empty() + }); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + } + + // Note that this vars_by_symbol is the one returned by the + // previous call to solve() + let state_for_ret_con = State { + env: saved_env, + mark: final_mark, + }; + + // Now solve the body, using the new vars_by_symbol which includes + // the assignments' name-to-variable mappings. + stack.push(WorkSoa::After(After::CheckForInfiniteTypes(local_def_vars))); + stack.push(WorkSoa::Constraint { + env: arena.alloc(new_env), + rank, + constraint: ret_con, + }); + + state_for_ret_con + } + } + } + IsOpenType(type_index) => { + let typ = &constraints.types[type_index.index()]; + + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let mut new_desc = subs.get(actual); + match new_desc.content { + Content::Structure(FlatType::TagUnion(tags, _)) => { + let new_ext = subs.fresh_unnamed_flex_var(); + let new_union = Content::Structure(FlatType::TagUnion(tags, new_ext)); + new_desc.content = new_union; + subs.set(actual, new_desc); + state + } + _ => { + // Today, an "open" constraint doesn't affect any types + // other than tag unions. Recursive tag unions are constructed + // at a later time (during occurs checks after tag unions are + // resolved), so that's not handled here either. + // NB: Handle record types here if we add presence constraints + // to their type inference as well. + state + } + } + } + IncludesTag(index) => { + let includes_tag = &constraints.includes_tags[index.index()]; + + let roc_can::constraint_soa::IncludesTag { + type_index, + tag_name, + types, + pattern_category, + region, + } = includes_tag; + + let typ = &constraints.types[type_index.index()]; + let tys = &constraints.types[types.indices()]; + let pattern_category = &constraints.pattern_categories[pattern_category.index()]; + + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let tag_ty = Type::TagUnion( + vec![(tag_name.clone(), tys.to_vec())], + Box::new(Type::EmptyTagUnion), + ); + let includes = type_to_var(subs, rank, pools, cached_aliases, &tag_ty); + + match unify(subs, actual, includes, Mode::PRESENT) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_to_include_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadPattern( + *region, + pattern_category.clone(), + expected_to_include_type, + PExpected::NoExpectation(actual_type), + ); + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + }; + } + + state +} + #[derive(Debug)] enum LocalDefVarsVec { Stack(arrayvec::ArrayVec), From 73bd647a7dddd7003afe5a342a71c5db73af8437 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 19:56:30 +0100 Subject: [PATCH 046/150] constrain module stuff --- compiler/constrain/src/module.rs | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 4214e49a3a..39955d7f1f 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,6 +1,7 @@ use crate::expr::constrain_decls; use roc_builtins::std::StdLib; use roc_can::constraint::{Constraint, LetConstraint}; +use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; use roc_can::def::Declaration; use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_module::symbol::{ModuleId, Symbol}; @@ -94,6 +95,62 @@ pub fn constrain_imported_values( ) } +pub fn constrain_imported_values_soa( + constraints: &mut Constraints, + imports: Vec, + body_con: ConstraintSoa, + var_store: &mut VarStore, +) -> (Vec, ConstraintSoa) { + let mut def_types = SendMap::default(); + let mut rigid_vars = Vec::new(); + + for import in imports { + let mut free_vars = FreeVars::default(); + let loc_symbol = import.loc_symbol; + + // an imported symbol can be either an alias or a value + match import.solved_type { + SolvedType::Alias(symbol, _, _, _, _) if symbol == loc_symbol.value => { + // do nothing, in the future the alias definitions should not be in the list of imported values + } + _ => { + let typ = roc_types::solved_types::to_type( + &import.solved_type, + &mut free_vars, + var_store, + ); + + def_types.insert( + loc_symbol.value, + Loc { + region: loc_symbol.region, + value: typ, + }, + ); + + for (_, var) in free_vars.named_vars { + rigid_vars.push(var); + } + + for var in free_vars.wildcards { + rigid_vars.push(var); + } + + // Variables can lose their name during type inference. But the unnamed + // variables are still part of a signature, and thus must be treated as rigids here! + for (_, var) in free_vars.unnamed_vars { + rigid_vars.push(var); + } + } + } + } + + ( + rigid_vars.clone(), + constraints.let_constraint(rigid_vars, [], def_types, ConstraintSoa::True, body_con), + ) +} + /// Run pre_constrain_imports to get imported_symbols and imported_aliases. pub fn constrain_imports( imported_symbols: Vec, @@ -111,6 +168,24 @@ pub fn constrain_imports( constraint } +/// Run pre_constrain_imports to get imported_symbols and imported_aliases. +pub fn constrain_imports_soa( + constraints: &mut Constraints, + imported_symbols: Vec, + constraint: ConstraintSoa, + var_store: &mut VarStore, +) -> ConstraintSoa { + let (_introduced_rigids, constraint) = + constrain_imported_values_soa(constraints, imported_symbols, constraint, var_store); + + // TODO determine what to do with those rigids + // for var in introduced_rigids { + // output.ftv.insert(var, format!("internal_{:?}", var).into()); + // } + + constraint +} + pub struct ConstrainableImports { pub imported_symbols: Vec, pub imported_aliases: MutMap, From c52029c2d1060e6f49ddc71c0402fcd0a25bb7ff Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 20:30:42 +0100 Subject: [PATCH 047/150] the debugging begins --- compiler/can/src/constraint_soa.rs | 32 ++++++++++++++++++++ compiler/collections/src/soa.rs | 16 ++++++++-- compiler/constrain/src/module.rs | 8 +++++ compiler/load/src/file.rs | 42 +++++++++++++++++++------- compiler/solve/src/module.rs | 26 +++++++++++++++++ compiler/solve/src/solve.rs | 47 ++++++++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 13 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 46ad6d2fc7..eb231a1af8 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -6,6 +6,7 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; use roc_types::types::{Category, PatternCategory, Type}; +#[derive(Debug)] pub struct Constraints { pub constraints: Vec, pub types: Vec, @@ -20,6 +21,37 @@ pub struct Constraints { } impl Constraints { + pub fn new() -> Self { + let constraints = Vec::new(); + let mut types = Vec::new(); + let variables = Vec::new(); + let def_types = Vec::new(); + let let_constraints = Vec::new(); + let mut categories = Vec::new(); + let pattern_categories = Vec::new(); + let expectations = Vec::new(); + let pattern_expectations = Vec::new(); + let includes_tags = Vec::new(); + + types.push(Type::EmptyRec); + types.push(Type::EmptyTagUnion); + + categories.push(Category::Record); + + Self { + constraints, + types, + variables, + def_types, + let_constraints, + categories, + pattern_categories, + expectations, + pattern_expectations, + includes_tags, + } + } + pub const EMPTY_RECORD: Index = Index::new(0); pub const EMPTY_TAG_UNION: Index = Index::new(1); diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs index 47f93ba7c2..ef1bde877c 100644 --- a/compiler/collections/src/soa.rs +++ b/compiler/collections/src/soa.rs @@ -1,11 +1,17 @@ use std::usize; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Index { index: u32, _marker: std::marker::PhantomData, } +impl std::fmt::Debug for Index { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Index({})", self.index) + } +} + impl Index { pub const fn new(index: u32) -> Self { Self { @@ -27,13 +33,19 @@ impl Index { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Slice { start: u32, length: u16, _marker: std::marker::PhantomData, } +impl std::fmt::Debug for Slice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Slice(start = {}, length = {})", self.start, self.length) + } +} + impl Default for Slice { fn default() -> Self { Self { diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 39955d7f1f..553106162d 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -27,6 +27,14 @@ pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constra constrain_decls(home, declarations) } +pub fn constrain_module_soa( + constraints: &mut Constraints, + declarations: &[Declaration], + home: ModuleId, +) -> ConstraintSoa { + crate::soa_expr::constrain_decls(constraints, home, declarations) +} + #[derive(Debug, Clone)] pub struct Import { pub loc_symbol: Loc, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index e1aabf3441..7f3dda5549 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -6,11 +6,13 @@ use crossbeam::thread; use parking_lot::Mutex; use roc_builtins::std::StdLib; use roc_can::constraint::Constraint; +use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; use roc_constrain::module::{ - constrain_imports, pre_constrain_imports, ConstrainableImports, Import, + constrain_imports, constrain_imports_soa, constrain_module_soa, pre_constrain_imports, + ConstrainableImports, Import, }; use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; @@ -229,6 +231,7 @@ fn start_phase<'a>( module, ident_ids, module_timing, + constraints, constraint, var_store, imported_modules, @@ -241,6 +244,7 @@ fn start_phase<'a>( module, ident_ids, module_timing, + constraints, constraint, var_store, imported_modules, @@ -391,7 +395,8 @@ struct ConstrainedModule { module: Module, declarations: Vec, imported_modules: MutMap, - constraint: Constraint, + constraints: Constraints, + constraint: ConstraintSoa, ident_ids: IdentIds, var_store: VarStore, dep_idents: MutMap, @@ -728,7 +733,8 @@ enum BuildTask<'a> { ident_ids: IdentIds, imported_symbols: Vec, module_timing: ModuleTiming, - constraint: Constraint, + constraints: Constraints, + constraint: ConstraintSoa, var_store: VarStore, declarations: Vec, dep_idents: MutMap, @@ -3027,7 +3033,8 @@ impl<'a> BuildTask<'a> { module: Module, ident_ids: IdentIds, module_timing: ModuleTiming, - constraint: Constraint, + constraints: Constraints, + constraint: ConstraintSoa, var_store: VarStore, imported_modules: MutMap, exposed_types: &mut SubsByModule, @@ -3057,6 +3064,7 @@ impl<'a> BuildTask<'a> { module, ident_ids, imported_symbols, + constraints, constraint, var_store, declarations, @@ -3073,7 +3081,8 @@ fn run_solve<'a>( ident_ids: IdentIds, mut module_timing: ModuleTiming, imported_symbols: Vec, - constraint: Constraint, + mut constraints: Constraints, + constraint: ConstraintSoa, mut var_store: VarStore, decls: Vec, dep_idents: MutMap, @@ -3082,9 +3091,16 @@ fn run_solve<'a>( // We have more constraining work to do now, so we'll add it to our timings. let constrain_start = SystemTime::now(); + dbg!(&constraint, &constraints); + // Finish constraining the module by wrapping the existing Constraint // in the ones we just computed. We can do this off the main thread. - let constraint = constrain_imports(imported_symbols, constraint, &mut var_store); + let constraint = constrain_imports_soa( + &mut constraints, + imported_symbols, + constraint, + &mut var_store, + ); let constrain_end = SystemTime::now(); @@ -3097,12 +3113,11 @@ fn run_solve<'a>( .. } = module; - if false { - debug_assert!(constraint.validate(), "{:?}", &constraint); - } + // TODO + // if false { debug_assert!(constraint.validate(), "{:?}", &constraint); } let (solved_subs, solved_env, problems) = - roc_solve::module::run_solve(rigid_variables, constraint, var_store); + roc_solve::module::run_solve_soa(&constraints, constraint, rigid_variables, var_store); let exposed_vars_by_symbol: Vec<_> = solved_env .vars_by_symbol() @@ -3247,7 +3262,9 @@ fn canonicalize_and_constrain<'a>( } }; - let constraint = constrain_module(&module_output.declarations, module_id); + let mut constraints = Constraints::new(); + let constraint = + constrain_module_soa(&mut constraints, &module_output.declarations, module_id); let module = Module { module_id, @@ -3263,6 +3280,7 @@ fn canonicalize_and_constrain<'a>( declarations: module_output.declarations, imported_modules, var_store, + constraints, constraint, ident_ids: module_output.ident_ids, dep_idents, @@ -3745,6 +3763,7 @@ fn run_task<'a>( module, module_timing, imported_symbols, + constraints, constraint, var_store, ident_ids, @@ -3756,6 +3775,7 @@ fn run_task<'a>( ident_ids, module_timing, imported_symbols, + constraints, constraint, var_store, declarations, diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index 5cdd710458..31730a8b29 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,5 +1,6 @@ use crate::solve; use roc_can::constraint::Constraint; +use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -39,6 +40,31 @@ pub fn run_solve( (solved_subs, solved_env, problems) } +pub fn run_solve_soa( + constraints: &Constraints, + constraint: ConstraintSoa, + rigid_variables: MutMap, + var_store: VarStore, +) -> (Solved, solve::Env, Vec) { + let env = solve::Env::default(); + + let mut subs = Subs::new_from_varstore(var_store); + + for (var, name) in rigid_variables { + subs.rigid_var(var, name); + } + + // Now that the module is parsed, canonicalized, and constrained, + // we need to type check it. + let mut problems = Vec::new(); + + // Run the solver to populate Subs. + let (solved_subs, solved_env) = + solve::run_soa(constraints, &env, &mut problems, subs, &constraint); + + (solved_subs, solved_env, problems) +} + pub fn make_solved_types( solved_subs: &Solved, exposed_vars_by_symbol: &[(Symbol, Variable)], diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index f62f15413e..b3964adf17 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -210,6 +210,53 @@ pub fn run_in_place( state.env } +use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; + +pub fn run_soa( + constraints: &Constraints, + env: &Env, + problems: &mut Vec, + mut subs: Subs, + constraint: &ConstraintSoa, +) -> (Solved, Env) { + let env = run_in_place_soa(constraints, env, problems, &mut subs, constraint); + + (Solved(subs), env) +} + +/// Modify an existing subs in-place instead +pub fn run_in_place_soa( + constraints: &Constraints, + env: &Env, + problems: &mut Vec, + subs: &mut Subs, + constraint: &ConstraintSoa, +) -> Env { + let mut pools = Pools::default(); + let state = State { + env: env.clone(), + mark: Mark::NONE.next(), + }; + let rank = Rank::toplevel(); + + let arena = Bump::new(); + + let state = solve_soa( + &arena, + constraints, + env, + state, + rank, + &mut pools, + problems, + &mut MutMap::default(), + subs, + constraint, + ); + + state.env +} + enum After { CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), } From 289e1a7ae1e9d1aa2c8d8a51bb2f527a2f5afa75 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 20:57:55 +0100 Subject: [PATCH 048/150] fix ordering bug --- compiler/can/src/constraint_soa.rs | 6 ++++-- compiler/load/src/file.rs | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index eb231a1af8..11cfb4d710 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -212,9 +212,10 @@ impl Constraints { I: IntoIterator, C: IntoIterator, { - let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + let defs_constraint = self.and_constraint(defs_constraint); - self.and_constraint(defs_constraint); + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + self.constraints.push(defs_constraint); self.constraints.push(Constraint::True); let let_contraint = LetConstraint { @@ -261,6 +262,7 @@ impl Constraints { Constraint::Let(let_index) } + #[must_use] pub fn and_constraint(&mut self, constraints: I) -> Constraint where I: IntoIterator, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 7f3dda5549..fef0bd9f3e 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -3091,8 +3091,6 @@ fn run_solve<'a>( // We have more constraining work to do now, so we'll add it to our timings. let constrain_start = SystemTime::now(); - dbg!(&constraint, &constraints); - // Finish constraining the module by wrapping the existing Constraint // in the ones we just computed. We can do this off the main thread. let constraint = constrain_imports_soa( From 54c6292b4bf948ac5efb2a735610af2a4bf0606f Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 20:59:51 +0100 Subject: [PATCH 049/150] clippy --- compiler/can/src/constraint_soa.rs | 6 ++++++ compiler/load/src/file.rs | 6 ++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 11cfb4d710..26f7a6a0b8 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -20,6 +20,12 @@ pub struct Constraints { pub includes_tags: Vec, } +impl Default for Constraints { + fn default() -> Self { + Self::new() + } +} + impl Constraints { pub fn new() -> Self { let constraints = Vec::new(); diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fef0bd9f3e..a9324a9776 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -5,16 +5,14 @@ use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::thread; use parking_lot::Mutex; use roc_builtins::std::StdLib; -use roc_can::constraint::Constraint; use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; use roc_constrain::module::{ - constrain_imports, constrain_imports_soa, constrain_module_soa, pre_constrain_imports, - ConstrainableImports, Import, + constrain_imports_soa, constrain_module_soa, pre_constrain_imports, ConstrainableImports, + ExposedModuleTypes, Import, SubsByModule, }; -use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, From 8d2e0a738c2ea024151a3b25ca3cfcabc3186c90 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 21:08:14 +0100 Subject: [PATCH 050/150] exploiting exactsizeiterator --- compiler/can/src/constraint_soa.rs | 21 +++++++++++++++------ compiler/constrain/src/soa_expr.rs | 8 ++++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs index 26f7a6a0b8..df1c478fd5 100644 --- a/compiler/can/src/constraint_soa.rs +++ b/compiler/can/src/constraint_soa.rs @@ -217,6 +217,7 @@ impl Constraints { where I: IntoIterator, C: IntoIterator, + C::IntoIter: ExactSizeIterator, { let defs_constraint = self.and_constraint(defs_constraint); @@ -268,20 +269,28 @@ impl Constraints { Constraint::Let(let_index) } - #[must_use] pub fn and_constraint(&mut self, constraints: I) -> Constraint where I: IntoIterator, + I::IntoIter: ExactSizeIterator, { - let start = self.constraints.len() as u32; + let mut it = constraints.into_iter(); - self.constraints.extend(constraints); + match it.len() { + 0 => Constraint::True, + 1 => it.next().unwrap(), + _ => { + let start = self.constraints.len() as u32; - let end = self.constraints.len() as u32; + self.constraints.extend(it); - let slice = Slice::new(start, (end - start) as u16); + let end = self.constraints.len() as u32; - Constraint::And(slice) + let slice = Slice::new(start, (end - start) as u16); + + Constraint::And(slice) + } + } } pub fn lookup( diff --git a/compiler/constrain/src/soa_expr.rs b/compiler/constrain/src/soa_expr.rs index 9d8bd17b19..ae483e1fae 100644 --- a/compiler/constrain/src/soa_expr.rs +++ b/compiler/constrain/src/soa_expr.rs @@ -1091,8 +1091,10 @@ pub fn constrain_expr( let category = Category::LowLevelOpResult(*op); + // Deviation: elm uses an additional And here let eq = constraints.equal_types(ret_type, expected, category, region); - constraints.exists_many(vars, arg_cons.into_iter().chain(std::iter::once(eq))) + arg_cons.push(eq); + constraints.exists_many(vars, arg_cons) } ForeignCall { args, @@ -1132,8 +1134,10 @@ pub fn constrain_expr( let category = Category::ForeignCall; + // Deviation: elm uses an additional And here let eq = constraints.equal_types(ret_type, expected, category, region); - constraints.exists_many(vars, arg_cons.into_iter().chain(std::iter::once(eq))) + arg_cons.push(eq); + constraints.exists_many(vars, arg_cons) } RuntimeError(_) => { // Runtime Errors have no constraints because they're going to crash. From 0eb98a4c5916a092b677c57bb41059540613bbe7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 21:19:25 +0100 Subject: [PATCH 051/150] move over constraint --- compiler/can/src/constraint.rs | 539 ++++++++++++++++------- compiler/can/src/constraint_soa.rs | 398 ----------------- compiler/can/src/lib.rs | 1 - compiler/constrain/src/builtins.rs | 134 +----- compiler/constrain/src/lib.rs | 4 +- compiler/constrain/src/module.rs | 107 +---- compiler/constrain/src/soa_expr.rs | 40 +- compiler/constrain/src/soa_pattern.rs | 2 +- compiler/load/src/file.rs | 2 +- compiler/solve/src/module.rs | 26 +- compiler/solve/src/solve.rs | 593 +------------------------- 11 files changed, 429 insertions(+), 1417 deletions(-) delete mode 100644 compiler/can/src/constraint_soa.rs diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index e36bfc22a6..df1c478fd5 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -1,175 +1,398 @@ use crate::expected::{Expected, PExpected}; -use roc_collections::all::{MutSet, SendMap}; -use roc_module::{ident::TagName, symbol::Symbol}; +use roc_collections::soa::{Index, Slice}; +use roc_module::ident::TagName; +use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; use roc_types::types::{Category, PatternCategory, Type}; -use roc_types::{subs::Variable, types::VariableDetail}; -/// A presence constraint is an additive constraint that defines the lower bound -/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the -/// type `t1` must contain at least the tag `A`. The additive nature of these -/// constraints makes them behaviorally different from unification-based constraints. -#[derive(Debug, Clone, PartialEq)] -pub enum PresenceConstraint { - IncludesTag(TagName, Vec, Region, PatternCategory), - IsOpen, - Pattern(Region, PatternCategory, PExpected), +#[derive(Debug)] +pub struct Constraints { + pub constraints: Vec, + pub types: Vec, + pub variables: Vec, + pub def_types: Vec<(Symbol, Loc>)>, + pub let_constraints: Vec, + pub categories: Vec, + pub pattern_categories: Vec, + pub expectations: Vec>, + pub pattern_expectations: Vec>, + pub includes_tags: Vec, } +impl Default for Constraints { + fn default() -> Self { + Self::new() + } +} + +impl Constraints { + pub fn new() -> Self { + let constraints = Vec::new(); + let mut types = Vec::new(); + let variables = Vec::new(); + let def_types = Vec::new(); + let let_constraints = Vec::new(); + let mut categories = Vec::new(); + let pattern_categories = Vec::new(); + let expectations = Vec::new(); + let pattern_expectations = Vec::new(); + let includes_tags = Vec::new(); + + types.push(Type::EmptyRec); + types.push(Type::EmptyTagUnion); + + categories.push(Category::Record); + + Self { + constraints, + types, + variables, + def_types, + let_constraints, + categories, + pattern_categories, + expectations, + pattern_expectations, + includes_tags, + } + } + + pub const EMPTY_RECORD: Index = Index::new(0); + pub const EMPTY_TAG_UNION: Index = Index::new(1); + + pub const CATEGORY_RECORD: Index = Index::new(0); + + #[inline(always)] + pub fn push_type(&mut self, typ: Type) -> Index { + match typ { + Type::EmptyRec => Self::EMPTY_RECORD, + Type::EmptyTagUnion => Self::EMPTY_TAG_UNION, + other => Index::push_new(&mut self.types, other), + } + } + + #[inline(always)] + pub fn push_expected_type(&mut self, expected: Expected) -> Index> { + Index::push_new(&mut self.expectations, expected) + } + + #[inline(always)] + pub fn push_category(&mut self, category: Category) -> Index { + match category { + Category::Record => Self::CATEGORY_RECORD, + other => Index::push_new(&mut self.categories, other), + } + } + + pub fn equal_types( + &mut self, + typ: Type, + expected: Expected, + category: Category, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.expectations, expected); + let category_index = Index::push_new(&mut self.categories, category); + + Constraint::Eq(type_index, expected_index, category_index, region) + } + + pub fn equal_pattern_types( + &mut self, + typ: Type, + expected: PExpected, + category: PatternCategory, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.pattern_expectations, expected); + let category_index = Index::push_new(&mut self.pattern_categories, category); + + Constraint::Pattern(type_index, expected_index, category_index, region) + } + + pub fn pattern_presence( + &mut self, + typ: Type, + expected: PExpected, + category: PatternCategory, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.pattern_expectations, expected); + let category_index = Index::push_new(&mut self.pattern_categories, category); + + Constraint::PatternPresence(type_index, expected_index, category_index, region) + } + + pub fn is_open_type(&mut self, typ: Type) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + + Constraint::IsOpenType(type_index) + } + + pub fn includes_tag( + &mut self, + typ: Type, + tag_name: TagName, + types: I, + category: PatternCategory, + region: Region, + ) -> Constraint + where + I: IntoIterator, + { + let type_index = Index::push_new(&mut self.types, typ); + let category_index = Index::push_new(&mut self.pattern_categories, category); + let types_slice = Slice::extend_new(&mut self.types, types); + + let includes_tag = IncludesTag { + type_index, + tag_name, + types: types_slice, + pattern_category: category_index, + region, + }; + + let includes_tag_index = Index::push_new(&mut self.includes_tags, includes_tag); + + Constraint::IncludesTag(includes_tag_index) + } + + fn variable_slice(&mut self, it: I) -> Slice + where + I: IntoIterator, + { + let start = self.variables.len(); + self.variables.extend(it); + let length = self.variables.len() - start; + + Slice::new(start as _, length as _) + } + + fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Loc)> + where + I: IntoIterator)>, + { + let start = self.def_types.len(); + + for (symbol, loc_type) in it { + let type_index = Index::new(self.types.len() as _); + let Loc { region, value } = loc_type; + self.types.push(value); + + self.def_types.push((symbol, Loc::at(region, type_index))); + } + + let length = self.def_types.len() - start; + + Slice::new(start as _, length as _) + } + + pub fn exists(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint + where + I: IntoIterator, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.constraints.push(defs_constraint); + self.constraints.push(Constraint::True); + + let let_contraint = LetConstraint { + rigid_vars: Slice::default(), + flex_vars: self.variable_slice(flex_vars), + def_types: Slice::default(), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + + pub fn exists_many(&mut self, flex_vars: I, defs_constraint: C) -> Constraint + where + I: IntoIterator, + C: IntoIterator, + C::IntoIter: ExactSizeIterator, + { + let defs_constraint = self.and_constraint(defs_constraint); + + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + self.constraints.push(defs_constraint); + self.constraints.push(Constraint::True); + + let let_contraint = LetConstraint { + rigid_vars: Slice::default(), + flex_vars: self.variable_slice(flex_vars), + def_types: Slice::default(), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + + pub fn let_constraint( + &mut self, + rigid_vars: I1, + flex_vars: I2, + def_types: I3, + defs_constraint: Constraint, + ret_constraint: Constraint, + ) -> Constraint + where + I1: IntoIterator, + I2: IntoIterator, + I3: IntoIterator)>, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.constraints.push(defs_constraint); + self.constraints.push(ret_constraint); + + let let_contraint = LetConstraint { + rigid_vars: self.variable_slice(rigid_vars), + flex_vars: self.variable_slice(flex_vars), + def_types: self.def_types_slice(def_types), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + + pub fn and_constraint(&mut self, constraints: I) -> Constraint + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + { + let mut it = constraints.into_iter(); + + match it.len() { + 0 => Constraint::True, + 1 => it.next().unwrap(), + _ => { + let start = self.constraints.len() as u32; + + self.constraints.extend(it); + + let end = self.constraints.len() as u32; + + let slice = Slice::new(start, (end - start) as u16); + + Constraint::And(slice) + } + } + } + + pub fn lookup( + &mut self, + symbol: Symbol, + expected: Expected, + region: Region, + ) -> Constraint { + Constraint::Lookup( + symbol, + Index::push_new(&mut self.expectations, expected), + region, + ) + } + pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool { + match constraint { + Constraint::Eq(..) => false, + Constraint::Store(..) => false, + Constraint::Lookup(..) => false, + Constraint::Pattern(..) => false, + Constraint::True => false, + Constraint::SaveTheEnvironment => true, + Constraint::Let(index) => { + let let_constraint = &self.let_constraints[index.index()]; + + let offset = let_constraint.defs_and_ret_constraint.index(); + let defs_constraint = &self.constraints[offset]; + let ret_constraint = &self.constraints[offset + 1]; + + self.contains_save_the_environment(defs_constraint) + || self.contains_save_the_environment(ret_constraint) + } + Constraint::And(slice) => { + let constraints = &self.constraints[slice.indices()]; + + constraints + .iter() + .any(|c| self.contains_save_the_environment(c)) + } + Constraint::IsOpenType(_) => false, + Constraint::IncludesTag(_) => false, + Constraint::PatternPresence(_, _, _, _) => false, + } + } + + pub fn store( + &mut self, + typ: Type, + variable: Variable, + filename: &'static str, + line_number: u32, + ) -> Constraint { + let type_index = Index::new(self.types.len() as _); + + self.types.push(typ); + + Constraint::Store(type_index, variable, filename, line_number) + } +} + +static_assertions::assert_eq_size!([u8; 4 * 8], Constraint); +static_assertions::assert_eq_size!([u8; 3 * 8 + 4], LetConstraint); + #[derive(Debug, Clone, PartialEq)] pub enum Constraint { - Eq(Type, Expected, Category, Region), - Store(Type, Variable, &'static str, u32), - Lookup(Symbol, Expected, Region), - Pattern(Region, PatternCategory, Type, PExpected), + Eq(Index, Index>, Index, Region), + Store(Index, Variable, &'static str, u32), + Lookup(Symbol, Index>, Region), + Pattern( + Index, + Index>, + Index, + Region, + ), True, // Used for things that always unify, e.g. blanks and runtime errors SaveTheEnvironment, - Let(Box), - And(Vec), - Present(Type, PresenceConstraint), + Let(Index), + And(Slice), + /// Presence constraints + IsOpenType(Index), // Theory; always applied to a variable? if yes the use that + IncludesTag(Index), + PatternPresence( + Index, + Index>, + Index, + Region, + ), } #[derive(Debug, Clone, PartialEq)] pub struct LetConstraint { - pub rigid_vars: Vec, - pub flex_vars: Vec, - pub def_types: SendMap>, - pub defs_constraint: Constraint, - pub ret_constraint: Constraint, + pub rigid_vars: Slice, + pub flex_vars: Slice, + pub def_types: Slice<(Symbol, Loc)>, + pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } -// VALIDATE - -#[derive(Default, Clone)] -struct Declared { - pub rigid_vars: MutSet, - pub flex_vars: MutSet, -} - -impl Constraint { - pub fn validate(&self) -> bool { - let mut unbound = Default::default(); - - validate_help(self, &Declared::default(), &mut unbound); - - if !unbound.type_variables.is_empty() { - panic!("found unbound type variables {:?}", &unbound.type_variables); - } - - if !unbound.lambda_set_variables.is_empty() { - panic!( - "found unbound lambda set variables {:?}", - &unbound.lambda_set_variables - ); - } - - if !unbound.recursion_variables.is_empty() { - panic!( - "found unbound recursion variables {:?}", - &unbound.recursion_variables - ); - } - - true - } - - pub fn contains_save_the_environment(&self) -> bool { - match self { - Constraint::Eq(_, _, _, _) => false, - Constraint::Store(_, _, _, _) => false, - Constraint::Lookup(_, _, _) => false, - Constraint::Pattern(_, _, _, _) => false, - Constraint::True => false, - Constraint::SaveTheEnvironment => true, - Constraint::Let(boxed) => { - boxed.ret_constraint.contains_save_the_environment() - || boxed.defs_constraint.contains_save_the_environment() - } - Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), - Constraint::Present(_, _) => false, - } - } -} - -fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) { - for var in &detail.type_variables { - if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) { - accum.type_variables.insert(*var); - } - } - - // lambda set variables are always flex - for var in &detail.lambda_set_variables { - if declared.rigid_vars.contains(var) { - panic!("lambda set variable {:?} is declared as rigid", var); - } - - if !declared.flex_vars.contains(var) { - accum.lambda_set_variables.push(*var); - } - } - - // recursion vars should be always rigid - for var in &detail.recursion_variables { - if declared.flex_vars.contains(var) { - panic!("recursion variable {:?} is declared as flex", var); - } - - if !declared.rigid_vars.contains(var) { - accum.recursion_variables.insert(*var); - } - } -} - -fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut VariableDetail) { - use Constraint::*; - - match constraint { - True | SaveTheEnvironment | Lookup(_, _, _) => { /* nothing */ } - Store(typ, var, _, _) => { - subtract(declared, &typ.variables_detail(), accum); - - if !declared.flex_vars.contains(var) { - accum.type_variables.insert(*var); - } - } - Constraint::Eq(typ, expected, _, _) => { - subtract(declared, &typ.variables_detail(), accum); - subtract(declared, &expected.get_type_ref().variables_detail(), accum); - } - Constraint::Pattern(_, _, typ, expected) => { - subtract(declared, &typ.variables_detail(), accum); - subtract(declared, &expected.get_type_ref().variables_detail(), accum); - } - Constraint::Let(letcon) => { - let mut declared = declared.clone(); - declared - .rigid_vars - .extend(letcon.rigid_vars.iter().copied()); - declared.flex_vars.extend(letcon.flex_vars.iter().copied()); - - validate_help(&letcon.defs_constraint, &declared, accum); - validate_help(&letcon.ret_constraint, &declared, accum); - } - Constraint::And(inner) => { - for c in inner { - validate_help(c, declared, accum); - } - } - Constraint::Present(typ, constr) => { - subtract(declared, &typ.variables_detail(), accum); - match constr { - PresenceConstraint::IncludesTag(_, tys, _, _) => { - for ty in tys { - subtract(declared, &ty.variables_detail(), accum); - } - } - PresenceConstraint::IsOpen => {} - PresenceConstraint::Pattern(_, _, expected) => { - subtract(declared, &typ.variables_detail(), accum); - subtract(declared, &expected.get_type_ref().variables_detail(), accum); - } - } - } - } +#[derive(Debug, Clone, PartialEq)] +pub struct IncludesTag { + pub type_index: Index, + pub tag_name: TagName, + pub types: Slice, + pub pattern_category: Index, + pub region: Region, } diff --git a/compiler/can/src/constraint_soa.rs b/compiler/can/src/constraint_soa.rs deleted file mode 100644 index df1c478fd5..0000000000 --- a/compiler/can/src/constraint_soa.rs +++ /dev/null @@ -1,398 +0,0 @@ -use crate::expected::{Expected, PExpected}; -use roc_collections::soa::{Index, Slice}; -use roc_module::ident::TagName; -use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region}; -use roc_types::subs::Variable; -use roc_types::types::{Category, PatternCategory, Type}; - -#[derive(Debug)] -pub struct Constraints { - pub constraints: Vec, - pub types: Vec, - pub variables: Vec, - pub def_types: Vec<(Symbol, Loc>)>, - pub let_constraints: Vec, - pub categories: Vec, - pub pattern_categories: Vec, - pub expectations: Vec>, - pub pattern_expectations: Vec>, - pub includes_tags: Vec, -} - -impl Default for Constraints { - fn default() -> Self { - Self::new() - } -} - -impl Constraints { - pub fn new() -> Self { - let constraints = Vec::new(); - let mut types = Vec::new(); - let variables = Vec::new(); - let def_types = Vec::new(); - let let_constraints = Vec::new(); - let mut categories = Vec::new(); - let pattern_categories = Vec::new(); - let expectations = Vec::new(); - let pattern_expectations = Vec::new(); - let includes_tags = Vec::new(); - - types.push(Type::EmptyRec); - types.push(Type::EmptyTagUnion); - - categories.push(Category::Record); - - Self { - constraints, - types, - variables, - def_types, - let_constraints, - categories, - pattern_categories, - expectations, - pattern_expectations, - includes_tags, - } - } - - pub const EMPTY_RECORD: Index = Index::new(0); - pub const EMPTY_TAG_UNION: Index = Index::new(1); - - pub const CATEGORY_RECORD: Index = Index::new(0); - - #[inline(always)] - pub fn push_type(&mut self, typ: Type) -> Index { - match typ { - Type::EmptyRec => Self::EMPTY_RECORD, - Type::EmptyTagUnion => Self::EMPTY_TAG_UNION, - other => Index::push_new(&mut self.types, other), - } - } - - #[inline(always)] - pub fn push_expected_type(&mut self, expected: Expected) -> Index> { - Index::push_new(&mut self.expectations, expected) - } - - #[inline(always)] - pub fn push_category(&mut self, category: Category) -> Index { - match category { - Category::Record => Self::CATEGORY_RECORD, - other => Index::push_new(&mut self.categories, other), - } - } - - pub fn equal_types( - &mut self, - typ: Type, - expected: Expected, - category: Category, - region: Region, - ) -> Constraint { - let type_index = Index::push_new(&mut self.types, typ); - let expected_index = Index::push_new(&mut self.expectations, expected); - let category_index = Index::push_new(&mut self.categories, category); - - Constraint::Eq(type_index, expected_index, category_index, region) - } - - pub fn equal_pattern_types( - &mut self, - typ: Type, - expected: PExpected, - category: PatternCategory, - region: Region, - ) -> Constraint { - let type_index = Index::push_new(&mut self.types, typ); - let expected_index = Index::push_new(&mut self.pattern_expectations, expected); - let category_index = Index::push_new(&mut self.pattern_categories, category); - - Constraint::Pattern(type_index, expected_index, category_index, region) - } - - pub fn pattern_presence( - &mut self, - typ: Type, - expected: PExpected, - category: PatternCategory, - region: Region, - ) -> Constraint { - let type_index = Index::push_new(&mut self.types, typ); - let expected_index = Index::push_new(&mut self.pattern_expectations, expected); - let category_index = Index::push_new(&mut self.pattern_categories, category); - - Constraint::PatternPresence(type_index, expected_index, category_index, region) - } - - pub fn is_open_type(&mut self, typ: Type) -> Constraint { - let type_index = Index::push_new(&mut self.types, typ); - - Constraint::IsOpenType(type_index) - } - - pub fn includes_tag( - &mut self, - typ: Type, - tag_name: TagName, - types: I, - category: PatternCategory, - region: Region, - ) -> Constraint - where - I: IntoIterator, - { - let type_index = Index::push_new(&mut self.types, typ); - let category_index = Index::push_new(&mut self.pattern_categories, category); - let types_slice = Slice::extend_new(&mut self.types, types); - - let includes_tag = IncludesTag { - type_index, - tag_name, - types: types_slice, - pattern_category: category_index, - region, - }; - - let includes_tag_index = Index::push_new(&mut self.includes_tags, includes_tag); - - Constraint::IncludesTag(includes_tag_index) - } - - fn variable_slice(&mut self, it: I) -> Slice - where - I: IntoIterator, - { - let start = self.variables.len(); - self.variables.extend(it); - let length = self.variables.len() - start; - - Slice::new(start as _, length as _) - } - - fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Loc)> - where - I: IntoIterator)>, - { - let start = self.def_types.len(); - - for (symbol, loc_type) in it { - let type_index = Index::new(self.types.len() as _); - let Loc { region, value } = loc_type; - self.types.push(value); - - self.def_types.push((symbol, Loc::at(region, type_index))); - } - - let length = self.def_types.len() - start; - - Slice::new(start as _, length as _) - } - - pub fn exists(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint - where - I: IntoIterator, - { - let defs_and_ret_constraint = Index::new(self.constraints.len() as _); - - self.constraints.push(defs_constraint); - self.constraints.push(Constraint::True); - - let let_contraint = LetConstraint { - rigid_vars: Slice::default(), - flex_vars: self.variable_slice(flex_vars), - def_types: Slice::default(), - defs_and_ret_constraint, - }; - - let let_index = Index::new(self.let_constraints.len() as _); - self.let_constraints.push(let_contraint); - - Constraint::Let(let_index) - } - - pub fn exists_many(&mut self, flex_vars: I, defs_constraint: C) -> Constraint - where - I: IntoIterator, - C: IntoIterator, - C::IntoIter: ExactSizeIterator, - { - let defs_constraint = self.and_constraint(defs_constraint); - - let defs_and_ret_constraint = Index::new(self.constraints.len() as _); - self.constraints.push(defs_constraint); - self.constraints.push(Constraint::True); - - let let_contraint = LetConstraint { - rigid_vars: Slice::default(), - flex_vars: self.variable_slice(flex_vars), - def_types: Slice::default(), - defs_and_ret_constraint, - }; - - let let_index = Index::new(self.let_constraints.len() as _); - self.let_constraints.push(let_contraint); - - Constraint::Let(let_index) - } - - pub fn let_constraint( - &mut self, - rigid_vars: I1, - flex_vars: I2, - def_types: I3, - defs_constraint: Constraint, - ret_constraint: Constraint, - ) -> Constraint - where - I1: IntoIterator, - I2: IntoIterator, - I3: IntoIterator)>, - { - let defs_and_ret_constraint = Index::new(self.constraints.len() as _); - - self.constraints.push(defs_constraint); - self.constraints.push(ret_constraint); - - let let_contraint = LetConstraint { - rigid_vars: self.variable_slice(rigid_vars), - flex_vars: self.variable_slice(flex_vars), - def_types: self.def_types_slice(def_types), - defs_and_ret_constraint, - }; - - let let_index = Index::new(self.let_constraints.len() as _); - self.let_constraints.push(let_contraint); - - Constraint::Let(let_index) - } - - pub fn and_constraint(&mut self, constraints: I) -> Constraint - where - I: IntoIterator, - I::IntoIter: ExactSizeIterator, - { - let mut it = constraints.into_iter(); - - match it.len() { - 0 => Constraint::True, - 1 => it.next().unwrap(), - _ => { - let start = self.constraints.len() as u32; - - self.constraints.extend(it); - - let end = self.constraints.len() as u32; - - let slice = Slice::new(start, (end - start) as u16); - - Constraint::And(slice) - } - } - } - - pub fn lookup( - &mut self, - symbol: Symbol, - expected: Expected, - region: Region, - ) -> Constraint { - Constraint::Lookup( - symbol, - Index::push_new(&mut self.expectations, expected), - region, - ) - } - pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool { - match constraint { - Constraint::Eq(..) => false, - Constraint::Store(..) => false, - Constraint::Lookup(..) => false, - Constraint::Pattern(..) => false, - Constraint::True => false, - Constraint::SaveTheEnvironment => true, - Constraint::Let(index) => { - let let_constraint = &self.let_constraints[index.index()]; - - let offset = let_constraint.defs_and_ret_constraint.index(); - let defs_constraint = &self.constraints[offset]; - let ret_constraint = &self.constraints[offset + 1]; - - self.contains_save_the_environment(defs_constraint) - || self.contains_save_the_environment(ret_constraint) - } - Constraint::And(slice) => { - let constraints = &self.constraints[slice.indices()]; - - constraints - .iter() - .any(|c| self.contains_save_the_environment(c)) - } - Constraint::IsOpenType(_) => false, - Constraint::IncludesTag(_) => false, - Constraint::PatternPresence(_, _, _, _) => false, - } - } - - pub fn store( - &mut self, - typ: Type, - variable: Variable, - filename: &'static str, - line_number: u32, - ) -> Constraint { - let type_index = Index::new(self.types.len() as _); - - self.types.push(typ); - - Constraint::Store(type_index, variable, filename, line_number) - } -} - -static_assertions::assert_eq_size!([u8; 4 * 8], Constraint); -static_assertions::assert_eq_size!([u8; 3 * 8 + 4], LetConstraint); - -#[derive(Debug, Clone, PartialEq)] -pub enum Constraint { - Eq(Index, Index>, Index, Region), - Store(Index, Variable, &'static str, u32), - Lookup(Symbol, Index>, Region), - Pattern( - Index, - Index>, - Index, - Region, - ), - True, // Used for things that always unify, e.g. blanks and runtime errors - SaveTheEnvironment, - Let(Index), - And(Slice), - /// Presence constraints - IsOpenType(Index), // Theory; always applied to a variable? if yes the use that - IncludesTag(Index), - PatternPresence( - Index, - Index>, - Index, - Region, - ), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct LetConstraint { - pub rigid_vars: Slice, - pub flex_vars: Slice, - pub def_types: Slice<(Symbol, Loc)>, - pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct IncludesTag { - pub type_index: Index, - pub tag_name: TagName, - pub types: Slice, - pub pattern_category: Index, - pub region: Region, -} diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index e293771f6c..fd813a423f 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -4,7 +4,6 @@ pub mod annotation; pub mod builtins; pub mod constraint; -pub mod constraint_soa; pub mod def; pub mod effect_module; pub mod env; diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index df7c0e9bb9..7ff498f3e2 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,11 +1,7 @@ use arrayvec::ArrayVec; -use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::LetConstraint; -use roc_can::constraint_soa; -use roc_can::constraint_soa::Constraints; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::Expected::{self, *}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; -use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::Region; @@ -14,39 +10,11 @@ use roc_types::types::Reason; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; -#[must_use] -pub fn add_numeric_bound_constr( - constrs: &mut Vec, - num_type: Type, - bound: impl TypedNumericBound, - region: Region, - category: Category, -) -> Type { - let range = bound.bounded_range(); - - let total_num_type = num_type; - - match range.len() { - 0 => total_num_type, - 1 => { - let actual_type = Variable(range[0]); - constrs.push(Eq( - total_num_type.clone(), - Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), - category, - region, - )); - total_num_type - } - _ => RangedNumber(Box::new(total_num_type), range), - } -} - #[must_use] #[inline(always)] pub fn add_numeric_bound_constr_soa( constraints: &mut Constraints, - num_constraints: &mut impl Extend, + num_constraints: &mut impl Extend, num_type: Type, bound: impl TypedNumericBound, region: Region, @@ -72,87 +40,6 @@ pub fn add_numeric_bound_constr_soa( } } -#[inline(always)] -pub fn int_literal( - num_var: Variable, - precision_var: Variable, - expected: Expected, - region: Region, - bound: IntBound, -) -> Constraint { - let reason = Reason::IntLiteral; - - let mut constrs = Vec::with_capacity(3); - // Always add the bound first; this improves the resolved type quality in case it's an alias - // like "U8". - let num_type = add_numeric_bound_constr( - &mut constrs, - Variable(num_var), - bound, - region, - Category::Num, - ); - constrs.extend(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_int(Type::Variable(precision_var)), region), - Category::Int, - region, - ), - Eq(num_type, expected, Category::Int, region), - ]); - - exists(vec![num_var], And(constrs)) -} - -#[inline(always)] -pub fn float_literal( - num_var: Variable, - precision_var: Variable, - expected: Expected, - region: Region, - bound: FloatBound, -) -> Constraint { - let reason = Reason::FloatLiteral; - - let mut constrs = Vec::with_capacity(3); - let num_type = add_numeric_bound_constr( - &mut constrs, - Variable(num_var), - bound, - region, - Category::Float, - ); - constrs.extend(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_float(Type::Variable(precision_var)), region), - Category::Float, - region, - ), - Eq(num_type, expected, Category::Float, region), - ]); - - exists(vec![num_var, precision_var], And(constrs)) -} - -#[inline(always)] -pub fn num_literal( - num_var: Variable, - expected: Expected, - region: Region, - bound: NumericBound, -) -> Constraint { - let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); - - let mut constrs = Vec::with_capacity(3); - let num_type = - add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num); - constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); - - exists(vec![num_var], And(constrs)) -} - #[inline(always)] pub fn int_literal_soa( constraints: &mut Constraints, @@ -161,7 +48,7 @@ pub fn int_literal_soa( expected: Expected, region: Region, bound: IntBound, -) -> constraint_soa::Constraint { +) -> Constraint { let reason = Reason::IntLiteral; // Always add the bound first; this improves the resolved type quality in case it's an alias like "U8". @@ -198,7 +85,7 @@ pub fn float_literal_soa( expected: Expected, region: Region, bound: FloatBound, -) -> constraint_soa::Constraint { +) -> Constraint { let reason = Reason::FloatLiteral; let mut constrs = ArrayVec::<_, 3>::new(); @@ -232,7 +119,7 @@ pub fn num_literal_soa( expected: Expected, region: Region, bound: NumericBound, -) -> constraint_soa::Constraint { +) -> Constraint { let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); let mut constrs = ArrayVec::<_, 2>::new(); @@ -251,17 +138,6 @@ pub fn num_literal_soa( constraints.exists([num_var], and_constraint) } -#[inline(always)] -pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars, - def_types: SendMap::default(), - defs_constraint: constraint, - ret_constraint: Constraint::True, - })) -} - #[inline(always)] pub fn builtin_type(symbol: Symbol, args: Vec) -> Type { Type::Apply(symbol, args, Region::zero()) diff --git a/compiler/constrain/src/lib.rs b/compiler/constrain/src/lib.rs index 67edd169ed..c2c51392f0 100644 --- a/compiler/constrain/src/lib.rs +++ b/compiler/constrain/src/lib.rs @@ -2,8 +2,8 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] pub mod builtins; -pub mod expr; +// pub mod expr; pub mod module; -pub mod pattern; +// pub mod pattern; pub mod soa_expr; pub mod soa_pattern; diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 553106162d..d63438e835 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,7 +1,5 @@ -use crate::expr::constrain_decls; use roc_builtins::std::StdLib; -use roc_can::constraint::{Constraint, LetConstraint}; -use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::Declaration; use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_module::symbol::{ModuleId, Symbol}; @@ -18,20 +16,16 @@ pub enum ExposedModuleTypes { Valid(MutMap, MutMap), } -pub struct ConstrainedModule { - pub unused_imports: MutMap, - pub constraint: Constraint, -} - -pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constraint { - constrain_decls(home, declarations) -} +// pub struct ConstrainedModule { +// pub unused_imports: MutMap, +// pub constraint: Constraint, +// } pub fn constrain_module_soa( constraints: &mut Constraints, declarations: &[Declaration], home: ModuleId, -) -> ConstraintSoa { +) -> Constraint { crate::soa_expr::constrain_decls(constraints, home, declarations) } @@ -41,12 +35,12 @@ pub struct Import { pub solved_type: SolvedType, } -pub fn constrain_imported_values( +pub fn constrain_imported_values_soa( + constraints: &mut Constraints, imports: Vec, body_con: Constraint, var_store: &mut VarStore, ) -> (Vec, Constraint) { - use Constraint::*; let mut def_types = SendMap::default(); let mut rigid_vars = Vec::new(); @@ -93,96 +87,17 @@ pub fn constrain_imported_values( ( rigid_vars.clone(), - Let(Box::new(LetConstraint { - rigid_vars, - flex_vars: Vec::new(), - def_types, - defs_constraint: True, - ret_constraint: body_con, - })), + constraints.let_constraint(rigid_vars, [], def_types, Constraint::True, body_con), ) } -pub fn constrain_imported_values_soa( - constraints: &mut Constraints, - imports: Vec, - body_con: ConstraintSoa, - var_store: &mut VarStore, -) -> (Vec, ConstraintSoa) { - let mut def_types = SendMap::default(); - let mut rigid_vars = Vec::new(); - - for import in imports { - let mut free_vars = FreeVars::default(); - let loc_symbol = import.loc_symbol; - - // an imported symbol can be either an alias or a value - match import.solved_type { - SolvedType::Alias(symbol, _, _, _, _) if symbol == loc_symbol.value => { - // do nothing, in the future the alias definitions should not be in the list of imported values - } - _ => { - let typ = roc_types::solved_types::to_type( - &import.solved_type, - &mut free_vars, - var_store, - ); - - def_types.insert( - loc_symbol.value, - Loc { - region: loc_symbol.region, - value: typ, - }, - ); - - for (_, var) in free_vars.named_vars { - rigid_vars.push(var); - } - - for var in free_vars.wildcards { - rigid_vars.push(var); - } - - // Variables can lose their name during type inference. But the unnamed - // variables are still part of a signature, and thus must be treated as rigids here! - for (_, var) in free_vars.unnamed_vars { - rigid_vars.push(var); - } - } - } - } - - ( - rigid_vars.clone(), - constraints.let_constraint(rigid_vars, [], def_types, ConstraintSoa::True, body_con), - ) -} - -/// Run pre_constrain_imports to get imported_symbols and imported_aliases. -pub fn constrain_imports( - imported_symbols: Vec, - constraint: Constraint, - var_store: &mut VarStore, -) -> Constraint { - let (_introduced_rigids, constraint) = - constrain_imported_values(imported_symbols, constraint, var_store); - - // TODO determine what to do with those rigids - // for var in introduced_rigids { - // output.ftv.insert(var, format!("internal_{:?}", var).into()); - // } - - constraint -} - /// Run pre_constrain_imports to get imported_symbols and imported_aliases. pub fn constrain_imports_soa( constraints: &mut Constraints, imported_symbols: Vec, - constraint: ConstraintSoa, + constraint: Constraint, var_store: &mut VarStore, -) -> ConstraintSoa { +) -> Constraint { let (_introduced_rigids, constraint) = constrain_imported_values_soa(constraints, imported_symbols, constraint, var_store); diff --git a/compiler/constrain/src/soa_expr.rs b/compiler/constrain/src/soa_expr.rs index ae483e1fae..ec3a1cb37c 100644 --- a/compiler/constrain/src/soa_expr.rs +++ b/compiler/constrain/src/soa_expr.rs @@ -4,9 +4,7 @@ use crate::builtins::{ }; use crate::soa_pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; -// use roc_can::constraint::Constraint::{self, *}; -// use roc_can::constraint::LetConstraint; -use roc_can::constraint_soa::{Constraint, Constraints}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; @@ -21,23 +19,23 @@ use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField}; -/// This is for constraining Defs -#[derive(Default, Debug)] -pub struct Info { - pub vars: Vec, - pub constraints: Vec, - pub def_types: SendMap>, -} - -impl Info { - pub fn with_capacity(capacity: usize) -> Self { - Info { - vars: Vec::with_capacity(capacity), - constraints: Vec::with_capacity(capacity), - def_types: SendMap::default(), - } - } -} + /// This is for constraining Defs + #[derive(Default, Debug)] + pub struct Info { + pub vars: Vec, + pub constraints: Vec, + pub def_types: SendMap>, + } + + impl Info { + pub fn with_capacity(capacity: usize) -> Self { + Info { + vars: Vec::with_capacity(capacity), + constraints: Vec::with_capacity(capacity), + def_types: SendMap::default(), + } + } + } pub struct Env { /// Whenever we encounter a user-defined type variable (a "rigid" var for short), @@ -1662,7 +1660,7 @@ fn instantiate_rigids( annotation.substitute(&rigid_substitution); } - if let Some(new_headers) = crate::pattern::headers_from_annotation( + if let Some(new_headers) = crate::soa_pattern::headers_from_annotation( &loc_pattern.value, &Loc::at(loc_pattern.region, annotation.clone()), ) { diff --git a/compiler/constrain/src/soa_pattern.rs b/compiler/constrain/src/soa_pattern.rs index 721a1488d7..54de2ce469 100644 --- a/compiler/constrain/src/soa_pattern.rs +++ b/compiler/constrain/src/soa_pattern.rs @@ -1,6 +1,6 @@ use crate::builtins; use crate::soa_expr::{constrain_expr, Env}; -use roc_can::constraint_soa::{Constraint, Constraints}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::{DestructType, RecordDestruct}; diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index a9324a9776..97b0cea115 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -5,7 +5,7 @@ use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::thread; use parking_lot::Mutex; use roc_builtins::std::StdLib; -use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; +use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index 31730a8b29..c3c84e9720 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,6 +1,5 @@ use crate::solve; -use roc_can::constraint::Constraint; -use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; +use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -17,29 +16,6 @@ pub struct SolvedModule { pub problems: Vec, } -pub fn run_solve( - rigid_variables: MutMap, - constraint: Constraint, - var_store: VarStore, -) -> (Solved, solve::Env, Vec) { - let env = solve::Env::default(); - - let mut subs = Subs::new_from_varstore(var_store); - - for (var, name) in rigid_variables { - subs.rigid_var(var, name); - } - - // Now that the module is parsed, canonicalized, and constrained, - // we need to type check it. - let mut problems = Vec::new(); - - // Run the solver to populate Subs. - let (solved_subs, solved_env) = solve::run(&env, &mut problems, subs, &constraint); - - (solved_subs, solved_env, problems) -} - pub fn run_solve_soa( constraints: &Constraints, constraint: ConstraintSoa, diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index b3964adf17..6bd9b13110 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,5 +1,5 @@ use bumpalo::Bump; -use roc_can::constraint::Constraint; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; use roc_module::ident::TagName; @@ -168,56 +168,12 @@ struct State { mark: Mark, } -pub fn run( - env: &Env, - problems: &mut Vec, - mut subs: Subs, - constraint: &Constraint, -) -> (Solved, Env) { - let env = run_in_place(env, problems, &mut subs, constraint); - - (Solved(subs), env) -} - -/// Modify an existing subs in-place instead -pub fn run_in_place( - env: &Env, - problems: &mut Vec, - subs: &mut Subs, - constraint: &Constraint, -) -> Env { - let mut pools = Pools::default(); - let state = State { - env: env.clone(), - mark: Mark::NONE.next(), - }; - let rank = Rank::toplevel(); - - let arena = Bump::new(); - - let state = solve( - &arena, - env, - state, - rank, - &mut pools, - problems, - &mut MutMap::default(), - subs, - constraint, - ); - - state.env -} - -use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints}; - pub fn run_soa( constraints: &Constraints, env: &Env, problems: &mut Vec, mut subs: Subs, - constraint: &ConstraintSoa, + constraint: &Constraint, ) -> (Solved, Env) { let env = run_in_place_soa(constraints, env, problems, &mut subs, constraint); @@ -230,7 +186,7 @@ pub fn run_in_place_soa( env: &Env, problems: &mut Vec, subs: &mut Subs, - constraint: &ConstraintSoa, + constraint: &Constraint, ) -> Env { let mut pools = Pools::default(); let state = State { @@ -261,553 +217,20 @@ enum After { CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), } -enum Work<'a> { - Constraint { - env: &'a Env, - rank: Rank, - constraint: &'a Constraint, - after: Option, - }, - /// Something to be done after a constraint and all its dependencies are fully solved. - After(After), -} - enum WorkSoa<'a> { Constraint { env: &'a Env, rank: Rank, - constraint: &'a roc_can::constraint_soa::Constraint, + constraint: &'a Constraint, }, /// Something to be done after a constraint and all its dependencies are fully solved. After(After), } -#[allow(clippy::too_many_arguments)] -fn solve( - arena: &Bump, - env: &Env, - mut state: State, - rank: Rank, - pools: &mut Pools, - problems: &mut Vec, - cached_aliases: &mut MutMap, - subs: &mut Subs, - constraint: &roc_can::constraint::Constraint, -) -> State { - use roc_can::constraint::PresenceConstraint; - use Constraint::*; - - let mut stack = vec![Work::Constraint { - env, - rank, - constraint, - after: None, - }]; - - while let Some(work_item) = stack.pop() { - let (env, rank, constraint) = match work_item { - Work::After(After::CheckForInfiniteTypes(def_vars)) => { - for (symbol, loc_var) in def_vars.iter() { - check_for_infinite_type(subs, problems, *symbol, *loc_var); - } - // No constraint to be solved - continue; - } - Work::Constraint { - env, - rank, - constraint, - after, - } => { - // Push the `after` on first so that we look at it immediately after finishing all - // the children of this constraint. - if let Some(after) = after { - stack.push(Work::After(after)); - } - (env, rank, constraint) - } - }; - - state = match constraint { - True => state, - SaveTheEnvironment => { - // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment - let mut copy = state; - - copy.env = env.clone(); - - copy - } - Eq(typ, expectation, category, region) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - category.clone(), - actual_type, - expectation.clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - Store(source, target, _filename, _linenr) => { - // a special version of Eq that is used to store types in the AST. - // IT DOES NOT REPORT ERRORS! - let actual = type_to_var(subs, rank, pools, cached_aliases, source); - let target = *target; - - match unify(subs, actual, target, Mode::EQ) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, _actual_type, _expected_type) => { - introduce(subs, rank, pools, &vars); - - // ERROR NOT REPORTED - - state - } - BadType(vars, _) => { - introduce(subs, rank, pools, &vars); - - // ERROR NOT REPORTED - - state - } - } - } - Lookup(symbol, expectation, region) => { - match env.get_var_by_symbol(symbol) { - Some(var) => { - // Deep copy the vars associated with this symbol before unifying them. - // Otherwise, suppose we have this: - // - // identity = \a -> a - // - // x = identity 5 - // - // When we call (identity 5), it's important that we not unify - // on identity's original vars. If we do, the type of `identity` will be - // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; - // the type of `identity` is more general than that! - // - // Instead, we want to unify on a *copy* of its vars. If the copy unifies - // successfully (in this case, to `Int -> Int`), we can use that to - // infer the type of this lookup (in this case, `Int`) without ever - // having mutated the original. - // - // If this Lookup is targeting a value in another module, - // then we copy from that module's Subs into our own. If the value - // is being looked up in this module, then we use our Subs as both - // the source and destination. - let actual = deep_copy_var(subs, rank, pools, var); - let expected = type_to_var( - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - Category::Lookup(*symbol), - actual_type, - expectation.clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - None => { - problems.push(TypeError::UnexposedLookup(*symbol)); - - state - } - } - } - And(sub_constraints) => { - for sub_constraint in sub_constraints.iter().rev() { - stack.push(Work::Constraint { - env, - rank, - constraint: sub_constraint, - after: None, - }) - } - - state - } - Pattern(region, category, typ, expectation) - | Present(typ, PresenceConstraint::Pattern(region, category, expectation)) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - let mode = match constraint { - Present(_, _) => Mode::PRESENT, - _ => Mode::EQ, - }; - - match unify(subs, actual, expected, mode) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadPattern( - *region, - category.clone(), - actual_type, - expectation.clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - Let(let_con) => { - match &let_con.ret_constraint { - True if let_con.rigid_vars.is_empty() => { - introduce(subs, rank, pools, &let_con.flex_vars); - - // If the return expression is guaranteed to solve, - // solve the assignments themselves and move on. - stack.push(Work::Constraint { - env, - rank, - constraint: &let_con.defs_constraint, - after: None, - }); - state - } - ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - // TODO: make into `WorkItem` with `After` - let state = solve( - arena, - env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - // Add a variable for each def to new_vars_by_env. - let mut local_def_vars = - LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let var = - type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); - } - - stack.push(Work::Constraint { - env: arena.alloc(new_env), - rank, - constraint: ret_con, - after: Some(After::CheckForInfiniteTypes(local_def_vars)), - }); - - state - } - ret_con => { - let rigid_vars = &let_con.rigid_vars; - let flex_vars = &let_con.flex_vars; - - // work in the next pool to localize header - let next_rank = rank.next(); - - // introduce variables - for &var in rigid_vars.iter().chain(flex_vars.iter()) { - subs.set_rank(var, next_rank); - } - - // determine the next pool - if next_rank.into_usize() < pools.len() { - // Nothing to do, we already accounted for the next rank, no need to - // adjust the pools - } else { - // we should be off by one at this point - debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); - pools.extend_to(next_rank.into_usize()); - } - - let pool: &mut Vec = pools.get_mut(next_rank); - - // Replace the contents of this pool with rigid_vars and flex_vars - pool.clear(); - pool.reserve(rigid_vars.len() + flex_vars.len()); - pool.extend(rigid_vars.iter()); - pool.extend(flex_vars.iter()); - - // run solver in next pool - - // Add a variable for each def to local_def_vars. - let mut local_def_vars = - LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let def_type = &loc_type.value; - - let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } - - // Solve the assignments' constraints first. - // TODO: make into `WorkItem` with `After` - let State { - env: saved_env, - mark, - } = solve( - arena, - env, - state, - next_rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - let young_mark = mark; - let visit_mark = young_mark.next(); - let final_mark = visit_mark.next(); - - debug_assert_eq!( - { - let offenders = pools - .get(next_rank) - .iter() - .filter(|var| { - let current_rank = - subs.get_rank(roc_types::subs::Variable::clone(var)); - - current_rank.into_usize() > next_rank.into_usize() - }) - .collect::>(); - - let result = offenders.len(); - - if result > 0 { - dbg!(&subs, &offenders, &let_con.def_types); - } - - result - }, - 0 - ); - - // pop pool - generalize(subs, young_mark, visit_mark, next_rank, pools); - - pools.get_mut(next_rank).clear(); - - // check that things went well - debug_assert!({ - // NOTE the `subs.redundant` check is added for the uniqueness - // inference, and does not come from elm. It's unclear whether this is - // a bug with uniqueness inference (something is redundant that - // shouldn't be) or that it just never came up in elm. - let failing: Vec<_> = rigid_vars - .iter() - .filter(|&var| { - !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE - }) - .collect(); - - if !failing.is_empty() { - println!("Rigids {:?}", &rigid_vars); - println!("Failing {:?}", failing); - } - - failing.is_empty() - }); - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); - } - - // Note that this vars_by_symbol is the one returned by the - // previous call to solve() - let state_for_ret_con = State { - env: saved_env, - mark: final_mark, - }; - - // Now solve the body, using the new vars_by_symbol which includes - // the assignments' name-to-variable mappings. - stack.push(Work::Constraint { - env: arena.alloc(new_env), - rank, - constraint: ret_con, - after: Some(After::CheckForInfiniteTypes(local_def_vars)), - }); - - state_for_ret_con - } - } - } - Present(typ, PresenceConstraint::IsOpen) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - let mut new_desc = subs.get(actual); - match new_desc.content { - Content::Structure(FlatType::TagUnion(tags, _)) => { - let new_ext = subs.fresh_unnamed_flex_var(); - let new_union = Content::Structure(FlatType::TagUnion(tags, new_ext)); - new_desc.content = new_union; - subs.set(actual, new_desc); - state - } - _ => { - // Today, an "open" constraint doesn't affect any types - // other than tag unions. Recursive tag unions are constructed - // at a later time (during occurs checks after tag unions are - // resolved), so that's not handled here either. - // NB: Handle record types here if we add presence constraints - // to their type inference as well. - state - } - } - } - Present( - typ, - PresenceConstraint::IncludesTag(tag_name, tys, region, pattern_category), - ) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - let tag_ty = Type::TagUnion( - vec![(tag_name.clone(), tys.clone())], - Box::new(Type::EmptyTagUnion), - ); - let includes = type_to_var(subs, rank, pools, cached_aliases, &tag_ty); - - match unify(subs, actual, includes, Mode::PRESENT) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_to_include_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadPattern( - *region, - pattern_category.clone(), - expected_to_include_type, - PExpected::NoExpectation(actual_type), - ); - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - }; - } - - state -} - #[allow(clippy::too_many_arguments)] fn solve_soa( arena: &Bump, - constraints: &roc_can::constraint_soa::Constraints, + constraints: &Constraints, env: &Env, mut state: State, rank: Rank, @@ -815,9 +238,9 @@ fn solve_soa( problems: &mut Vec, cached_aliases: &mut MutMap, subs: &mut Subs, - constraint: &roc_can::constraint_soa::Constraint, + constraint: &Constraint, ) -> State { - use roc_can::constraint_soa::Constraint::*; + use Constraint::*; let initial = WorkSoa::Constraint { env, @@ -1300,7 +723,7 @@ fn solve_soa( IncludesTag(index) => { let includes_tag = &constraints.includes_tags[index.index()]; - let roc_can::constraint_soa::IncludesTag { + let roc_can::constraint::IncludesTag { type_index, tag_name, types, From 546afc9661e733e6ede1498a60e18146428931a2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 21:21:37 +0100 Subject: [PATCH 052/150] drop soa suffix --- compiler/solve/src/module.rs | 3 +-- compiler/solve/src/solve.rs | 41 ++++++++++++++++-------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index c3c84e9720..1404fc1d0d 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -35,8 +35,7 @@ pub fn run_solve_soa( let mut problems = Vec::new(); // Run the solver to populate Subs. - let (solved_subs, solved_env) = - solve::run_soa(constraints, &env, &mut problems, subs, &constraint); + let (solved_subs, solved_env) = solve::run(constraints, &env, &mut problems, subs, &constraint); (solved_subs, solved_env, problems) } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 6bd9b13110..6abe72cced 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -168,20 +168,20 @@ struct State { mark: Mark, } -pub fn run_soa( +pub fn run( constraints: &Constraints, env: &Env, problems: &mut Vec, mut subs: Subs, constraint: &Constraint, ) -> (Solved, Env) { - let env = run_in_place_soa(constraints, env, problems, &mut subs, constraint); + let env = run_in_place(constraints, env, problems, &mut subs, constraint); (Solved(subs), env) } /// Modify an existing subs in-place instead -pub fn run_in_place_soa( +pub fn run_in_place( constraints: &Constraints, env: &Env, problems: &mut Vec, @@ -197,7 +197,7 @@ pub fn run_in_place_soa( let arena = Bump::new(); - let state = solve_soa( + let state = solve( &arena, constraints, env, @@ -213,22 +213,17 @@ pub fn run_in_place_soa( state.env } -enum After { - CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), -} - -enum WorkSoa<'a> { +enum SolveWork<'a> { Constraint { env: &'a Env, rank: Rank, constraint: &'a Constraint, }, - /// Something to be done after a constraint and all its dependencies are fully solved. - After(After), + CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), } #[allow(clippy::too_many_arguments)] -fn solve_soa( +fn solve( arena: &Bump, constraints: &Constraints, env: &Env, @@ -242,7 +237,7 @@ fn solve_soa( ) -> State { use Constraint::*; - let initial = WorkSoa::Constraint { + let initial = SolveWork::Constraint { env, rank, constraint, @@ -251,14 +246,14 @@ fn solve_soa( while let Some(work_item) = stack.pop() { let (env, rank, constraint) = match work_item { - WorkSoa::After(After::CheckForInfiniteTypes(def_vars)) => { + SolveWork::CheckForInfiniteTypes(def_vars) => { for (symbol, loc_var) in def_vars.iter() { check_for_infinite_type(subs, problems, *symbol, *loc_var); } // No constraint to be solved continue; } - WorkSoa::Constraint { + SolveWork::Constraint { env, rank, constraint, @@ -423,7 +418,7 @@ fn solve_soa( And(slice) => { let it = constraints.constraints[slice.indices()].iter().rev(); for sub_constraint in it { - stack.push(WorkSoa::Constraint { + stack.push(SolveWork::Constraint { env, rank, constraint: sub_constraint, @@ -499,7 +494,7 @@ fn solve_soa( // If the return expression is guaranteed to solve, // solve the assignments themselves and move on. - stack.push(WorkSoa::Constraint { + stack.push(SolveWork::Constraint { env, rank, constraint: defs_constraint, @@ -508,7 +503,7 @@ fn solve_soa( } ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { // TODO: make into `WorkItem` with `After` - let state = solve_soa( + let state = solve( arena, constraints, env, @@ -543,8 +538,8 @@ fn solve_soa( new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); } - stack.push(WorkSoa::After(After::CheckForInfiniteTypes(local_def_vars))); - stack.push(WorkSoa::Constraint { + stack.push(SolveWork::CheckForInfiniteTypes(local_def_vars)); + stack.push(SolveWork::Constraint { env: arena.alloc(new_env), rank, constraint: ret_con, @@ -604,7 +599,7 @@ fn solve_soa( let State { env: saved_env, mark, - } = solve_soa( + } = solve( arena, constraints, env, @@ -685,8 +680,8 @@ fn solve_soa( // Now solve the body, using the new vars_by_symbol which includes // the assignments' name-to-variable mappings. - stack.push(WorkSoa::After(After::CheckForInfiniteTypes(local_def_vars))); - stack.push(WorkSoa::Constraint { + stack.push(SolveWork::CheckForInfiniteTypes(local_def_vars)); + stack.push(SolveWork::Constraint { env: arena.alloc(new_env), rank, constraint: ret_con, From 828483393af6c9fff88d1224ee42a2af7e39200b Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 21:25:13 +0100 Subject: [PATCH 053/150] move expr/pattern constraint gen --- compiler/constrain/src/expr.rs | 911 ++++++----- compiler/constrain/src/lib.rs | 6 +- compiler/constrain/src/module.rs | 6 +- compiler/constrain/src/pattern.rs | 156 +- compiler/constrain/src/soa_expr.rs | 2012 ------------------------- compiler/constrain/src/soa_pattern.rs | 540 ------- 6 files changed, 630 insertions(+), 3001 deletions(-) delete mode 100644 compiler/constrain/src/soa_expr.rs delete mode 100644 compiler/constrain/src/soa_pattern.rs diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index cbced89851..083339638f 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1,10 +1,10 @@ use crate::builtins::{ - empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type, + empty_list_type, float_literal_soa, int_literal_soa, list_type, num_literal_soa, num_u32, + str_type, }; use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; -use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::LetConstraint; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; @@ -19,34 +19,23 @@ use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField}; -/// This is for constraining Defs -#[derive(Default, Debug)] -pub struct Info { - pub vars: Vec, - pub constraints: Vec, - pub def_types: SendMap>, -} - -impl Info { - pub fn with_capacity(capacity: usize) -> Self { - Info { - vars: Vec::with_capacity(capacity), - constraints: Vec::with_capacity(capacity), - def_types: SendMap::default(), - } - } -} - -#[inline(always)] -pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars, - def_types: SendMap::default(), - defs_constraint: constraint, - ret_constraint: Constraint::True, - })) -} + /// This is for constraining Defs + #[derive(Default, Debug)] + pub struct Info { + pub vars: Vec, + pub constraints: Vec, + pub def_types: SendMap>, + } + + impl Info { + pub fn with_capacity(capacity: usize) -> Self { + Info { + vars: Vec::with_capacity(capacity), + constraints: Vec::with_capacity(capacity), + def_types: SendMap::default(), + } + } + } pub struct Env { /// Whenever we encounter a user-defined type variable (a "rigid" var for short), @@ -57,6 +46,7 @@ pub struct Env { } fn constrain_untyped_args( + constraints: &mut Constraints, env: &Env, arguments: &[(Variable, Loc)], closure_type: Type, @@ -74,6 +64,7 @@ fn constrain_untyped_args( pattern_types.push(pattern_type); constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -91,21 +82,24 @@ fn constrain_untyped_args( } pub fn constrain_expr( + constraints: &mut Constraints, env: &Env, region: Region, expr: &Expr, expected: Expected, ) -> Constraint { match expr { - &Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound), - &Num(var, _, _, bound) => num_literal(var, expected, region, bound), - &Float(var, precision, _, _, bound) => { - float_literal(var, precision, expected, region, bound) + &Int(var, precision, _, _, bound) => { + int_literal_soa(constraints, var, precision, expected, region, bound) } - EmptyRecord => constrain_empty_record(region, expected), + &Num(var, _, _, bound) => num_literal_soa(constraints, var, expected, region, bound), + &Float(var, precision, _, _, bound) => { + float_literal_soa(constraints, var, precision, expected, region, bound) + } + EmptyRecord => constrain_empty_record(constraints, region, expected), Expr::Record { record_var, fields } => { if fields.is_empty() { - constrain_empty_record(region, expected) + constrain_empty_record(constraints, region, expected) } else { let mut field_exprs = SendMap::default(); let mut field_types = SendMap::default(); @@ -113,18 +107,19 @@ pub fn constrain_expr( // Constraints need capacity for each field // + 1 for the record itself + 1 for record var - let mut constraints = Vec::with_capacity(2 + fields.len()); + let mut rec_constraints = Vec::with_capacity(2 + fields.len()); for (label, field) in fields { let field_var = field.var; let loc_field_expr = &field.loc_expr; - let (field_type, field_con) = constrain_field(env, field_var, &*loc_field_expr); + let (field_type, field_con) = + constrain_field(constraints, env, field_var, &*loc_field_expr); field_vars.push(field_var); field_exprs.insert(label.clone(), loc_field_expr); field_types.insert(label.clone(), RecordField::Required(field_type)); - constraints.push(field_con); + rec_constraints.push(field_con); } let record_type = Type::Record( @@ -134,11 +129,17 @@ pub fn constrain_expr( // lifetime parameter on `Type` Box::new(Type::EmptyRec), ); - let record_con = Eq(record_type, expected.clone(), Category::Record, region); - constraints.push(record_con); + let record_con = constraints.equal_types( + record_type, + expected.clone(), + Category::Record, + region, + ); + + rec_constraints.push(record_con); // variable to store in the AST - let stored_con = Eq( + let stored_con = constraints.equal_types( Type::Variable(*record_var), expected, Category::Storage(std::file!(), std::line!()), @@ -146,9 +147,10 @@ pub fn constrain_expr( ); field_vars.push(*record_var); - constraints.push(stored_con); + rec_constraints.push(stored_con); - exists(field_vars, And(constraints)) + let and_constraint = constraints.and_constraint(rec_constraints); + constraints.exists(field_vars, and_constraint) } } Update { @@ -162,6 +164,7 @@ pub fn constrain_expr( let mut cons = Vec::with_capacity(updates.len() + 1); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { let (var, tipe, con) = constrain_field_update( + constraints, env, var, loc_expr.region, @@ -177,18 +180,19 @@ pub fn constrain_expr( let record_type = Type::Variable(*record_var); // NOTE from elm compiler: fields_type is separate so that Error propagates better - let fields_con = Eq( + let fields_con = constraints.equal_types( record_type.clone(), NoExpectation(fields_type), Category::Record, region, ); - let record_con = Eq(record_type.clone(), expected, Category::Record, region); + let record_con = + constraints.equal_types(record_type.clone(), expected, Category::Record, region); vars.push(*record_var); vars.push(*ext_var); - let con = Lookup( + let con = constraints.lookup( *symbol, ForReason( Reason::RecordUpdateKeys( @@ -209,22 +213,26 @@ pub fn constrain_expr( cons.insert(1, con); cons.insert(2, record_con); - exists(vars, And(cons)) + let and_constraint = constraints.and_constraint(cons); + constraints.exists(vars, and_constraint) } - Str(_) => Eq(str_type(), expected, Category::Str, region), - SingleQuote(_) => Eq(num_u32(), expected, Category::Character, region), + Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region), + SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region), List { elem_var, loc_elems, } => { if loc_elems.is_empty() { - exists( - vec![*elem_var], - Eq(empty_list_type(*elem_var), expected, Category::List, region), - ) + let eq = constraints.equal_types( + empty_list_type(*elem_var), + expected, + Category::List, + region, + ); + constraints.exists(vec![*elem_var], eq) } else { let list_elem_type = Type::Variable(*elem_var); - let mut constraints = Vec::with_capacity(1 + loc_elems.len()); + let mut list_constraints = Vec::with_capacity(1 + loc_elems.len()); for (index, loc_elem) in loc_elems.iter().enumerate() { let elem_expected = ForReason( @@ -234,20 +242,26 @@ pub fn constrain_expr( list_elem_type.clone(), loc_elem.region, ); - let constraint = - constrain_expr(env, loc_elem.region, &loc_elem.value, elem_expected); + let constraint = constrain_expr( + constraints, + env, + loc_elem.region, + &loc_elem.value, + elem_expected, + ); - constraints.push(constraint); + list_constraints.push(constraint); } - constraints.push(Eq( + list_constraints.push(constraints.equal_types( list_type(list_elem_type), expected, Category::List, region, )); - exists(vec![*elem_var], And(constraints)) + let and_constraint = constraints.and_constraint(list_constraints); + constraints.exists([*elem_var], and_constraint) } } Call(boxed, loc_args, called_via) => { @@ -269,7 +283,8 @@ pub fn constrain_expr( arity: loc_args.len() as u8, }; - let fn_con = constrain_expr(env, loc_fn.region, &loc_fn.value, fn_expected); + let fn_con = + constrain_expr(constraints, env, loc_fn.region, &loc_fn.value, fn_expected); // The function's return type let ret_type = Variable(*ret_var); @@ -296,7 +311,13 @@ pub fn constrain_expr( arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), region); - let arg_con = constrain_expr(env, loc_arg.region, &loc_arg.value, expected_arg); + let arg_con = constrain_expr( + constraints, + env, + loc_arg.region, + &loc_arg.value, + expected_arg, + ); vars.push(*arg_var); arg_types.push(arg_type); @@ -315,19 +336,19 @@ pub fn constrain_expr( let category = Category::CallResult(opt_symbol, *called_via); - exists( - vars, - And(vec![ - fn_con, - Eq(fn_type, expected_fn_type, category.clone(), fn_region), - And(arg_cons), - Eq(ret_type, expected, category, region), - ]), - ) + let and_cons = [ + fn_con, + constraints.equal_types(fn_type, expected_fn_type, category.clone(), fn_region), + constraints.and_constraint(arg_cons), + constraints.equal_types(ret_type, expected, category, region), + ]; + + let and_constraint = constraints.and_constraint(and_cons); + constraints.exists(vars, and_constraint) } Var(symbol) => { // make lookup constraint to lookup this symbol's type in the environment - Lookup(*symbol, expected, region) + constraints.lookup(*symbol, expected, region) } Closure(ClosureData { function_type: fn_var, @@ -349,8 +370,13 @@ pub fn constrain_expr( let closure_type = Type::Variable(closure_var); let return_type = Type::Variable(ret_var); - let (mut vars, pattern_state, function_type) = - constrain_untyped_args(env, arguments, closure_type, return_type.clone()); + let (mut vars, pattern_state, function_type) = constrain_untyped_args( + constraints, + env, + arguments, + closure_type, + return_type.clone(), + ); vars.push(ret_var); vars.push(closure_var); @@ -358,8 +384,13 @@ pub fn constrain_expr( vars.push(*fn_var); let body_type = NoExpectation(return_type); - let ret_constraint = - constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); + let ret_constraint = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); // make sure the captured symbols are sorted! debug_assert_eq!(captured_symbols.clone(), { @@ -369,6 +400,7 @@ pub fn constrain_expr( }); let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -377,28 +409,28 @@ pub fn constrain_expr( &mut vars, ); - exists( - vars, - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: pattern_state.vars, - def_types: pattern_state.headers, - defs_constraint: And(pattern_state.constraints), - ret_constraint, - })), - // "the closure's type is equal to expected type" - Eq(function_type.clone(), expected, Category::Lambda, region), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - Eq( - Type::Variable(*fn_var), - NoExpectation(function_type), - Category::Storage(std::file!(), std::line!()), - region, - ), - closure_constraint, - ]), - ) + let pattern_state_constraints = constraints.and_constraint(pattern_state.constraints); + let cons = [ + constraints.let_constraint( + [], + pattern_state.vars, + pattern_state.headers, + pattern_state_constraints, + ret_constraint, + ), + // "the closure's type is equal to expected type" + constraints.equal_types(function_type.clone(), expected, Category::Lambda, region), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + constraints.equal_types( + Type::Variable(*fn_var), + NoExpectation(function_type), + Category::Storage(std::file!(), std::line!()), + region, + ), + closure_constraint, + ]; + + constraints.exists_many(vars, cons) } Expect(loc_cond, continuation) => { @@ -408,16 +440,22 @@ pub fn constrain_expr( }; let cond_con = constrain_expr( + constraints, env, loc_cond.region, &loc_cond.value, expect_bool(loc_cond.region), ); - let continuation_con = - constrain_expr(env, continuation.region, &continuation.value, expected); + let continuation_con = constrain_expr( + constraints, + env, + continuation.region, + &continuation.value, + expected, + ); - exists(vec![], And(vec![cond_con, continuation_con])) + constraints.exists_many([], [cond_con, continuation_con]) } If { @@ -434,7 +472,7 @@ pub fn constrain_expr( // TODO why does this cond var exist? is it for error messages? let first_cond_region = branches[0].0.region; - let cond_var_is_bool_con = Eq( + let cond_var_is_bool_con = constraints.equal_types( Type::Variable(*cond_var), expect_bool(first_cond_region), Category::If, @@ -448,6 +486,7 @@ pub fn constrain_expr( let num_branches = branches.len() + 1; for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { let cond_con = constrain_expr( + constraints, env, loc_cond.region, &loc_cond.value, @@ -455,6 +494,7 @@ pub fn constrain_expr( ); let then_con = constrain_expr( + constraints, env, loc_body.region, &loc_body.value, @@ -475,6 +515,7 @@ pub fn constrain_expr( } let else_con = constrain_expr( + constraints, env, final_else.region, &final_else.value, @@ -490,7 +531,7 @@ pub fn constrain_expr( ), ); - let ast_con = Eq( + let ast_con = constraints.equal_types( Type::Variable(*branch_var), NoExpectation(tipe), Category::Storage(std::file!(), std::line!()), @@ -500,11 +541,12 @@ pub fn constrain_expr( branch_cons.push(ast_con); branch_cons.push(else_con); - exists(vec![*cond_var, *branch_var], And(branch_cons)) + constraints.exists_many([*cond_var, *branch_var], branch_cons) } _ => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { let cond_con = constrain_expr( + constraints, env, loc_cond.region, &loc_cond.value, @@ -512,6 +554,7 @@ pub fn constrain_expr( ); let then_con = constrain_expr( + constraints, env, loc_body.region, &loc_body.value, @@ -529,6 +572,7 @@ pub fn constrain_expr( branch_cons.push(then_con); } let else_con = constrain_expr( + constraints, env, final_else.region, &final_else.value, @@ -542,7 +586,7 @@ pub fn constrain_expr( ), ); - branch_cons.push(Eq( + branch_cons.push(constraints.equal_types( Type::Variable(*branch_var), expected, Category::Storage(std::file!(), std::line!()), @@ -550,7 +594,7 @@ pub fn constrain_expr( )); branch_cons.push(else_con); - exists(vec![*cond_var, *branch_var], And(branch_cons)) + constraints.exists_many([*cond_var, *branch_var], branch_cons) } } } @@ -565,14 +609,15 @@ pub fn constrain_expr( let cond_var = *cond_var; let cond_type = Variable(cond_var); let expr_con = constrain_expr( + constraints, env, region, &loc_cond.value, NoExpectation(cond_type.clone()), ); - let mut constraints = Vec::with_capacity(branches.len() + 1); - constraints.push(expr_con); + let mut branch_constraints = Vec::with_capacity(branches.len() + 1); + branch_constraints.push(expr_con); match &expected { FromAnnotation(name, arity, ann_source, _typ) => { @@ -587,6 +632,7 @@ pub fn constrain_expr( Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( + constraints, env, when_branch.value.region, when_branch, @@ -608,12 +654,17 @@ pub fn constrain_expr( ), ); - constraints.push(branch_con); + branch_constraints.push(branch_con); } - constraints.push(Eq(typ, expected, Category::When, region)); + branch_constraints.push(constraints.equal_types( + typ, + expected, + Category::When, + region, + )); - return exists(vec![cond_var, *expr_var], And(constraints)); + return constraints.exists_many([cond_var, *expr_var], branch_constraints); } _ => { @@ -624,6 +675,7 @@ pub fn constrain_expr( let pattern_region = Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( + constraints, env, region, when_branch, @@ -646,20 +698,26 @@ pub fn constrain_expr( branch_cons.push(branch_con); } - constraints.push(And(vec![ - // Record the original conditional expression's constraint. - // Each branch's pattern must have the same type - // as the condition expression did. - And(branch_cons), - // The return type of each branch must equal - // the return type of the entire when-expression. - Eq(branch_type, expected, Category::When, region), - ])); + // Deviation: elm adds another layer of And nesting + // + // Record the original conditional expression's constraint. + // Each branch's pattern must have the same type + // as the condition expression did. + // + // The return type of each branch must equal the return type of + // the entire when-expression. + branch_cons.push(constraints.equal_types( + branch_type, + expected, + Category::When, + region, + )); + branch_constraints.push(constraints.and_constraint(branch_cons)); } } // exhautiveness checking happens when converting to mono::Expr - exists(vec![cond_var, *expr_var], And(constraints)) + constraints.exists_many([cond_var, *expr_var], branch_constraints) } Access { record_var, @@ -683,7 +741,7 @@ pub fn constrain_expr( let category = Category::Access(field.clone()); - let record_con = Eq( + let record_con = constraints.equal_types( Type::Variable(*record_var), record_expected.clone(), category.clone(), @@ -691,6 +749,7 @@ pub fn constrain_expr( ); let constraint = constrain_expr( + constraints, &Env { home: env.home, rigids: ImMap::default(), @@ -700,13 +759,10 @@ pub fn constrain_expr( record_expected, ); - exists( - vec![*record_var, field_var, ext_var], - And(vec![ - constraint, - Eq(field_type, expected, category, region), - record_con, - ]), + let eq = constraints.equal_types(field_type, expected, category, region); + constraints.exists_many( + [*record_var, field_var, ext_var], + [constraint, eq, record_con], ) } Accessor { @@ -731,7 +787,7 @@ pub fn constrain_expr( let category = Category::Accessor(field.clone()); let record_expected = Expected::NoExpectation(record_type.clone()); - let record_con = Eq( + let record_con = constraints.equal_types( Type::Variable(*record_var), record_expected, category.clone(), @@ -749,37 +805,44 @@ pub fn constrain_expr( Box::new(field_type), ); - exists( - vec![*record_var, *function_var, *closure_var, field_var, ext_var], - And(vec![ - Eq(function_type.clone(), expected, category.clone(), region), - Eq( - function_type, - NoExpectation(Variable(*function_var)), - category, - region, - ), - record_con, - ]), + let cons = [ + constraints.equal_types(function_type.clone(), expected, category.clone(), region), + constraints.equal_types( + function_type, + NoExpectation(Variable(*function_var)), + category, + region, + ), + record_con, + ]; + + constraints.exists_many( + [*record_var, *function_var, *closure_var, field_var, ext_var], + cons, ) } LetRec(defs, loc_ret, var) => { - let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone()); + let body_con = constrain_expr( + constraints, + env, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); - exists( - vec![*var], - And(vec![ - constrain_recursive_defs(env, defs, body_con), - // Record the type of tne entire def-expression in the variable. - // Code gen will need that later! - Eq( - Type::Variable(*var), - expected, - Category::Storage(std::file!(), std::line!()), - loc_ret.region, - ), - ]), - ) + let cons = [ + constrain_recursive_defs(constraints, env, defs, body_con), + // Record the type of tne entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected, + Category::Storage(std::file!(), std::line!()), + loc_ret.region, + ), + ]; + + constraints.exists_many([*var], cons) } LetNonRec(def, loc_ret, var) => { let mut stack = Vec::with_capacity(1); @@ -793,24 +856,28 @@ pub fn constrain_expr( loc_ret = new_loc_ret; } - let mut body_con = - constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone()); + let mut body_con = constrain_expr( + constraints, + env, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); while let Some((def, var, ret_region)) = stack.pop() { - body_con = exists( - vec![*var], - And(vec![ - constrain_def(env, def, body_con), - // Record the type of the entire def-expression in the variable. - // Code gen will need that later! - Eq( - Type::Variable(*var), - expected.clone(), - Category::Storage(std::file!(), std::line!()), - ret_region, - ), - ]), - ) + let cons = [ + constrain_def(constraints, env, def, body_con), + // Record the type of the entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected.clone(), + Category::Storage(std::file!(), std::line!()), + ret_region, + ), + ]; + + body_con = constraints.exists_many([*var], cons) } body_con @@ -827,6 +894,7 @@ pub fn constrain_expr( for (var, loc_expr) in arguments { let arg_con = constrain_expr( + constraints, env, loc_expr.region, &loc_expr.value, @@ -838,7 +906,7 @@ pub fn constrain_expr( types.push(Type::Variable(*var)); } - let union_con = Eq( + let union_con = constraints.equal_types( Type::TagUnion( vec![(name.clone(), types)], Box::new(Type::Variable(*ext_var)), @@ -850,7 +918,7 @@ pub fn constrain_expr( }, region, ); - let ast_con = Eq( + let ast_con = constraints.equal_types( Type::Variable(*variant_var), expected, Category::Storage(std::file!(), std::line!()), @@ -862,7 +930,7 @@ pub fn constrain_expr( arg_cons.push(union_con); arg_cons.push(ast_con); - exists(vars, And(arg_cons)) + constraints.exists_many(vars, arg_cons) } ZeroArgumentTag { variant_var, @@ -877,6 +945,7 @@ pub fn constrain_expr( for (var, loc_expr) in arguments { let arg_con = constrain_expr( + constraints, env, loc_expr.region, &loc_expr.value, @@ -888,7 +957,7 @@ pub fn constrain_expr( types.push(Type::Variable(*var)); } - let union_con = Eq( + let union_con = constraints.equal_types( Type::FunctionOrTagUnion( name.clone(), *closure_name, @@ -901,7 +970,7 @@ pub fn constrain_expr( }, region, ); - let ast_con = Eq( + let ast_con = constraints.equal_types( Type::Variable(*variant_var), expected, Category::Storage(std::file!(), std::line!()), @@ -913,7 +982,7 @@ pub fn constrain_expr( arg_cons.push(union_con); arg_cons.push(ast_con); - exists(vars, And(arg_cons)) + constraints.exists_many(vars, arg_cons) } OpaqueRef { @@ -937,6 +1006,7 @@ pub fn constrain_expr( // Constrain the argument let arg_con = constrain_expr( + constraints, env, arg_loc_expr.region, &arg_loc_expr.value, @@ -945,7 +1015,7 @@ pub fn constrain_expr( // Link the entire wrapped opaque type (with the now-constrained argument) to the // expected type - let opaque_con = Eq( + let opaque_con = constraints.equal_types( opaque_type, expected.clone(), Category::OpaqueWrap(*name), @@ -955,7 +1025,7 @@ pub fn constrain_expr( // Link the entire wrapped opaque type (with the now-constrained argument) to the type // variables of the opaque type // TODO: better expectation here - let link_type_variables_con = Eq( + let link_type_variables_con = constraints.equal_types( arg_type, Expected::NoExpectation((**specialized_def_type).clone()), Category::OpaqueArg, @@ -963,7 +1033,7 @@ pub fn constrain_expr( ); // Store the entire wrapped opaque type in `opaque_var` - let storage_con = Eq( + let storage_con = constraints.equal_types( Type::Variable(*opaque_var), expected, Category::Storage(std::file!(), std::line!()), @@ -979,14 +1049,9 @@ pub fn constrain_expr( v.0.expect_variable("all lambda sets should be fresh variables here") })); - exists( + constraints.exists_many( vars, - And(vec![ - arg_con, - opaque_con, - link_type_variables_con, - storage_con, - ]), + [arg_con, opaque_con, link_type_variables_con, storage_con], ) } @@ -1010,7 +1075,7 @@ pub fn constrain_expr( arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); - let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); + let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); arg_types.push(arg_type); arg_cons.push(arg_con); @@ -1024,13 +1089,10 @@ pub fn constrain_expr( let category = Category::LowLevelOpResult(*op); - exists( - vars, - And(vec![ - And(arg_cons), - Eq(ret_type, expected, category, region), - ]), - ) + // Deviation: elm uses an additional And here + let eq = constraints.equal_types(ret_type, expected, category, region); + arg_cons.push(eq); + constraints.exists_many(vars, arg_cons) } ForeignCall { args, @@ -1056,7 +1118,7 @@ pub fn constrain_expr( arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); - let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); + let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); arg_types.push(arg_type); arg_cons.push(arg_con); @@ -1070,30 +1132,34 @@ pub fn constrain_expr( let category = Category::ForeignCall; - exists( - vars, - And(vec![ - And(arg_cons), - Eq(ret_type, expected, category, region), - ]), - ) + // Deviation: elm uses an additional And here + let eq = constraints.equal_types(ret_type, expected, category, region); + arg_cons.push(eq); + constraints.exists_many(vars, arg_cons) } RuntimeError(_) => { // Runtime Errors have no constraints because they're going to crash. - True + Constraint::True } } } #[inline(always)] fn constrain_when_branch( + constraints: &mut Constraints, env: &Env, region: Region, when_branch: &WhenBranch, pattern_expected: PExpected, expr_expected: Expected, ) -> Constraint { - let ret_constraint = constrain_expr(env, region, &when_branch.value.value, expr_expected); + let ret_constraint = constrain_expr( + constraints, + env, + region, + &when_branch.value.value, + expr_expected, + ); let mut state = PatternState { headers: SendMap::default(), @@ -1105,6 +1171,7 @@ fn constrain_when_branch( // then unify that variable with the expectation? for loc_pattern in &when_branch.patterns { constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1115,6 +1182,7 @@ fn constrain_when_branch( if let Some(loc_guard) = &when_branch.guard { let guard_constraint = constrain_expr( + constraints, env, region, &loc_guard.value, @@ -1126,46 +1194,70 @@ fn constrain_when_branch( ); // must introduce the headers from the pattern before constraining the guard - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), - ret_constraint: Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: Vec::new(), - def_types: SendMap::default(), - defs_constraint: guard_constraint, - ret_constraint, - })), - })) - } else { - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), + let state_constraints = constraints.and_constraint(state.constraints); + let inner = constraints.let_constraint( + [], + [], + SendMap::default(), + guard_constraint, ret_constraint, - })) + ); + + constraints.let_constraint([], state.vars, state.headers, state_constraints, inner) + } else { + let state_constraints = constraints.and_constraint(state.constraints); + constraints.let_constraint( + [], + state.vars, + state.headers, + state_constraints, + ret_constraint, + ) } } -fn constrain_field(env: &Env, field_var: Variable, loc_expr: &Loc) -> (Type, Constraint) { +fn constrain_field( + constraints: &mut Constraints, + env: &Env, + field_var: Variable, + loc_expr: &Loc, +) -> (Type, Constraint) { let field_type = Variable(field_var); let field_expected = NoExpectation(field_type.clone()); - let constraint = constrain_expr(env, loc_expr.region, &loc_expr.value, field_expected); + let constraint = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + field_expected, + ); (field_type, constraint) } #[inline(always)] -fn constrain_empty_record(region: Region, expected: Expected) -> Constraint { - Eq(EmptyRec, expected, Category::Record, region) +fn constrain_empty_record( + constraints: &mut Constraints, + region: Region, + expected: Expected, +) -> Constraint { + let expected_index = constraints.push_expected_type(expected); + + Constraint::Eq( + Constraints::EMPTY_RECORD, + expected_index, + Constraints::CATEGORY_RECORD, + region, + ) } /// Constrain top-level module declarations #[inline(always)] -pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { +pub fn constrain_decls( + constraints: &mut Constraints, + home: ModuleId, + decls: &[Declaration], +) -> Constraint { let mut constraint = Constraint::SaveTheEnvironment; let mut env = Env { @@ -1180,10 +1272,10 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { match decl { Declaration::Declare(def) | Declaration::Builtin(def) => { - constraint = constrain_def(&env, def, constraint); + constraint = constrain_def(constraints, &env, def, constraint); } Declaration::DeclareRec(defs) => { - constraint = constrain_recursive_defs(&env, defs, constraint); + constraint = constrain_recursive_defs(constraints, &env, defs, constraint); } Declaration::InvalidCycle(_) => { // invalid cycles give a canonicalization error. we skip them here. @@ -1193,12 +1285,17 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { } // this assert make the "root" of the constraint wasn't dropped - debug_assert!(constraint.contains_save_the_environment()); + debug_assert!(constraints.contains_save_the_environment(&constraint)); constraint } -fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) -> PatternState { +fn constrain_def_pattern( + constraints: &mut Constraints, + env: &Env, + loc_pattern: &Loc, + expr_type: Type, +) -> PatternState { let pattern_expected = PExpected::NoExpectation(expr_type); let mut state = PatternState { @@ -1208,6 +1305,7 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) }; constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1218,11 +1316,17 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) state } -fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { +fn constrain_def( + constraints: &mut Constraints, + env: &Env, + def: &Def, + body_con: Constraint, +) -> Constraint { let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); - let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone()); + let mut def_pattern_state = + constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); def_pattern_state.vars.push(expr_var); @@ -1257,7 +1361,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { signature.clone(), ); - def_pattern_state.constraints.push(Eq( + def_pattern_state.constraints.push(constraints.equal_types( expr_type, annotation_expected.clone(), Category::Storage(std::file!(), std::line!()), @@ -1328,6 +1432,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { ); constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1342,7 +1447,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { def_pattern_state.vars.push(*pattern_var); pattern_types.push(Type::Variable(*pattern_var)); - let pattern_con = Eq( + let pattern_con = constraints.equal_types( Type::Variable(*pattern_var), Expected::NoExpectation(loc_ann.clone()), Category::Storage(std::file!(), std::line!()), @@ -1354,6 +1459,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { } let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -1371,60 +1477,71 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { ret_type.clone(), ); - let ret_constraint = - constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); + let ret_constraint = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); vars.push(*fn_var); - let defs_constraint = And(state.constraints); + let defs_constraint = constraints.and_constraint(state.constraints); - exists( - vars, - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint, - ret_constraint, - })), - Eq( - Type::Variable(closure_var), - Expected::FromAnnotation( - def.loc_pattern.clone(), - arity, - AnnotationSource::TypedBody { - region: annotation.region, - }, - *signature_closure_type.clone(), - ), - Category::ClosureSize, - region, + let cons = [ + constraints.let_constraint( + [], + state.vars, + state.headers, + defs_constraint, + ret_constraint, + ), + constraints.equal_types( + Type::Variable(closure_var), + Expected::FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + *signature_closure_type.clone(), ), - Store(signature.clone(), *fn_var, std::file!(), std::line!()), - Store(signature, expr_var, std::file!(), std::line!()), - Store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]), - ) + Category::ClosureSize, + region, + ), + constraints.store(signature.clone(), *fn_var, std::file!(), std::line!()), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]; + + let and_constraint = constraints.and_constraint(cons); + constraints.exists(vars, and_constraint) } _ => { let expected = annotation_expected; - let ret_constraint = - constrain_expr(env, def.loc_expr.region, &def.loc_expr.value, expected); + let ret_constraint = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + expected, + ); - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: vec![], - def_types: SendMap::default(), - defs_constraint: True, + let cons = [ + constraints.let_constraint( + [], + [], + SendMap::default(), + Constraint::True, ret_constraint, - })), + ), // Store type into AST vars. We use Store so errors aren't reported twice - Store(signature, expr_var, std::file!(), std::line!()), - ]) + constraints.store(signature, expr_var, std::file!(), std::line!()), + ]; + constraints.and_constraint(cons) } } } @@ -1432,6 +1549,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { // no annotation, so no extra work with rigids constrain_expr( + constraints, env, def.loc_expr.region, &def.loc_expr.value, @@ -1440,22 +1558,27 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { } }; - Let(Box::new(LetConstraint { - rigid_vars: new_rigids, - flex_vars: def_pattern_state.vars, - def_types: def_pattern_state.headers, - defs_constraint: Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), // always empty - flex_vars: Vec::new(), // empty, because our functions have no arguments - def_types: SendMap::default(), // empty, because our functions have no arguments! - defs_constraint: And(def_pattern_state.constraints), - ret_constraint: expr_con, - })), - ret_constraint: body_con, - })) + let and_constraint = constraints.and_constraint(def_pattern_state.constraints); + + let def_con = constraints.let_constraint( + [], + [], + SendMap::default(), // empty, because our functions have no arguments! + and_constraint, + expr_con, + ); + + constraints.let_constraint( + new_rigids, + def_pattern_state.vars, + def_pattern_state.headers, + def_con, + body_con, + ) } fn constrain_closure_size( + constraints: &mut Constraints, name: Symbol, region: Region, captured_symbols: &[(Symbol, Variable)], @@ -1477,7 +1600,7 @@ fn constrain_closure_size( tag_arguments.push(Type::Variable(*var)); // make the variable equal to the looked-up type of symbol - captured_symbols_constraints.push(Constraint::Lookup( + captured_symbols_constraints.push(constraints.lookup( *symbol, Expected::NoExpectation(Type::Variable(*var)), Region::zero(), @@ -1498,7 +1621,7 @@ fn constrain_closure_size( ) }; - let finalizer = Eq( + let finalizer = constraints.equal_types( Type::Variable(closure_var), NoExpectation(closure_type), Category::ClosureSize, @@ -1507,7 +1630,7 @@ fn constrain_closure_size( captured_symbols_constraints.push(finalizer); - Constraint::And(captured_symbols_constraints) + constraints.and_constraint(captured_symbols_constraints) } fn instantiate_rigids( @@ -1559,8 +1682,14 @@ fn instantiate_rigids( annotation } -fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Constraint { +fn constrain_recursive_defs( + constraints: &mut Constraints, + env: &Env, + defs: &[Def], + body_con: Constraint, +) -> Constraint { rec_defs_help( + constraints, env, defs, body_con, @@ -1570,6 +1699,7 @@ fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Co } pub fn rec_defs_help( + constraints: &mut Constraints, env: &Env, defs: &[Def], body_con: Constraint, @@ -1580,7 +1710,8 @@ pub fn rec_defs_help( let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); - let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone()); + let mut def_pattern_state = + constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); def_pattern_state.vars.push(expr_var); @@ -1588,6 +1719,7 @@ pub fn rec_defs_help( match &def.annotation { None => { let expr_con = constrain_expr( + constraints, env, def.loc_expr.region, &def.loc_expr.value, @@ -1595,13 +1727,13 @@ pub fn rec_defs_help( ); // TODO investigate if this let can be safely removed - let def_con = Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: Vec::new(), // empty because Roc function defs have no args - def_types: SendMap::default(), // empty because Roc function defs have no args - defs_constraint: True, // I think this is correct, once again because there are no args - ret_constraint: expr_con, - })); + let def_con = constraints.let_constraint( + [], + [], // empty because Roc function defs have no args + SendMap::default(), // empty because Roc function defs have no args + Constraint::True, // I think this is correct, once again because there are no args + expr_con, + ); flex_info.vars = def_pattern_state.vars; flex_info.constraints.push(def_con); @@ -1695,6 +1827,7 @@ pub fn rec_defs_help( ); constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1709,7 +1842,7 @@ pub fn rec_defs_help( def_pattern_state.vars.push(*pattern_var); pattern_types.push(Type::Variable(*pattern_var)); - let pattern_con = Eq( + let pattern_con = constraints.equal_types( Type::Variable(*pattern_var), Expected::NoExpectation(loc_ann.clone()), Category::Storage(std::file!(), std::line!()), @@ -1721,6 +1854,7 @@ pub fn rec_defs_help( } let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -1736,6 +1870,7 @@ pub fn rec_defs_help( ); let body_type = NoExpectation(ret_type.clone()); let expr_con = constrain_expr( + constraints, env, loc_body_expr.region, &loc_body_expr.value, @@ -1744,64 +1879,81 @@ pub fn rec_defs_help( vars.push(*fn_var); - let def_con = exists( - vars, - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: And(state.constraints), - ret_constraint: expr_con, - })), - Eq(fn_type.clone(), expected.clone(), Category::Lambda, region), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - // Store type into AST vars. We use Store so errors aren't reported twice - Store(signature.clone(), *fn_var, std::file!(), std::line!()), - Store(signature, expr_var, std::file!(), std::line!()), - Store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]), - ); + let state_constraints = constraints.and_constraint(state.constraints); + let cons = [ + constraints.let_constraint( + [], + state.vars, + state.headers, + state_constraints, + expr_con, + ), + constraints.equal_types( + fn_type.clone(), + expected.clone(), + Category::Lambda, + region, + ), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + // Store type into AST vars. We use Store so errors aren't reported twice + constraints.store( + signature.clone(), + *fn_var, + std::file!(), + std::line!(), + ), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]; + + let and_constraint = constraints.and_constraint(cons); + let def_con = constraints.exists(vars, and_constraint); rigid_info.vars.extend(&new_rigids); - rigid_info.constraints.push(Let(Box::new(LetConstraint { - rigid_vars: new_rigids, - flex_vars: def_pattern_state.vars, - def_types: SendMap::default(), // no headers introduced (at this level) - defs_constraint: def_con, - ret_constraint: True, - }))); + rigid_info.constraints.push(constraints.let_constraint( + new_rigids, + def_pattern_state.vars, + SendMap::default(), // no headers introduced (at this level) + def_con, + Constraint::True, + )); rigid_info.def_types.extend(def_pattern_state.headers); } _ => { let expected = annotation_expected; - let ret_constraint = - constrain_expr(env, def.loc_expr.region, &def.loc_expr.value, expected); + let ret_constraint = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + expected, + ); - let def_con = And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: vec![], - def_types: SendMap::default(), - defs_constraint: True, + let cons = [ + constraints.let_constraint( + [], + [], + SendMap::default(), + Constraint::True, ret_constraint, - })), + ), // Store type into AST vars. We use Store so errors aren't reported twice - Store(signature, expr_var, std::file!(), std::line!()), - ]); + constraints.store(signature, expr_var, std::file!(), std::line!()), + ]; + let def_con = constraints.and_constraint(cons); rigid_info.vars.extend(&new_rigids); - rigid_info.constraints.push(Let(Box::new(LetConstraint { - rigid_vars: new_rigids, - flex_vars: def_pattern_state.vars, - def_types: SendMap::default(), // no headers introduced (at this level) - defs_constraint: def_con, - ret_constraint: True, - }))); + rigid_info.constraints.push(constraints.let_constraint( + new_rigids, + def_pattern_state.vars, + SendMap::default(), // no headers introduced (at this level) + def_con, + Constraint::True, + )); rigid_info.def_types.extend(def_pattern_state.headers); } } @@ -1809,29 +1961,42 @@ pub fn rec_defs_help( } } - Let(Box::new(LetConstraint { - rigid_vars: rigid_info.vars, - flex_vars: Vec::new(), - def_types: rigid_info.def_types, - defs_constraint: True, - ret_constraint: Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: flex_info.vars, - def_types: flex_info.def_types.clone(), - defs_constraint: Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: Vec::new(), - def_types: flex_info.def_types, - defs_constraint: True, - ret_constraint: And(flex_info.constraints), - })), - ret_constraint: And(vec![And(rigid_info.constraints), body_con]), - })), - })) + let flex_constraints = constraints.and_constraint(flex_info.constraints); + let inner_inner = constraints.let_constraint( + [], + [], + flex_info.def_types.clone(), + Constraint::True, + flex_constraints, + ); + + let rigid_constraints = { + let mut temp = rigid_info.constraints; + temp.push(body_con); + + constraints.and_constraint(temp) + }; + + let inner = constraints.let_constraint( + [], + flex_info.vars, + flex_info.def_types, + inner_inner, + rigid_constraints, + ); + + constraints.let_constraint( + rigid_info.vars, + [], + rigid_info.def_types, + Constraint::True, + inner, + ) } #[inline(always)] fn constrain_field_update( + constraints: &mut Constraints, env: &Env, var: Variable, region: Region, @@ -1841,7 +2006,7 @@ fn constrain_field_update( let field_type = Type::Variable(var); let reason = Reason::RecordUpdateValue(field); let expected = ForReason(reason, field_type.clone(), region); - let con = constrain_expr(env, loc_expr.region, &loc_expr.value, expected); + let con = constrain_expr(constraints, env, loc_expr.region, &loc_expr.value, expected); (var, field_type, con) } diff --git a/compiler/constrain/src/lib.rs b/compiler/constrain/src/lib.rs index c2c51392f0..94067ab076 100644 --- a/compiler/constrain/src/lib.rs +++ b/compiler/constrain/src/lib.rs @@ -2,8 +2,6 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] pub mod builtins; -// pub mod expr; +pub mod expr; pub mod module; -// pub mod pattern; -pub mod soa_expr; -pub mod soa_pattern; +pub mod pattern; diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index d63438e835..056d62554e 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -16,17 +16,13 @@ pub enum ExposedModuleTypes { Valid(MutMap, MutMap), } -// pub struct ConstrainedModule { -// pub unused_imports: MutMap, -// pub constraint: Constraint, -// } pub fn constrain_module_soa( constraints: &mut Constraints, declarations: &[Declaration], home: ModuleId, ) -> Constraint { - crate::soa_expr::constrain_decls(constraints, home, declarations) + crate::expr::constrain_decls(constraints, home, declarations) } #[derive(Debug, Clone)] diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index e7a3f0f7fc..db3e29549a 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -1,6 +1,6 @@ use crate::builtins; use crate::expr::{constrain_expr, Env}; -use roc_can::constraint::{Constraint, PresenceConstraint}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::{DestructType, RecordDestruct}; @@ -153,6 +153,7 @@ fn headers_from_annotation_help( /// initialize the Vecs in PatternState using with_capacity /// based on its knowledge of their lengths. pub fn constrain_pattern( + constraints: &mut Constraints, env: &Env, pattern: &Pattern, region: Region, @@ -167,20 +168,18 @@ pub fn constrain_pattern( // A -> "" // _ -> "" // so, we know that "x" (in this case, a tag union) must be open. - state.constraints.push(Constraint::Present( - expected.get_type(), - PresenceConstraint::IsOpen, - )); + state + .constraints + .push(constraints.is_open_type(expected.get_type())); } UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => { // Erroneous patterns don't add any constraints. } Identifier(symbol) | Shadowed(_, _, symbol) => { - state.constraints.push(Constraint::Present( - expected.get_type_ref().clone(), - PresenceConstraint::IsOpen, - )); + state + .constraints + .push(constraints.is_open_type(expected.get_type_ref().clone())); state.headers.insert( *symbol, Loc { @@ -195,7 +194,8 @@ pub fn constrain_pattern( let num_type = builtins::num_num(Type::Variable(var)); - let num_type = builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr_soa( + constraints, &mut state.constraints, num_type, bound, @@ -203,18 +203,19 @@ pub fn constrain_pattern( Category::Num, ); - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Num, + state.constraints.push(constraints.equal_pattern_types( num_type, expected, + PatternCategory::Num, + region, )); } &IntLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr_soa( + constraints, &mut state.constraints, Type::Variable(num_var), bound, @@ -225,7 +226,7 @@ pub fn constrain_pattern( // Link the free num var with the int var and our expectation. let int_type = builtins::num_int(Type::Variable(precision_var)); - state.constraints.push(Constraint::Eq( + state.constraints.push(constraints.equal_types( num_type, // TODO check me if something breaks! Expected::NoExpectation(int_type), Category::Int, @@ -233,18 +234,19 @@ pub fn constrain_pattern( )); // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Int, + state.constraints.push(constraints.equal_pattern_types( Type::Variable(num_var), expected, + PatternCategory::Int, + region, )); } &FloatLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr_soa( + constraints, &mut state.constraints, Type::Variable(num_var), bound, @@ -255,7 +257,7 @@ pub fn constrain_pattern( // Link the free num var with the float var and our expectation. let float_type = builtins::num_float(Type::Variable(precision_var)); - state.constraints.push(Constraint::Eq( + state.constraints.push(constraints.equal_types( num_type.clone(), // TODO check me if something breaks! Expected::NoExpectation(float_type), Category::Float, @@ -263,29 +265,29 @@ pub fn constrain_pattern( )); // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Float, + state.constraints.push(constraints.equal_pattern_types( num_type, // TODO check me if something breaks! expected, + PatternCategory::Float, + region, )); } StrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, + state.constraints.push(constraints.equal_pattern_types( builtins::str_type(), expected, + PatternCategory::Str, + region, )); } SingleQuote(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Character, + state.constraints.push(constraints.equal_pattern_types( builtins::num_u32(), expected, + PatternCategory::Character, + region, )); } @@ -322,36 +324,39 @@ pub fn constrain_pattern( let field_type = match typ { DestructType::Guard(guard_var, loc_guard) => { - state.constraints.push(Constraint::Present( + state.constraints.push(constraints.pattern_presence( Type::Variable(*guard_var), - PresenceConstraint::Pattern( - region, - PatternCategory::PatternGuard, - PExpected::ForReason( - PReason::PatternGuard, - pat_type.clone(), - loc_guard.region, - ), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.clone(), + loc_guard.region, ), + PatternCategory::PatternGuard, + region, )); state.vars.push(*guard_var); - constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state); + constrain_pattern( + constraints, + env, + &loc_guard.value, + loc_guard.region, + expected, + state, + ); RecordField::Demanded(pat_type) } DestructType::Optional(expr_var, loc_expr) => { - state.constraints.push(Constraint::Present( + state.constraints.push(constraints.pattern_presence( Type::Variable(*expr_var), - PresenceConstraint::Pattern( - region, - PatternCategory::PatternDefault, - PExpected::ForReason( - PReason::OptionalField, - pat_type.clone(), - loc_expr.region, - ), + PExpected::ForReason( + PReason::OptionalField, + pat_type.clone(), + loc_expr.region, ), + PatternCategory::PatternDefault, + region, )); state.vars.push(*expr_var); @@ -362,8 +367,13 @@ pub fn constrain_pattern( loc_expr.region, ); - let expr_con = - constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + let expr_con = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + expr_expected, + ); state.constraints.push(expr_con); RecordField::Optional(pat_type) @@ -381,16 +391,18 @@ pub fn constrain_pattern( let record_type = Type::Record(field_types, Box::new(ext_type)); - let whole_con = Constraint::Eq( + let whole_con = constraints.equal_types( Type::Variable(*whole_var), Expected::NoExpectation(record_type), Category::Storage(std::file!(), std::line!()), region, ); - let record_con = Constraint::Present( + let record_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, PatternCategory::Record, expected), + expected, + PatternCategory::Record, + region, ); state.constraints.push(whole_con); @@ -417,24 +429,31 @@ pub fn constrain_pattern( pattern_type, region, ); - constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + expected, + state, + ); } let pat_category = PatternCategory::Ctor(tag_name.clone()); - let whole_con = Constraint::Present( + let whole_con = constraints.includes_tag( expected.clone().get_type(), - PresenceConstraint::IncludesTag( - tag_name.clone(), - argument_types.clone(), - region, - pat_category.clone(), - ), + tag_name.clone(), + argument_types.clone(), + pat_category.clone(), + region, ); - let tag_con = Constraint::Present( + let tag_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, pat_category, expected), + expected, + pat_category, + region, ); state.vars.push(*whole_var); @@ -466,6 +485,7 @@ pub fn constrain_pattern( // First, add a constraint for the argument "who" let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone()); constrain_pattern( + constraints, env, &loc_arg_pattern.value, loc_arg_pattern.region, @@ -474,7 +494,7 @@ pub fn constrain_pattern( ); // Next, link `whole_var` to the opaque type of "@Id who" - let whole_con = Constraint::Eq( + let whole_con = constraints.equal_types( Type::Variable(*whole_var), Expected::NoExpectation(opaque_type), Category::Storage(std::file!(), std::line!()), @@ -484,7 +504,7 @@ pub fn constrain_pattern( // Link the entire wrapped opaque type (with the now-constrained argument) to the type // variables of the opaque type // TODO: better expectation here - let link_type_variables_con = Constraint::Eq( + let link_type_variables_con = constraints.equal_types( (**specialized_def_type).clone(), Expected::NoExpectation(arg_pattern_type), Category::OpaqueWrap(*opaque), @@ -492,9 +512,11 @@ pub fn constrain_pattern( ); // Next, link `whole_var` (the type of "@Id who") to the expected type - let opaque_pattern_con = Constraint::Present( + let opaque_pattern_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected), + expected, + PatternCategory::Opaque(*opaque), + region, ); state diff --git a/compiler/constrain/src/soa_expr.rs b/compiler/constrain/src/soa_expr.rs deleted file mode 100644 index ec3a1cb37c..0000000000 --- a/compiler/constrain/src/soa_expr.rs +++ /dev/null @@ -1,2012 +0,0 @@ -use crate::builtins::{ - empty_list_type, float_literal_soa, int_literal_soa, list_type, num_literal_soa, num_u32, - str_type, -}; -use crate::soa_pattern::{constrain_pattern, PatternState}; -use roc_can::annotation::IntroducedVariables; -use roc_can::constraint::{Constraint, Constraints}; -use roc_can::def::{Declaration, Def}; -use roc_can::expected::Expected::{self, *}; -use roc_can::expected::PExpected; -use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{ClosureData, Field, WhenBranch}; -use roc_can::pattern::Pattern; -use roc_collections::all::{HumanIndex, ImMap, MutSet, SendMap}; -use roc_module::ident::{Lowercase, TagName}; -use roc_module::symbol::{ModuleId, Symbol}; -use roc_region::all::{Loc, Region}; -use roc_types::subs::Variable; -use roc_types::types::Type::{self, *}; -use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField}; - - /// This is for constraining Defs - #[derive(Default, Debug)] - pub struct Info { - pub vars: Vec, - pub constraints: Vec, - pub def_types: SendMap>, - } - - impl Info { - pub fn with_capacity(capacity: usize) -> Self { - Info { - vars: Vec::with_capacity(capacity), - constraints: Vec::with_capacity(capacity), - def_types: SendMap::default(), - } - } - } - -pub struct Env { - /// Whenever we encounter a user-defined type variable (a "rigid" var for short), - /// for example `a` in the annotation `identity : a -> a`, we add it to this - /// map so that expressions within that annotation can share these vars. - pub rigids: ImMap, - pub home: ModuleId, -} - -fn constrain_untyped_args( - constraints: &mut Constraints, - env: &Env, - arguments: &[(Variable, Loc)], - closure_type: Type, - return_type: Type, -) -> (Vec, PatternState, Type) { - let mut vars = Vec::with_capacity(arguments.len()); - let mut pattern_types = Vec::with_capacity(arguments.len()); - - let mut pattern_state = PatternState::default(); - - for (pattern_var, loc_pattern) in arguments { - let pattern_type = Type::Variable(*pattern_var); - let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); - - pattern_types.push(pattern_type); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut pattern_state, - ); - - vars.push(*pattern_var); - } - - let function_type = - Type::Function(pattern_types, Box::new(closure_type), Box::new(return_type)); - - (vars, pattern_state, function_type) -} - -pub fn constrain_expr( - constraints: &mut Constraints, - env: &Env, - region: Region, - expr: &Expr, - expected: Expected, -) -> Constraint { - match expr { - &Int(var, precision, _, _, bound) => { - int_literal_soa(constraints, var, precision, expected, region, bound) - } - &Num(var, _, _, bound) => num_literal_soa(constraints, var, expected, region, bound), - &Float(var, precision, _, _, bound) => { - float_literal_soa(constraints, var, precision, expected, region, bound) - } - EmptyRecord => constrain_empty_record(constraints, region, expected), - Expr::Record { record_var, fields } => { - if fields.is_empty() { - constrain_empty_record(constraints, region, expected) - } else { - let mut field_exprs = SendMap::default(); - let mut field_types = SendMap::default(); - let mut field_vars = Vec::with_capacity(fields.len()); - - // Constraints need capacity for each field - // + 1 for the record itself + 1 for record var - let mut rec_constraints = Vec::with_capacity(2 + fields.len()); - - for (label, field) in fields { - let field_var = field.var; - let loc_field_expr = &field.loc_expr; - let (field_type, field_con) = - constrain_field(constraints, env, field_var, &*loc_field_expr); - - field_vars.push(field_var); - field_exprs.insert(label.clone(), loc_field_expr); - field_types.insert(label.clone(), RecordField::Required(field_type)); - - rec_constraints.push(field_con); - } - - let record_type = Type::Record( - field_types, - // TODO can we avoid doing Box::new on every single one of these? - // We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a - // lifetime parameter on `Type` - Box::new(Type::EmptyRec), - ); - let record_con = constraints.equal_types( - record_type, - expected.clone(), - Category::Record, - region, - ); - - rec_constraints.push(record_con); - - // variable to store in the AST - let stored_con = constraints.equal_types( - Type::Variable(*record_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - field_vars.push(*record_var); - rec_constraints.push(stored_con); - - let and_constraint = constraints.and_constraint(rec_constraints); - constraints.exists(field_vars, and_constraint) - } - } - Update { - record_var, - ext_var, - symbol, - updates, - } => { - let mut fields: SendMap> = SendMap::default(); - let mut vars = Vec::with_capacity(updates.len() + 2); - let mut cons = Vec::with_capacity(updates.len() + 1); - for (field_name, Field { var, loc_expr, .. }) in updates.clone() { - let (var, tipe, con) = constrain_field_update( - constraints, - env, - var, - loc_expr.region, - field_name.clone(), - &loc_expr, - ); - fields.insert(field_name, RecordField::Required(tipe)); - vars.push(var); - cons.push(con); - } - - let fields_type = Type::Record(fields, Box::new(Type::Variable(*ext_var))); - let record_type = Type::Variable(*record_var); - - // NOTE from elm compiler: fields_type is separate so that Error propagates better - let fields_con = constraints.equal_types( - record_type.clone(), - NoExpectation(fields_type), - Category::Record, - region, - ); - let record_con = - constraints.equal_types(record_type.clone(), expected, Category::Record, region); - - vars.push(*record_var); - vars.push(*ext_var); - - let con = constraints.lookup( - *symbol, - ForReason( - Reason::RecordUpdateKeys( - *symbol, - updates - .iter() - .map(|(key, field)| (key.clone(), field.region)) - .collect(), - ), - record_type, - region, - ), - region, - ); - - // ensure constraints are solved in this order, gives better errors - cons.insert(0, fields_con); - cons.insert(1, con); - cons.insert(2, record_con); - - let and_constraint = constraints.and_constraint(cons); - constraints.exists(vars, and_constraint) - } - Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region), - SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region), - List { - elem_var, - loc_elems, - } => { - if loc_elems.is_empty() { - let eq = constraints.equal_types( - empty_list_type(*elem_var), - expected, - Category::List, - region, - ); - constraints.exists(vec![*elem_var], eq) - } else { - let list_elem_type = Type::Variable(*elem_var); - let mut list_constraints = Vec::with_capacity(1 + loc_elems.len()); - - for (index, loc_elem) in loc_elems.iter().enumerate() { - let elem_expected = ForReason( - Reason::ElemInList { - index: HumanIndex::zero_based(index), - }, - list_elem_type.clone(), - loc_elem.region, - ); - let constraint = constrain_expr( - constraints, - env, - loc_elem.region, - &loc_elem.value, - elem_expected, - ); - - list_constraints.push(constraint); - } - - list_constraints.push(constraints.equal_types( - list_type(list_elem_type), - expected, - Category::List, - region, - )); - - let and_constraint = constraints.and_constraint(list_constraints); - constraints.exists([*elem_var], and_constraint) - } - } - Call(boxed, loc_args, called_via) => { - let (fn_var, loc_fn, closure_var, ret_var) = &**boxed; - // The expression that evaluates to the function being called, e.g. `foo` in - // (foo) bar baz - let opt_symbol = if let Var(symbol) = loc_fn.value { - Some(symbol) - } else { - None - }; - - let fn_type = Variable(*fn_var); - let fn_region = loc_fn.region; - let fn_expected = NoExpectation(fn_type.clone()); - - let fn_reason = Reason::FnCall { - name: opt_symbol, - arity: loc_args.len() as u8, - }; - - let fn_con = - constrain_expr(constraints, env, loc_fn.region, &loc_fn.value, fn_expected); - - // The function's return type - let ret_type = Variable(*ret_var); - - // type of values captured in the closure - let closure_type = Variable(*closure_var); - - // This will be used in the occurs check - let mut vars = Vec::with_capacity(2 + loc_args.len()); - - vars.push(*fn_var); - vars.push(*ret_var); - vars.push(*closure_var); - - let mut arg_types = Vec::with_capacity(loc_args.len()); - let mut arg_cons = Vec::with_capacity(loc_args.len()); - - for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() { - let region = loc_arg.region; - let arg_type = Variable(*arg_var); - - let reason = Reason::FnArg { - name: opt_symbol, - arg_index: HumanIndex::zero_based(index), - }; - let expected_arg = ForReason(reason, arg_type.clone(), region); - let arg_con = constrain_expr( - constraints, - env, - loc_arg.region, - &loc_arg.value, - expected_arg, - ); - - vars.push(*arg_var); - arg_types.push(arg_type); - arg_cons.push(arg_con); - } - - let expected_fn_type = ForReason( - fn_reason, - Function( - arg_types, - Box::new(closure_type), - Box::new(ret_type.clone()), - ), - region, - ); - - let category = Category::CallResult(opt_symbol, *called_via); - - let and_cons = [ - fn_con, - constraints.equal_types(fn_type, expected_fn_type, category.clone(), fn_region), - constraints.and_constraint(arg_cons), - constraints.equal_types(ret_type, expected, category, region), - ]; - - let and_constraint = constraints.and_constraint(and_cons); - constraints.exists(vars, and_constraint) - } - Var(symbol) => { - // make lookup constraint to lookup this symbol's type in the environment - constraints.lookup(*symbol, expected, region) - } - Closure(ClosureData { - function_type: fn_var, - closure_type: closure_var, - closure_ext_var, - return_type: ret_var, - arguments, - loc_body: boxed, - captured_symbols, - name, - .. - }) => { - // NOTE defs are treated somewhere else! - let loc_body_expr = &**boxed; - - let ret_var = *ret_var; - let closure_var = *closure_var; - let closure_ext_var = *closure_ext_var; - - let closure_type = Type::Variable(closure_var); - let return_type = Type::Variable(ret_var); - let (mut vars, pattern_state, function_type) = constrain_untyped_args( - constraints, - env, - arguments, - closure_type, - return_type.clone(), - ); - - vars.push(ret_var); - vars.push(closure_var); - vars.push(closure_ext_var); - vars.push(*fn_var); - - let body_type = NoExpectation(return_type); - let ret_constraint = constrain_expr( - constraints, - env, - loc_body_expr.region, - &loc_body_expr.value, - body_type, - ); - - // make sure the captured symbols are sorted! - debug_assert_eq!(captured_symbols.clone(), { - let mut copy = captured_symbols.clone(); - copy.sort(); - copy - }); - - let closure_constraint = constrain_closure_size( - constraints, - *name, - region, - captured_symbols, - closure_var, - closure_ext_var, - &mut vars, - ); - - let pattern_state_constraints = constraints.and_constraint(pattern_state.constraints); - let cons = [ - constraints.let_constraint( - [], - pattern_state.vars, - pattern_state.headers, - pattern_state_constraints, - ret_constraint, - ), - // "the closure's type is equal to expected type" - constraints.equal_types(function_type.clone(), expected, Category::Lambda, region), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - constraints.equal_types( - Type::Variable(*fn_var), - NoExpectation(function_type), - Category::Storage(std::file!(), std::line!()), - region, - ), - closure_constraint, - ]; - - constraints.exists_many(vars, cons) - } - - Expect(loc_cond, continuation) => { - let expect_bool = |region| { - let bool_type = Type::Variable(Variable::BOOL); - Expected::ForReason(Reason::ExpectCondition, bool_type, region) - }; - - let cond_con = constrain_expr( - constraints, - env, - loc_cond.region, - &loc_cond.value, - expect_bool(loc_cond.region), - ); - - let continuation_con = constrain_expr( - constraints, - env, - continuation.region, - &continuation.value, - expected, - ); - - constraints.exists_many([], [cond_con, continuation_con]) - } - - If { - cond_var, - branch_var, - branches, - final_else, - } => { - let expect_bool = |region| { - let bool_type = Type::Variable(Variable::BOOL); - Expected::ForReason(Reason::IfCondition, bool_type, region) - }; - let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3); - - // TODO why does this cond var exist? is it for error messages? - let first_cond_region = branches[0].0.region; - let cond_var_is_bool_con = constraints.equal_types( - Type::Variable(*cond_var), - expect_bool(first_cond_region), - Category::If, - first_cond_region, - ); - - branch_cons.push(cond_var_is_bool_con); - - match expected { - FromAnnotation(name, arity, ann_source, tipe) => { - let num_branches = branches.len() + 1; - for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { - let cond_con = constrain_expr( - constraints, - env, - loc_cond.region, - &loc_cond.value, - expect_bool(loc_cond.region), - ); - - let then_con = constrain_expr( - constraints, - env, - loc_body.region, - &loc_body.value, - FromAnnotation( - name.clone(), - arity, - AnnotationSource::TypedIfBranch { - index: HumanIndex::zero_based(index), - num_branches, - region: ann_source.region(), - }, - tipe.clone(), - ), - ); - - branch_cons.push(cond_con); - branch_cons.push(then_con); - } - - let else_con = constrain_expr( - constraints, - env, - final_else.region, - &final_else.value, - FromAnnotation( - name, - arity, - AnnotationSource::TypedIfBranch { - index: HumanIndex::zero_based(branches.len()), - num_branches, - region: ann_source.region(), - }, - tipe.clone(), - ), - ); - - let ast_con = constraints.equal_types( - Type::Variable(*branch_var), - NoExpectation(tipe), - Category::Storage(std::file!(), std::line!()), - region, - ); - - branch_cons.push(ast_con); - branch_cons.push(else_con); - - constraints.exists_many([*cond_var, *branch_var], branch_cons) - } - _ => { - for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { - let cond_con = constrain_expr( - constraints, - env, - loc_cond.region, - &loc_cond.value, - expect_bool(loc_cond.region), - ); - - let then_con = constrain_expr( - constraints, - env, - loc_body.region, - &loc_body.value, - ForReason( - Reason::IfBranch { - index: HumanIndex::zero_based(index), - total_branches: branches.len(), - }, - Type::Variable(*branch_var), - loc_body.region, - ), - ); - - branch_cons.push(cond_con); - branch_cons.push(then_con); - } - let else_con = constrain_expr( - constraints, - env, - final_else.region, - &final_else.value, - ForReason( - Reason::IfBranch { - index: HumanIndex::zero_based(branches.len()), - total_branches: branches.len() + 1, - }, - Type::Variable(*branch_var), - final_else.region, - ), - ); - - branch_cons.push(constraints.equal_types( - Type::Variable(*branch_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - )); - branch_cons.push(else_con); - - constraints.exists_many([*cond_var, *branch_var], branch_cons) - } - } - } - When { - cond_var, - expr_var, - loc_cond, - branches, - .. - } => { - // Infer the condition expression's type. - let cond_var = *cond_var; - let cond_type = Variable(cond_var); - let expr_con = constrain_expr( - constraints, - env, - region, - &loc_cond.value, - NoExpectation(cond_type.clone()), - ); - - let mut branch_constraints = Vec::with_capacity(branches.len() + 1); - branch_constraints.push(expr_con); - - match &expected { - FromAnnotation(name, arity, ann_source, _typ) => { - // NOTE deviation from elm. - // - // in elm, `_typ` is used, but because we have this `expr_var` too - // and need to constrain it, this is what works and gives better error messages - let typ = Type::Variable(*expr_var); - - for (index, when_branch) in branches.iter().enumerate() { - let pattern_region = - Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); - - let branch_con = constrain_when_branch( - constraints, - env, - when_branch.value.region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.clone(), - pattern_region, - ), - FromAnnotation( - name.clone(), - *arity, - AnnotationSource::TypedWhenBranch { - index: HumanIndex::zero_based(index), - region: ann_source.region(), - }, - typ.clone(), - ), - ); - - branch_constraints.push(branch_con); - } - - branch_constraints.push(constraints.equal_types( - typ, - expected, - Category::When, - region, - )); - - return constraints.exists_many([cond_var, *expr_var], branch_constraints); - } - - _ => { - let branch_type = Variable(*expr_var); - let mut branch_cons = Vec::with_capacity(branches.len()); - - for (index, when_branch) in branches.iter().enumerate() { - let pattern_region = - Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); - let branch_con = constrain_when_branch( - constraints, - env, - region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.clone(), - pattern_region, - ), - ForReason( - Reason::WhenBranch { - index: HumanIndex::zero_based(index), - }, - branch_type.clone(), - when_branch.value.region, - ), - ); - - branch_cons.push(branch_con); - } - - // Deviation: elm adds another layer of And nesting - // - // Record the original conditional expression's constraint. - // Each branch's pattern must have the same type - // as the condition expression did. - // - // The return type of each branch must equal the return type of - // the entire when-expression. - branch_cons.push(constraints.equal_types( - branch_type, - expected, - Category::When, - region, - )); - branch_constraints.push(constraints.and_constraint(branch_cons)); - } - } - - // exhautiveness checking happens when converting to mono::Expr - constraints.exists_many([cond_var, *expr_var], branch_constraints) - } - Access { - record_var, - ext_var, - field_var, - loc_expr, - field, - } => { - let ext_var = *ext_var; - let ext_type = Type::Variable(ext_var); - let field_var = *field_var; - let field_type = Type::Variable(field_var); - - let mut rec_field_types = SendMap::default(); - - let label = field.clone(); - rec_field_types.insert(label, RecordField::Demanded(field_type.clone())); - - let record_type = Type::Record(rec_field_types, Box::new(ext_type)); - let record_expected = Expected::NoExpectation(record_type); - - let category = Category::Access(field.clone()); - - let record_con = constraints.equal_types( - Type::Variable(*record_var), - record_expected.clone(), - category.clone(), - region, - ); - - let constraint = constrain_expr( - constraints, - &Env { - home: env.home, - rigids: ImMap::default(), - }, - region, - &loc_expr.value, - record_expected, - ); - - let eq = constraints.equal_types(field_type, expected, category, region); - constraints.exists_many( - [*record_var, field_var, ext_var], - [constraint, eq, record_con], - ) - } - Accessor { - name: closure_name, - function_var, - field, - record_var, - closure_ext_var: closure_var, - ext_var, - field_var, - } => { - let ext_var = *ext_var; - let ext_type = Variable(ext_var); - 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, Box::new(ext_type)); - - let category = Category::Accessor(field.clone()); - - let record_expected = Expected::NoExpectation(record_type.clone()); - let record_con = constraints.equal_types( - Type::Variable(*record_var), - record_expected, - category.clone(), - region, - ); - - let lambda_set = Type::ClosureTag { - name: *closure_name, - ext: *closure_var, - }; - - let function_type = Type::Function( - vec![record_type], - Box::new(lambda_set), - Box::new(field_type), - ); - - let cons = [ - constraints.equal_types(function_type.clone(), expected, category.clone(), region), - constraints.equal_types( - function_type, - NoExpectation(Variable(*function_var)), - category, - region, - ), - record_con, - ]; - - constraints.exists_many( - [*record_var, *function_var, *closure_var, field_var, ext_var], - cons, - ) - } - LetRec(defs, loc_ret, var) => { - let body_con = constrain_expr( - constraints, - env, - loc_ret.region, - &loc_ret.value, - expected.clone(), - ); - - let cons = [ - constrain_recursive_defs(constraints, env, defs, body_con), - // Record the type of tne entire def-expression in the variable. - // Code gen will need that later! - constraints.equal_types( - Type::Variable(*var), - expected, - Category::Storage(std::file!(), std::line!()), - loc_ret.region, - ), - ]; - - constraints.exists_many([*var], cons) - } - LetNonRec(def, loc_ret, var) => { - let mut stack = Vec::with_capacity(1); - - let mut loc_ret = loc_ret; - - stack.push((def, var, loc_ret.region)); - - while let LetNonRec(def, new_loc_ret, var) = &loc_ret.value { - stack.push((def, var, new_loc_ret.region)); - loc_ret = new_loc_ret; - } - - let mut body_con = constrain_expr( - constraints, - env, - loc_ret.region, - &loc_ret.value, - expected.clone(), - ); - - while let Some((def, var, ret_region)) = stack.pop() { - let cons = [ - constrain_def(constraints, env, def, body_con), - // Record the type of the entire def-expression in the variable. - // Code gen will need that later! - constraints.equal_types( - Type::Variable(*var), - expected.clone(), - Category::Storage(std::file!(), std::line!()), - ret_region, - ), - ]; - - body_con = constraints.exists_many([*var], cons) - } - - body_con - } - Tag { - variant_var, - ext_var, - name, - arguments, - } => { - let mut vars = Vec::with_capacity(arguments.len()); - let mut types = Vec::with_capacity(arguments.len()); - let mut arg_cons = Vec::with_capacity(arguments.len()); - - for (var, loc_expr) in arguments { - let arg_con = constrain_expr( - constraints, - env, - loc_expr.region, - &loc_expr.value, - Expected::NoExpectation(Type::Variable(*var)), - ); - - arg_cons.push(arg_con); - vars.push(*var); - types.push(Type::Variable(*var)); - } - - let union_con = constraints.equal_types( - Type::TagUnion( - vec![(name.clone(), types)], - Box::new(Type::Variable(*ext_var)), - ), - expected.clone(), - Category::TagApply { - tag_name: name.clone(), - args_count: arguments.len(), - }, - region, - ); - let ast_con = constraints.equal_types( - Type::Variable(*variant_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - vars.push(*variant_var); - vars.push(*ext_var); - arg_cons.push(union_con); - arg_cons.push(ast_con); - - constraints.exists_many(vars, arg_cons) - } - ZeroArgumentTag { - variant_var, - ext_var, - name, - arguments, - closure_name, - } => { - let mut vars = Vec::with_capacity(arguments.len()); - let mut types = Vec::with_capacity(arguments.len()); - let mut arg_cons = Vec::with_capacity(arguments.len()); - - for (var, loc_expr) in arguments { - let arg_con = constrain_expr( - constraints, - env, - loc_expr.region, - &loc_expr.value, - Expected::NoExpectation(Type::Variable(*var)), - ); - - arg_cons.push(arg_con); - vars.push(*var); - types.push(Type::Variable(*var)); - } - - let union_con = constraints.equal_types( - Type::FunctionOrTagUnion( - name.clone(), - *closure_name, - Box::new(Type::Variable(*ext_var)), - ), - expected.clone(), - Category::TagApply { - tag_name: name.clone(), - args_count: arguments.len(), - }, - region, - ); - let ast_con = constraints.equal_types( - Type::Variable(*variant_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - vars.push(*variant_var); - vars.push(*ext_var); - arg_cons.push(union_con); - arg_cons.push(ast_con); - - constraints.exists_many(vars, arg_cons) - } - - OpaqueRef { - opaque_var, - name, - argument, - specialized_def_type, - type_arguments, - lambda_set_variables, - } => { - let (arg_var, arg_loc_expr) = &**argument; - let arg_type = Type::Variable(*arg_var); - - let opaque_type = Type::Alias { - symbol: *name, - type_arguments: type_arguments.clone(), - lambda_set_variables: lambda_set_variables.clone(), - actual: Box::new(arg_type.clone()), - kind: AliasKind::Opaque, - }; - - // Constrain the argument - let arg_con = constrain_expr( - constraints, - env, - arg_loc_expr.region, - &arg_loc_expr.value, - Expected::NoExpectation(arg_type.clone()), - ); - - // Link the entire wrapped opaque type (with the now-constrained argument) to the - // expected type - let opaque_con = constraints.equal_types( - opaque_type, - expected.clone(), - Category::OpaqueWrap(*name), - region, - ); - - // Link the entire wrapped opaque type (with the now-constrained argument) to the type - // variables of the opaque type - // TODO: better expectation here - let link_type_variables_con = constraints.equal_types( - arg_type, - Expected::NoExpectation((**specialized_def_type).clone()), - Category::OpaqueArg, - arg_loc_expr.region, - ); - - // Store the entire wrapped opaque type in `opaque_var` - let storage_con = constraints.equal_types( - Type::Variable(*opaque_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - let mut vars = vec![*arg_var, *opaque_var]; - // Also add the fresh variables we created for the type argument and lambda sets - vars.extend(type_arguments.iter().map(|(_, t)| { - t.expect_variable("all type arguments should be fresh variables here") - })); - vars.extend(lambda_set_variables.iter().map(|v| { - v.0.expect_variable("all lambda sets should be fresh variables here") - })); - - constraints.exists_many( - vars, - [arg_con, opaque_con, link_type_variables_con, storage_con], - ) - } - - RunLowLevel { args, ret_var, op } => { - // This is a modified version of what we do for function calls. - - // The operation's return type - let ret_type = Variable(*ret_var); - - // This will be used in the occurs check - let mut vars = Vec::with_capacity(1 + args.len()); - - vars.push(*ret_var); - - let mut arg_types = Vec::with_capacity(args.len()); - let mut arg_cons = Vec::with_capacity(args.len()); - - let mut add_arg = |index, arg_type: Type, arg| { - let reason = Reason::LowLevelOpArg { - op: *op, - arg_index: HumanIndex::zero_based(index), - }; - let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); - let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); - - arg_types.push(arg_type); - arg_cons.push(arg_con); - }; - - for (index, (arg_var, arg)) in args.iter().enumerate() { - vars.push(*arg_var); - - add_arg(index, Variable(*arg_var), arg); - } - - let category = Category::LowLevelOpResult(*op); - - // Deviation: elm uses an additional And here - let eq = constraints.equal_types(ret_type, expected, category, region); - arg_cons.push(eq); - constraints.exists_many(vars, arg_cons) - } - ForeignCall { - args, - ret_var, - foreign_symbol, - } => { - // This is a modified version of what we do for function calls. - - // The operation's return type - let ret_type = Variable(*ret_var); - - // This will be used in the occurs check - let mut vars = Vec::with_capacity(1 + args.len()); - - vars.push(*ret_var); - - let mut arg_types = Vec::with_capacity(args.len()); - let mut arg_cons = Vec::with_capacity(args.len()); - - let mut add_arg = |index, arg_type: Type, arg| { - let reason = Reason::ForeignCallArg { - foreign_symbol: foreign_symbol.clone(), - arg_index: HumanIndex::zero_based(index), - }; - let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); - let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); - - arg_types.push(arg_type); - arg_cons.push(arg_con); - }; - - for (index, (arg_var, arg)) in args.iter().enumerate() { - vars.push(*arg_var); - - add_arg(index, Variable(*arg_var), arg); - } - - let category = Category::ForeignCall; - - // Deviation: elm uses an additional And here - let eq = constraints.equal_types(ret_type, expected, category, region); - arg_cons.push(eq); - constraints.exists_many(vars, arg_cons) - } - RuntimeError(_) => { - // Runtime Errors have no constraints because they're going to crash. - Constraint::True - } - } -} - -#[inline(always)] -fn constrain_when_branch( - constraints: &mut Constraints, - env: &Env, - region: Region, - when_branch: &WhenBranch, - pattern_expected: PExpected, - expr_expected: Expected, -) -> Constraint { - let ret_constraint = constrain_expr( - constraints, - env, - region, - &when_branch.value.value, - expr_expected, - ); - - let mut state = PatternState { - headers: SendMap::default(), - vars: Vec::with_capacity(1), - constraints: Vec::with_capacity(1), - }; - - // TODO investigate for error messages, is it better to unify all branches with a variable, - // then unify that variable with the expectation? - for loc_pattern in &when_branch.patterns { - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected.clone(), - &mut state, - ); - } - - if let Some(loc_guard) = &when_branch.guard { - let guard_constraint = constrain_expr( - constraints, - env, - region, - &loc_guard.value, - Expected::ForReason( - Reason::WhenGuard, - Type::Variable(Variable::BOOL), - loc_guard.region, - ), - ); - - // must introduce the headers from the pattern before constraining the guard - let state_constraints = constraints.and_constraint(state.constraints); - let inner = constraints.let_constraint( - [], - [], - SendMap::default(), - guard_constraint, - ret_constraint, - ); - - constraints.let_constraint([], state.vars, state.headers, state_constraints, inner) - } else { - let state_constraints = constraints.and_constraint(state.constraints); - constraints.let_constraint( - [], - state.vars, - state.headers, - state_constraints, - ret_constraint, - ) - } -} - -fn constrain_field( - constraints: &mut Constraints, - env: &Env, - field_var: Variable, - loc_expr: &Loc, -) -> (Type, Constraint) { - let field_type = Variable(field_var); - let field_expected = NoExpectation(field_type.clone()); - let constraint = constrain_expr( - constraints, - env, - loc_expr.region, - &loc_expr.value, - field_expected, - ); - - (field_type, constraint) -} - -#[inline(always)] -fn constrain_empty_record( - constraints: &mut Constraints, - region: Region, - expected: Expected, -) -> Constraint { - let expected_index = constraints.push_expected_type(expected); - - Constraint::Eq( - Constraints::EMPTY_RECORD, - expected_index, - Constraints::CATEGORY_RECORD, - region, - ) -} - -/// Constrain top-level module declarations -#[inline(always)] -pub fn constrain_decls( - constraints: &mut Constraints, - home: ModuleId, - decls: &[Declaration], -) -> Constraint { - let mut constraint = Constraint::SaveTheEnvironment; - - let mut env = Env { - home, - rigids: ImMap::default(), - }; - - for decl in decls.iter().rev() { - // Clear the rigids from the previous iteration. - // rigids are not shared between top-level definitions - env.rigids.clear(); - - match decl { - Declaration::Declare(def) | Declaration::Builtin(def) => { - constraint = constrain_def(constraints, &env, def, constraint); - } - Declaration::DeclareRec(defs) => { - constraint = constrain_recursive_defs(constraints, &env, defs, constraint); - } - Declaration::InvalidCycle(_) => { - // invalid cycles give a canonicalization error. we skip them here. - continue; - } - } - } - - // this assert make the "root" of the constraint wasn't dropped - debug_assert!(constraints.contains_save_the_environment(&constraint)); - - constraint -} - -fn constrain_def_pattern( - constraints: &mut Constraints, - env: &Env, - loc_pattern: &Loc, - expr_type: Type, -) -> PatternState { - let pattern_expected = PExpected::NoExpectation(expr_type); - - let mut state = PatternState { - headers: SendMap::default(), - vars: Vec::with_capacity(1), - constraints: Vec::with_capacity(1), - }; - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut state, - ); - - state -} - -fn constrain_def( - constraints: &mut Constraints, - env: &Env, - def: &Def, - body_con: Constraint, -) -> Constraint { - let expr_var = def.expr_var; - let expr_type = Type::Variable(expr_var); - - let mut def_pattern_state = - constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); - - def_pattern_state.vars.push(expr_var); - - let mut new_rigids = Vec::new(); - - let expr_con = match &def.annotation { - Some(annotation) => { - let arity = annotation.signature.arity(); - let rigids = &env.rigids; - let mut ftv = rigids.clone(); - - let signature = instantiate_rigids( - &annotation.signature, - &annotation.introduced_variables, - &mut new_rigids, - &mut ftv, - &def.loc_pattern, - &mut def_pattern_state.headers, - ); - - let env = &Env { - home: env.home, - rigids: ftv, - }; - - let annotation_expected = FromAnnotation( - def.loc_pattern.clone(), - arity, - AnnotationSource::TypedBody { - region: annotation.region, - }, - signature.clone(), - ); - - def_pattern_state.constraints.push(constraints.equal_types( - expr_type, - annotation_expected.clone(), - Category::Storage(std::file!(), std::line!()), - Region::span_across(&annotation.region, &def.loc_expr.region), - )); - - // when a def is annotated, and it's body is a closure, treat this - // as a named function (in elm terms) for error messages. - // - // This means we get errors like "the first argument of `f` is weird" - // instead of the more generic "something is wrong with the body of `f`" - match (&def.loc_expr.value, &signature) { - ( - Closure(ClosureData { - function_type: fn_var, - closure_type: closure_var, - closure_ext_var, - return_type: ret_var, - captured_symbols, - arguments, - loc_body, - name, - .. - }), - Type::Function(arg_types, signature_closure_type, ret_type), - ) => { - // NOTE if we ever have problems with the closure, the ignored `_closure_type` - // is probably a good place to start the investigation! - - let region = def.loc_expr.region; - - let loc_body_expr = &**loc_body; - let mut state = PatternState { - headers: SendMap::default(), - vars: Vec::with_capacity(arguments.len()), - constraints: Vec::with_capacity(1), - }; - let mut vars = Vec::with_capacity(state.vars.capacity() + 1); - let mut pattern_types = Vec::with_capacity(state.vars.capacity()); - let ret_var = *ret_var; - let closure_var = *closure_var; - let closure_ext_var = *closure_ext_var; - let ret_type = *ret_type.clone(); - - vars.push(ret_var); - vars.push(closure_var); - vars.push(closure_ext_var); - - let it = arguments.iter().zip(arg_types.iter()).enumerate(); - for (index, ((pattern_var, loc_pattern), loc_ann)) in it { - { - // ensure type matches the one in the annotation - let opt_label = - if let Pattern::Identifier(label) = def.loc_pattern.value { - Some(label) - } else { - None - }; - let pattern_type: &Type = loc_ann; - - let pattern_expected = PExpected::ForReason( - PReason::TypedArg { - index: HumanIndex::zero_based(index), - opt_name: opt_label, - }, - pattern_type.clone(), - loc_pattern.region, - ); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut state, - ); - } - - { - // NOTE: because we perform an equality with part of the signature - // this constraint must be to the def_pattern_state's constraints - def_pattern_state.vars.push(*pattern_var); - pattern_types.push(Type::Variable(*pattern_var)); - - let pattern_con = constraints.equal_types( - Type::Variable(*pattern_var), - Expected::NoExpectation(loc_ann.clone()), - Category::Storage(std::file!(), std::line!()), - loc_pattern.region, - ); - - def_pattern_state.constraints.push(pattern_con); - } - } - - let closure_constraint = constrain_closure_size( - constraints, - *name, - region, - captured_symbols, - closure_var, - closure_ext_var, - &mut vars, - ); - - let body_type = FromAnnotation( - def.loc_pattern.clone(), - arguments.len(), - AnnotationSource::TypedBody { - region: annotation.region, - }, - ret_type.clone(), - ); - - let ret_constraint = constrain_expr( - constraints, - env, - loc_body_expr.region, - &loc_body_expr.value, - body_type, - ); - - vars.push(*fn_var); - let defs_constraint = constraints.and_constraint(state.constraints); - - let cons = [ - constraints.let_constraint( - [], - state.vars, - state.headers, - defs_constraint, - ret_constraint, - ), - constraints.equal_types( - Type::Variable(closure_var), - Expected::FromAnnotation( - def.loc_pattern.clone(), - arity, - AnnotationSource::TypedBody { - region: annotation.region, - }, - *signature_closure_type.clone(), - ), - Category::ClosureSize, - region, - ), - constraints.store(signature.clone(), *fn_var, std::file!(), std::line!()), - constraints.store(signature, expr_var, std::file!(), std::line!()), - constraints.store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]; - - let and_constraint = constraints.and_constraint(cons); - constraints.exists(vars, and_constraint) - } - - _ => { - let expected = annotation_expected; - - let ret_constraint = constrain_expr( - constraints, - env, - def.loc_expr.region, - &def.loc_expr.value, - expected, - ); - - let cons = [ - constraints.let_constraint( - [], - [], - SendMap::default(), - Constraint::True, - ret_constraint, - ), - // Store type into AST vars. We use Store so errors aren't reported twice - constraints.store(signature, expr_var, std::file!(), std::line!()), - ]; - constraints.and_constraint(cons) - } - } - } - None => { - // no annotation, so no extra work with rigids - - constrain_expr( - constraints, - env, - def.loc_expr.region, - &def.loc_expr.value, - NoExpectation(expr_type), - ) - } - }; - - let and_constraint = constraints.and_constraint(def_pattern_state.constraints); - - let def_con = constraints.let_constraint( - [], - [], - SendMap::default(), // empty, because our functions have no arguments! - and_constraint, - expr_con, - ); - - constraints.let_constraint( - new_rigids, - def_pattern_state.vars, - def_pattern_state.headers, - def_con, - body_con, - ) -} - -fn constrain_closure_size( - constraints: &mut Constraints, - name: Symbol, - region: Region, - captured_symbols: &[(Symbol, Variable)], - closure_var: Variable, - closure_ext_var: Variable, - variables: &mut Vec, -) -> Constraint { - debug_assert!(variables.iter().any(|s| *s == closure_var)); - debug_assert!(variables.iter().any(|s| *s == closure_ext_var)); - - let mut tag_arguments = Vec::with_capacity(captured_symbols.len()); - let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len()); - - for (symbol, var) in captured_symbols { - // make sure the variable is registered - variables.push(*var); - - // this symbol is captured, so it must be part of the closure type - tag_arguments.push(Type::Variable(*var)); - - // make the variable equal to the looked-up type of symbol - captured_symbols_constraints.push(constraints.lookup( - *symbol, - Expected::NoExpectation(Type::Variable(*var)), - Region::zero(), - )); - } - - // pick a more efficient representation if we don't actually capture anything - let closure_type = if tag_arguments.is_empty() { - Type::ClosureTag { - name, - ext: closure_ext_var, - } - } else { - let tag_name = TagName::Closure(name); - Type::TagUnion( - vec![(tag_name, tag_arguments)], - Box::new(Type::Variable(closure_ext_var)), - ) - }; - - let finalizer = constraints.equal_types( - Type::Variable(closure_var), - NoExpectation(closure_type), - Category::ClosureSize, - region, - ); - - captured_symbols_constraints.push(finalizer); - - constraints.and_constraint(captured_symbols_constraints) -} - -fn instantiate_rigids( - annotation: &Type, - introduced_vars: &IntroducedVariables, - new_rigids: &mut Vec, - ftv: &mut ImMap, // rigids defined before the current annotation - loc_pattern: &Loc, - headers: &mut SendMap>, -) -> Type { - let mut annotation = annotation.clone(); - let mut rigid_substitution: ImMap = ImMap::default(); - - let outside_rigids: MutSet = ftv.values().copied().collect(); - - for (name, var) in introduced_vars.var_by_name.iter() { - if let Some(existing_rigid) = ftv.get(name) { - rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); - } else { - // It's possible to use this rigid in nested defs - ftv.insert(name.clone(), *var); - } - } - - // Instantiate rigid variables - if !rigid_substitution.is_empty() { - annotation.substitute(&rigid_substitution); - } - - if let Some(new_headers) = crate::soa_pattern::headers_from_annotation( - &loc_pattern.value, - &Loc::at(loc_pattern.region, annotation.clone()), - ) { - for (symbol, loc_type) in new_headers { - for var in loc_type.value.variables() { - // a rigid is only new if this annotation is the first occurrence of this rigid - if !outside_rigids.contains(&var) { - new_rigids.push(var); - } - } - headers.insert(symbol, loc_type); - } - } - - for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { - ftv.insert(format!("*{}", i).into(), *wildcard); - } - - annotation -} - -fn constrain_recursive_defs( - constraints: &mut Constraints, - env: &Env, - defs: &[Def], - body_con: Constraint, -) -> Constraint { - rec_defs_help( - constraints, - env, - defs, - body_con, - Info::with_capacity(defs.len()), - Info::with_capacity(defs.len()), - ) -} - -pub fn rec_defs_help( - constraints: &mut Constraints, - env: &Env, - defs: &[Def], - body_con: Constraint, - mut rigid_info: Info, - mut flex_info: Info, -) -> Constraint { - for def in defs { - let expr_var = def.expr_var; - let expr_type = Type::Variable(expr_var); - - let mut def_pattern_state = - constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); - - def_pattern_state.vars.push(expr_var); - - let mut new_rigids = Vec::new(); - match &def.annotation { - None => { - let expr_con = constrain_expr( - constraints, - env, - def.loc_expr.region, - &def.loc_expr.value, - NoExpectation(expr_type), - ); - - // TODO investigate if this let can be safely removed - let def_con = constraints.let_constraint( - [], - [], // empty because Roc function defs have no args - SendMap::default(), // empty because Roc function defs have no args - Constraint::True, // I think this is correct, once again because there are no args - expr_con, - ); - - flex_info.vars = def_pattern_state.vars; - flex_info.constraints.push(def_con); - flex_info.def_types.extend(def_pattern_state.headers); - } - - Some(annotation) => { - let arity = annotation.signature.arity(); - let mut ftv = env.rigids.clone(); - - let signature = instantiate_rigids( - &annotation.signature, - &annotation.introduced_variables, - &mut new_rigids, - &mut ftv, - &def.loc_pattern, - &mut def_pattern_state.headers, - ); - - let annotation_expected = FromAnnotation( - def.loc_pattern.clone(), - arity, - AnnotationSource::TypedBody { - region: annotation.region, - }, - signature.clone(), - ); - - // when a def is annotated, and it's body is a closure, treat this - // as a named function (in elm terms) for error messages. - // - // This means we get errors like "the first argument of `f` is weird" - // instead of the more generic "something is wrong with the body of `f`" - match (&def.loc_expr.value, &signature) { - ( - Closure(ClosureData { - function_type: fn_var, - closure_type: closure_var, - closure_ext_var, - return_type: ret_var, - captured_symbols, - arguments, - loc_body, - name, - .. - }), - Type::Function(arg_types, _closure_type, ret_type), - ) => { - // NOTE if we ever have trouble with closure type unification, the ignored - // `_closure_type` here is a good place to start investigating - - let expected = annotation_expected; - let region = def.loc_expr.region; - - let loc_body_expr = &**loc_body; - let mut state = PatternState { - headers: SendMap::default(), - vars: Vec::with_capacity(arguments.len()), - constraints: Vec::with_capacity(1), - }; - let mut vars = Vec::with_capacity(state.vars.capacity() + 1); - let mut pattern_types = Vec::with_capacity(state.vars.capacity()); - let ret_var = *ret_var; - let closure_var = *closure_var; - let closure_ext_var = *closure_ext_var; - let ret_type = *ret_type.clone(); - - vars.push(ret_var); - vars.push(closure_var); - vars.push(closure_ext_var); - - let it = arguments.iter().zip(arg_types.iter()).enumerate(); - for (index, ((pattern_var, loc_pattern), loc_ann)) in it { - { - // ensure type matches the one in the annotation - let opt_label = - if let Pattern::Identifier(label) = def.loc_pattern.value { - Some(label) - } else { - None - }; - let pattern_type: &Type = loc_ann; - - let pattern_expected = PExpected::ForReason( - PReason::TypedArg { - index: HumanIndex::zero_based(index), - opt_name: opt_label, - }, - pattern_type.clone(), - loc_pattern.region, - ); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut state, - ); - } - - { - // NOTE: because we perform an equality with part of the signature - // this constraint must be to the def_pattern_state's constraints - def_pattern_state.vars.push(*pattern_var); - pattern_types.push(Type::Variable(*pattern_var)); - - let pattern_con = constraints.equal_types( - Type::Variable(*pattern_var), - Expected::NoExpectation(loc_ann.clone()), - Category::Storage(std::file!(), std::line!()), - loc_pattern.region, - ); - - def_pattern_state.constraints.push(pattern_con); - } - } - - let closure_constraint = constrain_closure_size( - constraints, - *name, - region, - captured_symbols, - closure_var, - closure_ext_var, - &mut vars, - ); - - let fn_type = Type::Function( - pattern_types, - Box::new(Type::Variable(closure_var)), - Box::new(ret_type.clone()), - ); - let body_type = NoExpectation(ret_type.clone()); - let expr_con = constrain_expr( - constraints, - env, - loc_body_expr.region, - &loc_body_expr.value, - body_type, - ); - - vars.push(*fn_var); - - let state_constraints = constraints.and_constraint(state.constraints); - let cons = [ - constraints.let_constraint( - [], - state.vars, - state.headers, - state_constraints, - expr_con, - ), - constraints.equal_types( - fn_type.clone(), - expected.clone(), - Category::Lambda, - region, - ), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - // Store type into AST vars. We use Store so errors aren't reported twice - constraints.store( - signature.clone(), - *fn_var, - std::file!(), - std::line!(), - ), - constraints.store(signature, expr_var, std::file!(), std::line!()), - constraints.store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]; - - let and_constraint = constraints.and_constraint(cons); - let def_con = constraints.exists(vars, and_constraint); - - rigid_info.vars.extend(&new_rigids); - - rigid_info.constraints.push(constraints.let_constraint( - new_rigids, - def_pattern_state.vars, - SendMap::default(), // no headers introduced (at this level) - def_con, - Constraint::True, - )); - rigid_info.def_types.extend(def_pattern_state.headers); - } - _ => { - let expected = annotation_expected; - - let ret_constraint = constrain_expr( - constraints, - env, - def.loc_expr.region, - &def.loc_expr.value, - expected, - ); - - let cons = [ - constraints.let_constraint( - [], - [], - SendMap::default(), - Constraint::True, - ret_constraint, - ), - // Store type into AST vars. We use Store so errors aren't reported twice - constraints.store(signature, expr_var, std::file!(), std::line!()), - ]; - let def_con = constraints.and_constraint(cons); - - rigid_info.vars.extend(&new_rigids); - - rigid_info.constraints.push(constraints.let_constraint( - new_rigids, - def_pattern_state.vars, - SendMap::default(), // no headers introduced (at this level) - def_con, - Constraint::True, - )); - rigid_info.def_types.extend(def_pattern_state.headers); - } - } - } - } - } - - let flex_constraints = constraints.and_constraint(flex_info.constraints); - let inner_inner = constraints.let_constraint( - [], - [], - flex_info.def_types.clone(), - Constraint::True, - flex_constraints, - ); - - let rigid_constraints = { - let mut temp = rigid_info.constraints; - temp.push(body_con); - - constraints.and_constraint(temp) - }; - - let inner = constraints.let_constraint( - [], - flex_info.vars, - flex_info.def_types, - inner_inner, - rigid_constraints, - ); - - constraints.let_constraint( - rigid_info.vars, - [], - rigid_info.def_types, - Constraint::True, - inner, - ) -} - -#[inline(always)] -fn constrain_field_update( - constraints: &mut Constraints, - env: &Env, - var: Variable, - region: Region, - field: Lowercase, - loc_expr: &Loc, -) -> (Variable, Type, Constraint) { - let field_type = Type::Variable(var); - let reason = Reason::RecordUpdateValue(field); - let expected = ForReason(reason, field_type.clone(), region); - let con = constrain_expr(constraints, env, loc_expr.region, &loc_expr.value, expected); - - (var, field_type, con) -} diff --git a/compiler/constrain/src/soa_pattern.rs b/compiler/constrain/src/soa_pattern.rs deleted file mode 100644 index 54de2ce469..0000000000 --- a/compiler/constrain/src/soa_pattern.rs +++ /dev/null @@ -1,540 +0,0 @@ -use crate::builtins; -use crate::soa_expr::{constrain_expr, Env}; -use roc_can::constraint::{Constraint, Constraints}; -use roc_can::expected::{Expected, PExpected}; -use roc_can::pattern::Pattern::{self, *}; -use roc_can::pattern::{DestructType, RecordDestruct}; -use roc_collections::all::{HumanIndex, SendMap}; -use roc_module::ident::Lowercase; -use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region}; -use roc_types::subs::Variable; -use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type}; - -#[derive(Default)] -pub struct PatternState { - pub headers: SendMap>, - pub vars: Vec, - pub constraints: Vec, -} - -/// If there is a type annotation, the pattern state headers can be optimized by putting the -/// annotation in the headers. Normally -/// -/// x = 4 -/// -/// Would add `x => <42>` to the headers (i.e., symbol points to a type variable). If the -/// definition has an annotation, we instead now add `x => Int`. -pub fn headers_from_annotation( - pattern: &Pattern, - annotation: &Loc, -) -> Option>> { - let mut headers = SendMap::default(); - // Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int` - // in such incorrect cases we don't put the full annotation in headers, just a variable, and let - // inference generate a proper error. - let is_structurally_valid = headers_from_annotation_help(pattern, annotation, &mut headers); - - if is_structurally_valid { - Some(headers) - } else { - None - } -} - -fn headers_from_annotation_help( - pattern: &Pattern, - annotation: &Loc, - headers: &mut SendMap>, -) -> bool { - match pattern { - Identifier(symbol) | Shadowed(_, _, symbol) => { - headers.insert(*symbol, annotation.clone()); - true - } - Underscore - | MalformedPattern(_, _) - | UnsupportedPattern(_) - | OpaqueNotInScope(..) - | NumLiteral(..) - | IntLiteral(..) - | FloatLiteral(..) - | SingleQuote(_) - | StrLiteral(_) => true, - - RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { - Type::Record(fields, _) => { - for loc_destruct in destructs { - let destruct = &loc_destruct.value; - - // NOTE: We ignore both Guard and optionality when - // determining the type of the assigned def (which is what - // gets added to the header here). - // - // For example, no matter whether it's `{ x } = rec` or - // `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases - // the type of `x` within the binding itself is the same. - if let Some(field_type) = fields.get(&destruct.label) { - headers.insert( - destruct.symbol, - Loc::at(annotation.region, field_type.clone().into_inner()), - ); - } else { - return false; - } - } - true - } - Type::EmptyRec => destructs.is_empty(), - _ => false, - }, - - AppliedTag { - tag_name, - arguments, - .. - } => match annotation.value.shallow_dealias() { - Type::TagUnion(tags, _) => { - if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) { - if !arguments.len() == arg_types.len() { - return false; - } - - arguments - .iter() - .zip(arg_types.iter()) - .all(|(arg_pattern, arg_type)| { - headers_from_annotation_help( - &arg_pattern.1.value, - &Loc::at(annotation.region, arg_type.clone()), - headers, - ) - }) - } else { - false - } - } - _ => false, - }, - - UnwrappedOpaque { - whole_var: _, - opaque, - argument, - specialized_def_type: _, - type_arguments: pat_type_arguments, - lambda_set_variables: pat_lambda_set_variables, - } => match &annotation.value { - Type::Alias { - symbol, - kind: AliasKind::Opaque, - actual, - type_arguments, - lambda_set_variables, - } if symbol == opaque - && type_arguments.len() == pat_type_arguments.len() - && lambda_set_variables.len() == pat_lambda_set_variables.len() => - { - headers.insert(*opaque, annotation.clone()); - - let (_, argument_pat) = &**argument; - headers_from_annotation_help( - &argument_pat.value, - &Loc::at(annotation.region, (**actual).clone()), - headers, - ) - } - _ => false, - }, - } -} - -/// This accepts PatternState (rather than returning it) so that the caller can -/// initialize the Vecs in PatternState using with_capacity -/// based on its knowledge of their lengths. -pub fn constrain_pattern( - constraints: &mut Constraints, - env: &Env, - pattern: &Pattern, - region: Region, - expected: PExpected, - state: &mut PatternState, -) { - match pattern { - Underscore => { - // This is an underscore in a position where we destruct a variable, - // like a when expression: - // when x is - // A -> "" - // _ -> "" - // so, we know that "x" (in this case, a tag union) must be open. - state - .constraints - .push(constraints.is_open_type(expected.get_type())); - } - UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => { - // Erroneous patterns don't add any constraints. - } - - Identifier(symbol) | Shadowed(_, _, symbol) => { - state - .constraints - .push(constraints.is_open_type(expected.get_type_ref().clone())); - state.headers.insert( - *symbol, - Loc { - region, - value: expected.get_type(), - }, - ); - } - - &NumLiteral(var, _, _, bound) => { - state.vars.push(var); - - let num_type = builtins::num_num(Type::Variable(var)); - - let num_type = builtins::add_numeric_bound_constr_soa( - constraints, - &mut state.constraints, - num_type, - bound, - region, - Category::Num, - ); - - state.constraints.push(constraints.equal_pattern_types( - num_type, - expected, - PatternCategory::Num, - region, - )); - } - - &IntLiteral(num_var, precision_var, _, _, bound) => { - // First constraint on the free num var; this improves the resolved type quality in - // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr_soa( - constraints, - &mut state.constraints, - Type::Variable(num_var), - bound, - region, - Category::Int, - ); - - // Link the free num var with the int var and our expectation. - let int_type = builtins::num_int(Type::Variable(precision_var)); - - state.constraints.push(constraints.equal_types( - num_type, // TODO check me if something breaks! - Expected::NoExpectation(int_type), - Category::Int, - region, - )); - - // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(constraints.equal_pattern_types( - Type::Variable(num_var), - expected, - PatternCategory::Int, - region, - )); - } - - &FloatLiteral(num_var, precision_var, _, _, bound) => { - // First constraint on the free num var; this improves the resolved type quality in - // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr_soa( - constraints, - &mut state.constraints, - Type::Variable(num_var), - bound, - region, - Category::Float, - ); - - // Link the free num var with the float var and our expectation. - let float_type = builtins::num_float(Type::Variable(precision_var)); - - state.constraints.push(constraints.equal_types( - num_type.clone(), // TODO check me if something breaks! - Expected::NoExpectation(float_type), - Category::Float, - region, - )); - - // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(constraints.equal_pattern_types( - num_type, // TODO check me if something breaks! - expected, - PatternCategory::Float, - region, - )); - } - - StrLiteral(_) => { - state.constraints.push(constraints.equal_pattern_types( - builtins::str_type(), - expected, - PatternCategory::Str, - region, - )); - } - - SingleQuote(_) => { - state.constraints.push(constraints.equal_pattern_types( - builtins::num_u32(), - expected, - PatternCategory::Character, - region, - )); - } - - RecordDestructure { - whole_var, - ext_var, - destructs, - } => { - state.vars.push(*whole_var); - state.vars.push(*ext_var); - let ext_type = Type::Variable(*ext_var); - - let mut field_types: SendMap> = SendMap::default(); - - for Loc { - value: - RecordDestruct { - var, - label, - symbol, - typ, - }, - .. - } in destructs - { - let pat_type = Type::Variable(*var); - let expected = PExpected::NoExpectation(pat_type.clone()); - - if !state.headers.contains_key(symbol) { - state - .headers - .insert(*symbol, Loc::at(region, pat_type.clone())); - } - - let field_type = match typ { - DestructType::Guard(guard_var, loc_guard) => { - state.constraints.push(constraints.pattern_presence( - Type::Variable(*guard_var), - PExpected::ForReason( - PReason::PatternGuard, - pat_type.clone(), - loc_guard.region, - ), - PatternCategory::PatternGuard, - region, - )); - state.vars.push(*guard_var); - - constrain_pattern( - constraints, - env, - &loc_guard.value, - loc_guard.region, - expected, - state, - ); - - RecordField::Demanded(pat_type) - } - DestructType::Optional(expr_var, loc_expr) => { - state.constraints.push(constraints.pattern_presence( - Type::Variable(*expr_var), - PExpected::ForReason( - PReason::OptionalField, - pat_type.clone(), - loc_expr.region, - ), - PatternCategory::PatternDefault, - region, - )); - - state.vars.push(*expr_var); - - let expr_expected = Expected::ForReason( - Reason::RecordDefaultField(label.clone()), - pat_type.clone(), - loc_expr.region, - ); - - let expr_con = constrain_expr( - constraints, - env, - loc_expr.region, - &loc_expr.value, - expr_expected, - ); - state.constraints.push(expr_con); - - RecordField::Optional(pat_type) - } - DestructType::Required => { - // No extra constraints necessary. - RecordField::Demanded(pat_type) - } - }; - - field_types.insert(label.clone(), field_type); - - state.vars.push(*var); - } - - let record_type = Type::Record(field_types, Box::new(ext_type)); - - let whole_con = constraints.equal_types( - Type::Variable(*whole_var), - Expected::NoExpectation(record_type), - Category::Storage(std::file!(), std::line!()), - region, - ); - - let record_con = constraints.pattern_presence( - Type::Variable(*whole_var), - expected, - PatternCategory::Record, - region, - ); - - state.constraints.push(whole_con); - state.constraints.push(record_con); - } - AppliedTag { - whole_var, - ext_var, - tag_name, - arguments, - } => { - let mut argument_types = Vec::with_capacity(arguments.len()); - for (index, (pattern_var, loc_pattern)) in arguments.iter().enumerate() { - state.vars.push(*pattern_var); - - let pattern_type = Type::Variable(*pattern_var); - argument_types.push(pattern_type.clone()); - - let expected = PExpected::ForReason( - PReason::TagArg { - tag_name: tag_name.clone(), - index: HumanIndex::zero_based(index), - }, - pattern_type, - region, - ); - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - expected, - state, - ); - } - - let pat_category = PatternCategory::Ctor(tag_name.clone()); - - let whole_con = constraints.includes_tag( - expected.clone().get_type(), - tag_name.clone(), - argument_types.clone(), - pat_category.clone(), - region, - ); - - let tag_con = constraints.pattern_presence( - Type::Variable(*whole_var), - expected, - pat_category, - region, - ); - - state.vars.push(*whole_var); - state.vars.push(*ext_var); - state.constraints.push(whole_con); - state.constraints.push(tag_con); - } - - UnwrappedOpaque { - whole_var, - opaque, - argument, - specialized_def_type, - type_arguments, - lambda_set_variables, - } => { - // Suppose we are constraining the pattern \@Id who, where Id n := [ Id U64 n ] - let (arg_pattern_var, loc_arg_pattern) = &**argument; - let arg_pattern_type = Type::Variable(*arg_pattern_var); - - let opaque_type = Type::Alias { - symbol: *opaque, - type_arguments: type_arguments.clone(), - lambda_set_variables: lambda_set_variables.clone(), - actual: Box::new(arg_pattern_type.clone()), - kind: AliasKind::Opaque, - }; - - // First, add a constraint for the argument "who" - let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone()); - constrain_pattern( - constraints, - env, - &loc_arg_pattern.value, - loc_arg_pattern.region, - arg_pattern_expected, - state, - ); - - // Next, link `whole_var` to the opaque type of "@Id who" - let whole_con = constraints.equal_types( - Type::Variable(*whole_var), - Expected::NoExpectation(opaque_type), - Category::Storage(std::file!(), std::line!()), - region, - ); - - // Link the entire wrapped opaque type (with the now-constrained argument) to the type - // variables of the opaque type - // TODO: better expectation here - let link_type_variables_con = constraints.equal_types( - (**specialized_def_type).clone(), - Expected::NoExpectation(arg_pattern_type), - Category::OpaqueWrap(*opaque), - loc_arg_pattern.region, - ); - - // Next, link `whole_var` (the type of "@Id who") to the expected type - let opaque_pattern_con = constraints.pattern_presence( - Type::Variable(*whole_var), - expected, - PatternCategory::Opaque(*opaque), - region, - ); - - state - .vars - .extend_from_slice(&[*arg_pattern_var, *whole_var]); - // Also add the fresh variables we created for the type argument and lambda sets - state.vars.extend(type_arguments.iter().map(|(_, t)| { - t.expect_variable("all type arguments should be fresh variables here") - })); - state.vars.extend(lambda_set_variables.iter().map(|v| { - v.0.expect_variable("all lambda sets should be fresh variables here") - })); - - state.constraints.extend_from_slice(&[ - whole_con, - link_type_variables_con, - opaque_pattern_con, - ]); - } - } -} From 38d3d3169abff382ee26de7f90ec99123f3ee3e6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 21:30:38 +0100 Subject: [PATCH 054/150] drop final suffixes --- compiler/constrain/src/builtins.rs | 14 +++++----- compiler/constrain/src/expr.rs | 43 +++++++++++++++--------------- compiler/constrain/src/module.rs | 9 +++---- compiler/constrain/src/pattern.rs | 6 ++--- compiler/load/src/file.rs | 8 +++--- compiler/solve/src/module.rs | 2 +- 6 files changed, 40 insertions(+), 42 deletions(-) diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 7ff498f3e2..16cb4b25da 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -12,7 +12,7 @@ use roc_types::types::{AliasKind, Category}; #[must_use] #[inline(always)] -pub fn add_numeric_bound_constr_soa( +pub fn add_numeric_bound_constr( constraints: &mut Constraints, num_constraints: &mut impl Extend, num_type: Type, @@ -41,7 +41,7 @@ pub fn add_numeric_bound_constr_soa( } #[inline(always)] -pub fn int_literal_soa( +pub fn int_literal( constraints: &mut Constraints, num_var: Variable, precision_var: Variable, @@ -53,7 +53,7 @@ pub fn int_literal_soa( // Always add the bound first; this improves the resolved type quality in case it's an alias like "U8". let mut constrs = ArrayVec::<_, 3>::new(); - let num_type = add_numeric_bound_constr_soa( + let num_type = add_numeric_bound_constr( constraints, &mut constrs, Variable(num_var), @@ -78,7 +78,7 @@ pub fn int_literal_soa( } #[inline(always)] -pub fn float_literal_soa( +pub fn float_literal( constraints: &mut Constraints, num_var: Variable, precision_var: Variable, @@ -89,7 +89,7 @@ pub fn float_literal_soa( let reason = Reason::FloatLiteral; let mut constrs = ArrayVec::<_, 3>::new(); - let num_type = add_numeric_bound_constr_soa( + let num_type = add_numeric_bound_constr( constraints, &mut constrs, Variable(num_var), @@ -113,7 +113,7 @@ pub fn float_literal_soa( } #[inline(always)] -pub fn num_literal_soa( +pub fn num_literal( constraints: &mut Constraints, num_var: Variable, expected: Expected, @@ -123,7 +123,7 @@ pub fn num_literal_soa( let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); let mut constrs = ArrayVec::<_, 2>::new(); - let num_type = add_numeric_bound_constr_soa( + let num_type = add_numeric_bound_constr( constraints, &mut constrs, open_number_type, diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 083339638f..e3e602ddab 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1,6 +1,5 @@ use crate::builtins::{ - empty_list_type, float_literal_soa, int_literal_soa, list_type, num_literal_soa, num_u32, - str_type, + empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type, }; use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; @@ -19,23 +18,23 @@ use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField}; - /// This is for constraining Defs - #[derive(Default, Debug)] - pub struct Info { - pub vars: Vec, - pub constraints: Vec, - pub def_types: SendMap>, - } - - impl Info { - pub fn with_capacity(capacity: usize) -> Self { - Info { - vars: Vec::with_capacity(capacity), - constraints: Vec::with_capacity(capacity), - def_types: SendMap::default(), - } - } - } +/// This is for constraining Defs +#[derive(Default, Debug)] +pub struct Info { + pub vars: Vec, + pub constraints: Vec, + pub def_types: SendMap>, +} + +impl Info { + pub fn with_capacity(capacity: usize) -> Self { + Info { + vars: Vec::with_capacity(capacity), + constraints: Vec::with_capacity(capacity), + def_types: SendMap::default(), + } + } +} pub struct Env { /// Whenever we encounter a user-defined type variable (a "rigid" var for short), @@ -90,11 +89,11 @@ pub fn constrain_expr( ) -> Constraint { match expr { &Int(var, precision, _, _, bound) => { - int_literal_soa(constraints, var, precision, expected, region, bound) + int_literal(constraints, var, precision, expected, region, bound) } - &Num(var, _, _, bound) => num_literal_soa(constraints, var, expected, region, bound), + &Num(var, _, _, bound) => num_literal(constraints, var, expected, region, bound), &Float(var, precision, _, _, bound) => { - float_literal_soa(constraints, var, precision, expected, region, bound) + float_literal(constraints, var, precision, expected, region, bound) } EmptyRecord => constrain_empty_record(constraints, region, expected), Expr::Record { record_var, fields } => { diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 056d62554e..18b251efeb 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -16,8 +16,7 @@ pub enum ExposedModuleTypes { Valid(MutMap, MutMap), } - -pub fn constrain_module_soa( +pub fn constrain_module( constraints: &mut Constraints, declarations: &[Declaration], home: ModuleId, @@ -31,7 +30,7 @@ pub struct Import { pub solved_type: SolvedType, } -pub fn constrain_imported_values_soa( +pub fn constrain_imported_values( constraints: &mut Constraints, imports: Vec, body_con: Constraint, @@ -88,14 +87,14 @@ pub fn constrain_imported_values_soa( } /// Run pre_constrain_imports to get imported_symbols and imported_aliases. -pub fn constrain_imports_soa( +pub fn constrain_imports( constraints: &mut Constraints, imported_symbols: Vec, constraint: Constraint, var_store: &mut VarStore, ) -> Constraint { let (_introduced_rigids, constraint) = - constrain_imported_values_soa(constraints, imported_symbols, constraint, var_store); + constrain_imported_values(constraints, imported_symbols, constraint, var_store); // TODO determine what to do with those rigids // for var in introduced_rigids { diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index db3e29549a..f7b5675cb6 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -194,7 +194,7 @@ pub fn constrain_pattern( let num_type = builtins::num_num(Type::Variable(var)); - let num_type = builtins::add_numeric_bound_constr_soa( + let num_type = builtins::add_numeric_bound_constr( constraints, &mut state.constraints, num_type, @@ -214,7 +214,7 @@ pub fn constrain_pattern( &IntLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr_soa( + let num_type = builtins::add_numeric_bound_constr( constraints, &mut state.constraints, Type::Variable(num_var), @@ -245,7 +245,7 @@ pub fn constrain_pattern( &FloatLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - let num_type = builtins::add_numeric_bound_constr_soa( + let num_type = builtins::add_numeric_bound_constr( constraints, &mut state.constraints, Type::Variable(num_var), diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 97b0cea115..918fe56330 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -10,7 +10,7 @@ use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; use roc_constrain::module::{ - constrain_imports_soa, constrain_module_soa, pre_constrain_imports, ConstrainableImports, + constrain_imports, constrain_module, pre_constrain_imports, ConstrainableImports, ExposedModuleTypes, Import, SubsByModule, }; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; @@ -3091,7 +3091,7 @@ fn run_solve<'a>( // Finish constraining the module by wrapping the existing Constraint // in the ones we just computed. We can do this off the main thread. - let constraint = constrain_imports_soa( + let constraint = constrain_imports( &mut constraints, imported_symbols, constraint, @@ -3113,7 +3113,7 @@ fn run_solve<'a>( // if false { debug_assert!(constraint.validate(), "{:?}", &constraint); } let (solved_subs, solved_env, problems) = - roc_solve::module::run_solve_soa(&constraints, constraint, rigid_variables, var_store); + roc_solve::module::run_solve(&constraints, constraint, rigid_variables, var_store); let exposed_vars_by_symbol: Vec<_> = solved_env .vars_by_symbol() @@ -3260,7 +3260,7 @@ fn canonicalize_and_constrain<'a>( let mut constraints = Constraints::new(); let constraint = - constrain_module_soa(&mut constraints, &module_output.declarations, module_id); + constrain_module(&mut constraints, &module_output.declarations, module_id); let module = Module { module_id, diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index 1404fc1d0d..a767cfb2e7 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -16,7 +16,7 @@ pub struct SolvedModule { pub problems: Vec, } -pub fn run_solve_soa( +pub fn run_solve( constraints: &Constraints, constraint: ConstraintSoa, rigid_variables: MutMap, From 760331829fe21d10bee7f1651c4b81a1c6e7aa34 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 22:05:38 +0100 Subject: [PATCH 055/150] deduplicate categories --- compiler/can/src/constraint.rs | 98 +++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index df1c478fd5..b98184536f 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -33,16 +33,44 @@ impl Constraints { let variables = Vec::new(); let def_types = Vec::new(); let let_constraints = Vec::new(); - let mut categories = Vec::new(); - let pattern_categories = Vec::new(); + let mut categories = Vec::with_capacity(16); + let mut pattern_categories = Vec::with_capacity(16); let expectations = Vec::new(); let pattern_expectations = Vec::new(); let includes_tags = Vec::new(); - types.push(Type::EmptyRec); - types.push(Type::EmptyTagUnion); + types.extend([Type::EmptyRec, Type::EmptyTagUnion]); - categories.push(Category::Record); + categories.extend([ + Category::Record, + Category::ForeignCall, + Category::OpaqueArg, + Category::Lambda, + Category::ClosureSize, + Category::StrInterpolation, + Category::If, + Category::When, + Category::Float, + Category::Int, + Category::Num, + Category::List, + Category::Str, + Category::Character, + ]); + + pattern_categories.extend([ + PatternCategory::Record, + PatternCategory::EmptyRecord, + PatternCategory::PatternGuard, + PatternCategory::PatternDefault, + PatternCategory::Set, + PatternCategory::Map, + PatternCategory::Str, + PatternCategory::Num, + PatternCategory::Int, + PatternCategory::Float, + PatternCategory::Character, + ]); Self { constraints, @@ -62,6 +90,31 @@ impl Constraints { pub const EMPTY_TAG_UNION: Index = Index::new(1); pub const CATEGORY_RECORD: Index = Index::new(0); + pub const CATEGORY_FOREIGNCALL: Index = Index::new(1); + pub const CATEGORY_OPAQUEARG: Index = Index::new(2); + pub const CATEGORY_LAMBDA: Index = Index::new(3); + pub const CATEGORY_CLOSURESIZE: Index = Index::new(4); + pub const CATEGORY_STRINTERPOLATION: Index = Index::new(5); + pub const CATEGORY_IF: Index = Index::new(6); + pub const CATEGORY_WHEN: Index = Index::new(7); + pub const CATEGORY_FLOAT: Index = Index::new(8); + pub const CATEGORY_INT: Index = Index::new(9); + pub const CATEGORY_NUM: Index = Index::new(10); + pub const CATEGORY_LIST: Index = Index::new(11); + pub const CATEGORY_STR: Index = Index::new(12); + pub const CATEGORY_CHARACTER: Index = Index::new(13); + + pub const PCATEGORY_RECORD: Index = Index::new(0); + pub const PCATEGORY_EMPTYRECORD: Index = Index::new(1); + pub const PCATEGORY_PATTERNGUARD: Index = Index::new(2); + pub const PCATEGORY_PATTERNDEFAULT: Index = Index::new(3); + pub const PCATEGORY_SET: Index = Index::new(4); + pub const PCATEGORY_MAP: Index = Index::new(5); + pub const PCATEGORY_STR: Index = Index::new(6); + pub const PCATEGORY_NUM: Index = Index::new(7); + pub const PCATEGORY_INT: Index = Index::new(8); + pub const PCATEGORY_FLOAT: Index = Index::new(9); + pub const PCATEGORY_CHARACTER: Index = Index::new(10); #[inline(always)] pub fn push_type(&mut self, typ: Type) -> Index { @@ -81,10 +134,41 @@ impl Constraints { pub fn push_category(&mut self, category: Category) -> Index { match category { Category::Record => Self::CATEGORY_RECORD, + Category::ForeignCall => Self::CATEGORY_FOREIGNCALL, + Category::OpaqueArg => Self::CATEGORY_OPAQUEARG, + Category::Lambda => Self::CATEGORY_LAMBDA, + Category::ClosureSize => Self::CATEGORY_CLOSURESIZE, + Category::StrInterpolation => Self::CATEGORY_STRINTERPOLATION, + Category::If => Self::CATEGORY_IF, + Category::When => Self::CATEGORY_WHEN, + Category::Float => Self::CATEGORY_FLOAT, + Category::Int => Self::CATEGORY_INT, + Category::Num => Self::CATEGORY_NUM, + Category::List => Self::CATEGORY_LIST, + Category::Str => Self::CATEGORY_STR, + Category::Character => Self::CATEGORY_CHARACTER, other => Index::push_new(&mut self.categories, other), } } + #[inline(always)] + pub fn push_pattern_category(&mut self, category: PatternCategory) -> Index { + match category { + PatternCategory::Record => Self::PCATEGORY_RECORD, + PatternCategory::EmptyRecord => Self::PCATEGORY_EMPTYRECORD, + PatternCategory::PatternGuard => Self::PCATEGORY_PATTERNGUARD, + PatternCategory::PatternDefault => Self::PCATEGORY_PATTERNDEFAULT, + PatternCategory::Set => Self::PCATEGORY_SET, + PatternCategory::Map => Self::PCATEGORY_MAP, + PatternCategory::Str => Self::PCATEGORY_STR, + PatternCategory::Num => Self::PCATEGORY_NUM, + PatternCategory::Int => Self::PCATEGORY_INT, + PatternCategory::Float => Self::PCATEGORY_FLOAT, + PatternCategory::Character => Self::PCATEGORY_CHARACTER, + other => Index::push_new(&mut self.pattern_categories, other), + } + } + pub fn equal_types( &mut self, typ: Type, @@ -94,7 +178,7 @@ impl Constraints { ) -> Constraint { let type_index = Index::push_new(&mut self.types, typ); let expected_index = Index::push_new(&mut self.expectations, expected); - let category_index = Index::push_new(&mut self.categories, category); + let category_index = Self::push_category(self, category); Constraint::Eq(type_index, expected_index, category_index, region) } @@ -108,7 +192,7 @@ impl Constraints { ) -> Constraint { let type_index = Index::push_new(&mut self.types, typ); let expected_index = Index::push_new(&mut self.pattern_expectations, expected); - let category_index = Index::push_new(&mut self.pattern_categories, category); + let category_index = Self::push_pattern_category(self, category); Constraint::Pattern(type_index, expected_index, category_index, region) } From f8021fb4496e5bc2bebbc6b55ec4a2a6c4cf6f12 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 22:17:25 +0100 Subject: [PATCH 056/150] fix reporting tests --- reporting/tests/helpers/mod.rs | 11 ++++++++--- reporting/tests/test_reporting.rs | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 0485f9da9d..eb081ccfd3 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -1,7 +1,7 @@ extern crate bumpalo; use self::bumpalo::Bump; -use roc_can::constraint::Constraint; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::env::Env; use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; @@ -28,11 +28,12 @@ pub fn test_home() -> ModuleId { pub fn infer_expr( subs: Subs, problems: &mut Vec, + constraints: &Constraints, constraint: &Constraint, expr_var: Variable, ) -> (Content, Subs) { let env = solve::Env::default(); - let (solved, _) = solve::run(&env, problems, subs, constraint); + let (solved, _) = solve::run(constraints, &env, problems, subs, constraint); let content = solved .inner() @@ -96,6 +97,7 @@ pub struct CanExprOut { pub var_store: VarStore, pub var: Variable, pub constraint: Constraint, + pub constraints: Constraints, } #[derive(Debug)] @@ -152,7 +154,9 @@ pub fn can_expr_with<'a>( &loc_expr.value, ); + let mut constraints = Constraints::new(); let constraint = constrain_expr( + &mut constraints, &roc_constrain::expr::Env { rigids: ImMap::default(), home, @@ -174,7 +178,7 @@ pub fn can_expr_with<'a>( //load builtin values let (_introduced_rigids, constraint) = - constrain_imported_values(imports, constraint, &mut var_store); + constrain_imported_values(&mut constraints, imports, constraint, &mut var_store); let mut all_ident_ids = MutMap::default(); @@ -200,6 +204,7 @@ pub fn can_expr_with<'a>( interns, var, constraint, + constraints, }) } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 963b1ab832..6a5ba6a1e3 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -62,6 +62,7 @@ mod test_reporting { output, var_store, var, + constraints, constraint, home, interns, @@ -79,7 +80,8 @@ mod test_reporting { } let mut unify_problems = Vec::new(); - let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (_content, mut subs) = + infer_expr(subs, &mut unify_problems, &constraints, &constraint, var); name_all_type_vars(var, &mut subs); From da89152fef7b0e7a9738f664b13d7c2da80a7597 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Mar 2022 08:32:32 +0100 Subject: [PATCH 057/150] fix static assert --- compiler/can/src/constraint.rs | 3 +-- compiler/load/src/file.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index b98184536f..e134002c50 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -435,8 +435,7 @@ impl Constraints { } } -static_assertions::assert_eq_size!([u8; 4 * 8], Constraint); -static_assertions::assert_eq_size!([u8; 3 * 8 + 4], LetConstraint); +static_assertions::assert_eq_size!([u8; 3 * 8], Constraint); #[derive(Debug, Clone, PartialEq)] pub enum Constraint { diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 918fe56330..b7cfd80be4 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1039,7 +1039,7 @@ fn load<'a>( ) -> Result, LoadingProblem<'a>> { // When compiling to wasm, we cannot spawn extra threads // so we have a single-threaded implementation - if cfg!(target_family = "wasm") { + if true || cfg!(target_family = "wasm") { load_single_threaded( arena, load_start, From 3d30bcef03f2b6b7a8739b8fa8f035cf158e1a45 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Mar 2022 10:13:52 +0100 Subject: [PATCH 058/150] store filenames out of band --- compiler/can/src/constraint.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index e134002c50..489dc8e8ef 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -18,6 +18,7 @@ pub struct Constraints { pub expectations: Vec>, pub pattern_expectations: Vec>, pub includes_tags: Vec, + pub strings: Vec<&'static str>, } impl Default for Constraints { @@ -38,6 +39,7 @@ impl Constraints { let expectations = Vec::new(); let pattern_expectations = Vec::new(); let includes_tags = Vec::new(); + let strings = Vec::new(); types.extend([Type::EmptyRec, Type::EmptyTagUnion]); @@ -83,6 +85,7 @@ impl Constraints { expectations, pattern_expectations, includes_tags, + strings, } } @@ -427,11 +430,10 @@ impl Constraints { filename: &'static str, line_number: u32, ) -> Constraint { - let type_index = Index::new(self.types.len() as _); + let type_index = Index::push_new(&mut self.types, typ); + let string_index = Index::push_new(&mut self.strings, filename); - self.types.push(typ); - - Constraint::Store(type_index, variable, filename, line_number) + Constraint::Store(type_index, variable, string_index, line_number) } } @@ -440,7 +442,7 @@ static_assertions::assert_eq_size!([u8; 3 * 8], Constraint); #[derive(Debug, Clone, PartialEq)] pub enum Constraint { Eq(Index, Index>, Index, Region), - Store(Index, Variable, &'static str, u32), + Store(Index, Variable, Index<&'static str>, u32), Lookup(Symbol, Index>, Region), Pattern( Index, From a53a5b718effa67997174aa3af93ff2c26ce4017 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Mar 2022 10:36:18 +0100 Subject: [PATCH 059/150] clippy --- compiler/load/src/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index b7cfd80be4..918fe56330 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1039,7 +1039,7 @@ fn load<'a>( ) -> Result, LoadingProblem<'a>> { // When compiling to wasm, we cannot spawn extra threads // so we have a single-threaded implementation - if true || cfg!(target_family = "wasm") { + if cfg!(target_family = "wasm") { load_single_threaded( arena, load_start, From 79b65347fa8327ea0b536f62e1cc6984c969d6e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Mar 2022 23:13:56 +0000 Subject: [PATCH 060/150] repl_www: Improved UI for multiline input --- repl_www/public/repl.css | 2 -- repl_www/public/repl.js | 8 +++--- repl_www/public/repl/index.html | 48 ++++++++++++++++++--------------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/repl_www/public/repl.css b/repl_www/public/repl.css index f39bccbff6..554921b009 100644 --- a/repl_www/public/repl.css +++ b/repl_www/public/repl.css @@ -64,9 +64,7 @@ section.source { display: flex; flex-direction: column; } - section.source textarea { - height: 32px; padding: 8px; margin-bottom: 16px; } diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 008a08c8e7..7411cc1705 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -82,7 +82,9 @@ function onInputKeyup(event) { break; case ENTER: - onInputChange({ target: repl.elemSourceInput }); + if (!event.shiftKey) { + onInputChange({ target: repl.elemSourceInput }); + } break; default: @@ -168,8 +170,8 @@ function createHistoryEntry(inputText) { const historyIndex = repl.inputHistory.length; repl.inputHistory.push(inputText); - const inputElem = document.createElement("div"); - inputElem.textContent = "> " + inputText; + const inputElem = document.createElement("pre"); + inputElem.textContent = inputText; inputElem.classList.add("input"); const historyItem = document.createElement("div"); diff --git a/repl_www/public/repl/index.html b/repl_www/public/repl/index.html index 5bc4ac7e76..e294e24c28 100644 --- a/repl_www/public/repl/index.html +++ b/repl_www/public/repl/index.html @@ -1,29 +1,33 @@ + + Roc REPL + + - - Roc REPL - - + +
+
+

The rockin' Roc REPL

+
- -
-
-

The rockin' Roc REPL

-
- -
-
-
-
-
- -
- -
-
- - +
+
+
+
+
+
+ +
+
+ + From f8e23d93cc309daba0edf8e3f134f5c000b05af3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Mar 2022 08:14:50 +0000 Subject: [PATCH 061/150] repl_www: nicer line spacing --- repl_www/public/repl.css | 14 +++++++++----- repl_www/public/repl.js | 3 ++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/repl_www/public/repl.css b/repl_www/public/repl.css index 554921b009..2c39fc8a3d 100644 --- a/repl_www/public/repl.css +++ b/repl_www/public/repl.css @@ -43,16 +43,20 @@ section.history { margin: 16px 0; padding: 8px; } -#history-text .input { +.history-item { + margin-bottom: 24px; +} +.history-item .input { + margin: 0; margin-bottom: 8px; } -#history-text .output { - margin-bottom: 16px; +.history-item .output { + margin: 0; } -#history-text .output-ok { +.history-item .output-ok { color: #0f8; } -#history-text .output-error { +.history-item .output-error { color: #f00; } .code { diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 7411cc1705..4ad0323e15 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -176,6 +176,7 @@ function createHistoryEntry(inputText) { const historyItem = document.createElement("div"); historyItem.appendChild(inputElem); + historyItem.classList.add("history-item"); repl.elemHistory.appendChild(historyItem); repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; @@ -184,7 +185,7 @@ function createHistoryEntry(inputText) { } function updateHistoryEntry(index, ok, outputText) { - const outputElem = document.createElement("div"); + const outputElem = document.createElement("pre"); outputElem.textContent = outputText; outputElem.classList.add("output"); outputElem.classList.add(ok ? "output-ok" : "output-error"); From 3867b2da0119b5cd8576e0afbf534cc6e81c8f75 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Mar 2022 13:12:56 +0000 Subject: [PATCH 062/150] www: Change download URL for pre-built web REPL I'm making changes more frequently to the REPL than we are to the source code. I want to be able to deploy it from a script rather than opening new GitHub issues, so I decided to just commit the assets into a branch of a GitHub repo. The repo was only used as a prototype for the web REPL so I don't use it anymore. --- www/build.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/www/build.sh b/www/build.sh index ae07359997..86db1d0134 100755 --- a/www/build.sh +++ b/www/build.sh @@ -12,13 +12,18 @@ rm -rf build/ cp -r public/ build/ pushd build + # grab the source code and copy it to Netlify's server; if it's not there, fail the build. wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip # grab the pre-compiled REPL and copy it to Netlify's server; if it's not there, fail the build. -wget https://github.com/brian-carroll/mock-repl/files/8167902/roc_repl_wasm.tar.gz -tar xzvf roc_repl_wasm.tar.gz -rm roc_repl_wasm.tar.gz +wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip +unzip deploy.zip +mv mock-repl-deploy/* . +rmdir mock-repl-deploy +rm deploy.zip + +# Copy REPL webpage source files cp -r ../../repl_www/public/* . popd From 4252f093ad97c1b65cab388c587471728a751937 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Mar 2022 13:26:49 +0000 Subject: [PATCH 063/150] Instructions about Shift+Enter --- repl_www/public/repl/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repl_www/public/repl/index.html b/repl_www/public/repl/index.html index e294e24c28..4c71ebc6ed 100644 --- a/repl_www/public/repl/index.html +++ b/repl_www/public/repl/index.html @@ -24,7 +24,7 @@ autofocus id="source-input" class="code" - placeholder="Type some Roc code and press Enter" + placeholder="Type some Roc code and press Enter. (Use Shift+Enter for multi-line input)" > From db06c10b5fa55249e866e40a847d3f68383d1675 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Mar 2022 23:02:10 +0100 Subject: [PATCH 064/150] be smarter --- compiler/constrain/src/expr.rs | 85 +++++++++++++--------- compiler/constrain/src/pattern.rs | 14 ++-- compiler/solve/tests/solve_expr.rs | 38 ++++++++++ compiler/types/src/types.rs | 112 +++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 40 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index da99c6c50e..2d3d131019 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -11,7 +11,7 @@ use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; -use roc_collections::all::{ImMap, Index, MutSet, SendMap}; +use roc_collections::all::{ImMap, Index, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; @@ -52,7 +52,7 @@ pub struct Env { /// Whenever we encounter a user-defined type variable (a "rigid" var for short), /// for example `a` in the annotation `identity : a -> a`, we add it to this /// map so that expressions within that annotation can share these vars. - pub rigids: ImMap, + pub rigids: MutMap, pub home: ModuleId, } @@ -693,7 +693,7 @@ pub fn constrain_expr( let constraint = constrain_expr( &Env { home: env.home, - rigids: ImMap::default(), + rigids: MutMap::default(), }, region, &loc_expr.value, @@ -1170,7 +1170,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { let mut env = Env { home, - rigids: ImMap::default(), + rigids: MutMap::default(), }; for decl in decls.iter().rev() { @@ -1227,13 +1227,29 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { def_pattern_state.vars.push(expr_var); let mut new_rigids = Vec::new(); - let expr_con = match &def.annotation { Some(annotation) => { let arity = annotation.signature.arity(); let rigids = &env.rigids; let mut ftv = rigids.clone(); + /* + let mut new_rigids = Vec::new(); + + // pub wildcards: Vec, + // pub var_by_name: SendMap, + 'outer: for (outer_name, outer_var) in rigids.iter() { + for (inner_name, inner_var) in annotation.introduced_variables.var_by_name.iter() { + if outer_name == inner_name { + debug_assert_eq!(inner_var, outer_var); + continue 'outer; + } + } + + // the inner name is not in the outer scope; it's introduced here + } + */ + let signature = instantiate_rigids( &annotation.signature, &annotation.introduced_variables, @@ -1514,46 +1530,47 @@ fn instantiate_rigids( annotation: &Type, introduced_vars: &IntroducedVariables, new_rigids: &mut Vec, - ftv: &mut ImMap, // rigids defined before the current annotation + ftv: &mut MutMap, // rigids defined before the current annotation loc_pattern: &Loc, headers: &mut SendMap>, ) -> Type { - let mut annotation = annotation.clone(); + // find out if rigid type variables first occur in this annotation, + // or if they are already introduced in an outer annotation let mut rigid_substitution: ImMap = ImMap::default(); - - let outside_rigids: MutSet = ftv.values().copied().collect(); - for (name, var) in introduced_vars.var_by_name.iter() { - if let Some(existing_rigid) = ftv.get(name) { - rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); - } else { - // It's possible to use this rigid in nested defs - ftv.insert(name.clone(), *var); + use std::collections::hash_map::Entry::*; + + match ftv.entry(name.clone()) { + Occupied(occupied) => { + let existing_rigid = occupied.get(); + rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); + } + Vacant(vacant) => { + // It's possible to use this rigid in nested defs + vacant.insert(*var); + new_rigids.push(*var); + } } } - // Instantiate rigid variables + // wildcards are always freshly introduced in this annotation + for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { + ftv.insert(format!("*{}", i).into(), *wildcard); + new_rigids.push(*wildcard); + } + + let mut annotation = annotation.clone(); if !rigid_substitution.is_empty() { annotation.substitute(&rigid_substitution); } - if let Some(new_headers) = crate::pattern::headers_from_annotation( - &loc_pattern.value, - &Loc::at(loc_pattern.region, annotation.clone()), - ) { - for (symbol, loc_type) in new_headers { - for var in loc_type.value.variables() { - // a rigid is only new if this annotation is the first occurrence of this rigid - if !outside_rigids.contains(&var) { - new_rigids.push(var); - } - } - headers.insert(symbol, loc_type); - } - } - - for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { - ftv.insert(format!("*{}", i).into(), *wildcard); + let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation); + if let Pattern::Identifier(symbol) = loc_pattern.value { + headers.insert(symbol, Loc::at(loc_pattern.region, annotation.clone())); + } else if let Some(new_headers) = + crate::pattern::headers_from_annotation(&loc_pattern.value, &loc_annotation_ref) + { + headers.extend(new_headers) } annotation @@ -1584,7 +1601,6 @@ pub fn rec_defs_help( def_pattern_state.vars.push(expr_var); - let mut new_rigids = Vec::new(); match &def.annotation { None => { let expr_con = constrain_expr( @@ -1611,6 +1627,7 @@ pub fn rec_defs_help( Some(annotation) => { let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); + let mut new_rigids = Vec::new(); let signature = instantiate_rigids( &annotation.signature, diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 200f38dc74..c0aee74614 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -27,7 +27,7 @@ pub struct PatternState { /// definition has an annotation, we instead now add `x => Int`. pub fn headers_from_annotation( pattern: &Pattern, - annotation: &Loc, + annotation: &Loc<&Type>, ) -> Option>> { let mut headers = SendMap::default(); // Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int` @@ -44,12 +44,13 @@ pub fn headers_from_annotation( fn headers_from_annotation_help( pattern: &Pattern, - annotation: &Loc, + annotation: &Loc<&Type>, headers: &mut SendMap>, ) -> bool { match pattern { Identifier(symbol) | Shadowed(_, _, symbol) => { - headers.insert(*symbol, annotation.clone()); + let typ = Loc::at(annotation.region, annotation.value.clone()); + headers.insert(*symbol, typ); true } Underscore @@ -106,7 +107,7 @@ fn headers_from_annotation_help( .all(|(arg_pattern, arg_type)| { headers_from_annotation_help( &arg_pattern.1.value, - &Loc::at(annotation.region, arg_type.clone()), + &Loc::at(annotation.region, arg_type), headers, ) }) @@ -135,12 +136,13 @@ fn headers_from_annotation_help( && type_arguments.len() == pat_type_arguments.len() && lambda_set_variables.len() == pat_lambda_set_variables.len() => { - headers.insert(*opaque, annotation.clone()); + let typ = Loc::at(annotation.region, annotation.value.clone()); + headers.insert(*opaque, typ); let (_, argument_pat) = &**argument; headers_from_annotation_help( &argument_pat.value, - &Loc::at(annotation.region, (**actual).clone()), + &Loc::at(annotation.region, actual), headers, ) } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 6987ac4b4c..28566441d7 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5509,4 +5509,42 @@ mod solve_expr { r#"Id [ A, B, C { a : Str }e ] -> Str"#, ) } + + #[test] + fn inner_annotation_rigid() { + infer_eq_without_problem( + indoc!( + r#" + f : a -> a + f = + g : b -> b + g = \x -> x + + g + + f + "# + ), + r#"a -> a"#, + ) + } + + #[test] + fn inner_annotation_rigid_2() { + infer_eq_without_problem( + indoc!( + r#" + f : {} -> List a + f = + g : List a + g = [] + + \{} -> g + + f + "# + ), + r#"{} -> List a"#, + ) + } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 298fd32be8..c37e0f38ed 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -991,6 +991,118 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { } } +pub struct VariablesIter<'a> { + stack: Vec<&'a Type>, + recursion_variables: Vec, + variables: Vec, +} + +impl<'a> VariablesIter<'a> { + pub fn new(typ: &'a Type) -> Self { + Self { + stack: vec![typ], + recursion_variables: vec![], + variables: vec![], + } + } +} + +impl<'a> Iterator for VariablesIter<'a> { + type Item = Variable; + + fn next(&mut self) -> Option { + use Type::*; + + if let Some(var) = self.variables.pop() { + debug_assert!(!self.recursion_variables.contains(&var)); + + return Some(var); + } + + while let Some(tipe) = self.stack.pop() { + match tipe { + EmptyRec | EmptyTagUnion | Erroneous(_) => { + continue; + } + + ClosureTag { ext: v, .. } | Variable(v) => { + if !self.recursion_variables.contains(v) { + return Some(*v); + } + } + + Function(args, closure, ret) => { + self.stack.push(ret); + self.stack.push(closure); + self.stack.extend(args.iter().rev()); + } + Record(fields, ext) => { + use RecordField::*; + + self.stack.push(ext); + + for (_, field) in fields { + match field { + Optional(x) => self.stack.push(x), + Required(x) => self.stack.push(x), + Demanded(x) => self.stack.push(x), + }; + } + } + TagUnion(tags, ext) => { + self.stack.push(ext); + + for (_, args) in tags { + self.stack.extend(args); + } + } + FunctionOrTagUnion(_, _, ext) => { + self.stack.push(ext); + } + RecursiveTagUnion(rec, tags, ext) => { + self.recursion_variables.push(*rec); + self.stack.push(ext); + + for (_, args) in tags.iter().rev() { + self.stack.extend(args); + } + } + Alias { + type_arguments, + actual, + .. + } => { + self.stack.push(actual); + + for (_, args) in type_arguments.iter().rev() { + self.stack.push(args); + } + } + HostExposedAlias { + type_arguments: arguments, + actual, + .. + } => { + self.stack.push(actual); + + for (_, args) in arguments.iter().rev() { + self.stack.push(args); + } + } + RangedNumber(typ, vars) => { + self.stack.push(typ); + self.variables.extend(vars.iter().copied()); + } + Apply(_, args, _) => { + self.stack.extend(args); + } + } + } + + None + } +} + fn variables_help(tipe: &Type, accum: &mut ImSet) { use Type::*; From 39c4b878e5cf3dfe21428e25213987a8d6542dac Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Mar 2022 23:27:05 +0100 Subject: [PATCH 065/150] remove comment --- compiler/constrain/src/expr.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 2d3d131019..07e2c4c1df 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1233,23 +1233,6 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let rigids = &env.rigids; let mut ftv = rigids.clone(); - /* - let mut new_rigids = Vec::new(); - - // pub wildcards: Vec, - // pub var_by_name: SendMap, - 'outer: for (outer_name, outer_var) in rigids.iter() { - for (inner_name, inner_var) in annotation.introduced_variables.var_by_name.iter() { - if outer_name == inner_name { - debug_assert_eq!(inner_var, outer_var); - continue 'outer; - } - } - - // the inner name is not in the outer scope; it's introduced here - } - */ - let signature = instantiate_rigids( &annotation.signature, &annotation.introduced_variables, From dc92de7781dbe76b70ba7a5ae0e57a23fac30a30 Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Fri, 4 Mar 2022 17:32:09 -0500 Subject: [PATCH 066/150] Add plumbing for displaying RocDec in the repl. --- Cargo.lock | 4 ++++ repl_eval/Cargo.toml | 1 + repl_eval/src/eval.rs | 34 +++++++++++++++++----------------- repl_eval/src/lib.rs | 6 ++++++ roc_std/Cargo.toml | 5 ++++- roc_std/src/lib.rs | 29 +++++++++++++++++++++++++++++ 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1eb1cc622b..24fe390b48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3749,6 +3749,7 @@ dependencies = [ "roc_parse", "roc_region", "roc_reporting", + "roc_std", "roc_target", "roc_types", ] @@ -3823,6 +3824,9 @@ dependencies = [ [[package]] name = "roc_std" version = "0.1.0" +dependencies = [ + "roc_error_macros", +] [[package]] name = "roc_target" diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml index ebb3843553..37e58c2f50 100644 --- a/repl_eval/Cargo.toml +++ b/repl_eval/Cargo.toml @@ -18,5 +18,6 @@ roc_mono = {path = "../compiler/mono"} roc_parse = {path = "../compiler/parse"} roc_region = {path = "../compiler/region"} roc_reporting = {path = "../reporting"} +roc_std = {path = "../roc_std", default-features = false} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index e706ddde0a..ad48f1350a 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -13,6 +13,7 @@ use roc_mono::layout::{ }; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; +use roc_std::RocDec; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; @@ -277,6 +278,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { + macro_rules! helper { + ($ty:ty) => { + app.call_function(main_fn_name, |_, num: $ty| { + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }) + }; + } + let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { @@ -287,14 +296,6 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; - macro_rules! helper { - ($ty:ty) => { - app.call_function(main_fn_name, |_, num: $ty| { - num_to_ast(env, number_literal_to_ast(env.arena, num), content) - }) - }; - } - let result = match int_width { U8 | I8 => { // NOTE: `helper!` does not handle 8-bit numbers yet @@ -317,14 +318,6 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::Float(float_width)) => { use FloatWidth::*; - macro_rules! helper { - ($ty:ty) => { - app.call_function(main_fn_name, |_, num: $ty| { - num_to_ast(env, number_literal_to_ast(env.arena, num), content) - }) - }; - } - let result = match float_width { F32 => helper!(f32), F64 => helper!(f64), @@ -333,6 +326,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Ok(result) } + Layout::Builtin(Builtin::Decimal) => { + Ok(helper!(RocDec)) + } Layout::Builtin(Builtin::Str) => { let size = layout.stack_size(env.target_info) as usize; Ok( @@ -1246,5 +1242,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E /// This is centralized in case we want to format it differently later, /// e.g. adding underscores for large numbers fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> { - Expr::Num(arena.alloc(format!("{}", num))) + use std::fmt::Write; + + let mut string = bumpalo::collections::String::with_capacity_in(64, arena); + write!(string, "{}", num).unwrap(); + Expr::Num(string.into_bump_str()) } diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index 0aebe53507..f1b52d4530 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -1,4 +1,5 @@ use roc_parse::ast::Expr; +use roc_std::RocDec; pub mod eval; pub mod gen; @@ -47,5 +48,10 @@ pub trait ReplAppMemory { fn deref_f32(&self, addr: usize) -> f32; fn deref_f64(&self, addr: usize) -> f64; + fn deref_dec(&self, addr: usize) -> RocDec { + let bits = self.deref_i128(addr); + return RocDec(bits); + } + fn deref_str(&self, addr: usize) -> &str; } diff --git a/roc_std/Cargo.toml b/roc_std/Cargo.toml index 03576dbeda..265bd0fd40 100644 --- a/roc_std/Cargo.toml +++ b/roc_std/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository = "https://github.com/rtfeldman/roc" version = "0.1.0" +[dependencies] +roc_error_macros = { path = "../error_macros" } + [dev-dependencies] indoc = "1.0.3" pretty_assertions = "1.0.0" @@ -17,4 +20,4 @@ libc = "0.2.106" [features] default = ["platform"] -platform = [] \ No newline at end of file +platform = [] diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index d40457eac4..8aafa95fcf 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -4,6 +4,9 @@ use core::ffi::c_void; use core::fmt; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::Drop; +use core::str; +// use roc_error_macros::internal_error; +// uncomment when we figure out why it fails below. mod rc; mod roc_list; @@ -277,4 +280,30 @@ impl RocDec { pub fn from_str_to_i128_unsafe(val: &str) -> i128 { Self::from_str(val).unwrap().0 } + + fn to_str_helper(&self, bytes: &mut [u8; 1]) { + bytes[0] = 0; + // TODO + } + + pub fn to_str(&self) -> RocStr { + let mut bytes: [u8; 1] = [0]; + self.to_str_helper(&mut bytes); + unsafe {RocStr::from_slice(&bytes) } + } + +} + +impl fmt::Display for RocDec { + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut bytes: [u8; 1] = [0]; + self.to_str_helper(&mut bytes); + match str::from_utf8(&bytes) { + Ok(slice) => write!(fmtr, "{}", slice), + Err(payload) => panic!("Error in converting RocDec({}) to a string: {}", self.0, payload), + // Err(payload) => internal_error!("Error in converting RocDec({}) to a string: {}", self.0, payload), + // This raises a compile error: can't find eprintln + // Is this because we don't use std? + } + } } From 1d3aa261931d0c8fbac6d8f40b66fe46f9408fec Mon Sep 17 00:00:00 2001 From: Pierre-Henri Trivier Date: Fri, 4 Mar 2022 23:35:53 +0100 Subject: [PATCH 067/150] Fixes #2586 --- compiler/fmt/src/annotation.rs | 3 +++ compiler/fmt/tests/test_fmt.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 4380af17e2..4c389f7147 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -293,6 +293,9 @@ impl<'a> Formattable for TypeAnnotation<'a> { SpaceBefore(ann, spaces) => { buf.newline(); + + buf.indent(indent); + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); ann.format_with_options(buf, parens, Newlines::No, indent) } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index a49914abce..edea7957d3 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2984,6 +2984,18 @@ mod test_fmt { )); } + #[test] + fn multiline_higher_order_function() { + expr_formats_same(indoc!( + r#" + foo : + (Str -> Bool) -> Bool + + 42 + "# + )); + } + #[test] /// Test that everything under examples/ is formatted correctly /// If this test fails on your diff, it probably means you need to re-format the examples. From 4ca9ea0b89669578a2e9697a5b9c98da87888090 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Mar 2022 23:57:41 +0100 Subject: [PATCH 068/150] refactor After --- compiler/solve/src/solve.rs | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c7ce79b7a6..9bedf114f7 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -211,19 +211,13 @@ pub fn run_in_place( state.env } -enum After { - CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), -} - enum Work<'a> { Constraint { env: &'a Env, rank: Rank, constraint: &'a Constraint, - after: Option, }, - /// Something to be done after a constraint and all its dependencies are fully solved. - After(After), + CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), } #[allow(clippy::too_many_arguments)] @@ -238,16 +232,16 @@ fn solve( subs: &mut Subs, constraint: &Constraint, ) -> State { - let mut stack = vec![Work::Constraint { + let initial = Work::Constraint { env, rank, constraint, - after: None, - }]; + }; + let mut stack = vec![initial]; while let Some(work_item) = stack.pop() { let (env, rank, constraint) = match work_item { - Work::After(After::CheckForInfiniteTypes(def_vars)) => { + Work::CheckForInfiniteTypes(def_vars) => { for (symbol, loc_var) in def_vars.iter() { check_for_infinite_type(subs, problems, *symbol, *loc_var); } @@ -258,15 +252,7 @@ fn solve( env, rank, constraint, - after, - } => { - // Push the `after` on first so that we look at it immediately after finishing all - // the children of this constraint. - if let Some(after) = after { - stack.push(Work::After(after)); - } - (env, rank, constraint) - } + } => (env, rank, constraint), }; state = match constraint { @@ -421,7 +407,6 @@ fn solve( env, rank, constraint: sub_constraint, - after: None, }) } @@ -483,7 +468,6 @@ fn solve( env, rank, constraint: &let_con.defs_constraint, - after: None, }); state } @@ -523,11 +507,11 @@ fn solve( new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); } + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); stack.push(Work::Constraint { env: arena.alloc(new_env), rank, constraint: ret_con, - after: Some(After::CheckForInfiniteTypes(local_def_vars)), }); state @@ -667,11 +651,11 @@ fn solve( // Now solve the body, using the new vars_by_symbol which includes // the assignments' name-to-variable mappings. + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); stack.push(Work::Constraint { env: arena.alloc(new_env), rank, constraint: ret_con, - after: Some(After::CheckForInfiniteTypes(local_def_vars)), }); state_for_ret_con From 41df04184e10f986988ed71eb8a90028bf0cf2ca Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 00:26:03 +0100 Subject: [PATCH 069/150] make the simple case tail-recursive --- compiler/solve/src/solve.rs | 89 ++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 9bedf114f7..080a693493 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,6 +1,6 @@ use bumpalo::Bump; use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::PresenceConstraint; +use roc_can::constraint::{LetConstraint, PresenceConstraint}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; use roc_module::ident::TagName; @@ -218,6 +218,11 @@ enum Work<'a> { constraint: &'a Constraint, }, CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), + LetConSimple { + env: &'a Env, + rank: Rank, + let_con: &'a LetConstraint, + }, } #[allow(clippy::too_many_arguments)] @@ -241,6 +246,11 @@ fn solve( let mut stack = vec![initial]; while let Some(work_item) = stack.pop() { let (env, rank, constraint) = match work_item { + Work::Constraint { + env, + rank, + constraint, + } => (env, rank, constraint), Work::CheckForInfiniteTypes(def_vars) => { for (symbol, loc_var) in def_vars.iter() { check_for_infinite_type(subs, problems, *symbol, *loc_var); @@ -248,11 +258,35 @@ fn solve( // No constraint to be solved continue; } - Work::Constraint { - env, - rank, - constraint, - } => (env, rank, constraint), + Work::LetConSimple { env, rank, let_con } => { + // Add a variable for each def to new_vars_by_env. + let mut local_def_vars = LocalDefVarsVec::with_length(let_con.def_types.len()); + + for (symbol, loc_type) in let_con.def_types.iter() { + let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); + + local_def_vars.push(( + *symbol, + Loc { + value: var, + region: loc_type.region, + }, + )); + } + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + } + + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); + stack.push(Work::Constraint { + env: arena.alloc(new_env), + rank, + constraint: &let_con.ret_constraint, + }); + continue; + } }; state = match constraint { @@ -471,47 +505,12 @@ fn solve( }); state } - ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - // TODO: make into `WorkItem` with `After` - let state = solve( - arena, - env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - // Add a variable for each def to new_vars_by_env. - let mut local_def_vars = - LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let var = - type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); - } - - stack.push(Work::CheckForInfiniteTypes(local_def_vars)); + _ if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { + stack.push(Work::LetConSimple { env, rank, let_con }); stack.push(Work::Constraint { - env: arena.alloc(new_env), + env, rank, - constraint: ret_con, + constraint: &let_con.defs_constraint, }); state From 3a1add6ce81652d0a1df1800f116d037047189d6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 00:51:08 +0100 Subject: [PATCH 070/150] add LocalDefVarsVec::from_def_types --- compiler/solve/src/solve.rs | 71 +++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 080a693493..d8538383ea 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -2,7 +2,7 @@ use bumpalo::Bump; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{LetConstraint, PresenceConstraint}; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::MutMap; +use roc_collections::all::{MutMap, SendMap}; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -260,19 +260,13 @@ fn solve( } Work::LetConSimple { env, rank, let_con } => { // Add a variable for each def to new_vars_by_env. - let mut local_def_vars = LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } + let local_def_vars = LocalDefVarsVec::from_def_types( + rank, + pools, + cached_aliases, + subs, + &let_con.def_types, + ); let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { @@ -548,22 +542,13 @@ fn solve( // run solver in next pool // Add a variable for each def to local_def_vars. - let mut local_def_vars = - LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let def_type = &loc_type.value; - - let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } + let local_def_vars = LocalDefVarsVec::from_def_types( + next_rank, + pools, + cached_aliases, + subs, + &let_con.def_types, + ); // Solve the assignments' constraints first. // TODO: make into `WorkItem` with `After` @@ -759,6 +744,32 @@ impl LocalDefVarsVec { } } +impl LocalDefVarsVec<(Symbol, Loc)> { + fn from_def_types( + rank: Rank, + pools: &mut Pools, + cached_aliases: &mut MutMap, + subs: &mut Subs, + def_types: &SendMap>, + ) -> Self { + let mut local_def_vars = Self::with_length(def_types.len()); + + for (symbol, loc_type) in def_types.iter() { + let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); + + local_def_vars.push(( + *symbol, + Loc { + value: var, + region: loc_type.region, + }, + )); + } + + local_def_vars + } +} + use std::cell::RefCell; std::thread_local! { /// Scratchpad arena so we don't need to allocate a new one all the time From c7d8ae6c798be447ec8ec4e5919f29c8cdb7a938 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 01:02:27 +0100 Subject: [PATCH 071/150] make complex case tail-recursive --- compiler/solve/src/solve.rs | 200 ++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 90 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index d8538383ea..3a4f20b277 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -223,6 +223,13 @@ enum Work<'a> { rank: Rank, let_con: &'a LetConstraint, }, + LetConComplex { + env: &'a Env, + rank: Rank, + next_rank: Rank, + let_con: &'a LetConstraint, + local_def_vars: LocalDefVarsVec<(Symbol, Loc)>, + }, } #[allow(clippy::too_many_arguments)] @@ -259,6 +266,8 @@ fn solve( continue; } Work::LetConSimple { env, rank, let_con } => { + // NOTE be extremely careful with shadowing here + // Add a variable for each def to new_vars_by_env. let local_def_vars = LocalDefVarsVec::from_def_types( rank, @@ -281,6 +290,96 @@ fn solve( }); continue; } + Work::LetConComplex { + env, + rank, + next_rank, + let_con, + local_def_vars, + } => { + // NOTE be extremely careful with shadowing here + let mark = state.mark; + let saved_env = state.env; + + let rigid_vars = &let_con.rigid_vars; + + let young_mark = mark; + let visit_mark = young_mark.next(); + let final_mark = visit_mark.next(); + + debug_assert_eq!( + { + let offenders = pools + .get(next_rank) + .iter() + .filter(|var| { + let current_rank = + subs.get_rank(roc_types::subs::Variable::clone(var)); + + current_rank.into_usize() > next_rank.into_usize() + }) + .collect::>(); + + let result = offenders.len(); + + if result > 0 { + dbg!(&subs, &offenders, &let_con.def_types); + } + + result + }, + 0 + ); + + // pop pool + generalize(subs, young_mark, visit_mark, next_rank, pools); + + pools.get_mut(next_rank).clear(); + + // check that things went well + debug_assert!({ + // NOTE the `subs.redundant` check is added for the uniqueness + // inference, and does not come from elm. It's unclear whether this is + // a bug with uniqueness inference (something is redundant that + // shouldn't be) or that it just never came up in elm. + let failing: Vec<_> = rigid_vars + .iter() + .filter(|&var| !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE) + .collect(); + + if !failing.is_empty() { + println!("Rigids {:?}", &rigid_vars); + println!("Failing {:?}", failing); + } + + failing.is_empty() + }); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + } + + // Note that this vars_by_symbol is the one returned by the + // previous call to solve() + let state_for_ret_con = State { + env: saved_env, + mark: final_mark, + }; + + // Now solve the body, using the new vars_by_symbol which includes + // the assignments' name-to-variable mappings. + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); + stack.push(Work::Constraint { + env: arena.alloc(new_env), + rank, + constraint: &let_con.ret_constraint, + }); + + state = state_for_ret_con; + + continue; + } }; state = match constraint { @@ -509,7 +608,7 @@ fn solve( state } - ret_con => { + _ => { let rigid_vars = &let_con.rigid_vars; let flex_vars = &let_con.flex_vars; @@ -550,99 +649,20 @@ fn solve( &let_con.def_types, ); - // Solve the assignments' constraints first. - // TODO: make into `WorkItem` with `After` - let State { - env: saved_env, - mark, - } = solve( - arena, + stack.push(Work::LetConComplex { env, - state, - next_rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - let young_mark = mark; - let visit_mark = young_mark.next(); - let final_mark = visit_mark.next(); - - debug_assert_eq!( - { - let offenders = pools - .get(next_rank) - .iter() - .filter(|var| { - let current_rank = - subs.get_rank(roc_types::subs::Variable::clone(var)); - - current_rank.into_usize() > next_rank.into_usize() - }) - .collect::>(); - - let result = offenders.len(); - - if result > 0 { - dbg!(&subs, &offenders, &let_con.def_types); - } - - result - }, - 0 - ); - - // pop pool - generalize(subs, young_mark, visit_mark, next_rank, pools); - - pools.get_mut(next_rank).clear(); - - // check that things went well - debug_assert!({ - // NOTE the `subs.redundant` check is added for the uniqueness - // inference, and does not come from elm. It's unclear whether this is - // a bug with uniqueness inference (something is redundant that - // shouldn't be) or that it just never came up in elm. - let failing: Vec<_> = rigid_vars - .iter() - .filter(|&var| { - !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE - }) - .collect(); - - if !failing.is_empty() { - println!("Rigids {:?}", &rigid_vars); - println!("Failing {:?}", failing); - } - - failing.is_empty() - }); - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); - } - - // Note that this vars_by_symbol is the one returned by the - // previous call to solve() - let state_for_ret_con = State { - env: saved_env, - mark: final_mark, - }; - - // Now solve the body, using the new vars_by_symbol which includes - // the assignments' name-to-variable mappings. - stack.push(Work::CheckForInfiniteTypes(local_def_vars)); - stack.push(Work::Constraint { - env: arena.alloc(new_env), rank, - constraint: ret_con, + let_con, + local_def_vars, + next_rank, + }); + stack.push(Work::Constraint { + env, + rank: next_rank, + constraint: &let_con.defs_constraint, }); - state_for_ret_con + state } } } From a79f6c6cdd11622867ce91a065e3fcf57ebed712 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 01:29:21 +0100 Subject: [PATCH 072/150] restructure and add comments --- compiler/solve/src/solve.rs | 169 ++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 86 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 3a4f20b277..24fd26ca1a 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -226,9 +226,7 @@ enum Work<'a> { LetConComplex { env: &'a Env, rank: Rank, - next_rank: Rank, let_con: &'a LetConstraint, - local_def_vars: LocalDefVarsVec<(Symbol, Loc)>, }, } @@ -257,12 +255,17 @@ fn solve( env, rank, constraint, - } => (env, rank, constraint), + } => { + // the default case; actually solve this constraint + (env, rank, constraint) + } Work::CheckForInfiniteTypes(def_vars) => { + // after a LetCon, we must check if any of the variables that we introduced + // loop back to themselves after solving the ret_constraint for (symbol, loc_var) in def_vars.iter() { check_for_infinite_type(subs, problems, *symbol, *loc_var); } - // No constraint to be solved + continue; } Work::LetConSimple { env, rank, let_con } => { @@ -288,16 +291,13 @@ fn solve( rank, constraint: &let_con.ret_constraint, }); + continue; } - Work::LetConComplex { - env, - rank, - next_rank, - let_con, - local_def_vars, - } => { + Work::LetConComplex { env, rank, let_con } => { // NOTE be extremely careful with shadowing here + let next_rank = rank.next(); + let mark = state.mark; let saved_env = state.env; @@ -307,16 +307,22 @@ fn solve( let visit_mark = young_mark.next(); let final_mark = visit_mark.next(); + // Add a variable for each def to local_def_vars. + let local_def_vars = LocalDefVarsVec::from_def_types( + next_rank, + pools, + cached_aliases, + subs, + &let_con.def_types, + ); + debug_assert_eq!( { let offenders = pools .get(next_rank) .iter() .filter(|var| { - let current_rank = - subs.get_rank(roc_types::subs::Variable::clone(var)); - - current_rank.into_usize() > next_rank.into_usize() + subs.get_rank(**var).into_usize() > next_rank.into_usize() }) .collect::>(); @@ -385,7 +391,6 @@ fn solve( state = match constraint { True => state, SaveTheEnvironment => { - // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment let mut copy = state; copy.env = env.clone(); @@ -585,85 +590,77 @@ fn solve( } } Let(let_con) => { - match &let_con.ret_constraint { - True if let_con.rigid_vars.is_empty() => { - introduce(subs, rank, pools, &let_con.flex_vars); + if matches!(&let_con.ret_constraint, True) && let_con.rigid_vars.is_empty() { + introduce(subs, rank, pools, &let_con.flex_vars); - // If the return expression is guaranteed to solve, - // solve the assignments themselves and move on. - stack.push(Work::Constraint { - env, - rank, - constraint: &let_con.defs_constraint, - }); - state + // If the return expression is guaranteed to solve, + // solve the assignments themselves and move on. + stack.push(Work::Constraint { + env, + rank, + constraint: &let_con.defs_constraint, + }); + + state + } else if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() { + // items are popped from the stack in reverse order. That means that we'll + // first solve then defs_constraint, and then (eventually) the ret_constraint. + // + // Note that the LetConSimple gets the current env and rank, + // and not the env/rank from after solving the defs_constraint + stack.push(Work::LetConSimple { env, rank, let_con }); + stack.push(Work::Constraint { + env, + rank, + constraint: &let_con.defs_constraint, + }); + + state + } else { + let rigid_vars = &let_con.rigid_vars; + let flex_vars = &let_con.flex_vars; + + // work in the next pool to localize header + let next_rank = rank.next(); + + // introduce variables + for &var in rigid_vars.iter().chain(flex_vars.iter()) { + subs.set_rank(var, next_rank); } - _ if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - stack.push(Work::LetConSimple { env, rank, let_con }); - stack.push(Work::Constraint { - env, - rank, - constraint: &let_con.defs_constraint, - }); - state + // determine the next pool + if next_rank.into_usize() < pools.len() { + // Nothing to do, we already accounted for the next rank, no need to + // adjust the pools + } else { + // we should be off by one at this point + debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + pools.extend_to(next_rank.into_usize()); } - _ => { - let rigid_vars = &let_con.rigid_vars; - let flex_vars = &let_con.flex_vars; - // work in the next pool to localize header - let next_rank = rank.next(); + let pool: &mut Vec = pools.get_mut(next_rank); - // introduce variables - for &var in rigid_vars.iter().chain(flex_vars.iter()) { - subs.set_rank(var, next_rank); - } + // Replace the contents of this pool with rigid_vars and flex_vars + pool.clear(); + pool.reserve(rigid_vars.len() + flex_vars.len()); + pool.extend(rigid_vars.iter()); + pool.extend(flex_vars.iter()); - // determine the next pool - if next_rank.into_usize() < pools.len() { - // Nothing to do, we already accounted for the next rank, no need to - // adjust the pools - } else { - // we should be off by one at this point - debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); - pools.extend_to(next_rank.into_usize()); - } + // run solver in next pool - let pool: &mut Vec = pools.get_mut(next_rank); + // items are popped from the stack in reverse order. That means that we'll + // first solve then defs_constraint, and then (eventually) the ret_constraint. + // + // Note that the LetConSimple gets the current env and rank, + // and not the env/rank from after solving the defs_constraint + stack.push(Work::LetConComplex { env, rank, let_con }); + stack.push(Work::Constraint { + env, + rank: next_rank, + constraint: &let_con.defs_constraint, + }); - // Replace the contents of this pool with rigid_vars and flex_vars - pool.clear(); - pool.reserve(rigid_vars.len() + flex_vars.len()); - pool.extend(rigid_vars.iter()); - pool.extend(flex_vars.iter()); - - // run solver in next pool - - // Add a variable for each def to local_def_vars. - let local_def_vars = LocalDefVarsVec::from_def_types( - next_rank, - pools, - cached_aliases, - subs, - &let_con.def_types, - ); - - stack.push(Work::LetConComplex { - env, - rank, - let_con, - local_def_vars, - next_rank, - }); - stack.push(Work::Constraint { - env, - rank: next_rank, - constraint: &let_con.defs_constraint, - }); - - state - } + state } } Present(typ, PresenceConstraint::IsOpen) => { From 241dc5783e8bf6b8cb87273bfac9130ef8a0750c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 01:37:23 +0100 Subject: [PATCH 073/150] remove VariablesIter, it was unused --- compiler/solve/tests/solve_expr.rs | 38 ---------- compiler/types/src/types.rs | 112 ----------------------------- 2 files changed, 150 deletions(-) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 28566441d7..6987ac4b4c 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5509,42 +5509,4 @@ mod solve_expr { r#"Id [ A, B, C { a : Str }e ] -> Str"#, ) } - - #[test] - fn inner_annotation_rigid() { - infer_eq_without_problem( - indoc!( - r#" - f : a -> a - f = - g : b -> b - g = \x -> x - - g - - f - "# - ), - r#"a -> a"#, - ) - } - - #[test] - fn inner_annotation_rigid_2() { - infer_eq_without_problem( - indoc!( - r#" - f : {} -> List a - f = - g : List a - g = [] - - \{} -> g - - f - "# - ), - r#"{} -> List a"#, - ) - } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index c37e0f38ed..298fd32be8 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -991,118 +991,6 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { } } -pub struct VariablesIter<'a> { - stack: Vec<&'a Type>, - recursion_variables: Vec, - variables: Vec, -} - -impl<'a> VariablesIter<'a> { - pub fn new(typ: &'a Type) -> Self { - Self { - stack: vec![typ], - recursion_variables: vec![], - variables: vec![], - } - } -} - -impl<'a> Iterator for VariablesIter<'a> { - type Item = Variable; - - fn next(&mut self) -> Option { - use Type::*; - - if let Some(var) = self.variables.pop() { - debug_assert!(!self.recursion_variables.contains(&var)); - - return Some(var); - } - - while let Some(tipe) = self.stack.pop() { - match tipe { - EmptyRec | EmptyTagUnion | Erroneous(_) => { - continue; - } - - ClosureTag { ext: v, .. } | Variable(v) => { - if !self.recursion_variables.contains(v) { - return Some(*v); - } - } - - Function(args, closure, ret) => { - self.stack.push(ret); - self.stack.push(closure); - self.stack.extend(args.iter().rev()); - } - Record(fields, ext) => { - use RecordField::*; - - self.stack.push(ext); - - for (_, field) in fields { - match field { - Optional(x) => self.stack.push(x), - Required(x) => self.stack.push(x), - Demanded(x) => self.stack.push(x), - }; - } - } - TagUnion(tags, ext) => { - self.stack.push(ext); - - for (_, args) in tags { - self.stack.extend(args); - } - } - FunctionOrTagUnion(_, _, ext) => { - self.stack.push(ext); - } - RecursiveTagUnion(rec, tags, ext) => { - self.recursion_variables.push(*rec); - self.stack.push(ext); - - for (_, args) in tags.iter().rev() { - self.stack.extend(args); - } - } - Alias { - type_arguments, - actual, - .. - } => { - self.stack.push(actual); - - for (_, args) in type_arguments.iter().rev() { - self.stack.push(args); - } - } - HostExposedAlias { - type_arguments: arguments, - actual, - .. - } => { - self.stack.push(actual); - - for (_, args) in arguments.iter().rev() { - self.stack.push(args); - } - } - RangedNumber(typ, vars) => { - self.stack.push(typ); - self.variables.extend(vars.iter().copied()); - } - Apply(_, args, _) => { - self.stack.extend(args); - } - } - } - - None - } -} - fn variables_help(tipe: &Type, accum: &mut ImSet) { use Type::*; From 76b4e7ec2e56f48f697490769eabcb40a433bae7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 4 Mar 2022 19:46:52 -0500 Subject: [PATCH 074/150] Add Derek, Philippe, and Pierre-Henri to AUTHORS --- AUTHORS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index 58357ab2bd..bdacbd757f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -65,3 +65,6 @@ Mats Sigge <> Drew Lazzeri Tom Dohrmann Elijah Schow +Derek Gustafson +Philippe Vinchon +Pierre-Henri Trivier From 8c321c1aa499ddf0dff45ac69d2fd2f171fded60 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 02:12:10 +0100 Subject: [PATCH 075/150] fix a bug with taking a scratchpad arena --- compiler/solve/src/solve.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c7ce79b7a6..a8e17b3d4f 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -370,7 +370,8 @@ fn solve( // then we copy from that module's Subs into our own. If the value // is being looked up in this module, then we use our Subs as both // the source and destination. - let actual = deep_copy_var(subs, rank, pools, var); + let actual = deep_copy_var_in(subs, rank, pools, var, arena); + let expected = type_to_var( subs, rank, @@ -779,21 +780,16 @@ impl LocalDefVarsVec { use std::cell::RefCell; std::thread_local! { /// Scratchpad arena so we don't need to allocate a new one all the time - static SCRATCHPAD: RefCell = RefCell::new(bumpalo::Bump::with_capacity(4 * 1024)); + static SCRATCHPAD: RefCell> = RefCell::new(Some(bumpalo::Bump::with_capacity(4 * 1024))); } fn take_scratchpad() -> bumpalo::Bump { - let mut result = bumpalo::Bump::new(); - SCRATCHPAD.with(|f| { - result = f.replace(bumpalo::Bump::new()); - }); - - result + SCRATCHPAD.with(|f| f.take().unwrap()) } fn put_scratchpad(scratchpad: bumpalo::Bump) { SCRATCHPAD.with(|f| { - f.replace(scratchpad); + f.replace(Some(scratchpad)); }); } @@ -976,7 +972,7 @@ fn type_to_variable<'a>( return reserved; } else { // for any other rank, we need to copy; it takes care of adjusting the rank - return deep_copy_var(subs, rank, pools, reserved); + return deep_copy_var_in(subs, rank, pools, reserved, arena); } } @@ -1044,6 +1040,7 @@ fn type_to_variable<'a>( } } +#[inline(always)] fn alias_to_var<'a>( subs: &mut Subs, rank: Rank, @@ -1073,6 +1070,7 @@ fn alias_to_var<'a>( } } +#[inline(always)] fn roc_result_to_var<'a>( subs: &mut Subs, rank: Rank, @@ -1821,10 +1819,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { } } -fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { - let mut arena = take_scratchpad(); - - let mut visited = bumpalo::collections::Vec::with_capacity_in(4 * 1024, &arena); +fn deep_copy_var_in( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + var: Variable, + arena: &Bump, +) -> Variable { + let mut visited = bumpalo::collections::Vec::with_capacity_in(256, arena); let copy = deep_copy_var_help(subs, rank, pools, &mut visited, var); @@ -1840,9 +1842,6 @@ fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) } } - arena.reset(); - put_scratchpad(arena); - copy } @@ -2081,6 +2080,7 @@ fn deep_copy_var_help( } } +#[inline(always)] fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { let descriptor = Descriptor { content, From a021c097523cacdeeabde5460192c65d0fb2b99f Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Fri, 4 Mar 2022 22:52:13 -0500 Subject: [PATCH 076/150] Implement RocDec to [u8] algorithm. --- repl_eval/src/eval.rs | 4 +-- roc_std/src/lib.rs | 73 +++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index ad48f1350a..9ca48ec02f 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -326,9 +326,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Ok(result) } - Layout::Builtin(Builtin::Decimal) => { - Ok(helper!(RocDec)) - } + Layout::Builtin(Builtin::Decimal) => Ok(helper!(RocDec)), Layout::Builtin(Builtin::Str) => { let size = layout.stack_size(env.target_info) as usize; Ok( diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 8aafa95fcf..435fdcefc7 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -1,12 +1,11 @@ #![crate_type = "lib"] -#![no_std] +// #![no_std] use core::ffi::c_void; use core::fmt; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::Drop; use core::str; -// use roc_error_macros::internal_error; -// uncomment when we figure out why it fails below. +use std::io::Write; mod rc; mod roc_list; @@ -217,9 +216,10 @@ impl RocDec { pub const MIN: Self = Self(i128::MIN); pub const MAX: Self = Self(i128::MAX); - pub const DECIMAL_PLACES: u32 = 18; - - pub const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES); + const DECIMAL_PLACES: usize = 18; + const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES as u32); + const MAX_DIGITS: usize = 39; + const MAX_STR_LENGTH: usize = Self::MAX_DIGITS + 2; // + 2 here to account for the sign & decimal dot #[allow(clippy::should_implement_trait)] pub fn from_str(value: &str) -> Option { @@ -234,7 +234,7 @@ impl RocDec { }; let opt_after_point = match parts.next() { - Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => Some(answer), + Some(answer) if answer.len() <= Self::DECIMAL_PLACES => Some(answer), _ => None, }; @@ -250,7 +250,7 @@ impl RocDec { Ok(answer) => { // Translate e.g. the 1 from 0.1 into 10000000000000000000 // by "restoring" the elided trailing zeroes to the number! - let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len(); + let trailing_zeroes = Self::DECIMAL_PLACES - after_point.len(); let lo = answer * 10i128.pow(trailing_zeroes as u32); if !before_point.starts_with('-') { @@ -269,7 +269,7 @@ impl RocDec { // Calculate the high digits - the ones before the decimal point. match before_point.parse::() { - Ok(answer) => match answer.checked_mul(10i128.pow(Self::DECIMAL_PLACES)) { + Ok(answer) => match answer.checked_mul(Self::ONE_POINT_ZERO) { Some(hi) => hi.checked_add(lo).map(Self), None => None, }, @@ -281,29 +281,56 @@ impl RocDec { Self::from_str(val).unwrap().0 } - fn to_str_helper(&self, bytes: &mut [u8; 1]) { - bytes[0] = 0; - // TODO + fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) { + if self.0 == 0 { + write!(&mut bytes[..], "{}", "0.0").unwrap(); + return; + } + + let is_negative = (self.0 < 0) as usize; + + write!(&mut bytes[..], "{:019}", self.0).unwrap(); + // By using the :019 format, we're guaranteeing that numbers less than 1, say 0.01234 + // get their leading zeros placed in bytes for us. i.e. bytes = b"0012340000000000000" + + // If self represents 1234.5678, then bytes is b"1234567800000000000000". + let mut i = Self::DECIMAL_PLACES; + // Find the last place where we have actual data. + while bytes[i] == 0 { + i = i - 1; + } + // At this point i is 21 because bytes[21] is the final '0' in b"1234567800000000000000". + + let decimal_location = i - Self::DECIMAL_PLACES + 1 + is_negative; + // decimal_location = 4 + + while bytes[i] == ('0' as u8) { + bytes[i] = 0; + i = i - 1; + } + // Now i = 7, because bytes[7] = '8', and bytes = b"12345678" + + while i >= decimal_location { + bytes[i + 1] = bytes[i]; + } + // Now i = 4, and bytes = b"123455678" + + bytes[i] = '.' as u8; + // Finally bytes = b"1234.5678" } pub fn to_str(&self) -> RocStr { - let mut bytes: [u8; 1] = [0]; + let mut bytes = [0 as u8; Self::MAX_STR_LENGTH]; self.to_str_helper(&mut bytes); - unsafe {RocStr::from_slice(&bytes) } + unsafe { RocStr::from_slice(&bytes) } } - } impl fmt::Display for RocDec { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut bytes: [u8; 1] = [0]; + let mut bytes = [0 as u8; Self::MAX_STR_LENGTH]; self.to_str_helper(&mut bytes); - match str::from_utf8(&bytes) { - Ok(slice) => write!(fmtr, "{}", slice), - Err(payload) => panic!("Error in converting RocDec({}) to a string: {}", self.0, payload), - // Err(payload) => internal_error!("Error in converting RocDec({}) to a string: {}", self.0, payload), - // This raises a compile error: can't find eprintln - // Is this because we don't use std? - } + let result = unsafe { str::from_utf8_unchecked(&bytes) }; + write!(fmtr, "{}", result) } } From cbac0cf318ea065518b2645b67eb24d7802a7aef Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 5 Mar 2022 09:32:37 +0100 Subject: [PATCH 077/150] removed redundant roc_file.rs --- ast/src/roc_file.rs | 133 -------------------------------------------- 1 file changed, 133 deletions(-) delete mode 100644 ast/src/roc_file.rs diff --git a/ast/src/roc_file.rs b/ast/src/roc_file.rs deleted file mode 100644 index 5299921c62..0000000000 --- a/ast/src/roc_file.rs +++ /dev/null @@ -1,133 +0,0 @@ -use bumpalo::collections::Vec; -use bumpalo::Bump; -use roc_fmt::def::fmt_def; -use roc_fmt::module::fmt_module; -use roc_parse::ast::{Def, Module}; -use roc_parse::module::module_defs; -use roc_parse::parser; -use roc_parse::parser::{Parser, SyntaxError}; -use roc_region::all::Located; -use std::ffi::OsStr; -use std::path::Path; -use std::{fs, io}; - -#[derive(Debug)] -pub struct File<'a> { - path: &'a Path, - module_header: Module<'a>, - content: Vec<'a, Located>>, -} - -#[derive(Debug)] -pub enum ReadError<'a> { - Read(std::io::Error), - ParseDefs(SyntaxError<'a>), - ParseHeader(SyntaxError<'a>), - DoesntHaveRocExtension, -} - -impl<'a> File<'a> { - pub fn read(path: &'a Path, arena: &'a Bump) -> Result, ReadError<'a>> { - if path.extension() != Some(OsStr::new("roc")) { - return Err(ReadError::DoesntHaveRocExtension); - } - - let bytes = fs::read(path).map_err(ReadError::Read)?; - - let allocation = arena.alloc(bytes); - - let module_parse_state = parser::State::new(allocation); - let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); - - match parsed_module { - Ok((module, state)) => { - let parsed_defs = module_defs().parse(arena, state); - - match parsed_defs { - Ok((_, defs, _)) => Ok(File { - path, - module_header: module, - content: defs, - }), - Err((_, error, _)) => Err(ReadError::ParseDefs(error)), - } - } - Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), - } - } - - pub fn fmt(&self) -> String { - let arena = Bump::new(); - let mut formatted_file = String::new(); - - let mut module_header_buf = bumpalo::collections::String::new_in(&arena); - fmt_module(&mut module_header_buf, &self.module_header); - - formatted_file.push_str(module_header_buf.as_str()); - - for def in &self.content { - let mut def_buf = bumpalo::collections::String::new_in(&arena); - - fmt_def(&mut def_buf, &def.value, 0); - - formatted_file.push_str(def_buf.as_str()); - } - - formatted_file - } - - pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> { - let formatted_file = self.fmt(); - - fs::write(write_path, formatted_file) - } - - pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> { - self.fmt_then_write_to( - self.path - .with_file_name(new_name) - .with_extension("roc") - .as_path(), - ) - } - - pub fn fmt_then_write(&self) -> io::Result<()> { - self.fmt_then_write_to(self.path) - } -} - -#[cfg(test)] -mod test_file { - use crate::lang::roc_file; - use bumpalo::Bump; - use std::path::Path; - - #[test] - fn read_and_fmt_simple_roc_module() { - let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); - - let arena = Bump::new(); - - let file = roc_file::File::read(simple_module_path, &arena) - .expect("Could not read SimpleUnformatted.roc in test_file test"); - - assert_eq!( - file.fmt(), - indoc!( - r#" - interface Simple - exposes [ - v, x - ] - imports [] - - v : Str - - v = "Value!" - - x : Int - x = 4"# - ) - ); - } -} From 9302f2ca5e10a65b88de2d7e472de4d0b38814a9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 13:16:41 +0100 Subject: [PATCH 078/150] partially revert to working state --- compiler/constrain/src/expr.rs | 40 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 58bcf0d304..848009f399 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1639,9 +1639,11 @@ fn instantiate_rigids( loc_pattern: &Loc, headers: &mut SendMap>, ) -> Type { - // find out if rigid type variables first occur in this annotation, - // or if they are already introduced in an outer annotation + let mut annotation = annotation.clone(); let mut rigid_substitution: ImMap = ImMap::default(); + + let outside_rigids: Vec = ftv.values().copied().collect(); + for (name, var) in introduced_vars.var_by_name.iter() { use std::collections::hash_map::Entry::*; @@ -1653,29 +1655,33 @@ fn instantiate_rigids( Vacant(vacant) => { // It's possible to use this rigid in nested defs vacant.insert(*var); - new_rigids.push(*var); + // new_rigids.push(*var); } } } - // wildcards are always freshly introduced in this annotation - for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { - ftv.insert(format!("*{}", i).into(), *wildcard); - new_rigids.push(*wildcard); - } - - let mut annotation = annotation.clone(); + // Instantiate rigid variables if !rigid_substitution.is_empty() { annotation.substitute(&rigid_substitution); } - let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation); - if let Pattern::Identifier(symbol) = loc_pattern.value { - headers.insert(symbol, Loc::at(loc_pattern.region, annotation.clone())); - } else if let Some(new_headers) = - crate::pattern::headers_from_annotation(&loc_pattern.value, &loc_annotation_ref) - { - headers.extend(new_headers) + if let Some(new_headers) = crate::pattern::headers_from_annotation( + &loc_pattern.value, + &Loc::at(loc_pattern.region, &annotation), + ) { + for (symbol, loc_type) in new_headers { + for var in loc_type.value.variables() { + // a rigid is only new if this annotation is the first occurrence of this rigid + if !outside_rigids.contains(&var) { + new_rigids.push(var); + } + } + headers.insert(symbol, loc_type); + } + } + + for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { + ftv.insert(format!("*{}", i).into(), *wildcard); } annotation From d7eee71402ac8f1b5f22bc28e580dd4eb6a2f2df Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 13:47:01 +0100 Subject: [PATCH 079/150] track lambda set variables; only use annotation variables in annotations... --- compiler/can/src/annotation.rs | 10 +++++++++- compiler/can/src/def.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index de0812c4a2..160ccd834f 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -29,6 +29,7 @@ pub struct IntroducedVariables { // but a variable can only have one name. Therefore // `ftv : SendMap`. pub wildcards: Vec, + pub lambda_sets: Vec, pub var_by_name: SendMap, pub name_by_var: SendMap, pub host_exposed_aliases: MutMap, @@ -44,12 +45,17 @@ impl IntroducedVariables { self.wildcards.push(var); } + fn insert_lambda_set(&mut self, var: Variable) { + self.lambda_sets.push(var); + } + pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { self.host_exposed_aliases.insert(symbol, var); } pub fn union(&mut self, other: &Self) { self.wildcards.extend(other.wildcards.iter().cloned()); + self.lambda_sets.extend(other.lambda_sets.iter().cloned()); self.var_by_name.extend(other.var_by_name.clone()); self.name_by_var.extend(other.name_by_var.clone()); self.host_exposed_aliases @@ -280,7 +286,9 @@ fn can_annotation_help( references, ); - let closure = Type::Variable(var_store.fresh()); + let lambda_set = var_store.fresh(); + introduced_variables.insert_lambda_set(lambda_set); + let closure = Type::Variable(lambda_set); Type::Function(args, Box::new(closure), Box::new(ret)) } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 4f67ecda91..3916c17283 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1147,7 +1147,7 @@ fn canonicalize_pending_def<'a>( pattern_vars: vars_by_symbol.clone(), annotation: Some(Annotation { signature: typ.clone(), - introduced_variables: output.introduced_variables.clone(), + introduced_variables: ann.introduced_variables.clone(), aliases: ann.aliases.clone(), region: loc_ann.region, }), From 7c83e940bdb0976afb66d11dd6bda80e3f37e6e1 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 13:55:30 +0100 Subject: [PATCH 080/150] leave a TODO --- compiler/can/src/def.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 3916c17283..6411441d98 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1112,6 +1112,12 @@ fn canonicalize_pending_def<'a>( arguments: arguments.clone(), loc_body: body.clone(), }); + + // TODO exploit this fact to remove clones below + debug_assert_eq!( + vec![*defined_symbol], + scope.idents().map(|t| t.1 .0).filter(|x| vars_by_symbol.contains_key(x)).collect::>() + ); } // Store the referenced locals in the refs_by_symbol map, so we can later figure out From bcafc6612e2969a087d791a1a40a0bd5a12fee2e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 5 Mar 2022 08:07:25 -0500 Subject: [PATCH 081/150] Update Pierre-Henri's email in AUTHORS As requested in https://github.com/rtfeldman/roc/commit/76b4e7ec2e56f48f697490769eabcb40a433bae7#commitcomment-68027841 --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index bdacbd757f..a52e26ba97 100644 --- a/AUTHORS +++ b/AUTHORS @@ -67,4 +67,4 @@ Tom Dohrmann Elijah Schow Derek Gustafson Philippe Vinchon -Pierre-Henri Trivier +Pierre-Henri Trivier From 012a2d07a6accc072412113a830329fe5c3edea2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 14:27:45 +0100 Subject: [PATCH 082/150] add Inferred variables to IntroducedVariables --- compiler/can/src/annotation.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 160ccd834f..67a248239e 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -30,6 +30,7 @@ pub struct IntroducedVariables { // `ftv : SendMap`. pub wildcards: Vec, pub lambda_sets: Vec, + pub inferred: Vec, pub var_by_name: SendMap, pub name_by_var: SendMap, pub host_exposed_aliases: MutMap, @@ -45,6 +46,10 @@ impl IntroducedVariables { self.wildcards.push(var); } + pub fn insert_inferred(&mut self, var: Variable) { + self.inferred.push(var); + } + fn insert_lambda_set(&mut self, var: Variable) { self.lambda_sets.push(var); } @@ -56,6 +61,7 @@ impl IntroducedVariables { pub fn union(&mut self, other: &Self) { self.wildcards.extend(other.wildcards.iter().cloned()); self.lambda_sets.extend(other.lambda_sets.iter().cloned()); + self.inferred.extend(other.inferred.iter().cloned()); self.var_by_name.extend(other.var_by_name.clone()); self.name_by_var.extend(other.name_by_var.clone()); self.host_exposed_aliases @@ -620,6 +626,9 @@ fn can_annotation_help( // Inference variables aren't bound to a rigid or a wildcard, so all we have to do is // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠 let var = var_store.fresh(); + + introduced_variables.insert_inferred(var); + Type::Variable(var) } Malformed(string) => { From 6370a80c621ec2a2ad1458acf59148659b48bc9d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 14:29:34 +0100 Subject: [PATCH 083/150] make sure lambda sets within aliases are in IntroducedVariables --- compiler/can/src/annotation.rs | 7 +++++++ compiler/solve/tests/solve_expr.rs | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 67a248239e..6a0052d4a9 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -340,6 +340,7 @@ fn can_annotation_help( let (type_arguments, lambda_set_variables, actual) = instantiate_and_freshen_alias_type( var_store, + introduced_variables, &alias.type_variables, args, &alias.lambda_set_variables, @@ -645,6 +646,7 @@ fn can_annotation_help( pub fn instantiate_and_freshen_alias_type( var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, type_variables: &[Loc<(Lowercase, Variable)>], type_arguments: Vec, lambda_set_variables: &[LambdaSet], @@ -674,6 +676,7 @@ pub fn instantiate_and_freshen_alias_type( if let Type::Variable(var) = typ.0 { let fresh = var_store.fresh(); substitutions.insert(var, Type::Variable(fresh)); + introduced_variables.insert_lambda_set(fresh); new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh))); } else { unreachable!("at this point there should be only vars in there"); @@ -698,8 +701,12 @@ pub fn freshen_opaque_def( .map(|_| Type::Variable(var_store.fresh())) .collect(); + // TODO this gets ignored; is that a problem + let mut introduced_variables = IntroducedVariables::default(); + instantiate_and_freshen_alias_type( var_store, + &mut introduced_variables, &opaque.type_variables, fresh_arguments, &opaque.lambda_set_variables, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 6987ac4b4c..6ced6159a9 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5509,4 +5509,24 @@ mod solve_expr { r#"Id [ A, B, C { a : Str }e ] -> Str"#, ) } + + #[test] + fn lambda_set_within_alias_is_quantified() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ effectAlways ] to "./platform" + + Effect a : [ @Effect ({} -> a) ] + + effectAlways : a -> Effect a + effectAlways = \x -> + inner = \{} -> x + + @Effect inner + "# + ), + r#"a -> Effect [ inner a ]*"#, + ) + } } From 97b0e3df9bd34799ff881913e8e055eff85c27dc Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 14:50:23 +0100 Subject: [PATCH 084/150] more efficient approach for finding new rigids --- compiler/constrain/src/expr.rs | 30 +++++++++++++++++------------- reporting/tests/helpers/mod.rs | 2 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 848009f399..1e21eabb4c 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1642,8 +1642,6 @@ fn instantiate_rigids( let mut annotation = annotation.clone(); let mut rigid_substitution: ImMap = ImMap::default(); - let outside_rigids: Vec = ftv.values().copied().collect(); - for (name, var) in introduced_vars.var_by_name.iter() { use std::collections::hash_map::Entry::*; @@ -1655,11 +1653,27 @@ fn instantiate_rigids( Vacant(vacant) => { // It's possible to use this rigid in nested defs vacant.insert(*var); - // new_rigids.push(*var); + new_rigids.push(*var); } } } + // wildcards are always freshly introduced in this annotation + for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { + ftv.insert(format!("*{}", i).into(), *wildcard); + new_rigids.push(*wildcard); + } + + // lambda set vars are always freshly introduced in this annotation + for var in introduced_vars.lambda_sets.iter() { + new_rigids.push(*var); + } + + // lambda set vars are always freshly introduced in this annotation + for var in introduced_vars.inferred.iter() { + new_rigids.push(*var); + } + // Instantiate rigid variables if !rigid_substitution.is_empty() { annotation.substitute(&rigid_substitution); @@ -1670,20 +1684,10 @@ fn instantiate_rigids( &Loc::at(loc_pattern.region, &annotation), ) { for (symbol, loc_type) in new_headers { - for var in loc_type.value.variables() { - // a rigid is only new if this annotation is the first occurrence of this rigid - if !outside_rigids.contains(&var) { - new_rigids.push(var); - } - } headers.insert(symbol, loc_type); } } - for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { - ftv.insert(format!("*{}", i).into(), *wildcard); - } - annotation } diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index eb081ccfd3..a9eea79d7a 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -158,7 +158,7 @@ pub fn can_expr_with<'a>( let constraint = constrain_expr( &mut constraints, &roc_constrain::expr::Env { - rigids: ImMap::default(), + rigids: MutMap::default(), home, }, loc_expr.region, From 48b0bbe87437eca6f1188b3b56971e64cacf727f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 14:55:45 +0100 Subject: [PATCH 085/150] cleanup --- compiler/constrain/src/expr.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 1e21eabb4c..7273f7caa3 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1659,33 +1659,26 @@ fn instantiate_rigids( } // wildcards are always freshly introduced in this annotation - for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { - ftv.insert(format!("*{}", i).into(), *wildcard); - new_rigids.push(*wildcard); - } + new_rigids.extend(introduced_vars.wildcards.iter().copied()); + + // inferred vars are always freshly introduced in this annotation + new_rigids.extend(introduced_vars.inferred.iter().copied()); // lambda set vars are always freshly introduced in this annotation - for var in introduced_vars.lambda_sets.iter() { - new_rigids.push(*var); - } - - // lambda set vars are always freshly introduced in this annotation - for var in introduced_vars.inferred.iter() { - new_rigids.push(*var); - } + new_rigids.extend(introduced_vars.lambda_sets.iter().copied()); // Instantiate rigid variables if !rigid_substitution.is_empty() { annotation.substitute(&rigid_substitution); } - if let Some(new_headers) = crate::pattern::headers_from_annotation( - &loc_pattern.value, - &Loc::at(loc_pattern.region, &annotation), - ) { - for (symbol, loc_type) in new_headers { - headers.insert(symbol, loc_type); - } + let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation); + if let Pattern::Identifier(symbol) = loc_pattern.value { + headers.insert(symbol, Loc::at(loc_pattern.region, annotation.clone())); + } else if let Some(new_headers) = + crate::pattern::headers_from_annotation(&loc_pattern.value, &loc_annotation_ref) + { + headers.extend(new_headers) } annotation From 80956061dd540c57969d0e7be4b98c04abc53d6e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 15:03:59 +0100 Subject: [PATCH 086/150] fix formatting bug in type pretty-print --- compiler/solve/tests/solve_expr.rs | 2 +- compiler/types/src/pretty_print.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 6ced6159a9..881a4556e9 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5526,7 +5526,7 @@ mod solve_expr { @Effect inner "# ), - r#"a -> Effect [ inner a ]*"#, + r#"a -> Effect a"#, ) } } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index bc416fe16a..6824f4abd0 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -382,7 +382,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa _ => write_parens!(write_parens, buf, { write_symbol(env, *symbol, buf); - for var_index in args.into_iter() { + for var_index in args.named_type_arguments() { let var = subs[var_index]; buf.push(' '); write_content( From 0464da84c0158debc27b89a52b45c6eb91bb7d76 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 5 Mar 2022 14:21:33 +0000 Subject: [PATCH 087/150] Rename CLI --backend to --target since that's what it has always meant --- Earthfile | 2 +- cli/src/lib.rs | 87 +++++++++++++++++------------------- cli/tests/cli_run.rs | 6 +-- examples/hello-web/README.md | 2 +- 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/Earthfile b/Earthfile index 9062be8ea8..d11c9d1b6d 100644 --- a/Earthfile +++ b/Earthfile @@ -93,7 +93,7 @@ test-rust: RUN --mount=type=cache,target=$SCCACHE_DIR \ repl_test/test_wasm.sh && sccache --show-stats # run i386 (32-bit linux) cli tests - RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc + RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b790552538..e154eeb83e 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -34,7 +34,7 @@ pub const FLAG_DEV: &str = "dev"; pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_OPT_SIZE: &str = "opt-size"; pub const FLAG_LIB: &str = "lib"; -pub const FLAG_BACKEND: &str = "backend"; +pub const FLAG_TARGET: &str = "target"; pub const FLAG_TIME: &str = "time"; pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_PRECOMPILED: &str = "precompiled-host"; @@ -42,7 +42,6 @@ pub const FLAG_VALGRIND: &str = "valgrind"; pub const FLAG_CHECK: &str = "check"; pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_DIR: &str = "ROC_DIR"; -pub const BACKEND: &str = "BACKEND"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; @@ -76,12 +75,11 @@ pub fn build_app<'a>() -> App<'a> { .required(false), ) .arg( - Arg::new(FLAG_BACKEND) - .long(FLAG_BACKEND) - .about("Choose a different backend") - // .requires(BACKEND) - .default_value(Backend::default().as_str()) - .possible_values(Backend::OPTIONS) + Arg::new(FLAG_TARGET) + .long(FLAG_TARGET) + .about("Choose a different target") + .default_value(Target::default().as_str()) + .possible_values(Target::OPTIONS) .required(false), ) .arg( @@ -212,12 +210,11 @@ pub fn build_app<'a>() -> App<'a> { .required(false), ) .arg( - Arg::new(FLAG_BACKEND) - .long(FLAG_BACKEND) - .about("Choose a different backend") - // .requires(BACKEND) - .default_value(Backend::default().as_str()) - .possible_values(Backend::OPTIONS) + Arg::new(FLAG_TARGET) + .long(FLAG_TARGET) + .about("Choose a different target") + .default_value(Target::default().as_str()) + .possible_values(Target::OPTIONS) .required(false), ) .arg( @@ -273,12 +270,12 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { use std::str::FromStr; use BuildConfig::*; - let backend = match matches.value_of(FLAG_BACKEND) { - Some(name) => Backend::from_str(name).unwrap(), - None => Backend::default(), + let target = match matches.value_of(FLAG_TARGET) { + Some(name) => Target::from_str(name).unwrap(), + None => Target::default(), }; - let target = backend.to_triple(); + let triple = target.to_triple(); let arena = Bump::new(); let filename = matches.value_of(ROC_FILE).unwrap(); @@ -306,10 +303,10 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { let surgically_link = matches.is_present(FLAG_LINK); let precompiled = matches.is_present(FLAG_PRECOMPILED); - if surgically_link && !roc_linker::supported(&link_type, &target) { + if surgically_link && !roc_linker::supported(&link_type, &triple) { panic!( "Link type, {:?}, with target, {}, not supported by roc linker", - link_type, target + link_type, triple ); } @@ -338,7 +335,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { let target_valgrind = matches.is_present(FLAG_VALGRIND); let res_binary_path = build_file( &arena, - &target, + &triple, src_dir, path, opt_level, @@ -377,7 +374,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { Ok(outcome.status_code()) } BuildAndRun { roc_file_arg_index } => { - let mut cmd = match target.architecture { + let mut cmd = match triple.architecture { Architecture::Wasm32 => { // If possible, report the generated executable name relative to the current dir. let generated_filename = binary_path @@ -398,7 +395,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { _ => Command::new(&binary_path), }; - if let Architecture::Wasm32 = target.architecture { + if let Architecture::Wasm32 = triple.architecture { cmd.arg(binary_path); } @@ -503,43 +500,43 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) { println!("Running wasm files not support"); } -enum Backend { +enum Target { Host, X86_32, X86_64, Wasm32, } -impl Default for Backend { +impl Default for Target { fn default() -> Self { - Backend::Host + Target::Host } } -impl Backend { +impl Target { const fn as_str(&self) -> &'static str { match self { - Backend::Host => "host", - Backend::X86_32 => "x86_32", - Backend::X86_64 => "x86_64", - Backend::Wasm32 => "wasm32", + Target::Host => "host", + Target::X86_32 => "x86_32", + Target::X86_64 => "x86_64", + Target::Wasm32 => "wasm32", } } /// NOTE keep up to date! const OPTIONS: &'static [&'static str] = &[ - Backend::Host.as_str(), - Backend::X86_32.as_str(), - Backend::X86_64.as_str(), - Backend::Wasm32.as_str(), + Target::Host.as_str(), + Target::X86_32.as_str(), + Target::X86_64.as_str(), + Target::Wasm32.as_str(), ]; fn to_triple(&self) -> Triple { let mut triple = Triple::unknown(); match self { - Backend::Host => Triple::host(), - Backend::X86_32 => { + Target::Host => Triple::host(), + Target::X86_32 => { triple.architecture = Architecture::X86_32(X86_32Architecture::I386); triple.binary_format = BinaryFormat::Elf; @@ -548,13 +545,13 @@ impl Backend { triple } - Backend::X86_64 => { + Target::X86_64 => { triple.architecture = Architecture::X86_64; triple.binary_format = BinaryFormat::Elf; triple } - Backend::Wasm32 => { + Target::Wasm32 => { triple.architecture = Architecture::Wasm32; triple.binary_format = BinaryFormat::Wasm; @@ -564,21 +561,21 @@ impl Backend { } } -impl std::fmt::Display for Backend { +impl std::fmt::Display for Target { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } -impl std::str::FromStr for Backend { +impl std::str::FromStr for Target { type Err = (); fn from_str(s: &str) -> Result { match s { - "host" => Ok(Backend::Host), - "x86_32" => Ok(Backend::X86_32), - "x86_64" => Ok(Backend::X86_64), - "wasm32" => Ok(Backend::Wasm32), + "host" => Ok(Target::Host), + "x86_32" => Ok(Target::X86_32), + "x86_64" => Ok(Target::X86_64), + "wasm32" => Ok(Target::Wasm32), _ => Err(()), } } diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 9e7aee0d8b..164bdd86ce 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -194,7 +194,7 @@ mod cli_run { ) { assert_eq!(input_file, None, "Wasm does not support input files"); let mut flags = flags.to_vec(); - flags.push("--backend=wasm32"); + flags.push("--target=wasm32"); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat()); if !compile_out.stderr.is_empty() { @@ -565,7 +565,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--backend=x86_32"], + &["--target=x86_32"], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, @@ -575,7 +575,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--backend=x86_32", "--optimize"], + &["--target=x86_32", "--optimize"], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, diff --git a/examples/hello-web/README.md b/examples/hello-web/README.md index f0beccdb6b..4401c8d367 100644 --- a/examples/hello-web/README.md +++ b/examples/hello-web/README.md @@ -3,7 +3,7 @@ To run, go to the project home directory and run: ```bash -$ cargo run -- build --backend=wasm32 examples/hello-web/Hello.roc +$ cargo run -- build --target=wasm32 examples/hello-web/Hello.roc ``` Then `cd` into the example directory and run any web server that can handle WebAssembly. From 5e48577d29eb1c1a8cb60c83ad1676bbfcad3863 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 15:32:55 +0100 Subject: [PATCH 088/150] remove clones in most cases in can/def --- compiler/can/src/def.rs | 153 +++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 58 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 6411441d98..f6372dc782 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -864,6 +864,32 @@ fn pattern_to_vars_by_symbol( } } +fn single_typed_can_def( + loc_can_pattern: Loc, + loc_can_expr: Loc, + expr_var: Variable, + loc_annotation: Loc, + pattern_vars: SendMap, +) -> Def { + let def_annotation = Annotation { + signature: loc_annotation.value.typ, + introduced_variables: loc_annotation.value.introduced_variables, + aliases: loc_annotation.value.aliases, + region: loc_annotation.region, + }; + + Def { + expr_var, + loc_pattern: loc_can_pattern, + loc_expr: Loc { + region: loc_can_expr.region, + value: loc_can_expr.value, + }, + pattern_vars, + annotation: Some(def_annotation), + } +} + // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] @@ -1003,13 +1029,11 @@ fn canonicalize_pending_def<'a>( canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); // Record all the annotation's references in output.references.lookups - for symbol in ann.references { - output.references.lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + for symbol in ann.references.iter() { + output.references.lookups.insert(*symbol); + output.references.referenced_type_defs.insert(*symbol); } - let typ = ann.typ; - for (symbol, alias) in ann.aliases.clone() { aliases.insert(symbol, alias); } @@ -1041,9 +1065,6 @@ fn canonicalize_pending_def<'a>( // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; - // see below: a closure needs a fresh References! - let mut is_closure = false; - // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, @@ -1051,7 +1072,6 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let ( - &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), &Closure(ClosureData { function_type, @@ -1064,13 +1084,8 @@ fn canonicalize_pending_def<'a>( ref captured_symbols, .. }), - ) = ( - &loc_pattern.value, - &loc_can_pattern.value, - &loc_can_expr.value, - ) { - is_closure = true; - + ) = (&loc_can_pattern.value, &loc_can_expr.value) + { // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) let references = env.closures.remove(symbol).unwrap_or_else(|| { @@ -1113,52 +1128,74 @@ fn canonicalize_pending_def<'a>( loc_body: body.clone(), }); - // TODO exploit this fact to remove clones below - debug_assert_eq!( - vec![*defined_symbol], - scope.idents().map(|t| t.1 .0).filter(|x| vars_by_symbol.contains_key(x)).collect::>() + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + refs_by_symbol.insert(*defined_symbol, (loc_can_pattern.region, refs)); + + let symbol = *defined_symbol; + let def = single_typed_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Loc::at(loc_ann.region, ann), + vars_by_symbol.clone(), ); - } + can_defs_by_symbol.insert(symbol, def); + } else { + // Store the referenced locals in the refs_by_symbol map, so we can later figure out + // which defined names reference each other. - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. - for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } + if let Loc { + region, + value: Pattern::Identifier(symbol), + } = loc_can_pattern + { + let refs = can_output.references; + refs_by_symbol.insert(symbol, (region, refs)); - let refs = - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - if is_closure { - References::new() - } else { - can_output.references.clone() - }; - - refs_by_symbol.insert(*symbol, (*region, refs)); - - can_defs_by_symbol.insert( - *symbol, - Def { + let def = single_typed_can_def( + loc_can_pattern, + loc_can_expr, expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: typ.clone(), - introduced_variables: ann.introduced_variables.clone(), - aliases: ann.aliases.clone(), - region: loc_ann.region, - }), - }, - ); + Loc::at(loc_ann.region, ann), + vars_by_symbol.clone(), + ); + can_defs_by_symbol.insert(symbol, def); + } else { + for (_, (symbol, region)) in scope.idents() { + if !vars_by_symbol.contains_key(symbol) { + continue; + } + + let refs = can_output.references.clone(); + + refs_by_symbol.insert(*symbol, (*region, refs)); + + can_defs_by_symbol.insert( + *symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + region: loc_can_expr.region, + // TODO try to remove this .clone()! + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: Some(Annotation { + signature: ann.typ.clone(), + introduced_variables: ann.introduced_variables.clone(), + aliases: ann.aliases.clone(), + region: loc_ann.region, + }), + }, + ); + } + } } } // If we have a pattern, then the def has a body (that is, it's not a From b421df2a2834996b8f0587f10657e2002331f81d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 15:50:00 +0100 Subject: [PATCH 089/150] transform TypedDef to use fewer clones --- compiler/can/src/def.rs | 198 ++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 107 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index f6372dc782..5687c03fc2 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1024,21 +1024,23 @@ fn canonicalize_pending_def<'a>( InvalidAlias { .. } => { // invalid aliases and opaques (shadowed, incorrect patterns) get ignored } - TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - let ann = + TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { + let type_annotation = canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); // Record all the annotation's references in output.references.lookups - for symbol in ann.references.iter() { + for symbol in type_annotation.references.iter() { output.references.lookups.insert(*symbol); output.references.referenced_type_defs.insert(*symbol); } - for (symbol, alias) in ann.aliases.clone() { + for (symbol, alias) in type_annotation.aliases.clone() { aliases.insert(symbol, alias); } - output.introduced_variables.union(&ann.introduced_variables); + output + .introduced_variables + .union(&type_annotation.introduced_variables); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. @@ -1071,130 +1073,112 @@ fn canonicalize_pending_def<'a>( // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let ( - &Pattern::Identifier(ref defined_symbol), - &Closure(ClosureData { + if let Loc { + region, + value: Pattern::Identifier(symbol), + } = loc_can_pattern + { + if let &Closure(ClosureData { function_type, closure_type, closure_ext_var, return_type, - name: ref symbol, + name: ref closure_name, ref arguments, loc_body: ref body, ref captured_symbols, .. - }), - ) = (&loc_can_pattern.value, &loc_can_expr.value) - { - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(symbol).unwrap_or_else(|| { - panic!( - "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", - symbol, env.closures - ) - }); - - // Re-insert the closure into the map, under its defined name. - // closures don't have a name, and therefore pick a fresh symbol. But in this - // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` - // and we want to reference it by that name. - env.closures.insert(*defined_symbol, references); - - // The closure is self tail recursive iff it tail calls itself (by defined name). - let is_recursive = match can_output.tail_call { - Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive, - _ => Recursive::NotRecursive, - }; - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol - .entry(*defined_symbol) - .and_modify(|(_, refs)| { - refs.lookups = refs.lookups.without(defined_symbol); + }) = &loc_can_expr.value + { + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. (We'll re-insert it later.) + let references = env.closures.remove(closure_name).unwrap_or_else(|| { + panic!( + "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", + closure_name, env.closures + ) }); - // renamed_closure_def = Some(&defined_symbol); - loc_can_expr.value = Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: *defined_symbol, - captured_symbols: captured_symbols.clone(), - recursive: is_recursive, - arguments: arguments.clone(), - loc_body: body.clone(), - }); + // Re-insert the closure into the map, under its defined name. + // closures don't have a name, and therefore pick a fresh symbol. But in this + // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` + // and we want to reference it by that name. + env.closures.insert(symbol, references); - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; - refs_by_symbol.insert(*defined_symbol, (loc_can_pattern.region, refs)); + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.lookups = refs.lookups.without(&symbol); + }); + + // renamed_closure_def = Some(&symbol); + loc_can_expr.value = Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: symbol, + captured_symbols: captured_symbols.clone(), + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs)); + } else { + let refs = can_output.references; + refs_by_symbol.insert(symbol, (region, refs)); + } - let symbol = *defined_symbol; let def = single_typed_can_def( loc_can_pattern, loc_can_expr, expr_var, - Loc::at(loc_ann.region, ann), + Loc::at(loc_ann.region, type_annotation), vars_by_symbol.clone(), ); can_defs_by_symbol.insert(symbol, def); } else { - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. - - if let Loc { - region, - value: Pattern::Identifier(symbol), - } = loc_can_pattern - { - let refs = can_output.references; - refs_by_symbol.insert(symbol, (region, refs)); - - let def = single_typed_can_def( - loc_can_pattern, - loc_can_expr, - expr_var, - Loc::at(loc_ann.region, ann), - vars_by_symbol.clone(), - ); - can_defs_by_symbol.insert(symbol, def); - } else { - for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } - - let refs = can_output.references.clone(); - - refs_by_symbol.insert(*symbol, (*region, refs)); - - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: ann.typ.clone(), - introduced_variables: ann.introduced_variables.clone(), - aliases: ann.aliases.clone(), - region: loc_ann.region, - }), - }, - ); + for (_, (symbol, region)) in scope.idents() { + if !vars_by_symbol.contains_key(symbol) { + continue; } + + let refs = can_output.references.clone(); + + refs_by_symbol.insert(*symbol, (*region, refs)); + + can_defs_by_symbol.insert( + *symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + region: loc_can_expr.region, + // TODO try to remove this .clone()! + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: Some(Annotation { + signature: type_annotation.typ.clone(), + introduced_variables: type_annotation.introduced_variables.clone(), + aliases: type_annotation.aliases.clone(), + region: loc_ann.region, + }), + }, + ); } } } From 06ff93cace37d445594e64b7372354871cd51d01 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 15:50:41 +0100 Subject: [PATCH 090/150] Revert "remove clones in most cases in can/def" This reverts commit 5e48577d29eb1c1a8cb60c83ad1676bbfcad3863. Will put it into a separate PR --- compiler/can/src/def.rs | 155 +++++++++++++++------------------------- 1 file changed, 59 insertions(+), 96 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index f6372dc782..6411441d98 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -864,32 +864,6 @@ fn pattern_to_vars_by_symbol( } } -fn single_typed_can_def( - loc_can_pattern: Loc, - loc_can_expr: Loc, - expr_var: Variable, - loc_annotation: Loc, - pattern_vars: SendMap, -) -> Def { - let def_annotation = Annotation { - signature: loc_annotation.value.typ, - introduced_variables: loc_annotation.value.introduced_variables, - aliases: loc_annotation.value.aliases, - region: loc_annotation.region, - }; - - Def { - expr_var, - loc_pattern: loc_can_pattern, - loc_expr: Loc { - region: loc_can_expr.region, - value: loc_can_expr.value, - }, - pattern_vars, - annotation: Some(def_annotation), - } -} - // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] @@ -1029,11 +1003,13 @@ fn canonicalize_pending_def<'a>( canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); // Record all the annotation's references in output.references.lookups - for symbol in ann.references.iter() { - output.references.lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + for symbol in ann.references { + output.references.lookups.insert(symbol); + output.references.referenced_type_defs.insert(symbol); } + let typ = ann.typ; + for (symbol, alias) in ann.aliases.clone() { aliases.insert(symbol, alias); } @@ -1065,6 +1041,9 @@ fn canonicalize_pending_def<'a>( // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; + // see below: a closure needs a fresh References! + let mut is_closure = false; + // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, @@ -1072,6 +1051,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let ( + &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), &Closure(ClosureData { function_type, @@ -1084,8 +1064,13 @@ fn canonicalize_pending_def<'a>( ref captured_symbols, .. }), - ) = (&loc_can_pattern.value, &loc_can_expr.value) - { + ) = ( + &loc_pattern.value, + &loc_can_pattern.value, + &loc_can_expr.value, + ) { + is_closure = true; + // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) let references = env.closures.remove(symbol).unwrap_or_else(|| { @@ -1128,74 +1113,52 @@ fn canonicalize_pending_def<'a>( loc_body: body.clone(), }); - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - refs_by_symbol.insert(*defined_symbol, (loc_can_pattern.region, refs)); - - let symbol = *defined_symbol; - let def = single_typed_can_def( - loc_can_pattern, - loc_can_expr, - expr_var, - Loc::at(loc_ann.region, ann), - vars_by_symbol.clone(), + // TODO exploit this fact to remove clones below + debug_assert_eq!( + vec![*defined_symbol], + scope.idents().map(|t| t.1 .0).filter(|x| vars_by_symbol.contains_key(x)).collect::>() ); - can_defs_by_symbol.insert(symbol, def); - } else { - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. + } - if let Loc { - region, - value: Pattern::Identifier(symbol), - } = loc_can_pattern - { - let refs = can_output.references; - refs_by_symbol.insert(symbol, (region, refs)); - - let def = single_typed_can_def( - loc_can_pattern, - loc_can_expr, - expr_var, - Loc::at(loc_ann.region, ann), - vars_by_symbol.clone(), - ); - can_defs_by_symbol.insert(symbol, def); - } else { - for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } - - let refs = can_output.references.clone(); - - refs_by_symbol.insert(*symbol, (*region, refs)); - - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: ann.typ.clone(), - introduced_variables: ann.introduced_variables.clone(), - aliases: ann.aliases.clone(), - region: loc_ann.region, - }), - }, - ); - } + // Store the referenced locals in the refs_by_symbol map, so we can later figure out + // which defined names reference each other. + for (_, (symbol, region)) in scope.idents() { + if !vars_by_symbol.contains_key(symbol) { + continue; } + + let refs = + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + if is_closure { + References::new() + } else { + can_output.references.clone() + }; + + refs_by_symbol.insert(*symbol, (*region, refs)); + + can_defs_by_symbol.insert( + *symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + region: loc_can_expr.region, + // TODO try to remove this .clone()! + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: Some(Annotation { + signature: typ.clone(), + introduced_variables: ann.introduced_variables.clone(), + aliases: ann.aliases.clone(), + region: loc_ann.region, + }), + }, + ); } } // If we have a pattern, then the def has a body (that is, it's not a From 87e1b5bba62fa2aed7159f126c36523eeb9fdec9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 15:53:24 +0100 Subject: [PATCH 091/150] formatting --- compiler/can/src/def.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 6411441d98..ce0d157735 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1116,7 +1116,11 @@ fn canonicalize_pending_def<'a>( // TODO exploit this fact to remove clones below debug_assert_eq!( vec![*defined_symbol], - scope.idents().map(|t| t.1 .0).filter(|x| vars_by_symbol.contains_key(x)).collect::>() + scope + .idents() + .map(|t| t.1 .0) + .filter(|x| vars_by_symbol.contains_key(x)) + .collect::>() ); } From cd203d20a376f15b7a99951f0310e3cb06c99871 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 16:12:19 +0100 Subject: [PATCH 092/150] also remove clones for Body --- compiler/can/src/def.rs | 175 ++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 5687c03fc2..6bca698395 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -864,19 +864,19 @@ fn pattern_to_vars_by_symbol( } } -fn single_typed_can_def( +fn single_can_def( loc_can_pattern: Loc, loc_can_expr: Loc, expr_var: Variable, - loc_annotation: Loc, + opt_loc_annotation: Option>, pattern_vars: SendMap, ) -> Def { - let def_annotation = Annotation { + let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation { signature: loc_annotation.value.typ, introduced_variables: loc_annotation.value.introduced_variables, aliases: loc_annotation.value.aliases, region: loc_annotation.region, - }; + }); Def { expr_var, @@ -886,7 +886,7 @@ fn single_typed_can_def( value: loc_can_expr.value, }, pattern_vars, - annotation: Some(def_annotation), + annotation: def_annotation, } } @@ -1141,11 +1141,11 @@ fn canonicalize_pending_def<'a>( refs_by_symbol.insert(symbol, (region, refs)); } - let def = single_typed_can_def( + let def = single_can_def( loc_can_pattern, loc_can_expr, expr_var, - Loc::at(loc_ann.region, type_annotation), + Some(Loc::at(loc_ann.region, type_annotation)), vars_by_symbol.clone(), ); can_defs_by_symbol.insert(symbol, def); @@ -1211,108 +1211,109 @@ fn canonicalize_pending_def<'a>( // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; - // see below: a closure needs a fresh References! - let mut is_closure = false; - // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let ( - &ast::Pattern::Identifier(_name), - &Pattern::Identifier(ref defined_symbol), - &Closure(ClosureData { + if let Loc { + region, + value: Pattern::Identifier(symbol), + } = loc_can_pattern + { + if let &Closure(ClosureData { function_type, closure_type, closure_ext_var, return_type, - name: ref symbol, + name: ref closure_name, ref arguments, loc_body: ref body, ref captured_symbols, .. - }), - ) = ( - &loc_pattern.value, - &loc_can_pattern.value, - &loc_can_expr.value, - ) { - is_closure = true; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(symbol).unwrap_or_else(|| { - panic!( - "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", - symbol, env.closures - ) - }); - - // Re-insert the closure into the map, under its defined name. - // closures don't have a name, and therefore pick a fresh symbol. But in this - // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` - // and we want to reference it by that name. - env.closures.insert(*defined_symbol, references); - - // The closure is self tail recursive iff it tail calls itself (by defined name). - let is_recursive = match can_output.tail_call { - Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive, - _ => Recursive::NotRecursive, - }; - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol - .entry(*defined_symbol) - .and_modify(|(_, refs)| { - refs.lookups = refs.lookups.without(defined_symbol); + }) = &loc_can_expr.value + { + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. (We'll re-insert it later.) + let references = env.closures.remove(closure_name).unwrap_or_else(|| { + panic!( + "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", + closure_name, env.closures + ) }); - loc_can_expr.value = Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: *defined_symbol, - captured_symbols: captured_symbols.clone(), - recursive: is_recursive, - arguments: arguments.clone(), - loc_body: body.clone(), - }); - } + // Re-insert the closure into the map, under its defined name. + // closures don't have a name, and therefore pick a fresh symbol. But in this + // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` + // and we want to reference it by that name. + env.closures.insert(symbol, references); + + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.lookups = refs.lookups.without(&symbol); + }); + + loc_can_expr.value = Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: symbol, + captured_symbols: captured_symbols.clone(), + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }); - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. - for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) { - let refs = // Functions' references don't count in defs. // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // parent commit for the bug this fixed! - if is_closure { - References::new() - } else { - can_output.references.clone() - }; + let refs = References::new(); + refs_by_symbol.insert(symbol, (region, refs)); + } else { + let refs = can_output.references.clone(); + refs_by_symbol.insert(symbol, (region, refs)); + } - refs_by_symbol.insert(symbol, (region, refs)); - - can_defs_by_symbol.insert( - symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - // TODO try to remove this .clone()! - region: loc_can_expr.region, - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: None, - }, + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + None, + vars_by_symbol.clone(), ); + can_defs_by_symbol.insert(symbol, def); + } else { + // Store the referenced locals in the refs_by_symbol map, so we can later figure out + // which defined names reference each other. + for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) { + let refs = can_output.references.clone(); + refs_by_symbol.insert(symbol, (region, refs)); + + can_defs_by_symbol.insert( + symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + // TODO try to remove this .clone()! + region: loc_can_expr.region, + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: None, + }, + ); + } } output.union(can_output); From 0285a1b0ea7e8dbf19058368dd4388ad008d6938 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 16:41:29 +0100 Subject: [PATCH 093/150] cleanup --- compiler/can/src/def.rs | 99 +++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 6bca698395..1b5108dfe9 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -913,25 +913,25 @@ fn canonicalize_pending_def<'a>( AnnotationOnly(_, loc_can_pattern, loc_ann) => { // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them - let ann = + let type_annotation = canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); // Record all the annotation's references in output.references.lookups - for symbol in ann.references { - output.references.lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + for symbol in type_annotation.references.iter() { + output.references.lookups.insert(*symbol); + output.references.referenced_type_defs.insert(*symbol); } - aliases.extend(ann.aliases.clone()); + aliases.extend(type_annotation.aliases.clone()); - output.introduced_variables.union(&ann.introduced_variables); + output + .introduced_variables + .union(&type_annotation.introduced_variables); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); - let typ = ann.typ; - - let arity = typ.arity(); + let arity = type_annotation.typ.arity(); let problem = match &loc_can_pattern.value { Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { @@ -989,33 +989,44 @@ fn canonicalize_pending_def<'a>( } }; - for (_, (symbol, _)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } - - // We could potentially avoid some clones here by using Rc strategically, - // but the total amount of cloning going on here should typically be minimal. - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: typ.clone(), - introduced_variables: output.introduced_variables.clone(), - aliases: ann.aliases.clone(), - region: loc_ann.region, - }), - }, + if let Pattern::Identifier(symbol) = loc_can_pattern.value { + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), ); + can_defs_by_symbol.insert(symbol, def); + } else { + for (_, (symbol, _)) in scope.idents() { + if !vars_by_symbol.contains_key(symbol) { + continue; + } + + // We could potentially avoid some clones here by using Rc strategically, + // but the total amount of cloning going on here should typically be minimal. + can_defs_by_symbol.insert( + *symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + region: loc_can_expr.region, + // TODO try to remove this .clone()! + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: Some(Annotation { + signature: type_annotation.typ.clone(), + introduced_variables: output.introduced_variables.clone(), + aliases: type_annotation.aliases.clone(), + region: loc_ann.region, + }), + }, + ); + } } } @@ -1073,11 +1084,7 @@ fn canonicalize_pending_def<'a>( // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let Loc { - region, - value: Pattern::Identifier(symbol), - } = loc_can_pattern - { + if let Pattern::Identifier(symbol) = loc_can_pattern.value { if let &Closure(ClosureData { function_type, closure_type, @@ -1138,7 +1145,7 @@ fn canonicalize_pending_def<'a>( refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs)); } else { let refs = can_output.references; - refs_by_symbol.insert(symbol, (region, refs)); + refs_by_symbol.insert(symbol, (loc_ann.region, refs)); } let def = single_can_def( @@ -1217,11 +1224,7 @@ fn canonicalize_pending_def<'a>( // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let Loc { - region, - value: Pattern::Identifier(symbol), - } = loc_can_pattern - { + if let Pattern::Identifier(symbol) = loc_can_pattern.value { if let &Closure(ClosureData { function_type, closure_type, @@ -1277,10 +1280,10 @@ fn canonicalize_pending_def<'a>( // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // parent commit for the bug this fixed! let refs = References::new(); - refs_by_symbol.insert(symbol, (region, refs)); + refs_by_symbol.insert(symbol, (loc_pattern.region, refs)); } else { let refs = can_output.references.clone(); - refs_by_symbol.insert(symbol, (region, refs)); + refs_by_symbol.insert(symbol, (loc_pattern.region, refs)); } let def = single_can_def( From f2fa625886c9d54925bb655a6ef2121779574a0f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 16:48:33 +0100 Subject: [PATCH 094/150] add test --- reporting/tests/test_reporting.rs | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 6a5ba6a1e3..ea9df598eb 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8442,4 +8442,48 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn let_polymorphism_with_scoped_type_variables() { + report_problem_as( + indoc!( + r#" + f : a -> a + f = \x -> + y : a -> a + y = \z -> z + + n = y 1u8 + x1 = y x + (\_ -> x1) n + + f + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `y` is not what I expect: + + 6│ n = y 1u8 + ^^^ + + This argument is an integer of type: + + U8 + + But `y` needs the 1st argument to be: + + a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `U8` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "# + ), + ) + } } From 53962a4799a1b004550e44a8bd678550e2606f27 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 17:27:33 +0100 Subject: [PATCH 095/150] mark infer variables as flex --- compiler/constrain/src/expr.rs | 112 ++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 7273f7caa3..0a97de4327 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1329,19 +1329,21 @@ fn constrain_def( def_pattern_state.vars.push(expr_var); - let mut new_rigids = Vec::new(); - let expr_con = match &def.annotation { + match &def.annotation { Some(annotation) => { let arity = annotation.signature.arity(); let rigids = &env.rigids; let mut ftv = rigids.clone(); - let signature = instantiate_rigids( + let InstantiateRigids { + signature, + new_rigid_variables, + new_infer_variables, + } = instantiate_rigids( &annotation.signature, &annotation.introduced_variables, - &mut new_rigids, - &mut ftv, &def.loc_pattern, + &mut ftv, &mut def_pattern_state.headers, ); @@ -1513,8 +1515,16 @@ fn constrain_def( closure_constraint, ]; - let and_constraint = constraints.and_constraint(cons); - constraints.exists(vars, and_constraint) + let expr_con = constraints.exists_many(vars, cons); + + constrain_def_make_constraint( + constraints, + new_rigid_variables, + new_infer_variables, + expr_con, + body_con, + def_pattern_state, + ) } _ => { @@ -1539,35 +1549,62 @@ fn constrain_def( // Store type into AST vars. We use Store so errors aren't reported twice constraints.store(signature, expr_var, std::file!(), std::line!()), ]; - constraints.and_constraint(cons) + let expr_con = constraints.and_constraint(cons); + + constrain_def_make_constraint( + constraints, + new_rigid_variables, + new_infer_variables, + expr_con, + body_con, + def_pattern_state, + ) } } } None => { // no annotation, so no extra work with rigids - constrain_expr( + let expr_con = constrain_expr( constraints, env, def.loc_expr.region, &def.loc_expr.value, NoExpectation(expr_type), + ); + + constrain_def_make_constraint( + constraints, + vec![], + vec![], + expr_con, + body_con, + def_pattern_state, ) } - }; + } +} +fn constrain_def_make_constraint( + constraints: &mut Constraints, + new_rigid_variables: Vec, + new_infer_variables: Vec, + expr_con: Constraint, + body_con: Constraint, + def_pattern_state: PatternState, +) -> Constraint { let and_constraint = constraints.and_constraint(def_pattern_state.constraints); let def_con = constraints.let_constraint( [], - [], + new_infer_variables, SendMap::default(), // empty, because our functions have no arguments! and_constraint, expr_con, ); constraints.let_constraint( - new_rigids, + new_rigid_variables, def_pattern_state.vars, def_pattern_state.headers, def_con, @@ -1631,17 +1668,23 @@ fn constrain_closure_size( constraints.and_constraint(captured_symbols_constraints) } +pub struct InstantiateRigids { + pub signature: Type, + pub new_rigid_variables: Vec, + pub new_infer_variables: Vec, +} + fn instantiate_rigids( annotation: &Type, introduced_vars: &IntroducedVariables, - new_rigids: &mut Vec, - ftv: &mut MutMap, // rigids defined before the current annotation loc_pattern: &Loc, + ftv: &mut MutMap, // rigids defined before the current annotation headers: &mut SendMap>, -) -> Type { +) -> InstantiateRigids { let mut annotation = annotation.clone(); - let mut rigid_substitution: ImMap = ImMap::default(); + let mut new_rigid_variables = Vec::new(); + let mut rigid_substitution: ImMap = ImMap::default(); for (name, var) in introduced_vars.var_by_name.iter() { use std::collections::hash_map::Entry::*; @@ -1653,19 +1696,18 @@ fn instantiate_rigids( Vacant(vacant) => { // It's possible to use this rigid in nested defs vacant.insert(*var); - new_rigids.push(*var); + new_rigid_variables.push(*var); } } } // wildcards are always freshly introduced in this annotation - new_rigids.extend(introduced_vars.wildcards.iter().copied()); - - // inferred vars are always freshly introduced in this annotation - new_rigids.extend(introduced_vars.inferred.iter().copied()); + new_rigid_variables.extend(introduced_vars.wildcards.iter().copied()); // lambda set vars are always freshly introduced in this annotation - new_rigids.extend(introduced_vars.lambda_sets.iter().copied()); + new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied()); + + let new_infer_variables = introduced_vars.inferred.clone(); // Instantiate rigid variables if !rigid_substitution.is_empty() { @@ -1681,7 +1723,11 @@ fn instantiate_rigids( headers.extend(new_headers) } - annotation + InstantiateRigids { + signature: annotation, + new_rigid_variables, + new_infer_variables, + } } fn constrain_recursive_defs( @@ -1744,17 +1790,21 @@ pub fn rec_defs_help( Some(annotation) => { let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); - let mut new_rigids = Vec::new(); - let signature = instantiate_rigids( + let InstantiateRigids { + signature, + new_rigid_variables, + new_infer_variables, + } = instantiate_rigids( &annotation.signature, &annotation.introduced_variables, - &mut new_rigids, - &mut ftv, &def.loc_pattern, + &mut ftv, &mut def_pattern_state.headers, ); + flex_info.vars.extend(new_infer_variables); + let annotation_expected = FromAnnotation( def.loc_pattern.clone(), arity, @@ -1912,10 +1962,10 @@ pub fn rec_defs_help( let and_constraint = constraints.and_constraint(cons); let def_con = constraints.exists(vars, and_constraint); - rigid_info.vars.extend(&new_rigids); + rigid_info.vars.extend(&new_rigid_variables); rigid_info.constraints.push(constraints.let_constraint( - new_rigids, + new_rigid_variables, def_pattern_state.vars, SendMap::default(), // no headers introduced (at this level) def_con, @@ -1947,10 +1997,10 @@ pub fn rec_defs_help( ]; let def_con = constraints.and_constraint(cons); - rigid_info.vars.extend(&new_rigids); + rigid_info.vars.extend(&new_rigid_variables); rigid_info.constraints.push(constraints.let_constraint( - new_rigids, + new_rigid_variables, def_pattern_state.vars, SendMap::default(), // no headers introduced (at this level) def_con, From 81481e2599ad00e70feecb9e02d98f436746f27f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 27 Feb 2022 18:50:47 -0500 Subject: [PATCH 096/150] Report non-exhaustive patterns with guards --- compiler/exhaustive/src/lib.rs | 3 +++ compiler/mono/src/exhaustive.rs | 5 ++++- reporting/src/error/mono.rs | 10 +++++++++- reporting/tests/test_reporting.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs index 8ca1c78298..098425aa49 100644 --- a/compiler/exhaustive/src/lib.rs +++ b/compiler/exhaustive/src/lib.rs @@ -1,3 +1,6 @@ +//! Exhaustiveness checking, based on "Warning for pattern matching" (Luc Maranget, 2007). +//! http://moscova.inria.fr/~maranget/papers/warn/warn.pdf + use roc_collections::all::{HumanIndex, MutMap}; use roc_module::ident::{Lowercase, TagIdIntType, TagName}; use roc_region::all::Region; diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index ceaea825c6..bfcb3b3e90 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -177,7 +177,10 @@ fn to_nonredundant_rows( vec![Pattern::Ctor( union, tag_id, - vec![simplify(&loc_pat.value), guard_pattern], + // NB: ordering the guard pattern first seems to be better at catching + // non-exhaustive constructors in the second argument; see the paper to see if + // there is a way to improve this in general. + vec![guard_pattern, simplify(&loc_pat.value)], )] } else { vec![simplify(&loc_pat.value)] diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index 74e241281b..d3630f5571 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -160,7 +160,15 @@ fn pattern_to_doc_help<'b>( }, Ctor(union, tag_id, args) => { match union.render_as { - RenderAs::Guard => panic!("can this happen? inform Folkert"), + RenderAs::Guard => { + // #Guard + debug_assert_eq!( + union.alternatives[tag_id.0 as usize].name, + TagName::Global("#Guard".into()) + ); + debug_assert!(args.len() == 2); + pattern_to_doc_help(alloc, args[1].clone(), in_type_param) + } RenderAs::Record(field_names) => { let mut arg_docs = Vec::with_capacity(args.len()); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index ea9df598eb..94abdc31d2 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8486,4 +8486,31 @@ I need all branches in an `if` to have the same type! ), ) } + fn non_exhaustive_with_guard() { + report_problem_as( + indoc!( + r#" + x : [A] + when x is + A if True -> "" + "# + ), + indoc!( + r#" + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + + This `when` does not cover all the possibilities: + + 2│> when x is + 3│> A if True -> "" + + Other possibilities include: + + A + + I would have to crash if I saw one of those! Add branches for them! + "# + ), + ) + } } From 1b6624730322d6468a258084f6d9dc0de1a76bb7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 19:30:15 +0100 Subject: [PATCH 097/150] renaming --- compiler/solve/src/solve.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index b82994af92..7a4efb1e48 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -222,12 +222,17 @@ enum Work<'a> { constraint: &'a Constraint, }, CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), - LetConSimple { + /// The ret_con part of a let constraint that does NOT introduces rigid and/or flex variables + LetConNoVariables { env: &'a Env, rank: Rank, let_con: &'a LetConstraint, }, - LetConComplex { + /// The ret_con part of a let constraint that introduces rigid and/or flex variables + /// + /// These introduced variables must be generalized, hence this variant + /// is more complex than `LetConNoVariables`. + LetConIntroducesVariables { env: &'a Env, rank: Rank, let_con: &'a LetConstraint, @@ -274,7 +279,7 @@ fn solve( continue; } - Work::LetConSimple { env, rank, let_con } => { + Work::LetConNoVariables { env, rank, let_con } => { // NOTE be extremely careful with shadowing here let offset = let_con.defs_and_ret_constraint.index(); let ret_constraint = &constraints.constraints[offset + 1]; @@ -298,18 +303,16 @@ fn solve( stack.push(Work::Constraint { env: arena.alloc(new_env), rank, - constraint: &ret_constraint, + constraint: ret_constraint, }); continue; } - Work::LetConComplex { env, rank, let_con } => { + Work::LetConIntroducesVariables { env, rank, let_con } => { // NOTE be extremely careful with shadowing here let offset = let_con.defs_and_ret_constraint.index(); let ret_constraint = &constraints.constraints[offset + 1]; - let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; - let next_rank = rank.next(); let mark = state.mark; @@ -361,6 +364,8 @@ fn solve( // inference, and does not come from elm. It's unclear whether this is // a bug with uniqueness inference (something is redundant that // shouldn't be) or that it just never came up in elm. + let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + let failing: Vec<_> = rigid_vars .iter() .filter(|&var| !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE) @@ -392,7 +397,7 @@ fn solve( stack.push(Work::Constraint { env: arena.alloc(new_env), rank, - constraint: &ret_constraint, + constraint: ret_constraint, }); state = state_for_ret_con; @@ -627,14 +632,14 @@ fn solve( let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; if matches!(&ret_constraint, True) && let_con.rigid_vars.is_empty() { - introduce(subs, rank, pools, &flex_vars); + introduce(subs, rank, pools, flex_vars); // If the return expression is guaranteed to solve, // solve the assignments themselves and move on. stack.push(Work::Constraint { env, rank, - constraint: &defs_constraint, + constraint: defs_constraint, }); state @@ -644,11 +649,11 @@ fn solve( // // Note that the LetConSimple gets the current env and rank, // and not the env/rank from after solving the defs_constraint - stack.push(Work::LetConSimple { env, rank, let_con }); + stack.push(Work::LetConNoVariables { env, rank, let_con }); stack.push(Work::Constraint { env, rank, - constraint: &defs_constraint, + constraint: defs_constraint, }); state @@ -686,11 +691,11 @@ fn solve( // // Note that the LetConSimple gets the current env and rank, // and not the env/rank from after solving the defs_constraint - stack.push(Work::LetConComplex { env, rank, let_con }); + stack.push(Work::LetConIntroducesVariables { env, rank, let_con }); stack.push(Work::Constraint { env, rank: next_rank, - constraint: &defs_constraint, + constraint: defs_constraint, }); state From cc07c93fc3f4cbebb9db66c16b5c0e188aad7499 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 5 Mar 2022 13:34:49 -0500 Subject: [PATCH 098/150] Add note about lack of a guard in missing tag --- reporting/src/error/mono.rs | 3 ++- reporting/tests/test_reporting.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index d3630f5571..215bc3eeaf 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -167,7 +167,8 @@ fn pattern_to_doc_help<'b>( TagName::Global("#Guard".into()) ); debug_assert!(args.len() == 2); - pattern_to_doc_help(alloc, args[1].clone(), in_type_param) + let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); + tag.append(" (note the lack of a guard)") } RenderAs::Record(field_names) => { let mut arg_docs = Vec::with_capacity(args.len()); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 94abdc31d2..df31c1fafd 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8486,6 +8486,8 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] fn non_exhaustive_with_guard() { report_problem_as( indoc!( @@ -8506,7 +8508,7 @@ I need all branches in an `if` to have the same type! Other possibilities include: - A + A (note the lack of a guard) I would have to crash if I saw one of those! Add branches for them! "# From dc8a077fff73b945f02159f066b15b325d89a94a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 21:28:59 +0100 Subject: [PATCH 099/150] constraint.rs tweaks --- compiler/can/src/constraint.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 8ffd1ec896..ae24ba78c9 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -172,6 +172,7 @@ impl Constraints { } } + #[inline(always)] pub fn equal_types( &mut self, typ: Type, @@ -262,9 +263,15 @@ impl Constraints { fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Loc>)> where I: IntoIterator)>, + I::IntoIter: ExactSizeIterator, { + let it = it.into_iter(); + let start = self.def_types.len(); + // because we have an ExactSizeIterator, we can reserve space here + self.def_types.reserve(it.len()); + for (symbol, loc_type) in it { let Loc { region, value } = loc_type; let type_index = Index::push_new(&mut self.types, value); @@ -277,6 +284,7 @@ impl Constraints { Slice::new(start as _, length as _) } + #[inline(always)] pub fn exists(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint where I: IntoIterator, @@ -299,6 +307,7 @@ impl Constraints { Constraint::Let(let_index) } + #[inline(always)] pub fn exists_many(&mut self, flex_vars: I, defs_constraint: C) -> Constraint where I: IntoIterator, @@ -324,6 +333,7 @@ impl Constraints { Constraint::Let(let_index) } + #[inline(always)] pub fn let_constraint( &mut self, rigid_vars: I1, @@ -336,6 +346,7 @@ impl Constraints { I1: IntoIterator, I2: IntoIterator, I3: IntoIterator)>, + I3::IntoIter: ExactSizeIterator, { let defs_and_ret_constraint = Index::new(self.constraints.len() as _); @@ -355,6 +366,7 @@ impl Constraints { Constraint::Let(let_index) } + #[inline(always)] pub fn and_constraint(&mut self, constraints: I) -> Constraint where I: IntoIterator, From 6392e42166b8c94470b89f3ecdac650ec63357f8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 21:29:17 +0100 Subject: [PATCH 100/150] prevent frequent expectation cloning --- compiler/constrain/src/expr.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 0a97de4327..e534e5a45f 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1363,7 +1363,7 @@ fn constrain_def( def_pattern_state.constraints.push(constraints.equal_types( expr_type, - annotation_expected.clone(), + annotation_expected, Category::Storage(std::file!(), std::line!()), Region::span_across(&annotation.region, &def.loc_expr.region), )); @@ -1528,14 +1528,21 @@ fn constrain_def( } _ => { - let expected = annotation_expected; + let annotation_expected = FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature.clone(), + ); let ret_constraint = constrain_expr( constraints, env, def.loc_expr.region, &def.loc_expr.value, - expected, + annotation_expected, ); let cons = [ From c7c9a90d65fc8ad958e0809e43daba8dc0440783 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 21:47:49 +0100 Subject: [PATCH 101/150] restructure how def_types are stored in Constraints --- compiler/can/src/constraint.rs | 39 ++++++++++++++++++++++------------ compiler/solve/src/solve.rs | 19 ++++++----------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index ae24ba78c9..70573d252a 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -11,7 +11,7 @@ pub struct Constraints { pub constraints: Vec, pub types: Vec, pub variables: Vec, - pub def_types: Vec<(Symbol, Loc>)>, + pub loc_symbols: Vec<(Symbol, Region)>, pub let_constraints: Vec, pub categories: Vec, pub pattern_categories: Vec, @@ -32,7 +32,7 @@ impl Constraints { let constraints = Vec::new(); let mut types = Vec::new(); let variables = Vec::new(); - let def_types = Vec::new(); + let loc_symbols = Vec::new(); let let_constraints = Vec::new(); let mut categories = Vec::with_capacity(16); let mut pattern_categories = Vec::with_capacity(16); @@ -78,7 +78,7 @@ impl Constraints { constraints, types, variables, - def_types, + loc_symbols, let_constraints, categories, pattern_categories, @@ -260,28 +260,33 @@ impl Constraints { Slice::new(start as _, length as _) } - fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Loc>)> + fn def_types_slice(&mut self, it: I) -> DefTypes where I: IntoIterator)>, I::IntoIter: ExactSizeIterator, { let it = it.into_iter(); - let start = self.def_types.len(); + let types_start = self.types.len(); + let loc_symbols_start = self.loc_symbols.len(); // because we have an ExactSizeIterator, we can reserve space here - self.def_types.reserve(it.len()); + let length = it.len(); + + self.types.reserve(length); + self.loc_symbols.reserve(length); for (symbol, loc_type) in it { let Loc { region, value } = loc_type; - let type_index = Index::push_new(&mut self.types, value); - self.def_types.push((symbol, Loc::at(region, type_index))); + self.types.push(value); + self.loc_symbols.push((symbol, region)); } - let length = self.def_types.len() - start; - - Slice::new(start as _, length as _) + DefTypes { + types: Slice::new(types_start as _, length as _), + loc_symbols: Slice::new(loc_symbols_start as _, length as _), + } } #[inline(always)] @@ -297,7 +302,7 @@ impl Constraints { let let_contraint = LetConstraint { rigid_vars: Slice::default(), flex_vars: self.variable_slice(flex_vars), - def_types: Slice::default(), + def_types: DefTypes::default(), defs_and_ret_constraint, }; @@ -323,7 +328,7 @@ impl Constraints { let let_contraint = LetConstraint { rigid_vars: Slice::default(), flex_vars: self.variable_slice(flex_vars), - def_types: Slice::default(), + def_types: DefTypes::default(), defs_and_ret_constraint, }; @@ -476,11 +481,17 @@ pub enum Constraint { ), } +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct DefTypes { + pub types: Slice, + pub loc_symbols: Slice<(Symbol, Region)>, +} + #[derive(Debug, Clone, PartialEq)] pub struct LetConstraint { pub rigid_vars: Slice, pub flex_vars: Slice, - pub def_types: Slice<(Symbol, Loc>)>, + pub def_types: DefTypes, pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 7a4efb1e48..33dce4ec16 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -3,7 +3,6 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{Constraints, LetConstraint}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; -use roc_collections::soa::{Index, Slice}; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -819,23 +818,17 @@ impl LocalDefVarsVec<(Symbol, Loc)> { pools: &mut Pools, cached_aliases: &mut MutMap, subs: &mut Subs, - def_types_slice: Slice<(Symbol, Loc>)>, + def_types_slice: roc_can::constraint::DefTypes, ) -> Self { - let def_types = &constraints.def_types[def_types_slice.indices()]; + let types_slice = &constraints.types[def_types_slice.types.indices()]; + let loc_symbols_slice = &constraints.loc_symbols[def_types_slice.loc_symbols.indices()]; - let mut local_def_vars = Self::with_length(def_types.len()); + let mut local_def_vars = Self::with_length(types_slice.len()); - for (symbol, loc_type_index) in def_types.iter() { - let typ = &constraints.types[loc_type_index.value.index()]; + for ((symbol, region), typ) in loc_symbols_slice.iter().copied().zip(types_slice) { let var = type_to_var(subs, rank, pools, cached_aliases, typ); - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type_index.region, - }, - )); + local_def_vars.push((symbol, Loc { value: var, region })); } local_def_vars From 587c89e003c7b360d1ce5ff96ca4ddf5fb641499 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 21:51:20 +0100 Subject: [PATCH 102/150] use empty array for empty DefTypes sequence --- compiler/constrain/src/expr.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index e534e5a45f..2aca1252bb 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1194,13 +1194,7 @@ fn constrain_when_branch( // must introduce the headers from the pattern before constraining the guard let state_constraints = constraints.and_constraint(state.constraints); - let inner = constraints.let_constraint( - [], - [], - SendMap::default(), - guard_constraint, - ret_constraint, - ); + let inner = constraints.let_constraint([], [], [], guard_constraint, ret_constraint); constraints.let_constraint([], state.vars, state.headers, state_constraints, inner) } else { @@ -1546,13 +1540,7 @@ fn constrain_def( ); let cons = [ - constraints.let_constraint( - [], - [], - SendMap::default(), - Constraint::True, - ret_constraint, - ), + constraints.let_constraint([], [], [], Constraint::True, ret_constraint), // Store type into AST vars. We use Store so errors aren't reported twice constraints.store(signature, expr_var, std::file!(), std::line!()), ]; @@ -1605,7 +1593,7 @@ fn constrain_def_make_constraint( let def_con = constraints.let_constraint( [], new_infer_variables, - SendMap::default(), // empty, because our functions have no arguments! + [], // empty, because our functions have no arguments! and_constraint, expr_con, ); @@ -1783,8 +1771,8 @@ pub fn rec_defs_help( // TODO investigate if this let can be safely removed let def_con = constraints.let_constraint( [], - [], // empty because Roc function defs have no args - SendMap::default(), // empty because Roc function defs have no args + [], // empty because Roc function defs have no args + [], // empty because Roc function defs have no args Constraint::True, // I think this is correct, once again because there are no args expr_con, ); @@ -1974,7 +1962,7 @@ pub fn rec_defs_help( rigid_info.constraints.push(constraints.let_constraint( new_rigid_variables, def_pattern_state.vars, - SendMap::default(), // no headers introduced (at this level) + [], // no headers introduced (at this level) def_con, Constraint::True, )); @@ -1995,7 +1983,7 @@ pub fn rec_defs_help( constraints.let_constraint( [], [], - SendMap::default(), + [], Constraint::True, ret_constraint, ), @@ -2009,7 +1997,7 @@ pub fn rec_defs_help( rigid_info.constraints.push(constraints.let_constraint( new_rigid_variables, def_pattern_state.vars, - SendMap::default(), // no headers introduced (at this level) + [], // no headers introduced (at this level) def_con, Constraint::True, )); From 6380a9edf9a4c2080bf024930aac87bbebe35e6c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 22:29:22 +0100 Subject: [PATCH 103/150] remove collection that went unused --- compiler/constrain/src/expr.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 2aca1252bb..734f52a55c 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -100,7 +100,6 @@ pub fn constrain_expr( if fields.is_empty() { constrain_empty_record(constraints, region, expected) } else { - let mut field_exprs = SendMap::default(); let mut field_types = SendMap::default(); let mut field_vars = Vec::with_capacity(fields.len()); @@ -115,7 +114,6 @@ pub fn constrain_expr( constrain_field(constraints, env, field_var, &*loc_field_expr); field_vars.push(field_var); - field_exprs.insert(label.clone(), loc_field_expr); field_types.insert(label.clone(), RecordField::Required(field_type)); rec_constraints.push(field_con); From 9f11245b22cd3a1c86dcec137dc886699ea6dc00 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 22:29:30 +0100 Subject: [PATCH 104/150] add comment --- compiler/constrain/src/expr.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 734f52a55c..a0e2f5009c 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1707,6 +1707,11 @@ fn instantiate_rigids( annotation.substitute(&rigid_substitution); } + // TODO investigate when we can skip this. It seems to only be required for correctness + // for recursive functions. For non-recursive functions the final type is correct, but + // alias information is sometimes lost + // + // Skipping all of this cloning here would be neat! let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation); if let Pattern::Identifier(symbol) = loc_pattern.value { headers.insert(symbol, Loc::at(loc_pattern.region, annotation.clone())); From f2de6baa3607d672df72116dc3e600671df2aa62 Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Sat, 5 Mar 2022 17:28:12 -0500 Subject: [PATCH 105/150] Add functionality to turn RocDecs into strings. --- Cargo.lock | 20 +++++++++++++------- repl_eval/src/lib.rs | 2 +- roc_std/Cargo.toml | 2 +- roc_std/src/lib.rs | 23 ++++++++++++++++++----- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24fe390b48..b6ff4539e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1709,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f" dependencies = [ "lazy_static", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -3648,7 +3648,7 @@ dependencies = [ "roc_ident", "roc_region", "snafu", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -3671,7 +3671,7 @@ dependencies = [ "roc_target", "roc_types", "roc_unify", - "static_assertions", + "static_assertions 1.1.0", "ven_graph", "ven_pretty", ] @@ -3707,7 +3707,7 @@ dependencies = [ name = "roc_region" version = "0.1.0" dependencies = [ - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -3825,7 +3825,7 @@ dependencies = [ name = "roc_std" version = "0.1.0" dependencies = [ - "roc_error_macros", + "static_assertions 0.1.1", ] [[package]] @@ -3851,7 +3851,7 @@ dependencies = [ "roc_error_macros", "roc_module", "roc_region", - "static_assertions", + "static_assertions 1.1.0", "ven_ena", ] @@ -4271,6 +4271,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" + [[package]] name = "static_assertions" version = "1.1.0" @@ -4559,7 +4565,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e" dependencies = [ "cfg-if 1.0.0", "rand", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index f1b52d4530..1db9f294ad 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -50,7 +50,7 @@ pub trait ReplAppMemory { fn deref_dec(&self, addr: usize) -> RocDec { let bits = self.deref_i128(addr); - return RocDec(bits); + RocDec(bits) } fn deref_str(&self, addr: usize) -> &str; diff --git a/roc_std/Cargo.toml b/roc_std/Cargo.toml index 265bd0fd40..22337ad6fb 100644 --- a/roc_std/Cargo.toml +++ b/roc_std/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/rtfeldman/roc" version = "0.1.0" [dependencies] -roc_error_macros = { path = "../error_macros" } +static_assertions = "0.1" [dev-dependencies] indoc = "1.0.3" diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 435fdcefc7..6c25602f63 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -283,18 +283,23 @@ impl RocDec { fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) { if self.0 == 0 { - write!(&mut bytes[..], "{}", "0.0").unwrap(); + write!(&mut bytes[..], "{}", "0").unwrap(); return; } let is_negative = (self.0 < 0) as usize; - write!(&mut bytes[..], "{:019}", self.0).unwrap(); + static_assertions::const_assert!(Self::DECIMAL_PLACES + 1 == 19); + // The :019 in the following write! is computed as Self::DECIMAL_PLACES + 1. If you change + // Self::DECIMAL_PLACES, this assert should remind you to change that format string as + // well. + // // By using the :019 format, we're guaranteeing that numbers less than 1, say 0.01234 // get their leading zeros placed in bytes for us. i.e. bytes = b"0012340000000000000" + write!(&mut bytes[..], "{:019}", self.0).unwrap(); // If self represents 1234.5678, then bytes is b"1234567800000000000000". - let mut i = Self::DECIMAL_PLACES; + let mut i = Self::MAX_STR_LENGTH - 1; // Find the last place where we have actual data. while bytes[i] == 0 { i = i - 1; @@ -304,18 +309,26 @@ impl RocDec { let decimal_location = i - Self::DECIMAL_PLACES + 1 + is_negative; // decimal_location = 4 - while bytes[i] == ('0' as u8) { + while bytes[i] == ('0' as u8) && i >= decimal_location { bytes[i] = 0; i = i - 1; } // Now i = 7, because bytes[7] = '8', and bytes = b"12345678" + if i < decimal_location { + // This means that we've removed trailing zeros and are left with an integer. Our + // convention is to print these without a decimal point or trailing zeros, so we're done. + return; + } + while i >= decimal_location { bytes[i + 1] = bytes[i]; + i = i - 1; } + bytes[i + 1] = bytes[i]; // Now i = 4, and bytes = b"123455678" - bytes[i] = '.' as u8; + bytes[decimal_location] = '.' as u8; // Finally bytes = b"1234.5678" } From 813845e7d17f5a177f5b8a9608085e92c7f71802 Mon Sep 17 00:00:00 2001 From: Philippe Vinchon Date: Sat, 5 Mar 2022 22:31:57 +0000 Subject: [PATCH 106/150] Trim end and add empty line for POSIX --- cli/src/format.rs | 3 +++ compiler/fmt/src/lib.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/cli/src/format.rs b/cli/src/format.rs index ef2e853867..e8f4ce688f 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -180,6 +180,9 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) { for def in &ast.defs { fmt_def(buf, arena.alloc(def.value), 0); } + + buf.trim_end(); + buf.newline(); } /// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index 23667d9f4c..04ac68e720 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -90,4 +90,10 @@ impl<'a> Buf<'a> { self.spaces_to_flush = 0; } } + + pub fn trim_end(&mut self) { + while self.text.ends_with(char::is_whitespace) { + self.text.truncate(self.text.len() - 1); + } + } } From b32e7e0e2da6791d0f48cf230b235d99af93d647 Mon Sep 17 00:00:00 2001 From: Philippe Vinchon Date: Sat, 5 Mar 2022 22:32:34 +0000 Subject: [PATCH 107/150] Update example files --- examples/benchmarks/Closure.roc | 1 - examples/benchmarks/Quicksort.roc | 1 - examples/benchmarks/QuicksortApp.roc | 2 +- examples/benchmarks/RBTreeInsert.roc | 1 - examples/benchmarks/TestBase64.roc | 1 - examples/false-interpreter/Context.roc | 2 +- 6 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/benchmarks/Closure.roc b/examples/benchmarks/Closure.roc index fa19416759..d6bf6b3ff8 100644 --- a/examples/benchmarks/Closure.roc +++ b/examples/benchmarks/Closure.roc @@ -49,4 +49,3 @@ closure4 = \_ -> Task.succeed {} |> Task.after (\_ -> Task.succeed x) |> Task.map (\_ -> {}) - diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index cc83902313..c5fb45acc2 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -73,4 +73,3 @@ swap = \i, j, list -> _ -> [] - diff --git a/examples/benchmarks/QuicksortApp.roc b/examples/benchmarks/QuicksortApp.roc index 61c09205fc..11f5839392 100644 --- a/examples/benchmarks/QuicksortApp.roc +++ b/examples/benchmarks/QuicksortApp.roc @@ -22,4 +22,4 @@ main = sort : List I64 -> List I64 sort = \list -> - Quicksort.sortWith list (\x, y -> Num.compare x y) \ No newline at end of file + Quicksort.sortWith list (\x, y -> Num.compare x y) diff --git a/examples/benchmarks/RBTreeInsert.roc b/examples/benchmarks/RBTreeInsert.roc index cbe0a83ffa..442bbdefff 100644 --- a/examples/benchmarks/RBTreeInsert.roc +++ b/examples/benchmarks/RBTreeInsert.roc @@ -112,4 +112,3 @@ balance = \color, key, value, left, right -> _ -> Node color key value left right - diff --git a/examples/benchmarks/TestBase64.roc b/examples/benchmarks/TestBase64.roc index dba3d7aa01..e092b7ac07 100644 --- a/examples/benchmarks/TestBase64.roc +++ b/examples/benchmarks/TestBase64.roc @@ -21,4 +21,3 @@ main = Err _ -> Task.putLine "sadness" - diff --git a/examples/false-interpreter/Context.roc b/examples/false-interpreter/Context.roc index 57dbf1171c..e41529843b 100644 --- a/examples/false-interpreter/Context.roc +++ b/examples/false-interpreter/Context.roc @@ -122,4 +122,4 @@ inWhileScope = \ctx -> scope.whileInfo != None Err ListWasEmpty -> - False \ No newline at end of file + False From 97d9027271758e1e968bcb860123bc3e5a22627b Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 23:07:57 +0100 Subject: [PATCH 108/150] improve pool_to_rank_table --- compiler/solve/src/solve.rs | 19 +++++++++++++------ compiler/types/src/subs.rs | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 7a4efb1e48..c4018cbeff 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1512,13 +1512,20 @@ fn pool_to_rank_table( ) -> Pools { let mut pools = Pools::new(young_rank.into_usize() + 1); - // Sort the variables into buckets by rank. - for &var in young_vars.iter() { - let rank = subs.get_rank_set_mark(var, young_mark); + // the vast majority of young variables have young_rank + let mut young_vars = young_vars.to_vec(); + young_vars.retain(|var| { + let rank = subs.get_rank_set_mark(*var, young_mark); - debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); - pools.get_mut(rank).push(var); - } + if rank != young_rank { + pools.get_mut(rank).push(*var); + false + } else { + true + } + }); + + std::mem::swap(pools.get_mut(young_rank), &mut young_vars); pools } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b636c1efa8..243ac16f54 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1432,6 +1432,7 @@ impl Subs { mapper(self.get_ref_mut(key)); } + #[inline(always)] pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { let l_key = self.utable.get_root_key(key); From 0351b9fce7b5ba8b8d919d6724fa0743519a23e7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 23:29:33 +0100 Subject: [PATCH 109/150] improvements to adjust_rank --- compiler/solve/src/solve.rs | 16 ++++++++++------ vendor/ena/src/unify/mod.rs | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c4018cbeff..81090b1fb4 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1539,21 +1539,24 @@ fn adjust_rank( group_rank: Rank, var: Variable, ) -> Rank { - let (desc_rank, desc_mark) = subs.get_rank_mark(var); + let desc = subs.get_ref_mut(var); + + let desc_rank = desc.rank; + let desc_mark = desc.mark; if desc_mark == young_mark { - // Mark the variable as visited before adjusting content, as it may be cyclic. - subs.set_mark(var, visit_mark); - // SAFETY: in this function (and functions it calls, we ONLY modify rank and mark, never content! // hence, we can have an immutable reference to it even though we also have a mutable // reference to the Subs as a whole. This prevents a clone of the content, which turns out // to be quite expensive. let content = { - let ptr = &subs.get_ref(var).content as *const _; + let ptr = &desc.content as *const _; unsafe { &*ptr } }; + // Mark the variable as visited before adjusting content, as it may be cyclic. + desc.mark = visit_mark; + let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, content); subs.set_rank_mark(var, max_rank, visit_mark); @@ -1566,7 +1569,8 @@ fn adjust_rank( let min_rank = group_rank.min(desc_rank); // TODO from elm-compiler: how can min_rank ever be group_rank? - subs.set_rank_mark(var, min_rank, visit_mark); + desc.rank = min_rank; + desc.mark = visit_mark; min_rank } diff --git a/vendor/ena/src/unify/mod.rs b/vendor/ena/src/unify/mod.rs index 6eddcd4d06..00c120ff3d 100644 --- a/vendor/ena/src/unify/mod.rs +++ b/vendor/ena/src/unify/mod.rs @@ -470,7 +470,7 @@ where K1: Into, { let id = id.into(); - let id = self.get_root_key_without_compacting(id); + let id = self.get_root_key(id); self.value_mut(id) } From 6906d64ec38d8dfe1df66b02aabda7309a5a2b38 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 23:37:41 +0100 Subject: [PATCH 110/150] optimize occurs --- compiler/types/src/subs.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 243ac16f54..164367a28e 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1455,7 +1455,7 @@ impl Subs { } pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { - occurs(self, &ImSet::default(), var) + occurs(self, &[], var) } pub fn mark_tag_union_recursive( @@ -2381,7 +2381,7 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool { fn occurs( subs: &Subs, - seen: &ImSet, + seen: &[Variable], input_var: Variable, ) -> Result<(), (Variable, Vec)> { use self::Content::*; @@ -2396,9 +2396,9 @@ fn occurs( FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()), Structure(flat_type) => { - let mut new_seen = seen.clone(); + let mut new_seen = seen.to_owned(); - new_seen.insert(root_var); + new_seen.push(root_var); match flat_type { Apply(_, args) => { @@ -2447,8 +2447,7 @@ fn occurs( } } Alias(_, args, _, _) => { - let mut new_seen = seen.clone(); - new_seen.insert(root_var); + let mut new_seen = seen.to_owned(); for var_index in args.into_iter() { let var = subs[var_index]; @@ -2458,8 +2457,8 @@ fn occurs( Ok(()) } RangedNumber(typ, _range_vars) => { - let mut new_seen = seen.clone(); - new_seen.insert(root_var); + let mut new_seen = seen.to_owned(); + new_seen.push(root_var); short_circuit_help(subs, root_var, &new_seen, *typ)?; // _range_vars excluded because they are not explicitly part of the type. @@ -2470,10 +2469,11 @@ fn occurs( } } +#[inline(always)] fn short_circuit<'a, T>( subs: &Subs, root_key: Variable, - seen: &ImSet, + seen: &[Variable], iter: T, ) -> Result<(), (Variable, Vec)> where @@ -2486,10 +2486,11 @@ where Ok(()) } +#[inline(always)] fn short_circuit_help( subs: &Subs, root_key: Variable, - seen: &ImSet, + seen: &[Variable], var: Variable, ) -> Result<(), (Variable, Vec)> { if let Err((v, mut vec)) = occurs(subs, seen, var) { From 4806dc378f5c8d4a264ea0b1419e145c60c8f4a0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 5 Mar 2022 23:49:21 +0100 Subject: [PATCH 111/150] add comment and assert back --- compiler/solve/src/solve.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 81090b1fb4..b706351b3d 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1504,6 +1504,7 @@ fn generalize( } } +/// Sort the variables into buckets by rank. fn pool_to_rank_table( subs: &mut Subs, young_mark: Mark, @@ -1513,11 +1514,14 @@ fn pool_to_rank_table( let mut pools = Pools::new(young_rank.into_usize() + 1); // the vast majority of young variables have young_rank + // using `retain` here prevents many `pools.get_mut(young_rank)` lookups let mut young_vars = young_vars.to_vec(); young_vars.retain(|var| { let rank = subs.get_rank_set_mark(*var, young_mark); if rank != young_rank { + debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); + pools.get_mut(rank).push(*var); false } else { From 628f14fa7bf64b21d26d726181e6aa53c54dc63e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 00:35:45 +0100 Subject: [PATCH 112/150] fix accidentally remove line --- compiler/types/src/subs.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 164367a28e..2dcc41abee 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -357,6 +357,7 @@ fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt: write!(f, " m: {:?}", &this.mark)?; write!(f, " c: {:?}", &this.copy) } +// pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs); @@ -2448,6 +2449,7 @@ fn occurs( } Alias(_, args, _, _) => { let mut new_seen = seen.to_owned(); + new_seen.push(root_var); for var_index in args.into_iter() { let var = subs[var_index]; From f708811250cb5c5b1a9c5edf6a2bde7cfba2e52e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 00:50:41 +0100 Subject: [PATCH 113/150] move variable definition before macro that captures it --- repl_eval/src/eval.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 9ca48ec02f..a4dd0d7809 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -278,6 +278,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { + let (newtype_containers, content) = unroll_newtypes(env, content); + let content = unroll_aliases(env, content); + macro_rules! helper { ($ty:ty) => { app.call_function(main_fn_name, |_, num: $ty| { @@ -286,8 +289,6 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( }; } - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(app .call_function(main_fn_name, |mem: &A::Memory, num: bool| { From 21ca144de935af1780939d2987ff57d6c18265dc Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 6 Mar 2022 01:04:54 +0100 Subject: [PATCH 114/150] formatting --- compiler/types/src/subs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 2dcc41abee..36b937be02 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -357,7 +357,6 @@ fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt: write!(f, " m: {:?}", &this.mark)?; write!(f, " c: {:?}", &this.copy) } -// pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs); From 7ad55d67e277772881d63f577aa0ee7cc0260560 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 12:57:01 +0100 Subject: [PATCH 115/150] Make type_to_variable manually tail-recursive by using a work stack and reserving variables. fun stuff --- compiler/solve/src/solve.rs | 558 ++++++++++++++++++++---------------- 1 file changed, 318 insertions(+), 240 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 7a4efb1e48..2db56dbad6 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -865,14 +865,65 @@ fn type_to_var( _: &mut MutMap, typ: &Type, ) -> Variable { - let mut arena = take_scratchpad(); + if let Type::Variable(var) = typ { + *var + } else { + let mut arena = take_scratchpad(); - let var = type_to_variable(subs, rank, pools, &arena, typ); + // let var = type_to_variable(subs, rank, pools, &arena, typ); + let var = type_to_variable(subs, rank, pools, &arena, typ); - arena.reset(); - put_scratchpad(arena); + arena.reset(); + put_scratchpad(arena); - var + var + } +} + +enum RegisterVariable { + /// Based on the Type, we already know what variable this will be + Direct(Variable), + /// This Type needs more complicated Content. We reserve a Variable + /// for it, but put a placeholder Content in subs + Deferred, +} + +impl RegisterVariable { + fn from_type( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &'_ bumpalo::Bump, + typ: &Type, + ) -> Self { + use RegisterVariable::*; + + match typ { + Variable(var) => Direct(*var), + EmptyRec => Direct(Variable::EMPTY_RECORD), + EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), + Type::Alias { symbol, .. } => { + if let Some(reserved) = Variable::get_reserved(*symbol) { + if rank.is_none() { + // reserved variables are stored with rank NONE + return Direct(reserved); + } else { + // for any other rank, we need to copy; it takes care of adjusting the rank + let copied = deep_copy_var_in(subs, rank, pools, reserved, arena); + return Direct(copied); + } + } + + Deferred + } + _ => Deferred, + } + } +} + +#[derive(Debug)] +enum TypeToVar<'a> { + Defer(&'a Type, Variable), } fn type_to_variable<'a>( @@ -884,255 +935,280 @@ fn type_to_variable<'a>( ) -> Variable { use bumpalo::collections::Vec; - match typ { - Variable(var) => *var, - RangedNumber(typ, vars) => { - let ty_var = type_to_variable(subs, rank, pools, arena, typ); - let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); - let content = Content::RangedNumber(ty_var, vars); + let mut stack = Vec::with_capacity_in(8, arena); - register(subs, rank, pools, content) - } - Apply(symbol, arguments, _) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = type_to_variable(subs, rank, pools, arena, var_index); - subs.variables[target_index] = var; - } - - let flat_type = FlatType::Apply(*symbol, new_arguments); - let content = Content::Structure(flat_type); - - register(subs, rank, pools, content) - } - EmptyRec => Variable::EMPTY_RECORD, - EmptyTagUnion => Variable::EMPTY_TAG_UNION, - - ClosureTag { name, ext } => { - let tag_name = TagName::Closure(*name); - let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1); - - subs.tag_names.push(tag_name); - - // the first VariableSubsSlice in the array is a zero-length slice - let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1)); - - let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); - - register(subs, rank, pools, content) - } - - // This case is important for the rank of boolean variables - Function(arguments, closure_type, ret_type) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = type_to_variable(subs, rank, pools, arena, var_index); - subs.variables[target_index] = var; - } - - let ret_var = type_to_variable(subs, rank, pools, arena, ret_type); - let closure_var = type_to_variable(subs, rank, pools, arena, closure_type); - let content = Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); - - register(subs, rank, pools, content) - } - Record(fields, ext) => { - // An empty fields is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyRecord in canonicalization - debug_assert!(!fields.is_empty() || !ext.is_empty_record()); - - let mut field_vars = Vec::with_capacity_in(fields.len(), arena); - - for (field, field_type) in fields { - let field_var = - field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ)); - - field_vars.push((field.clone(), field_var)); - } - - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); - - let (it, new_ext_var) = - gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) - .expect("Something ended up weird in this record type"); - - let it = it - .into_iter() - .map(|(field, field_type)| (field.clone(), field_type)); - - field_vars.extend(it); - insertion_sort_by(&mut field_vars, RecordFields::compare); - - let record_fields = RecordFields::insert_into_subs(subs, field_vars); - - let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); - - register(subs, rank, pools, content) - } - TagUnion(tags, ext) => { - // An empty tags is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - register(subs, rank, pools, content) - } - FunctionOrTagUnion(tag_name, symbol, ext) => { - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); - - let (it, ext) = roc_types::types::gather_tags_unsorted_iter( - subs, - UnionTags::default(), - temp_ext_var, - ); - - for _ in it { - unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); - } - - let slice = SubsIndex::new(subs.tag_names.len() as u32); - subs.tag_names.push(tag_name.clone()); - - let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); - - register(subs, rank, pools, content) - } - RecursiveTagUnion(rec_var, tags, ext) => { - // An empty tags is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); - let content = - Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); - - let tag_union_var = register(subs, rank, pools, content); - - register_with_known_var( - subs, - *rec_var, - rank, - pools, - Content::RecursionVar { - opt_name: None, - structure: tag_union_var, - }, - ); - - tag_union_var - } - - Type::Alias { - symbol, - type_arguments, - actual, - lambda_set_variables, - kind, - } => { - if let Some(reserved) = Variable::get_reserved(*symbol) { - if rank.is_none() { - // reserved variables are stored with rank NONE - return reserved; - } else { - // for any other rank, we need to copy; it takes care of adjusting the rank - return deep_copy_var_in(subs, rank, pools, reserved, arena); + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var } } + }}; + } - let alias_variables = alias_to_var( - subs, - rank, - pools, - arena, - type_arguments, - lambda_set_variables, - ); + let result = helper!(typ); - let alias_variable = if let Symbol::RESULT_RESULT = *symbol { - roc_result_to_var(subs, rank, pools, arena, actual) - } else { - type_to_variable(subs, rank, pools, arena, actual) - }; - let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); + while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() { + match typ { + Variable(_) | EmptyRec | EmptyTagUnion => { + unreachable!("This variant should never be deferred!") + } + RangedNumber(typ, vars) => { + let ty_var = helper!(typ); + let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); + let content = Content::RangedNumber(ty_var, vars); - register(subs, rank, pools, content) - } - HostExposedAlias { - name: symbol, - type_arguments, - actual: alias_type, - actual_var, - lambda_set_variables, - .. - } => { - let alias_variables = alias_to_var( - subs, - rank, - pools, - arena, - type_arguments, - lambda_set_variables, - ); + register_with_known_var(subs, destination, rank, pools, content) + } + Apply(symbol, arguments, _) => { + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = helper!(var_index); + subs.variables[target_index] = var; + } - let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); - // TODO(opaques): I think host-exposed aliases should always be structural - // (when does it make sense to give a host an opaque type?) - let content = Content::Alias( - *symbol, - alias_variables, - alias_variable, - AliasKind::Structural, - ); - let result = register(subs, rank, pools, content); + let flat_type = FlatType::Apply(*symbol, new_arguments); + let content = Content::Structure(flat_type); - // We only want to unify the actual_var with the alias once - // if it's already redirected (and therefore, redundant) - // don't do it again - if !subs.redundant(*actual_var) { - let descriptor = subs.get(result); - subs.union(result, *actual_var, descriptor); + register_with_known_var(subs, destination, rank, pools, content) } - result - } - Erroneous(problem) => { - let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + ClosureTag { name, ext } => { + let tag_name = TagName::Closure(*name); + let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1); - register(subs, rank, pools, content) - } - } -} + subs.tag_names.push(tag_name); -#[inline(always)] -fn alias_to_var<'a>( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'a bumpalo::Bump, - type_arguments: &[(roc_module::ident::Lowercase, Type)], - lambda_set_variables: &[roc_types::types::LambdaSet], -) -> AliasVariables { - let length = type_arguments.len() + lambda_set_variables.len(); - let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + // the first VariableSubsSlice in the array is a zero-length slice + let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1)); - for (target_index, (_, arg_type)) in (new_variables.indices()).zip(type_arguments) { - let copy_var = type_to_variable(subs, rank, pools, arena, arg_type); - subs.variables[target_index] = copy_var; + let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + // This case is important for the rank of boolean variables + Function(arguments, closure_type, ret_type) => { + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = helper!(var_index); + subs.variables[target_index] = var; + } + + let ret_var = helper!(ret_type); + let closure_var = helper!(closure_type); + let content = + Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); + + register_with_known_var(subs, destination, rank, pools, content) + } + Record(fields, ext) => { + // An empty fields is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyRecord in canonicalization + debug_assert!(!fields.is_empty() || !ext.is_empty_record()); + + let mut field_vars = Vec::with_capacity_in(fields.len(), arena); + + for (field, field_type) in fields { + let field_var = { + use roc_types::types::RecordField::*; + match &field_type { + Optional(t) => Optional(helper!(t)), + Required(t) => Required(helper!(t)), + Demanded(t) => Demanded(helper!(t)), + } + }; + + field_vars.push((field.clone(), field_var)); + } + + let temp_ext_var = helper!(ext); + + let (it, new_ext_var) = + gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) + .expect("Something ended up weird in this record type"); + + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + insertion_sort_by(&mut field_vars, RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(subs, field_vars); + + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); + + register_with_known_var(subs, destination, rank, pools, content) + } + + TagUnion(tags, ext) => { + // An empty tags is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyTagUnion in canonicalization + debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + FunctionOrTagUnion(tag_name, symbol, ext) => { + let temp_ext_var = helper!(ext); + + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + subs, + UnionTags::default(), + temp_ext_var, + ); + + for _ in it { + unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); + } + + let slice = SubsIndex::new(subs.tag_names.len() as u32); + subs.tag_names.push(tag_name.clone()); + + let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + RecursiveTagUnion(rec_var, tags, ext) => { + // An empty tags is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyTagUnion in canonicalization + debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let content = + Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); + + let tag_union_var = destination; + register_with_known_var(subs, tag_union_var, rank, pools, content); + + register_with_known_var( + subs, + *rec_var, + rank, + pools, + Content::RecursionVar { + opt_name: None, + structure: tag_union_var, + }, + ); + + tag_union_var + } + + Type::Alias { + symbol, + type_arguments, + actual, + lambda_set_variables, + kind, + } => { + debug_assert!(Variable::get_reserved(*symbol).is_none()); + + let alias_variables = { + let length = type_arguments.len() + lambda_set_variables.len(); + let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + + for (target_index, (_, arg_type)) in + (new_variables.indices()).zip(type_arguments) + { + let copy_var = helper!(arg_type); + subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(type_arguments.len())) + .zip(lambda_set_variables); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + all_variables_len: length as _, + } + }; + + let alias_variable = if let Symbol::RESULT_RESULT = *symbol { + roc_result_to_var(subs, rank, pools, arena, actual) + } else { + helper!(actual) + }; + let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); + + register_with_known_var(subs, destination, rank, pools, content) + } + HostExposedAlias { + name: symbol, + type_arguments, + actual: alias_type, + actual_var, + lambda_set_variables, + .. + } => { + let alias_variables = { + let length = type_arguments.len() + lambda_set_variables.len(); + let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + + for (target_index, (_, arg_type)) in + (new_variables.indices()).zip(type_arguments) + { + let copy_var = helper!(arg_type); + subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(type_arguments.len())) + .zip(lambda_set_variables); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + all_variables_len: length as _, + } + }; + + // cannot use helper! here because this variable may be involved in unification below + let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); + // TODO(opaques): I think host-exposed aliases should always be structural + // (when does it make sense to give a host an opaque type?) + let content = Content::Alias( + *symbol, + alias_variables, + alias_variable, + AliasKind::Structural, + ); + // let result = register(subs, rank, pools, content); + let result = register_with_known_var(subs, destination, rank, pools, content); + + // We only want to unify the actual_var with the alias once + // if it's already redirected (and therefore, redundant) + // don't do it again + if !subs.redundant(*actual_var) { + let descriptor = subs.get(result); + subs.union(result, *actual_var, descriptor); + } + + result + } + Erroneous(problem) => { + let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + + register_with_known_var(subs, destination, rank, pools, content) + } + }; } - let it = (new_variables.indices().skip(type_arguments.len())).zip(lambda_set_variables); - for (target_index, ls) in it { - let copy_var = type_to_variable(subs, rank, pools, arena, &ls.0); - subs.variables[target_index] = copy_var; - } - - AliasVariables { - variables_start: new_variables.start, - type_variables_len: type_arguments.len() as _, - all_variables_len: length as _, - } + result } #[inline(always)] @@ -2167,7 +2243,7 @@ fn register_with_known_var( rank: Rank, pools: &mut Pools, content: Content, -) { +) -> Variable { let descriptor = Descriptor { content, rank, @@ -2178,4 +2254,6 @@ fn register_with_known_var( subs.set(var, descriptor); pools.get_mut(rank).push(var); + + var } From a37a895016d3629b86651cbee6f41e2777851659 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:11:33 +0100 Subject: [PATCH 116/150] optimize tag name cache --- compiler/solve/src/solve.rs | 14 +++++------ compiler/types/src/subs.rs | 46 +++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2db56dbad6..2227dc41bf 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1321,16 +1321,16 @@ fn sort_and_deduplicate(tag_vars: &mut bumpalo::collections::Vec<(TagName, T) fn find_tag_name_run(slice: &[(TagName, T)], subs: &mut Subs) -> Option> { use std::cmp::Ordering; - let tag_name = slice.get(0)?.0.clone(); + let tag_name = &slice.get(0)?.0; let mut result = None; // the `SubsSlice` that inserting `slice` into subs would give let bigger_slice = SubsSlice::new(subs.tag_names.len() as _, slice.len() as _); - match subs.tag_name_cache.entry(tag_name) { - Entry::Occupied(mut occupied) => { - let subs_slice = *occupied.get(); + match subs.tag_name_cache.get_mut(tag_name) { + Some(occupied) => { + let subs_slice = *occupied; let prefix_slice = SubsSlice::new(subs_slice.start, slice.len() as _); @@ -1364,12 +1364,12 @@ fn find_tag_name_run(slice: &[(TagName, T)], subs: &mut Subs) -> Option { // switch to the bigger slice that is not inserted yet, but will be soon - occupied.insert(bigger_slice); + *occupied = bigger_slice; } } } - Entry::Vacant(vacant) => { - vacant.insert(bigger_slice); + None => { + subs.tag_name_cache.push(tag_name, bigger_slice); } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b636c1efa8..048d251e2f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -68,7 +68,49 @@ pub struct Subs { pub field_names: Vec, pub record_fields: Vec>, pub variable_slices: Vec, - pub tag_name_cache: MutMap>, + pub tag_name_cache: TagNameCache, +} + +#[derive(Debug, Clone, Default)] +pub struct TagNameCache { + globals: Vec, + globals_slices: Vec>, + /// Currently private tags and closure tags; in the future just closure tags + symbols: Vec, + symbols_slices: Vec>, +} + +impl TagNameCache { + pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice> { + match tag_name { + TagName::Global(uppercase) => { + // force into block + match self.globals.iter().position(|u| u == uppercase) { + Some(index) => Some(&mut self.globals_slices[index]), + None => None, + } + } + TagName::Private(symbol) | TagName::Closure(symbol) => { + match self.symbols.iter().position(|s| s == symbol) { + Some(index) => Some(&mut self.symbols_slices[index]), + None => None, + } + } + } + } + + pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice) { + match tag_name { + TagName::Global(uppercase) => { + self.globals.push(uppercase.clone()); + self.globals_slices.push(slice); + } + TagName::Private(symbol) | TagName::Closure(symbol) => { + self.symbols.push(*symbol); + self.symbols_slices.push(slice); + } + } + } } impl Default for Subs { @@ -1248,7 +1290,7 @@ impl Subs { // store an empty slice at the first position // used for "TagOrFunction" variable_slices: vec![VariableSubsSlice::default()], - tag_name_cache: MutMap::default(), + tag_name_cache: TagNameCache::default(), }; // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing From 592a5ace193c5ed30846747e1ac836051d4fd91c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:14:47 +0100 Subject: [PATCH 117/150] optimize recursive call --- compiler/solve/src/solve.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2227dc41bf..0056bc68a8 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1466,8 +1466,7 @@ fn type_to_union_tags<'a>( let sorted = tags.len() == 1 || sorted_no_duplicates(tags); if ext.is_empty_tag_union() { - let ext = type_to_variable(subs, rank, pools, arena, &Type::EmptyTagUnion); - // let ext = Variable::EMPTY_TAG_UNION; + let ext = Variable::EMPTY_TAG_UNION; let union_tags = if sorted { insert_tags_fast_path(subs, rank, pools, arena, tags) From 8b526e4f58feb72ef09072a6074c2a22a27fce97 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:54:19 +0100 Subject: [PATCH 118/150] clippy --- compiler/solve/src/solve.rs | 1 - compiler/types/src/subs.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 0056bc68a8..5b446d1435 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -17,7 +17,6 @@ use roc_types::types::{ gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory, }; use roc_unify::unify::{unify, Mode, Unified::*}; -use std::collections::hash_map::Entry; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 048d251e2f..52f9177e8f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1,5 +1,5 @@ use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt}; -use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_collections::all::{ImMap, ImSet, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::symbol::Symbol; use std::fmt; From eec92204f1d980e809f1e190ee9d8f88fc5ec689 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 15:13:39 +0100 Subject: [PATCH 119/150] optimize type_to_union_tags --- compiler/solve/src/solve.rs | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 5b446d1435..8574ccabc3 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1049,7 +1049,8 @@ fn type_to_variable<'a>( // If hit, try to turn the value into an EmptyTagUnion in canonicalization debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let (union_tags, ext) = + type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); register_with_known_var(subs, destination, rank, pools, content) @@ -1079,7 +1080,8 @@ fn type_to_variable<'a>( // If hit, try to turn the value into an EmptyTagUnion in canonicalization debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let (union_tags, ext) = + type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); let content = Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); @@ -1381,10 +1383,24 @@ fn insert_tags_fast_path<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> UnionTags { let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var + } + } + }}; + } + match find_tag_name_run(tags, subs) { Some(new_tag_names) => { let it = (new_variable_slices.indices()).zip(tags); @@ -1394,7 +1410,7 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = type_to_variable(subs, rank, pools, arena, argument); + let var = helper!(argument); subs.variables[target_index] = var; } @@ -1415,7 +1431,7 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = type_to_variable(subs, rank, pools, arena, argument); + let var = helper!(argument); subs.variables[target_index] = var; } @@ -1433,14 +1449,28 @@ fn insert_tags_slow_path<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> UnionTags { + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var + } + } + }}; + } + for (tag, tag_argument_types) in tags { let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); for (i, arg) in (new_slice.indices()).zip(tag_argument_types) { - let var = type_to_variable(subs, rank, pools, arena, arg); + let var = helper!(arg); subs.variables[i] = var; } @@ -1457,8 +1487,9 @@ fn type_to_union_tags<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], ext: &Type, + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; @@ -1468,10 +1499,10 @@ fn type_to_union_tags<'a>( let ext = Variable::EMPTY_TAG_UNION; let union_tags = if sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags) + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) } else { let tag_vars = Vec::with_capacity_in(tags.len(), arena); - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars) + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) }; (union_tags, ext) @@ -1485,9 +1516,9 @@ fn type_to_union_tags<'a>( tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); let union_tags = if tag_vars.is_empty() && sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags) + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) } else { - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars) + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) }; (union_tags, ext) From 0f5c339b4f5bd685e9a6ca7cfa21d30632a64fc5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 15:29:23 +0100 Subject: [PATCH 120/150] and roc_result_to_var --- compiler/solve/src/solve.rs | 82 ++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 8574ccabc3..b5550412e4 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -918,6 +918,25 @@ impl RegisterVariable { _ => Deferred, } } + + #[inline(always)] + fn with_stack<'a>( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &'_ bumpalo::Bump, + typ: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, + ) -> Variable { + match Self::from_type(subs, rank, pools, arena, typ) { + Self::Direct(var) => var, + Self::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer(typ, var)); + var + } + } + } } #[derive(Debug)] @@ -1137,7 +1156,7 @@ fn type_to_variable<'a>( }; let alias_variable = if let Symbol::RESULT_RESULT = *symbol { - roc_result_to_var(subs, rank, pools, arena, actual) + roc_result_to_var(subs, rank, pools, arena, actual, &mut stack) } else { helper!(actual) }; @@ -1217,8 +1236,9 @@ fn roc_result_to_var<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, - result_type: &Type, + arena: &'_ bumpalo::Bump, + result_type: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> Variable { match result_type { Type::TagUnion(tags, ext) => { @@ -1230,8 +1250,10 @@ fn roc_result_to_var<'a>( debug_assert_eq!(ok, &subs.tag_names[1]); if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) { - let err_var = type_to_variable(subs, rank, pools, arena, err_type); - let ok_var = type_to_variable(subs, rank, pools, arena, ok_type); + let err_var = + RegisterVariable::with_stack(subs, rank, pools, arena, err_type, stack); + let ok_var = + RegisterVariable::with_stack(subs, rank, pools, arena, ok_type, stack); let start = subs.variables.len() as u32; let err_slice = SubsSlice::new(start, 1); @@ -1382,25 +1404,12 @@ fn insert_tags_fast_path<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); - macro_rules! helper { - ($typ:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { - RegisterVariable::Direct(var) => var, - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - stack.push(TypeToVar::Defer($typ, var)); - var - } - } - }}; - } - match find_tag_name_run(tags, subs) { Some(new_tag_names) => { let it = (new_variable_slices.indices()).zip(tags); @@ -1410,7 +1419,8 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = helper!(argument); + let var = + RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; } @@ -1431,7 +1441,8 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = helper!(argument); + let var = + RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; } @@ -1448,29 +1459,16 @@ fn insert_tags_slow_path<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { - macro_rules! helper { - ($typ:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { - RegisterVariable::Direct(var) => var, - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - stack.push(TypeToVar::Defer($typ, var)); - var - } - } - }}; - } - for (tag, tag_argument_types) in tags { let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); for (i, arg) in (new_slice.indices()).zip(tag_argument_types) { - let var = helper!(arg); + let var = RegisterVariable::with_stack(subs, rank, pools, arena, arg, stack); subs.variables[i] = var; } @@ -1486,10 +1484,10 @@ fn type_to_union_tags<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - ext: &Type, - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + ext: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; @@ -1509,7 +1507,7 @@ fn type_to_union_tags<'a>( } else { let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); + let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); let (it, ext) = roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); From f55aa0c4b69c0681bf3dd5c372336218c698b186 Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Sun, 6 Mar 2022 09:47:21 -0500 Subject: [PATCH 121/150] Add Dec in repl test. --- repl_test/src/tests.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index d455af342d..a12fa174be 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1036,3 +1036,17 @@ fn opaque_pattern_and_call() { r#"Package {} A : F {} [ A ]*"#, ) } + +#[test] +fn dec_in_repl() { + expect_success( + indoc!( + r#" + x: Dec + x=1.23 + x + "# + ), + r#"1.23 : Dec"#, + ) +} From 6672a174f2b7cfefd838eb9afdce070907d77473 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 09:51:38 -0500 Subject: [PATCH 122/150] Mention lack of if clause in missing guard pattern --- reporting/src/error/mono.rs | 10 +++++++++- reporting/tests/test_reporting.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index 215bc3eeaf..86e6f7bf36 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -136,6 +136,8 @@ fn pattern_to_doc<'b>( pattern_to_doc_help(alloc, pattern, false) } +const AFTER_TAG_INDENT: &str = " "; + fn pattern_to_doc_help<'b>( alloc: &'b RocDocAllocator<'b>, pattern: roc_exhaustive::Pattern, @@ -168,7 +170,13 @@ fn pattern_to_doc_help<'b>( ); debug_assert!(args.len() == 2); let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); - tag.append(" (note the lack of a guard)") + alloc.concat(vec![ + tag, + alloc.text(AFTER_TAG_INDENT), + alloc.text("(note the lack of an "), + alloc.keyword("if"), + alloc.text(" clause)"), + ]) } RenderAs::Record(field_names) => { let mut arg_docs = Vec::with_capacity(args.len()); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index df31c1fafd..caf40202e7 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8508,7 +8508,7 @@ I need all branches in an `if` to have the same type! Other possibilities include: - A (note the lack of a guard) + A (note the lack of an if clause) I would have to crash if I saw one of those! Add branches for them! "# From 3bff99b0a23006d29e4113628cc61b292ae4cb1a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 10:53:12 -0500 Subject: [PATCH 123/150] Register accessor closures when they are bound Previously we only registered record accessor closures in anonymous contexts, where we assume they must already be specialized based on the surrounding contexts. This is not true in general since one might bind an accessor to a name. Closes #2567 --- compiler/can/src/expr.rs | 2 + compiler/constrain/src/expr.rs | 24 +++++- compiler/mono/src/ir.rs | 122 ++++++++++++++++++++++----- compiler/solve/tests/solve_expr.rs | 14 +++ compiler/test_gen/src/gen_records.rs | 18 +++- 5 files changed, 154 insertions(+), 26 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index dc672693ff..fee1904ac4 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -144,6 +144,7 @@ pub enum Expr { name: Symbol, function_var: Variable, record_var: Variable, + closure_var: Variable, closure_ext_var: Variable, ext_var: Variable, field_var: Variable, @@ -740,6 +741,7 @@ pub fn canonicalize_expr<'a>( function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), + closure_var: var_store.fresh(), closure_ext_var: var_store.fresh(), field_var: var_store.fresh(), field: (*field).into(), diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 0a97de4327..d064aa955a 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -769,7 +769,8 @@ pub fn constrain_expr( function_var, field, record_var, - closure_ext_var: closure_var, + closure_var, + closure_ext_var, ext_var, field_var, } => { @@ -795,16 +796,24 @@ pub fn constrain_expr( let lambda_set = Type::ClosureTag { name: *closure_name, - ext: *closure_var, + ext: *closure_ext_var, }; + let closure_type = Type::Variable(*closure_var); + let function_type = Type::Function( vec![record_type], - Box::new(lambda_set), + Box::new(closure_type.clone()), Box::new(field_type), ); let cons = [ + constraints.equal_types( + closure_type, + NoExpectation(lambda_set), + category.clone(), + region, + ), constraints.equal_types(function_type.clone(), expected, category.clone(), region), constraints.equal_types( function_type, @@ -816,7 +825,14 @@ pub fn constrain_expr( ]; constraints.exists_many( - [*record_var, *function_var, *closure_var, field_var, ext_var], + [ + *record_var, + *function_var, + *closure_var, + *closure_ext_var, + field_var, + ext_var, + ], cons, ) } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 559b9dae1f..f32541d591 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -816,6 +816,11 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { + dbg!(env + .subs + .get_content_without_compacting(annotation) + .clone() + .dbg(env.subs)); let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); @@ -3088,6 +3093,53 @@ fn try_make_literal<'a>( } } +fn accessor_to_closure<'a>( + env: &mut Env<'a, '_>, + name: Symbol, + function_var: Variable, + record_var: Variable, + closure_var: Variable, + closure_ext_var: Variable, + ext_var: Variable, + field_var: Variable, + field: Lowercase, +) -> ClosureData { + // IDEA: convert accessor fromt + // + // .foo + // + // into + // + // (\r -> r.foo) + let record_symbol = env.unique_symbol(); + let body = roc_can::expr::Expr::Access { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))), + field, + }; + + let loc_body = Loc::at_zero(body); + + let arguments = vec![( + record_var, + Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), + )]; + + ClosureData { + function_type: function_var, + closure_type: closure_var, + closure_ext_var, + return_type: field_var, + name, + captured_symbols: vec![], + recursive: roc_can::expr::Recursive::NotRecursive, + arguments, + loc_body: Box::new(loc_body), + } +} + pub fn with_hole<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -3886,40 +3938,36 @@ pub fn with_hole<'a>( name, function_var, record_var, - closure_ext_var: _, + closure_var, + closure_ext_var, ext_var, field_var, field, } => { - // IDEA: convert accessor fromt - // - // .foo - // - // into - // - // (\r -> r.foo) - let record_symbol = env.unique_symbol(); - let body = roc_can::expr::Expr::Access { + let ClosureData { + name, + function_type, + arguments, + loc_body, + .. + } = accessor_to_closure( + env, + name, + function_var, record_var, + closure_var, + closure_ext_var, ext_var, field_var, - loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))), field, - }; - - let loc_body = Loc::at_zero(body); - - let arguments = vec![( - record_var, - Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), - )]; + ); match procs.insert_anonymous( env, name, - function_var, + function_type, arguments, - loc_body, + *loc_body, CapturedSymbols::None, field_var, layout_cache, @@ -5445,6 +5493,38 @@ pub fn from_can<'a>( return from_can(env, variable, cont.value, procs, layout_cache); } + roc_can::expr::Expr::Accessor { + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + } => { + let closure_data = accessor_to_closure( + env, + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + ); + + register_noncapturing_closure( + env, + procs, + layout_cache, + *symbol, + closure_data, + ); + + return from_can(env, variable, cont.value, procs, layout_cache); + } roc_can::expr::Expr::Var(original) => { // a variable is aliased, e.g. // diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 881a4556e9..70b321afcd 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5529,4 +5529,18 @@ mod solve_expr { r#"a -> Effect a"#, ) } + + #[test] + fn generalized_accessor_function_applied() { + infer_eq_without_problem( + indoc!( + r#" + returnFoo = .foo + + returnFoo { foo: "foo" } + "# + ), + "Str", + ) + } } diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index 520db04b03..0a78ac2750 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -14,7 +14,7 @@ use crate::helpers::wasm::assert_evals_to; use indoc::indoc; #[cfg(test)] -use roc_std::RocList; +use roc_std::{RocList, RocStr}; #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] @@ -1058,3 +1058,19 @@ fn call_with_bad_record_runtime_error() { "# )) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn generalized_accessor() { + assert_evals_to!( + indoc!( + r#" + returnFoo = .foo + + returnFoo { foo: "foo" } + "# + ), + RocStr::from("foo"), + RocStr + ); +} From 0692caf7ba2cad118b5907262f3750804b0b6269 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 11:07:31 -0500 Subject: [PATCH 124/150] Consolidate `Accessor` data --- compiler/can/src/def.rs | 8 +-- compiler/can/src/expr.rs | 81 +++++++++++++++++++++----- compiler/constrain/src/expr.rs | 6 +- compiler/mono/src/ir.rs | 101 +++------------------------------ 4 files changed, 83 insertions(+), 113 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1b5108dfe9..fad03f9099 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1085,7 +1085,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let Pattern::Identifier(symbol) = loc_can_pattern.value { - if let &Closure(ClosureData { + if let Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1095,7 +1095,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, ref captured_symbols, .. - }) = &loc_can_expr.value + }) = loc_can_expr.value { // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) @@ -1225,7 +1225,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let Pattern::Identifier(symbol) = loc_can_pattern.value { - if let &Closure(ClosureData { + if let Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1235,7 +1235,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, ref captured_symbols, .. - }) = &loc_can_expr.value + }) = loc_can_expr.value { // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index fee1904ac4..8a54994f68 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -138,18 +138,7 @@ pub enum Expr { field: Lowercase, }, /// field accessor as a function, e.g. (.foo) expr - Accessor { - /// accessors are desugared to closures; they need to have a name - /// so the closure can have a correct lambda set - name: Symbol, - function_var: Variable, - record_var: Variable, - closure_var: Variable, - closure_ext_var: Variable, - ext_var: Variable, - field_var: Variable, - field: Lowercase, - }, + Accessor(AccessorData), Update { record_var: Variable, @@ -218,6 +207,70 @@ pub struct ClosureData { pub loc_body: Box>, } +/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo` +/// 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)] +pub struct AccessorData { + pub name: Symbol, + pub function_var: Variable, + pub record_var: Variable, + pub closure_var: Variable, + pub closure_ext_var: Variable, + pub ext_var: Variable, + pub field_var: Variable, + pub field: Lowercase, +} + +impl AccessorData { + pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData { + let AccessorData { + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + } = self; + + // IDEA: convert accessor from + // + // .foo + // + // into + // + // (\r -> r.foo) + let body = Expr::Access { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol))), + field, + }; + + let loc_body = Loc::at_zero(body); + + let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))]; + + ClosureData { + function_type: function_var, + closure_type: closure_var, + closure_ext_var, + return_type: field_var, + name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(loc_body), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Field { pub var: Variable, @@ -736,7 +789,7 @@ pub fn canonicalize_expr<'a>( ) } ast::Expr::AccessorFunction(field) => ( - Accessor { + Accessor(AccessorData { name: env.gen_unique_symbol(), function_var: var_store.fresh(), record_var: var_store.fresh(), @@ -745,7 +798,7 @@ pub fn canonicalize_expr<'a>( closure_ext_var: var_store.fresh(), field_var: var_store.fresh(), field: (*field).into(), - }, + }), Output::default(), ), ast::Expr::GlobalTag(tag) => { diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index d064aa955a..2913b5276a 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -8,7 +8,7 @@ use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{ClosureData, Field, WhenBranch}; +use roc_can::expr::{AccessorData, ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; @@ -764,7 +764,7 @@ pub fn constrain_expr( [constraint, eq, record_con], ) } - Accessor { + Accessor(AccessorData { name: closure_name, function_var, field, @@ -773,7 +773,7 @@ pub fn constrain_expr( closure_ext_var, ext_var, field_var, - } => { + }) => { let ext_var = *ext_var; let ext_type = Variable(ext_var); let field_var = *field_var; diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index f32541d591..b352c0468e 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3093,53 +3093,6 @@ fn try_make_literal<'a>( } } -fn accessor_to_closure<'a>( - env: &mut Env<'a, '_>, - name: Symbol, - function_var: Variable, - record_var: Variable, - closure_var: Variable, - closure_ext_var: Variable, - ext_var: Variable, - field_var: Variable, - field: Lowercase, -) -> ClosureData { - // IDEA: convert accessor fromt - // - // .foo - // - // into - // - // (\r -> r.foo) - let record_symbol = env.unique_symbol(); - let body = roc_can::expr::Expr::Access { - record_var, - ext_var, - field_var, - loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))), - field, - }; - - let loc_body = Loc::at_zero(body); - - let arguments = vec![( - record_var, - Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), - )]; - - ClosureData { - function_type: function_var, - closure_type: closure_var, - closure_ext_var, - return_type: field_var, - name, - captured_symbols: vec![], - recursive: roc_can::expr::Recursive::NotRecursive, - arguments, - loc_body: Box::new(loc_body), - } -} - pub fn with_hole<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -3934,33 +3887,17 @@ pub fn with_hole<'a>( stmt } - Accessor { - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - } => { + Accessor(accessor_data) => { + let field_var = accessor_data.field_var; + let fresh_record_symbol = env.unique_symbol(); + let ClosureData { name, function_type, arguments, loc_body, .. - } = accessor_to_closure( - env, - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - ); + } = accessor_data.to_closure_data(fresh_record_symbol); match procs.insert_anonymous( env, @@ -3975,7 +3912,7 @@ pub fn with_hole<'a>( Ok(_) => { let raw_layout = return_on_layout_error!( env, - layout_cache.raw_from_var(env.arena, function_var, env.subs) + layout_cache.raw_from_var(env.arena, function_type, env.subs) ); match raw_layout { @@ -5493,34 +5430,14 @@ pub fn from_can<'a>( return from_can(env, variable, cont.value, procs, layout_cache); } - roc_can::expr::Expr::Accessor { - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - } => { - let closure_data = accessor_to_closure( - env, - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - ); - + roc_can::expr::Expr::Accessor(accessor_data) => { + let fresh_record_symbol = env.unique_symbol(); register_noncapturing_closure( env, procs, layout_cache, *symbol, - closure_data, + accessor_data.to_closure_data(fresh_record_symbol), ); return from_can(env, variable, cont.value, procs, layout_cache); From 8f6f20b57deb43428be1a3d747d0601ed5ed8da1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Mar 2022 11:08:59 -0500 Subject: [PATCH 125/150] Avoid reallocation when ensuring trailing newline --- cli/src/format.rs | 3 +-- compiler/fmt/src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index e8f4ce688f..d14fa95ac0 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -181,8 +181,7 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) { fmt_def(buf, arena.alloc(def.value), 0); } - buf.trim_end(); - buf.newline(); + buf.fmt_end_of_file(); } /// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index 04ac68e720..9f54990955 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -91,9 +91,49 @@ impl<'a> Buf<'a> { } } - pub fn trim_end(&mut self) { - while self.text.ends_with(char::is_whitespace) { - self.text.truncate(self.text.len() - 1); + /// Ensures the text ends in a newline with no whitespace preceding it. + pub fn fmt_end_of_file(&mut self) { + fmt_text_eof(&mut self.text) + } +} + +/// Ensures the text ends in a newline with no whitespace preceding it. +fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) { + let mut chars_rev = text.chars().rev(); + let mut last_whitespace = None; + let mut last_whitespace_index = text.len(); + + // Keep going until we either run out of characters or encounter one + // that isn't whitespace. + loop { + match chars_rev.next() { + Some(ch) if ch.is_whitespace() => { + last_whitespace = Some(ch); + last_whitespace_index -= 1; + } + _ => { + break; + } + } + } + + match last_whitespace { + Some('\n') => { + // There may have been more whitespace after this newline; remove it! + text.truncate(last_whitespace_index + 1); + } + Some(_) => { + // There's some whitespace at the end of this file, but the first + // whitespace char after the last non-whitespace char isn't a newline. + // So replace that whitespace char (and everything after it) with a newline. + text.replace_range(last_whitespace_index.., "\n"); + } + None => { + debug_assert!(last_whitespace_index == text.len()); + debug_assert!(!text.ends_with(char::is_whitespace)); + + // This doesn't end in whitespace at all, so add a newline. + text.push('\n'); } } } From 49fcd03941deccbca6238801c97d7659ad5bb14f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Mar 2022 11:09:49 -0500 Subject: [PATCH 126/150] Test trailing newline logic --- compiler/fmt/src/lib.rs | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index 9f54990955..cea8f97438 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -137,3 +137,69 @@ fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) { } } } + +#[test] +fn eof_text_ends_with_newline() { + use bumpalo::{collections::String, Bump}; + + let arena = Bump::new(); + let input = "This should be a newline:\n"; + let mut text = String::from_str_in(input, &arena); + + fmt_text_eof(&mut text); + + // This should be unchanged! + assert_eq!(text.as_str(), input); +} + +#[test] +fn eof_text_ends_with_whitespace() { + use bumpalo::{collections::String, Bump}; + + let arena = Bump::new(); + let input = "This should be a newline: \t"; + let mut text = String::from_str_in(input, &arena); + + fmt_text_eof(&mut text); + + assert_eq!(text.as_str(), "This should be a newline:\n"); +} + +#[test] +fn eof_text_ends_with_whitespace_then_newline() { + use bumpalo::{collections::String, Bump}; + + let arena = Bump::new(); + let input = "This should be a newline: \n"; + let mut text = String::from_str_in(input, &arena); + + fmt_text_eof(&mut text); + + assert_eq!(text.as_str(), "This should be a newline:\n"); +} + +#[test] +fn eof_text_ends_with_no_whitespace() { + use bumpalo::{collections::String, Bump}; + + let arena = Bump::new(); + let input = "This should be a newline:"; + let mut text = String::from_str_in(input, &arena); + + fmt_text_eof(&mut text); + + assert_eq!(text.as_str(), "This should be a newline:\n"); +} + +#[test] +fn eof_text_is_empty() { + use bumpalo::{collections::String, Bump}; + + let arena = Bump::new(); + let input = ""; + let mut text = String::from_str_in(input, &arena); + + fmt_text_eof(&mut text); + + assert_eq!(text.as_str(), "\n"); +} From cc8c94576236f4cbb7234fef783ea54140ca964b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 11:16:03 -0500 Subject: [PATCH 127/150] Remove stray dbg --- compiler/mono/src/ir.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index b352c0468e..67fd1b3432 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -816,11 +816,6 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { - dbg!(env - .subs - .get_content_without_compacting(annotation) - .clone() - .dbg(env.subs)); let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); From a8eff47f8780363aa944508381943cac46513ef4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Mar 2022 11:26:38 -0500 Subject: [PATCH 128/150] Use len_utf8 over hardcoded 1 This will compile to the same thing but makes the reason for the addition more self-documenting. --- compiler/fmt/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index cea8f97438..c7e8b6ca7a 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -120,7 +120,7 @@ fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) { match last_whitespace { Some('\n') => { // There may have been more whitespace after this newline; remove it! - text.truncate(last_whitespace_index + 1); + text.truncate(last_whitespace_index + '\n'.len_utf8()); } Some(_) => { // There's some whitespace at the end of this file, but the first From 1c9e0c57bf68622ccce9c113da6f47f46f6bd734 Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Sun, 6 Mar 2022 11:52:25 -0500 Subject: [PATCH 129/150] Make RocDecs display properly in webrepl. --- examples/cli/platform/Cargo.lock | 9 +++++++++ examples/false-interpreter/platform/Cargo.lock | 9 +++++++++ examples/gui/platform/Cargo.lock | 11 ++++++++++- examples/hello-rust/platform/Cargo.lock | 9 +++++++++ roc_std/src/lib.rs | 17 ++++++++++------- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/examples/cli/platform/Cargo.lock b/examples/cli/platform/Cargo.lock index f61398f7b6..39d14572e3 100644 --- a/examples/cli/platform/Cargo.lock +++ b/examples/cli/platform/Cargo.lock @@ -19,3 +19,12 @@ checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" [[package]] name = "roc_std" version = "0.1.0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" diff --git a/examples/false-interpreter/platform/Cargo.lock b/examples/false-interpreter/platform/Cargo.lock index cfd1e1e09d..9132862791 100644 --- a/examples/false-interpreter/platform/Cargo.lock +++ b/examples/false-interpreter/platform/Cargo.lock @@ -19,3 +19,12 @@ checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" [[package]] name = "roc_std" version = "0.1.0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" diff --git a/examples/gui/platform/Cargo.lock b/examples/gui/platform/Cargo.lock index 9ce6ea7d8d..8f8005c26d 100644 --- a/examples/gui/platform/Cargo.lock +++ b/examples/gui/platform/Cargo.lock @@ -2090,6 +2090,9 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" [[package]] name = "roc_std" version = "0.1.0" +dependencies = [ + "static_assertions 0.1.1", +] [[package]] name = "rodio" @@ -2292,6 +2295,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2403,7 +2412,7 @@ checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ "cfg-if 1.0.0", "rand", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] diff --git a/examples/hello-rust/platform/Cargo.lock b/examples/hello-rust/platform/Cargo.lock index b113b7e1cd..ebd0a436cf 100644 --- a/examples/hello-rust/platform/Cargo.lock +++ b/examples/hello-rust/platform/Cargo.lock @@ -19,3 +19,12 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" [[package]] name = "roc_std" version = "0.1.0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 6c25602f63..4cbef19c7b 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -281,10 +281,10 @@ impl RocDec { Self::from_str(val).unwrap().0 } - fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) { + fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) -> usize { if self.0 == 0 { write!(&mut bytes[..], "{}", "0").unwrap(); - return; + return 0; } let is_negative = (self.0 < 0) as usize; @@ -318,9 +318,10 @@ impl RocDec { if i < decimal_location { // This means that we've removed trailing zeros and are left with an integer. Our // convention is to print these without a decimal point or trailing zeros, so we're done. - return; + return i; } + let ret = i + 1; while i >= decimal_location { bytes[i + 1] = bytes[i]; i = i - 1; @@ -330,20 +331,22 @@ impl RocDec { bytes[decimal_location] = '.' as u8; // Finally bytes = b"1234.5678" + + ret } pub fn to_str(&self) -> RocStr { let mut bytes = [0 as u8; Self::MAX_STR_LENGTH]; - self.to_str_helper(&mut bytes); - unsafe { RocStr::from_slice(&bytes) } + let last_idx = self.to_str_helper(&mut bytes); + unsafe { RocStr::from_slice(&bytes[0..last_idx]) } } } impl fmt::Display for RocDec { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { let mut bytes = [0 as u8; Self::MAX_STR_LENGTH]; - self.to_str_helper(&mut bytes); - let result = unsafe { str::from_utf8_unchecked(&bytes) }; + let last_idx = self.to_str_helper(&mut bytes); + let result = unsafe { str::from_utf8_unchecked(&bytes[0..last_idx]) }; write!(fmtr, "{}", result) } } From 41e56519efef508cbbbbfe3b1118a8161f26382d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 18:00:25 +0100 Subject: [PATCH 130/150] prevent a subs lookup by reserving a Variable --- compiler/solve/src/solve.rs | 26 ++++++++++++++------------ compiler/types/src/subs.rs | 4 ++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 8854c64295..98a082bf16 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1928,7 +1928,8 @@ fn deep_copy_var_help( use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; - let desc = subs.get_without_compacting(var); + let subs_len = subs.len(); + let desc = subs.get_ref_mut(var); if let Some(copy) = desc.copy.into_variable() { return copy; @@ -1945,8 +1946,13 @@ fn deep_copy_var_help( copy: OptVariable::NONE, }; - let content = desc.content; - let copy = subs.fresh(make_descriptor(content.clone())); + let content = desc.content.clone(); + + // Safety: Here we make a variable that is 1 position out of bounds. + // The reason is that we can now keep the mutable reference to `desc` + // Below, we actually push a new variable onto subs meaning the `copy` + // variable is in-bounds before it is ever used. + let copy = unsafe { Variable::from_index(subs_len as u32) }; pools.get_mut(max_rank).push(copy); @@ -1954,15 +1960,11 @@ fn deep_copy_var_help( // avoid making multiple copies of the variable we are instantiating. // // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); + desc.mark = Mark::NONE; + desc.copy = copy.into(); + + let actual_copy = subs.fresh(make_descriptor(content.clone())); + debug_assert_eq!(copy, actual_copy); // Now we recursively copy the content of the variable. // We have already marked the variable as copied, so we diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 36b937be02..e17a808bcc 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -736,8 +736,8 @@ impl Variable { /// # Safety /// - /// This should only ever be called from tests! - pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self { + /// It is not guaranteed that the variable is in bounds. + pub unsafe fn from_index(v: u32) -> Self { debug_assert!(v >= Self::NUM_RESERVED_VARS as u32); Variable(v) } From 29bf4195a1988c86cb453998f2c74ceef4acce08 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 18:08:32 +0100 Subject: [PATCH 131/150] make FlatType Copy --- compiler/solve/src/solve.rs | 3 ++- compiler/types/src/solved_types.rs | 5 +++- compiler/types/src/subs.rs | 43 ++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 98a082bf16..5ee4a73e8c 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1091,7 +1091,8 @@ fn type_to_variable<'a>( result } Erroneous(problem) => { - let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + let problem_index = SubsIndex::push_new(&mut subs.problems, problem.clone()); + let content = Content::Structure(FlatType::Erroneous(problem_index)); register(subs, rank, pools, content) } diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 472b1fbd0f..5dcb705602 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -401,7 +401,10 @@ impl SolvedType { } EmptyRecord => SolvedType::EmptyRecord, EmptyTagUnion => SolvedType::EmptyTagUnion, - Erroneous(problem) => SolvedType::Erroneous(*problem.clone()), + Erroneous(problem_index) => { + let problem = subs.problems[problem_index.index as usize].clone(); + SolvedType::Erroneous(problem) + } } } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index e17a808bcc..15db3b78e1 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -69,6 +69,7 @@ pub struct Subs { pub record_fields: Vec>, pub variable_slices: Vec, pub tag_name_cache: MutMap>, + pub problems: Vec, } impl Default for Subs { @@ -284,6 +285,14 @@ impl SubsIndex { _marker: std::marker::PhantomData, } } + + pub fn push_new(vector: &mut Vec, value: T) -> Self { + let index = Self::new(vector.len() as _); + + vector.push(value); + + index + } } impl IntoIterator for SubsSlice { @@ -1241,14 +1250,15 @@ impl Subs { let mut subs = Subs { utable: UnificationTable::default(), - variables: Default::default(), + variables: Vec::new(), tag_names, - field_names: Default::default(), - record_fields: Default::default(), + field_names: Vec::new(), + record_fields: Vec::new(), // store an empty slice at the first position // used for "TagOrFunction" variable_slices: vec![VariableSubsSlice::default()], tag_name_cache: MutMap::default(), + problems: Vec::new(), }; // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing @@ -1817,7 +1827,7 @@ impl Content { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum FlatType { Apply(Symbol, VariableSubsSlice), Func(VariableSubsSlice, Variable, Variable), @@ -1825,7 +1835,7 @@ pub enum FlatType { TagUnion(UnionTags, Variable), FunctionOrTagUnion(SubsIndex, Symbol, Variable), RecursiveTagUnion(Variable, UnionTags, Variable), - Erroneous(Box), + Erroneous(SubsIndex), EmptyRecord, EmptyTagUnion, } @@ -3140,8 +3150,9 @@ fn flat_type_to_err_type( } } - Erroneous(problem) => { - state.problems.push(*problem); + Erroneous(problem_index) => { + let problem = subs.problems[problem_index.index as usize].clone(); + state.problems.push(problem); ErrorType::Error } @@ -3255,6 +3266,7 @@ struct StorageSubsOffsets { field_names: u32, record_fields: u32, variable_slices: u32, + problems: u32, } impl StorageSubs { @@ -3274,6 +3286,7 @@ impl StorageSubs { field_names: self.subs.field_names.len() as u32, record_fields: self.subs.record_fields.len() as u32, variable_slices: self.subs.variable_slices.len() as u32, + problems: self.subs.problems.len() as u32, }; let offsets = StorageSubsOffsets { @@ -3283,6 +3296,7 @@ impl StorageSubs { field_names: target.field_names.len() as u32, record_fields: target.record_fields.len() as u32, variable_slices: target.variable_slices.len() as u32, + problems: target.problems.len() as u32, }; // The first Variable::NUM_RESERVED_VARS are the same in every subs, @@ -3327,6 +3341,7 @@ impl StorageSubs { target.tag_names.extend(self.subs.tag_names); target.field_names.extend(self.subs.field_names); target.record_fields.extend(self.subs.record_fields); + target.problems.extend(self.subs.problems); debug_assert_eq!( target.utable.len(), @@ -3343,6 +3358,7 @@ impl StorageSubs { Self::offset_variable(&offsets, v) } } + fn offset_flat_type(offsets: &StorageSubsOffsets, flat_type: &FlatType) -> FlatType { match flat_type { FlatType::Apply(symbol, arguments) => { @@ -3371,7 +3387,9 @@ impl StorageSubs { Self::offset_union_tags(offsets, *union_tags), Self::offset_variable(offsets, *ext), ), - FlatType::Erroneous(problem) => FlatType::Erroneous(problem.clone()), + FlatType::Erroneous(problem) => { + FlatType::Erroneous(Self::offset_problem(offsets, *problem)) + } FlatType::EmptyRecord => FlatType::EmptyRecord, FlatType::EmptyTagUnion => FlatType::EmptyTagUnion, } @@ -3458,6 +3476,15 @@ impl StorageSubs { slice } + + fn offset_problem( + offsets: &StorageSubsOffsets, + mut problem_index: SubsIndex, + ) -> SubsIndex { + problem_index.index += offsets.problems; + + problem_index + } } use std::cell::RefCell; From 1ff8261a667848393a258deac6812dfa509198be Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 18:51:23 +0100 Subject: [PATCH 132/150] make Content Copy --- compiler/types/src/pretty_print.rs | 40 +++++++++++----- compiler/types/src/solved_types.rs | 5 +- compiler/types/src/subs.rs | 76 ++++++++++++++++-------------- compiler/unify/src/unify.rs | 15 ++++-- 4 files changed, 83 insertions(+), 53 deletions(-) diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 6824f4abd0..ce6b338282 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -1,4 +1,6 @@ -use crate::subs::{AliasVariables, Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; +use crate::subs::{ + AliasVariables, Content, FlatType, GetSubsSlice, Subs, SubsIndex, UnionTags, Variable, +}; use crate::types::{name_type_var, RecordField}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; @@ -134,20 +136,22 @@ fn find_names_needed( } } RecursionVar { - opt_name: Some(name), + opt_name: Some(name_index), .. } - | FlexVar(Some(name)) => { + | FlexVar(Some(name_index)) => { // This root already has a name. Nothing more to do here! // User-defined names are already taken. // We must not accidentally generate names that collide with them! - names_taken.insert(name.clone()); + let name = subs.field_names[name_index.index as usize].clone(); + names_taken.insert(name); } - RigidVar(name) => { + RigidVar(name_index) => { // User-defined names are already taken. // We must not accidentally generate names that collide with them! - names_taken.insert(name.clone()); + let name = subs.field_names[name_index.index as usize].clone(); + names_taken.insert(name); } Structure(Apply(_, args)) => { for index in args.into_iter() { @@ -257,16 +261,19 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { match old_content { FlexVar(None) => { - let content = FlexVar(Some(name)); + let name_index = SubsIndex::push_new(&mut subs.field_names, name); + let content = FlexVar(Some(name_index)); subs.set_content(root, content); } RecursionVar { opt_name: None, structure, } => { + let structure = *structure; + let name_index = SubsIndex::push_new(&mut subs.field_names, name); let content = RecursionVar { - structure: *structure, - opt_name: Some(name), + structure, + opt_name: Some(name_index), }; subs.set_content(root, content); } @@ -311,11 +318,20 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa use crate::subs::Content::*; match content { - FlexVar(Some(name)) => buf.push_str(name.as_str()), + FlexVar(Some(name_index)) => { + let name = &subs.field_names[name_index.index as usize]; + buf.push_str(name.as_str()) + } FlexVar(None) => buf.push_str(WILDCARD), - RigidVar(name) => buf.push_str(name.as_str()), + RigidVar(name_index) => { + let name = &subs.field_names[name_index.index as usize]; + buf.push_str(name.as_str()) + } RecursionVar { opt_name, .. } => match opt_name { - Some(name) => buf.push_str(name.as_str()), + Some(name_index) => { + let name = &subs.field_names[name_index.index as usize]; + buf.push_str(name.as_str()) + } None => buf.push_str(WILDCARD), }, Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens), diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 5dcb705602..076be203d3 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -257,7 +257,10 @@ impl SolvedType { // TODO should there be a SolvedType RecursionVar variant? Self::from_var_help(subs, recursion_vars, *structure) } - RigidVar(name) => SolvedType::Rigid(name.clone()), + RigidVar(name_index) => { + let name = &subs.field_names[name_index.index as usize]; + SolvedType::Rigid(name.clone()) + } Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type), Alias(symbol, args, actual_var, kind) => { let mut new_args = Vec::with_capacity(args.len()); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 15db3b78e1..8d18f73cde 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1334,7 +1334,8 @@ impl Subs { } pub fn rigid_var(&mut self, var: Variable, name: Lowercase) { - let content = Content::RigidVar(name); + let name_index = SubsIndex::push_new(&mut self.field_names, name); + let content = Content::RigidVar(name_index); let desc = Descriptor::from(content); self.set(var, desc); @@ -1691,19 +1692,19 @@ roc_error_macros::assert_sizeof_aarch64!((Variable, Option), 4 * 8); roc_error_macros::assert_sizeof_wasm!((Variable, Option), 4 * 4); roc_error_macros::assert_sizeof_default!((Variable, Option), 4 * 8); -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum Content { /// A type variable which the user did not name in an annotation, /// /// When we auto-generate a type var name, e.g. the "a" in (a -> a), we /// change the Option in here from None to Some. - FlexVar(Option), + FlexVar(Option>), /// name given in a user-written annotation - RigidVar(Lowercase), + RigidVar(SubsIndex), /// name given to a recursion variable RecursionVar { structure: Variable, - opt_name: Option, + opt_name: Option>, }, Structure(FlatType), Alias(Symbol, AliasVariables, Variable, AliasKind), @@ -2701,18 +2702,23 @@ fn get_var_names( match desc.content { Error | FlexVar(None) => taken_names, - FlexVar(Some(name)) => { - add_name(subs, 0, name, var, |name| FlexVar(Some(name)), taken_names) - } + FlexVar(Some(name_index)) => add_name( + subs, + 0, + name_index, + var, + |name| FlexVar(Some(name)), + taken_names, + ), RecursionVar { opt_name, structure, } => match opt_name { - Some(name) => add_name( + Some(name_index) => add_name( subs, 0, - name, + name_index, var, |name| RecursionVar { opt_name: Some(name), @@ -2723,7 +2729,7 @@ fn get_var_names( None => taken_names, }, - RigidVar(name) => add_name(subs, 0, name, var, RigidVar, taken_names), + RigidVar(name_index) => add_name(subs, 0, name_index, var, RigidVar, taken_names), Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| { get_var_names(subs, subs[arg_var], answer) @@ -2813,14 +2819,16 @@ fn get_var_names( fn add_name( subs: &mut Subs, index: usize, - given_name: Lowercase, + given_name_index: SubsIndex, var: Variable, content_from_name: F, taken_names: ImMap, ) -> ImMap where - F: FnOnce(Lowercase) -> Content, + F: FnOnce(SubsIndex) -> Content, { + let given_name = subs.field_names[given_name_index.index as usize].clone(); + let indexed_name = if index == 0 { given_name.clone() } else { @@ -2832,7 +2840,9 @@ where match taken_names.get(&indexed_name) { None => { if indexed_name != given_name { - subs.set_content(var, content_from_name(indexed_name.clone())); + let indexed_name_index = + SubsIndex::push_new(&mut subs.field_names, indexed_name.clone()); + subs.set_content(var, content_from_name(indexed_name_index)); } let mut answer = taken_names.clone(); @@ -2848,7 +2858,7 @@ where add_name( subs, index + 1, - given_name, + given_name_index, var, content_from_name, taken_names, @@ -2859,26 +2869,13 @@ where } fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType { - let mut desc = subs.get(var); + let desc = subs.get(var); if desc.mark == Mark::OCCURS { ErrorType::Infinite } else { subs.set_mark(var, Mark::OCCURS); - if false { - // useful for debugging - match desc.content { - Content::FlexVar(_) => { - desc.content = Content::FlexVar(Some(format!("{:?}", var).into())); - } - Content::RigidVar(_) => { - desc.content = Content::RigidVar(format!("{:?}", var).into()); - } - _ => {} - } - } - let err_type = content_to_err_type(subs, state, var, desc.content); subs.set_mark(var, desc.mark); @@ -2898,15 +2895,20 @@ fn content_to_err_type( match content { Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type), - FlexVar(Some(name)) => ErrorType::FlexVar(name), + FlexVar(Some(name_index)) => { + let name = subs.field_names[name_index.index as usize].clone(); + ErrorType::FlexVar(name) + } FlexVar(opt_name) => { let name = match opt_name { - Some(name) => name, + Some(name_index) => subs.field_names[name_index.index as usize].clone(), None => { + // set the name so when this variable occurs elsewhere in the type it gets the same name let name = get_fresh_var_name(state); + let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone()); - subs.set_content(var, FlexVar(Some(name.clone()))); + subs.set_content(var, FlexVar(Some(name_index))); name } @@ -2915,15 +2917,19 @@ fn content_to_err_type( ErrorType::FlexVar(name) } - RigidVar(name) => ErrorType::RigidVar(name), + RigidVar(name_index) => { + let name = subs.field_names[name_index.index as usize].clone(); + ErrorType::RigidVar(name) + } RecursionVar { opt_name, .. } => { let name = match opt_name { - Some(name) => name, + Some(name_index) => subs.field_names[name_index.index as usize].clone(), None => { let name = get_fresh_var_name(state); + let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone()); - subs.set_content(var, FlexVar(Some(name.clone()))); + subs.set_content(var, FlexVar(Some(name_index))); name } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 957b7537ff..8d70b8b121 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1351,11 +1351,16 @@ fn unify_zip_slices( } #[inline(always)] -fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content) -> Outcome { +fn unify_rigid( + subs: &mut Subs, + ctx: &Context, + name: &SubsIndex, + other: &Content, +) -> Outcome { match other { FlexVar(_) => { // If the other is flex, rigid wins! - merge(subs, ctx, RigidVar(name.clone())) + merge(subs, ctx, RigidVar(*name)) } RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => { if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { @@ -1378,13 +1383,13 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content fn unify_flex( subs: &mut Subs, ctx: &Context, - opt_name: &Option, + opt_name: &Option>, other: &Content, ) -> Outcome { match other { FlexVar(None) => { // If both are flex, and only left has a name, keep the name around. - merge(subs, ctx, FlexVar(opt_name.clone())) + merge(subs, ctx, FlexVar(*opt_name)) } FlexVar(Some(_)) @@ -1408,7 +1413,7 @@ fn unify_recursion( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - opt_name: &Option, + opt_name: &Option>, structure: Variable, other: &Content, ) -> Outcome { From a9c8e2dc3e0e351608276243f01bae45a14c1b23 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 19:07:18 +0100 Subject: [PATCH 133/150] copy instead of clone Content --- compiler/mono/src/layout.rs | 8 ++++---- compiler/solve/src/solve.rs | 6 +++--- compiler/types/src/subs.rs | 6 +++--- compiler/unify/src/unify.rs | 18 +++++++++--------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 693f26fb0b..ce9a1a16fd 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -74,7 +74,7 @@ impl<'a> RawFunctionLayout<'a> { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), RecursionVar { structure, .. } => { let structure_content = env.subs.get_content_without_compacting(structure); - Self::new_help(env, structure, structure_content.clone()) + Self::new_help(env, structure, *structure_content) } Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), RangedNumber(typ, _) => Self::from_var(env, typ), @@ -207,7 +207,7 @@ impl<'a> RawFunctionLayout<'a> { unreachable!("The initial variable of a signature cannot be seen already") } else { let content = env.subs.get_content_without_compacting(var); - Self::new_help(env, var, content.clone()) + Self::new_help(env, var, *content) } } } @@ -928,7 +928,7 @@ impl<'a> Layout<'a> { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), RecursionVar { structure, .. } => { let structure_content = env.subs.get_content_without_compacting(structure); - Self::new_help(env, structure, structure_content.clone()) + Self::new_help(env, structure, *structure_content) } Structure(flat_type) => layout_from_flat_type(env, flat_type), @@ -968,7 +968,7 @@ impl<'a> Layout<'a> { Ok(Layout::RecursivePointer) } else { let content = env.subs.get_content_without_compacting(var); - Self::new_help(env, var, content.clone()) + Self::new_help(env, var, *content) } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 5ee4a73e8c..33b236ef63 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1791,7 +1791,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { match &desc.content { RigidVar(name) => { // what it's all about: convert the rigid var into a flex var - let name = name.clone(); + let name = *name; // NOTE: we must write to the mutually borrowed `desc` value here // using `subs.set` does not work (unclear why, really) @@ -1947,7 +1947,7 @@ fn deep_copy_var_help( copy: OptVariable::NONE, }; - let content = desc.content.clone(); + let content = desc.content; // Safety: Here we make a variable that is 1 position out of bounds. // The reason is that we can now keep the mutable reference to `desc` @@ -1964,7 +1964,7 @@ fn deep_copy_var_help( desc.mark = Mark::NONE; desc.copy = copy.into(); - let actual_copy = subs.fresh(make_descriptor(content.clone())); + let actual_copy = subs.fresh(make_descriptor(content)); debug_assert_eq!(copy, actual_copy); // Now we recursively copy the content of the variable. diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 8d18f73cde..97283ae872 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -3405,14 +3405,14 @@ impl StorageSubs { use Content::*; match content { - FlexVar(opt_name) => FlexVar(opt_name.clone()), - RigidVar(name) => RigidVar(name.clone()), + FlexVar(opt_name) => FlexVar(*opt_name), + RigidVar(name) => RigidVar(*name), RecursionVar { structure, opt_name, } => RecursionVar { structure: Self::offset_variable(offsets, *structure), - opt_name: opt_name.clone(), + opt_name: *opt_name, }, Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)), Alias(symbol, alias_variables, actual, kind) => Alias( diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 8d70b8b121..88d0e0da98 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -331,7 +331,7 @@ fn unify_alias( } if problems.is_empty() { - problems.extend(merge(subs, ctx, other_content.clone())); + problems.extend(merge(subs, ctx, *other_content)); } // if problems.is_empty() { problems.extend(unify_pool(subs, pool, real_var, *other_real_var)); } @@ -374,7 +374,7 @@ fn unify_structure( match other { FlexVar(_) => { // If the other is flex, Structure wins! - let outcome = merge(subs, ctx, Structure(flat_type.clone())); + let outcome = merge(subs, ctx, Structure(*flat_type)); // And if we see a flex variable on the right hand side of a presence // constraint, we know we need to open up the structure we're trying to unify with. @@ -1158,7 +1158,7 @@ fn unify_flat_type( use roc_types::subs::FlatType::*; match (left, right) { - (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), + (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(*left)), (Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields(subs) => { unify_pool(subs, pool, *ext, ctx.second, ctx.mode) @@ -1172,7 +1172,7 @@ fn unify_flat_type( unify_record(subs, pool, ctx, *fields1, *ext1, *fields2, *ext2) } - (EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(left.clone())), + (EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(*left)), (TagUnion(tags, ext), EmptyTagUnion) if tags.is_empty() => { unify_pool(subs, pool, *ext, ctx.second, ctx.mode) @@ -1277,7 +1277,7 @@ fn unify_flat_type( if tag_name_1_ref == tag_name_2_ref { let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode); if problems.is_empty() { - let content = subs.get_content_without_compacting(ctx.second).clone(); + let content = *subs.get_content_without_compacting(ctx.second); merge(subs, ctx, content) } else { problems @@ -1369,7 +1369,7 @@ fn unify_rigid( mismatch!("Rigid {:?} with {:?}", ctx.first, &other) } else { // We are treating rigid vars as flex vars; admit this - merge(subs, ctx, other.clone()) + merge(subs, ctx, *other) } } Error => { @@ -1401,7 +1401,7 @@ fn unify_flex( // TODO special-case boolean here // In all other cases, if left is flex, defer to right. // (This includes using right's name if both are flex and named.) - merge(subs, ctx, other.clone()) + merge(subs, ctx, *other) } Error => merge(subs, ctx, Error), @@ -1424,7 +1424,7 @@ fn unify_recursion( } => { // NOTE: structure and other_structure may not be unified yet, but will be // we should not do that here, it would create an infinite loop! - let name = opt_name.clone().or_else(|| other_opt_name.clone()); + let name = (*opt_name).or_else(|| *other_opt_name); merge( subs, ctx, @@ -1446,7 +1446,7 @@ fn unify_recursion( ctx, RecursionVar { structure, - opt_name: opt_name.clone(), + opt_name: *opt_name, }, ), From 836967b9197ac50b16cb39985387d3e9426b1bce Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 19:07:38 +0100 Subject: [PATCH 134/150] some other clippy things in tests --- cli/tests/cli_run.rs | 6 +++--- compiler/fmt/tests/test_fmt.rs | 2 +- compiler/load/tests/test_load.rs | 2 +- reporting/tests/helpers/mod.rs | 5 ++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 164bdd86ce..7b13d5b039 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -64,15 +64,15 @@ mod cli_run { } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { - let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat()); + let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat()); let err = compile_out.stdout.trim(); - let err = strip_colors(&err); + let err = strip_colors(err); assert_multiline_str_eq!(err, expected.into()); } fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { let flags = &["--check"]; - let out = run_roc(&[&["format", &file.to_str().unwrap()], &flags[..]].concat()); + let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat()); if expects_success_exit_code { assert!(out.status.success()); diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index edea7957d3..81b55b118d 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -3014,7 +3014,7 @@ mod test_fmt { for entry in walkdir::WalkDir::new(&root) { let entry = entry.unwrap(); let path = entry.path(); - if path.extension() == Some(&std::ffi::OsStr::new("roc")) { + if path.extension() == Some(std::ffi::OsStr::new("roc")) { count += 1; let src = std::fs::read_to_string(path).unwrap(); println!("Now trying to format {}", path.display()); diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 53c8f62cd0..d9ba9608b2 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -47,7 +47,7 @@ mod test_load { let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); - let alloc = RocDocAllocator::new(&src_lines, home, &interns); + let alloc = RocDocAllocator::new(&src_lines, home, interns); let reports = problems .into_iter() .map(|problem| can_problem(&alloc, &lines, filename.clone(), problem).pretty(&alloc)); diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index a9eea79d7a..234793ced0 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -35,10 +35,9 @@ pub fn infer_expr( let env = solve::Env::default(); let (solved, _) = solve::run(constraints, &env, problems, subs, constraint); - let content = solved + let content = *solved .inner() - .get_content_without_compacting(expr_var) - .clone(); + .get_content_without_compacting(expr_var); (content, solved.into_inner()) } From 1a79870e1a5dfac7c21efabe1917e16f2ac70242 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 20:43:29 +0100 Subject: [PATCH 135/150] formatting --- reporting/tests/helpers/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 234793ced0..9563cea7b0 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -35,9 +35,7 @@ pub fn infer_expr( let env = solve::Env::default(); let (solved, _) = solve::run(constraints, &env, problems, subs, constraint); - let content = *solved - .inner() - .get_content_without_compacting(expr_var); + let content = *solved.inner().get_content_without_compacting(expr_var); (content, solved.into_inner()) } From f1ba27a1c9291d3563e4af61d0086372bd45a56c Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Sun, 6 Mar 2022 17:23:14 -0500 Subject: [PATCH 136/150] Off by 1 error in Dec printer. --- roc_std/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 4cbef19c7b..41fb698ae1 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -284,7 +284,7 @@ impl RocDec { fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) -> usize { if self.0 == 0 { write!(&mut bytes[..], "{}", "0").unwrap(); - return 0; + return 1; } let is_negative = (self.0 < 0) as usize; @@ -318,7 +318,7 @@ impl RocDec { if i < decimal_location { // This means that we've removed trailing zeros and are left with an integer. Our // convention is to print these without a decimal point or trailing zeros, so we're done. - return i; + return i + 1; } let ret = i + 1; @@ -332,7 +332,7 @@ impl RocDec { bytes[decimal_location] = '.' as u8; // Finally bytes = b"1234.5678" - ret + ret + 1 } pub fn to_str(&self) -> RocStr { From e83cac5ef045c7c5fcb1b6e0f75d2a49d6300bb5 Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Sun, 6 Mar 2022 18:29:14 -0500 Subject: [PATCH 137/150] Move rust toolchain information from shell.nix to rust-toolchain.toml. --- rust-toolchain.toml | 3 +++ shell.nix | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..02cb8fcb53 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +profile = "default" diff --git a/shell.nix b/shell.nix index 7f85013d3b..ce867939ad 100644 --- a/shell.nix +++ b/shell.nix @@ -66,10 +66,7 @@ let # tools for development environment less ]) ++ (with unstable-pkgs; [ - rustc - cargo - clippy - rustfmt + rustup ]); in pkgs.mkShell { From 42180317c596bb58f0c91e6d3eba0eab32e43a28 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Mar 2022 19:36:46 -0500 Subject: [PATCH 138/150] Add missing #[test] annotation --- repl_test/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index b5d949e323..66938b6e26 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -994,6 +994,7 @@ fn issue_2588_record_with_function_and_nonfunction() { ) } +#[test] fn opaque_apply() { expect_success( indoc!( From ad89e93af548bf72264c13a8c87de1cfe132de9c Mon Sep 17 00:00:00 2001 From: Derek Gustafson Date: Sun, 6 Mar 2022 19:51:19 -0500 Subject: [PATCH 139/150] Update rust-toolchain.toml Co-authored-by: Richard Feldman --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 02cb8fcb53..c605b9b898 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "stable" +channel = "1.58.0" profile = "default" From eed7e3df715ca82d0a698d72a2986302364d58dd Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 12:06:17 -0500 Subject: [PATCH 140/150] Report invalid extension types during canonicalization Closes #2541 --- compiler/can/src/annotation.rs | 117 +++++++++++++++++++++------- compiler/problem/src/can.rs | 10 +++ reporting/src/error/canonicalize.rs | 33 +++++++- reporting/tests/test_reporting.rs | 50 ++++++++++++ 4 files changed, 182 insertions(+), 28 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 6a0052d4a9..cd7aff093a 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -375,7 +375,8 @@ fn can_annotation_help( As( loc_inner, _spaces, - alias_header @ TypeHeader { + alias_header + @ TypeHeader { name, vars: loc_vars, }, @@ -520,19 +521,16 @@ fn can_annotation_help( } Record { fields, ext } => { - let ext_type = match ext { - Some(loc_ann) => can_annotation_help( - env, - &loc_ann.value, - region, - scope, - var_store, - introduced_variables, - local_aliases, - references, - ), - None => Type::EmptyRec, - }; + let ext_type = can_extension_type( + env, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ext, + roc_problem::can::ExtensionTypeKind::Record, + ); if fields.is_empty() { match ext { @@ -561,19 +559,16 @@ fn can_annotation_help( } } TagUnion { tags, ext, .. } => { - let ext_type = match ext { - Some(loc_ann) => can_annotation_help( - env, - &loc_ann.value, - loc_ann.region, - scope, - var_store, - introduced_variables, - local_aliases, - references, - ), - None => Type::EmptyTagUnion, - }; + let ext_type = can_extension_type( + env, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ext, + roc_problem::can::ExtensionTypeKind::TagUnion, + ); if tags.is_empty() { match ext { @@ -644,6 +639,74 @@ fn can_annotation_help( } } +#[allow(clippy::too_many_arguments)] +fn can_extension_type<'a>( + env: &mut Env, + scope: &mut Scope, + var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, + local_aliases: &mut SendMap, + references: &mut MutSet, + opt_ext: &Option<&Loc>>, + ext_problem_kind: roc_problem::can::ExtensionTypeKind, +) -> Type { + fn valid_record_ext_type(typ: &Type) -> bool { + // Include erroneous types so that we don't overreport errors. + matches!( + typ, + Type::EmptyRec | Type::Record(..) | Type::Variable(..) | Type::Erroneous(..) + ) + } + fn valid_tag_ext_type(typ: &Type) -> bool { + matches!( + typ, + Type::EmptyTagUnion | Type::TagUnion(..) | Type::Variable(..) | Type::Erroneous(..) + ) + } + + use roc_problem::can::ExtensionTypeKind; + + let (empty_ext_type, valid_extension_type): (_, fn(&Type) -> bool) = match ext_problem_kind { + ExtensionTypeKind::Record => (Type::EmptyRec, valid_record_ext_type), + ExtensionTypeKind::TagUnion => (Type::EmptyTagUnion, valid_tag_ext_type), + }; + + match opt_ext { + Some(loc_ann) => { + let ext_type = can_annotation_help( + env, + &loc_ann.value, + loc_ann.region, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ); + if valid_extension_type(&ext_type) { + ext_type + } else { + // Report an error but mark the extension variable to be inferred + // so that we're as permissive as possible. + // + // THEORY: invalid extension types can appear in this position. Otherwise + // they would be caught as errors during unification. + env.problem(roc_problem::can::Problem::InvalidExtensionType { + region: loc_ann.region, + kind: ext_problem_kind, + }); + + let var = var_store.fresh(); + + introduced_variables.insert_inferred(var); + + Type::Variable(var) + } + } + None => empty_ext_type, + } +} + pub fn instantiate_and_freshen_alias_type( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index a616c7d099..e5d23518f9 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -84,6 +84,16 @@ pub enum Problem { def_region: Region, differing_recursion_region: Region, }, + InvalidExtensionType { + region: Region, + kind: ExtensionTypeKind, + }, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ExtensionTypeKind { + Record, + TagUnion, } #[derive(Clone, Debug, PartialEq)] diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index ab009aba3e..6473db7388 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -1,7 +1,9 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; -use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; +use roc_problem::can::{ + BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, +}; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; use std::path::PathBuf; @@ -33,6 +35,7 @@ const OPAQUE_NOT_DEFINED: &str = "OPAQUE TYPE NOT DEFINED"; const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE TYPE DECLARED OUTSIDE SCOPE"; const OPAQUE_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED"; const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS"; +const INVALID_EXTENSION_TYPE: &str = "INVALID_EXTENSION_TYPE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -492,6 +495,34 @@ pub fn can_problem<'b>( title = NESTED_DATATYPE.to_string(); severity = Severity::RuntimeError; } + + Problem::InvalidExtensionType { region, kind } => { + let (kind_str, can_only_contain) = match kind { + ExtensionTypeKind::Record => ("record", "a type variable or another record"), + ExtensionTypeKind::TagUnion => { + ("tag union", "a type variable or another tag union") + } + }; + + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This "), + alloc.text(kind_str), + alloc.reflow(" extension type is invalid:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.concat(vec![ + alloc.note("A "), + alloc.reflow(kind_str), + alloc.reflow(" extension variable can only contain "), + alloc.reflow(can_only_contain), + alloc.reflow("."), + ]), + ]); + + title = INVALID_EXTENSION_TYPE.to_string(); + severity = Severity::RuntimeError; + } }; Report { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index caf40202e7..5935e66eed 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8515,4 +8515,54 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn invalid_record_extension_type() { + report_problem_as( + indoc!( + r#" + f : { x : Nat }U32 + f + "# + ), + indoc!( + r#" + ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + + This record extension type is invalid: + + 1│ f : { x : Nat }U32 + ^^^ + + Note: A record extension variable can only contain a type variable or + another record. + "# + ), + ) + } + + #[test] + fn invalid_tag_extension_type() { + report_problem_as( + indoc!( + r#" + f : [ A ]U32 + f + "# + ), + indoc!( + r#" + ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + + This tag union extension type is invalid: + + 1│ f : [ A ]U32 + ^^^ + + Note: A tag union extension variable can only contain a type variable + or another tag union. + "# + ), + ) + } } From 74c972916d8ebd547548ac84f810c66e0fb07e84 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 12:11:12 -0500 Subject: [PATCH 141/150] Remove unnecessary whitespace in reporting tests --- reporting/tests/test_reporting.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 5935e66eed..4f1fc68969 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7305,11 +7305,11 @@ I need all branches in an `if` to have the same type! {} The first pattern is trying to match {}: - + {} - + But the expression between `when` and `is` has the type: - + {} "# ), number, $suffix, carets, kind, typ, bad_type), @@ -8463,20 +8463,20 @@ I need all branches in an `if` to have the same type! indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - + The 1st argument to `y` is not what I expect: - + 6│ n = y 1u8 ^^^ - + This argument is an integer of type: - + U8 - + But `y` needs the 1st argument to be: - + a - + Tip: The type annotation uses the type variable `a` to say that this definition can produce any type of value. But in the body I see that it will only produce a `U8` value of a single specific type. Maybe From aae5a4603428120db162ef5bbde359289a64e22b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 13:36:10 -0500 Subject: [PATCH 142/150] Fmt --- compiler/can/src/annotation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index cd7aff093a..1fbe8a3652 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -375,8 +375,7 @@ fn can_annotation_help( As( loc_inner, _spaces, - alias_header - @ TypeHeader { + alias_header @ TypeHeader { name, vars: loc_vars, }, From 47e490407553acd51f7676fb90eaff0f440bc35b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 22:15:02 -0500 Subject: [PATCH 143/150] Chase aliases when checking for valid extension types --- compiler/can/src/annotation.rs | 2 +- compiler/solve/tests/solve_expr.rs | 30 ++++++++++++++++++++++++++++++ compiler/types/src/types.rs | 7 ++++--- reporting/tests/test_reporting.rs | 4 ++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 1fbe8a3652..9ace3a1a7d 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -682,7 +682,7 @@ fn can_extension_type<'a>( local_aliases, references, ); - if valid_extension_type(&ext_type) { + if valid_extension_type(&ext_type.shallow_dealias()) { ext_type } else { // Report an error but mark the extension variable to be inferred diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 70b321afcd..4c4f36de14 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5543,4 +5543,34 @@ mod solve_expr { "Str", ) } + + #[test] + fn record_extension_variable_is_alias() { + infer_eq_without_problem( + indoc!( + r#" + Other a b : { y: a, z: b } + + f : { x : Str }(Other Str Str) + f + "# + ), + r#"{ x : Str, y : Str, z : Str }"#, + ) + } + + #[test] + fn tag_extension_variable_is_alias() { + infer_eq_without_problem( + indoc!( + r#" + Other : [ B, C ] + + f : [ A ]Other + f + "# + ), + r#"[ A, B, C ]"#, + ) + } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 6bc9b7ed9e..c6f4f360f3 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -722,10 +722,11 @@ impl Type { /// a shallow dealias, continue until the first constructor is not an alias. pub fn shallow_dealias(&self) -> &Self { - match self { - Type::Alias { actual, .. } => actual.shallow_dealias(), - _ => self, + let mut result = self; + while let Type::Alias { actual, .. } = result { + result = actual; } + result } pub fn instantiate_aliases( diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 4f1fc68969..247db7a0ec 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8546,7 +8546,7 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - f : [ A ]U32 + f : [ A ]Str f "# ), @@ -8556,7 +8556,7 @@ I need all branches in an `if` to have the same type! This tag union extension type is invalid: - 1│ f : [ A ]U32 + 1│ f : [ A ]Str ^^^ Note: A tag union extension variable can only contain a type variable From 01255a17d578620b45807973d60bed5b7fb34ac8 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 22:19:45 -0500 Subject: [PATCH 144/150] Remove unnecessary reference --- compiler/can/src/annotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 9ace3a1a7d..1892b552c8 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -682,7 +682,7 @@ fn can_extension_type<'a>( local_aliases, references, ); - if valid_extension_type(&ext_type.shallow_dealias()) { + if valid_extension_type(ext_type.shallow_dealias()) { ext_type } else { // Report an error but mark the extension variable to be inferred From 611c5a69cb4a0aeae8346484244ab15714a91f43 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sun, 6 Mar 2022 22:32:11 -0800 Subject: [PATCH 145/150] remove commented line --- compiler/gen_dev/src/generic64/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 1bc07d0954..aead93b40c 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -361,7 +361,6 @@ impl< self.join_map.clear(); self.free_map.clear(); self.buf.clear(); - // self.helper_proc_symbols.clear(); self.storage_manager.reset(); } From 02e7cf016bb840d8653bd4e5882f3cdcec0b722d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sun, 6 Mar 2022 22:52:25 -0800 Subject: [PATCH 146/150] fix jumps with primitive arguments --- compiler/gen_dev/src/generic64/storage.rs | 37 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index be5eeaea8a..55eaf67ead 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -763,7 +763,7 @@ impl< }); } _ if layout.stack_size(self.target_info) == 0 => {} - _ if layout.safe_to_memcpy() => { + _ if layout.safe_to_memcpy() && layout.stack_size(self.target_info) > 8 => { let (from_offset, size) = self.stack_offset_and_size(sym); debug_assert!(from_offset % 8 == 0); debug_assert!(size % 8 == 0); @@ -1032,10 +1032,9 @@ impl< let base_offset = self.claim_stack_size(8); self.symbol_storage_map.insert( *symbol, - Stack(ReferencedPrimitive { + Stack(Primitive { base_offset, - size: 8, - sign_extend: false, + reg: None, }), ); self.allocation_map @@ -1081,17 +1080,41 @@ impl< Reg(_) => { internal_error!("Register storage is not allowed for jumping to joinpoint") } - Stack(ReferencedPrimitive { base_offset, .. } | Complex { base_offset, .. }) => { + Stack(Complex { base_offset, .. }) => { // TODO: This might be better not to call. // Maybe we want a more memcpy like method to directly get called here. // That would also be capable of asserting the size. // Maybe copy stack to stack or something. self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout); } + Stack(Primitive { + base_offset, + reg: None, + }) => match layout { + single_register_integers!() => { + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg64(buf, *base_offset, reg); + } + single_register_floats!() => { + let reg = self.load_to_float_reg(buf, sym); + ASM::mov_base32_freg64(buf, *base_offset, reg); + } + _ => { + internal_error!( + "cannot load non-primitive layout ({:?}) to primitive stack location", + layout + ); + } + }, NoData => {} - Stack(Primitive { .. }) => { + Stack(Primitive { reg: Some(_), .. }) => { internal_error!( - "Primitive stack storage is not allowed for jumping to joinpoint" + "primitives with register storage are not allowed for jumping to joinpoint" + ) + } + Stack(ReferencedPrimitive { .. }) => { + internal_error!( + "referenced primitive stack storage is not allowed for jumping to joinpoint" ) } } From 2b3545233a594b66687fe1fc78fc02156b230e28 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sun, 6 Mar 2022 22:56:49 -0800 Subject: [PATCH 147/150] switch storage manager to default clone --- compiler/gen_dev/src/generic64/aarch64.rs | 2 ++ compiler/gen_dev/src/generic64/mod.rs | 4 ++-- compiler/gen_dev/src/generic64/storage.rs | 22 +--------------------- compiler/gen_dev/src/generic64/x86_64.rs | 3 +++ 4 files changed, 8 insertions(+), 23 deletions(-) diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 970e531968..e1eb27d8aa 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -66,10 +66,12 @@ impl RegTrait for AArch64FloatReg { } } +#[derive(Copy, Clone)] pub struct AArch64Assembler {} // AArch64Call may need to eventually be split by OS, // but I think with how we use it, they may all be the same. +#[derive(Copy, Clone)] pub struct AArch64Call {} const STACK_ALIGNMENT: u8 = 16; diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index aead93b40c..7e299c2b98 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -22,7 +22,7 @@ use storage::StorageManager; // TODO: on all number functions double check and deal with over/underflow. pub trait CallConv>: - Sized + Sized + Copy { const BASE_PTR_REG: GeneralReg; const STACK_PTR_REG: GeneralReg; @@ -109,7 +109,7 @@ pub trait CallConv: Sized { +pub trait Assembler: Sized + Copy { fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn abs_freg64_freg64( buf: &mut Vec<'_, u8>, diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index 55eaf67ead..8f874d7462 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -76,6 +76,7 @@ enum Storage { NoData, } +#[derive(Clone)] pub struct StorageManager< 'a, GeneralReg: RegTrait, @@ -181,27 +182,6 @@ impl< self.fn_call_stack_size = 0; } - pub fn clone(&self) -> Self { - Self { - phantom_asm: PhantomData, - phantom_cc: PhantomData, - env: self.env, - target_info: self.target_info, - symbol_storage_map: self.symbol_storage_map.clone(), - allocation_map: self.allocation_map.clone(), - join_param_map: self.join_param_map.clone(), - general_free_regs: self.general_free_regs.clone(), - general_used_regs: self.general_used_regs.clone(), - general_used_callee_saved_regs: self.general_used_callee_saved_regs.clone(), - float_free_regs: self.float_free_regs.clone(), - float_used_regs: self.float_used_regs.clone(), - float_used_callee_saved_regs: self.float_used_callee_saved_regs.clone(), - free_stack_chunks: self.free_stack_chunks.clone(), - stack_size: self.stack_size, - fn_call_stack_size: self.fn_call_stack_size, - } - } - pub fn target_info(&self) -> TargetInfo { self.target_info } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 2eff83ecd2..a3cb970d97 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -64,8 +64,11 @@ impl RegTrait for X86_64FloatReg { } } +#[derive(Copy, Clone)] pub struct X86_64Assembler {} +#[derive(Copy, Clone)] pub struct X86_64WindowsFastcall {} +#[derive(Copy, Clone)] pub struct X86_64SystemV {} const STACK_ALIGNMENT: u8 = 16; From 6a76f3f2090395e9e4e3c9ad847041551ba36a5a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 7 Mar 2022 09:29:05 +0100 Subject: [PATCH 148/150] clippy fix --- editor/src/editor/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index b0bc5e471d..1351572319 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -406,7 +406,7 @@ async fn create_device( let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference, - compatible_surface: Some(&surface), + compatible_surface: Some(surface), force_fallback_adapter, }) .await From 4da491cf9389ed03f15f27bf91bbd20471e6c94e Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 7 Mar 2022 11:32:38 +0100 Subject: [PATCH 149/150] added rust-toolchain.toml --- Earthfile | 2 +- rust-toolchain.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 rust-toolchain.toml diff --git a/Earthfile b/Earthfile index d11c9d1b6d..9801229848 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.58.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages +FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages WORKDIR /earthbuild prep-debian: diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..44e678d36b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.58.0" # make sure to update the rust version in Earthfile as well \ No newline at end of file From b2b0bbc1ad70cae56ee35dcdf4add05f169539c4 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 7 Mar 2022 14:23:01 +0100 Subject: [PATCH 150/150] added newline to rust-toolchain.toml --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6262dc79cd..d7f514dc9c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "1.58.0" # make sure to update the rust version in Earthfile as well -profile = "default" \ No newline at end of file +profile = "default"