From 82bb341e9ca8687736c103d39739c9fa6319ed31 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 1 Dec 2021 19:26:01 +0100 Subject: [PATCH 01/24] 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 02/24] 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 fd8dfd284d0ddc273ee60271658c89b8203cdcc6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 1 Mar 2022 22:41:46 +0100 Subject: [PATCH 03/24] 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 ec099bbdec5fc61809a8b76e869e09c143fd59a6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Mar 2022 14:46:30 +0100 Subject: [PATCH 04/24] 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 05/24] 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 06/24] 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 07/24] 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 08/24] 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 09/24] 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 10/24] 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 11/24] 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 12/24] 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 13/24] 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 14/24] 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 15/24] 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 16/24] 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 17/24] 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 18/24] 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 19/24] 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 20/24] 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 21/24] 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 22/24] 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 23/24] 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 24/24] 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,