From b59d33a1d580f11aabff84116ae2c0263904aabc Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 19 Apr 2022 23:00:05 +0200 Subject: [PATCH 01/89] refactor roc_collections --- compiler/can/src/annotation.rs | 2 +- compiler/can/src/def.rs | 3 +- compiler/can/src/effect_module.rs | 2 +- compiler/can/src/env.rs | 2 +- compiler/can/src/expr.rs | 2 +- compiler/can/src/module.rs | 2 +- compiler/can/src/procedure.rs | 2 +- compiler/collections/src/all.rs | 96 ----------------------------- compiler/collections/src/lib.rs | 4 ++ compiler/collections/src/vec_set.rs | 95 ++++++++++++++++++++++++++++ compiler/load_internal/src/file.rs | 4 +- 11 files changed, 108 insertions(+), 106 deletions(-) create mode 100644 compiler/collections/src/vec_set.rs diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 97b74f2101..92caf9298b 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::all::{ImMap, MutMap, MutSet, SendMap, VecSet}; +use roc_collections::{ImMap, MutMap, MutSet, SendMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index cd4d2c79d5..1acf368e9b 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -10,8 +10,7 @@ use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pa use crate::procedure::References; use crate::scope::create_alias; use crate::scope::Scope; -use roc_collections::all::ImSet; -use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap}; +use roc_collections::{default_hasher, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_parse::ast; diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 3695757117..2e58dc1bd1 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -4,7 +4,7 @@ use crate::env::Env; use crate::expr::{ClosureData, Expr, Recursive}; use crate::pattern::Pattern; use crate::scope::Scope; -use roc_collections::all::{SendMap, VecSet}; +use roc_collections::{SendMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::TagName; use roc_module::symbol::Symbol; diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index 1d63c283c1..8c3f3fb8b1 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -1,5 +1,5 @@ use crate::procedure::References; -use roc_collections::all::{MutMap, VecSet}; +use roc_collections::{MutMap, VecSet}; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index fb14cf2db1..63c44001bd 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -9,7 +9,7 @@ use crate::num::{ use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use roc_collections::all::{MutMap, MutSet, SendMap, VecSet}; +use roc_collections::{MutMap, MutSet, SendMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 974230051c..dbd4a30274 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -7,7 +7,7 @@ use crate::operator::desugar_def; use crate::pattern::Pattern; use crate::scope::Scope; use bumpalo::Bump; -use roc_collections::all::{MutMap, SendMap, VecSet}; +use roc_collections::{MutMap, SendMap, VecSet}; use roc_module::ident::Lowercase; use roc_module::ident::{Ident, TagName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 3aa1b92153..c3f7088957 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -1,6 +1,6 @@ use crate::expr::Expr; use crate::pattern::Pattern; -use roc_collections::all::VecSet; +use roc_collections::VecSet; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index 6c1a2b78c4..391f48e488 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -220,99 +220,3 @@ macro_rules! mut_map { } }; } - -#[derive(Clone, Debug, PartialEq)] -pub struct VecSet { - elements: Vec, -} - -impl Default for VecSet { - fn default() -> Self { - Self { - elements: Vec::new(), - } - } -} - -impl VecSet { - pub fn with_capacity(capacity: usize) -> Self { - Self { - elements: Vec::with_capacity(capacity), - } - } - - pub fn len(&self) -> usize { - self.elements.len() - } - - pub fn is_empty(&self) -> bool { - self.elements.is_empty() - } - - pub fn swap_remove(&mut self, index: usize) -> T { - self.elements.swap_remove(index) - } - - pub fn insert(&mut self, value: T) -> bool { - if self.elements.contains(&value) { - true - } else { - self.elements.push(value); - - false - } - } - - pub fn contains(&self, value: &T) -> bool { - self.elements.contains(value) - } - - pub fn remove(&mut self, value: &T) { - match self.elements.iter().position(|x| x == value) { - None => { - // just do nothing - } - Some(index) => { - self.elements.swap_remove(index); - } - } - } - - pub fn iter(&self) -> impl Iterator { - self.elements.iter() - } -} - -impl Extend for VecSet { - fn extend>(&mut self, iter: T) { - let it = iter.into_iter(); - let hint = it.size_hint(); - - match hint { - (0, Some(0)) => { - // done, do nothing - } - (1, Some(1)) | (2, Some(2)) => { - for value in it { - self.insert(value); - } - } - _ => { - self.elements.extend(it); - - self.elements.sort(); - self.elements.dedup(); - } - } - } -} - -impl IntoIterator for VecSet { - type Item = T; - - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.elements.into_iter() - } -} diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index 16f8d165dc..c5d3e2c31c 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -4,3 +4,7 @@ pub mod all; pub mod soa; +mod vec_set; + +pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +pub use vec_set::VecSet; diff --git a/compiler/collections/src/vec_set.rs b/compiler/collections/src/vec_set.rs new file mode 100644 index 0000000000..4869084677 --- /dev/null +++ b/compiler/collections/src/vec_set.rs @@ -0,0 +1,95 @@ +#[derive(Clone, Debug, PartialEq)] +pub struct VecSet { + elements: Vec, +} + +impl Default for VecSet { + fn default() -> Self { + Self { + elements: Vec::new(), + } + } +} + +impl VecSet { + pub fn with_capacity(capacity: usize) -> Self { + Self { + elements: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + self.elements.len() + } + + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + pub fn swap_remove(&mut self, index: usize) -> T { + self.elements.swap_remove(index) + } + + pub fn insert(&mut self, value: T) -> bool { + if self.elements.contains(&value) { + true + } else { + self.elements.push(value); + + false + } + } + + pub fn contains(&self, value: &T) -> bool { + self.elements.contains(value) + } + + pub fn remove(&mut self, value: &T) { + match self.elements.iter().position(|x| x == value) { + None => { + // just do nothing + } + Some(index) => { + self.elements.swap_remove(index); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.elements.iter() + } +} + +impl Extend for VecSet { + fn extend>(&mut self, iter: T) { + let it = iter.into_iter(); + let hint = it.size_hint(); + + match hint { + (0, Some(0)) => { + // done, do nothing + } + (1, Some(1)) | (2, Some(2)) => { + for value in it { + self.insert(value); + } + } + _ => { + self.elements.extend(it); + + self.elements.sort(); + self.elements.dedup(); + } + } + } +} + +impl IntoIterator for VecSet { + type Item = T; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.elements.into_iter() + } +} diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 893369354d..f8f60cfe4c 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -10,7 +10,7 @@ use roc_can::abilities::AbilitiesStore; 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, VecSet}; +use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecSet}; use roc_constrain::module::{ constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule, ExposedModuleTypes, @@ -1110,7 +1110,7 @@ pub 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 84727b31e567081ed25622d5517722f4f0049a0b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 08:23:44 -0400 Subject: [PATCH 02/89] Only support --target with `roc build` --- cli/src/lib.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 792071fbcd..6bb78f495a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -231,18 +231,10 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_PRECOMPILED) .long(FLAG_PRECOMPILED) - .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)") + .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)") .possible_values(["true", "false"]) .required(false), ) - .arg( - Arg::new(FLAG_TARGET) - .long(FLAG_TARGET) - .about("Choose a different target") - .default_value(Target::default().as_str()) - .possible_values(Target::OPTIONS) - .required(false), - ) .arg( Arg::new(ROC_FILE) .about("The .roc file of an app to build and run") From 8b144c446dbe44bc8e40be0bb6d5840ad865e1d8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 16:30:20 +0200 Subject: [PATCH 03/89] remove PartialEq for a bunch of types that we should not compare --- ast/src/solve_type.rs | 2 +- compiler/can/src/annotation.rs | 8 +- compiler/can/src/constraint.rs | 8 +- compiler/can/src/def.rs | 10 +- compiler/can/src/expected.rs | 4 +- compiler/can/src/expr.rs | 14 +-- compiler/can/src/pattern.rs | 6 +- compiler/can/src/procedure.rs | 2 +- compiler/can/tests/can_inline.rs | 110 ---------------------- compiler/can/tests/test_can.rs | 77 ++++++++------- compiler/collections/src/lib.rs | 2 + compiler/load_internal/tests/test_load.rs | 48 ++++------ compiler/mono/src/ir.rs | 4 +- compiler/solve/src/solve.rs | 2 +- compiler/test_mono/src/tests.rs | 2 +- 15 files changed, 94 insertions(+), 205 deletions(-) delete mode 100644 compiler/can/tests/can_inline.rs diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index f15b128827..096a405d5c 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -75,7 +75,7 @@ use crate::mem_pool::shallow_clone::ShallowClone; // Ranks are used to limit the number of type variables considered for generalization. Only those inside // of the let (so those used in inferring the type of `\x -> x`) are considered. -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 92caf9298b..87dc27f336 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::{ImMap, MutMap, MutSet, SendMap, VecSet}; +use roc_collections::{ImMap, MutMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; @@ -11,7 +11,7 @@ use roc_types::types::{ Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension, }; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Annotation { pub typ: Type, pub introduced_variables: IntroducedVariables, @@ -53,14 +53,14 @@ pub struct AbleVariable { pub first_seen: Region, } -#[derive(Clone, Debug, PartialEq, Default)] +#[derive(Clone, Debug, Default)] pub struct IntroducedVariables { pub wildcards: Vec>, pub lambda_sets: Vec, pub inferred: Vec>, pub named: VecSet, pub able: VecSet, - pub host_exposed_aliases: MutMap, + pub host_exposed_aliases: VecMap, } impl IntroducedVariables { diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 58d7b1f065..c858834263 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -601,7 +601,7 @@ impl Constraints { roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8); -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub enum Constraint { Eq( EitherIndex, @@ -643,13 +643,13 @@ pub enum Constraint { ), } -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct DefTypes { pub types: Slice, pub loc_symbols: Slice<(Symbol, Region)>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct LetConstraint { pub rigid_vars: Slice, pub flex_vars: Slice, @@ -657,7 +657,7 @@ pub struct LetConstraint { pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct IncludesTag { pub type_index: Index, pub tag_name: TagName, diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1acf368e9b..0fbe917e30 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -29,7 +29,7 @@ use std::collections::HashMap; use std::fmt::Debug; use ven_graph::{strongly_connected_components, topological_sort}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Def { pub loc_pattern: Loc, pub loc_expr: Loc, @@ -38,7 +38,7 @@ pub struct Def { pub annotation: Option, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Annotation { pub signature: Type, pub introduced_variables: IntroducedVariables, @@ -56,7 +56,7 @@ pub struct CanDefs { /// A Def that has had patterns and type annnotations canonicalized, /// but no Expr canonicalization has happened yet. Also, it has had spaces /// and nesting resolved, and knows whether annotations are standalone or not. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] enum PendingValueDef<'a> { /// A standalone annotation with no body AnnotationOnly( @@ -79,7 +79,7 @@ enum PendingValueDef<'a> { ), } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] enum PendingTypeDef<'a> { /// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. Alias { @@ -105,7 +105,7 @@ enum PendingTypeDef<'a> { } // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum Declaration { Declare(Def), diff --git a/compiler/can/src/expected.rs b/compiler/can/src/expected.rs index aad8bd42b4..df156e00fc 100644 --- a/compiler/can/src/expected.rs +++ b/compiler/can/src/expected.rs @@ -2,7 +2,7 @@ use crate::pattern::Pattern; use roc_region::all::{Loc, Region}; use roc_types::types::{AnnotationSource, PReason, Reason}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Expected { NoExpectation(T), FromAnnotation(Loc, usize, AnnotationSource, T), @@ -10,7 +10,7 @@ pub enum Expected { } /// Like Expected, but for Patterns. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum PExpected { NoExpectation(T), ForReason(PReason, T, Region), diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 63c44001bd..9ec675185f 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -9,7 +9,7 @@ use crate::num::{ use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use roc_collections::{MutMap, MutSet, SendMap, VecSet}; +use roc_collections::{MutMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -23,12 +23,12 @@ use roc_types::types::{Alias, LambdaSet, Type}; use std::fmt::{Debug, Display}; use std::{char, u32}; -#[derive(Clone, Default, Debug, PartialEq)] +#[derive(Clone, Default, Debug)] pub struct Output { pub references: References, pub tail_call: Option, pub introduced_variables: IntroducedVariables, - pub aliases: SendMap, + pub aliases: VecMap, pub non_closures: VecSet, } @@ -62,7 +62,7 @@ impl Display for IntValue { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Expr { // Literals @@ -194,7 +194,7 @@ pub enum Expr { // Compiles, but will crash if reached RuntimeError(RuntimeError), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct ClosureData { pub function_type: Variable, pub closure_type: Variable, @@ -271,7 +271,7 @@ impl AccessorData { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Field { pub var: Variable, // The region of the full `foo: f bar`, rather than just `f bar` @@ -286,7 +286,7 @@ pub enum Recursive { TailRecursive = 2, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct WhenBranch { pub patterns: Vec>, pub value: Loc, diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index e620f41f00..c47af17fb4 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -17,7 +17,7 @@ use roc_types::types::{LambdaSet, Type}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Pattern { Identifier(Symbol), AppliedTag { @@ -82,7 +82,7 @@ pub enum Pattern { MalformedPattern(MalformedPatternProblem, Region), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, @@ -90,7 +90,7 @@ pub struct RecordDestruct { pub typ: DestructType, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum DestructType { Required, Optional(Variable, Loc), diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index c3f7088957..fd0fd5d372 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -5,7 +5,7 @@ use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Procedure { pub name: Option>, pub is_self_tail_recursive: bool, diff --git a/compiler/can/tests/can_inline.rs b/compiler/can/tests/can_inline.rs deleted file mode 100644 index ac26cb90a3..0000000000 --- a/compiler/can/tests/can_inline.rs +++ /dev/null @@ -1,110 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate roc_can; -extern crate roc_parse; -extern crate roc_region; - -mod helpers; - -#[cfg(test)] -mod can_inline { - use crate::helpers::{can_expr_with, test_home}; - use bumpalo::Bump; - use roc_can::expr::inline_calls; - use roc_can::expr::Expr::{self, *}; - use roc_can::scope::Scope; - use roc_types::subs::VarStore; - - fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) { - let arena = Bump::new(); - let scope = &mut Scope::new(test_home(), var_store); - let actual_out = can_expr_with(&arena, test_home(), input); - let actual = inline_calls(var_store, scope, actual_out.loc_expr.value); - - assert_eq!(actual, expected); - } - - #[test] - fn inline_empty_record() { - // fn inline_list_len() { - let var_store = &mut VarStore::default(); - - assert_inlines_to( - indoc!( - r#" - {} - "# - ), - EmptyRecord, - var_store, - ); - - // TODO testing with hardcoded variables is very brittle. - // Should find a better way to test this! - // (One idea would be to traverse both Exprs and zero out all the Variables, - // so they always pass equality.) - // let aliases = SendMap::default(); - // assert_inlines_to( - // indoc!( - // r#" - // Int.isZero 5 - // "# - // ), - // LetNonRec( - // Box::new(Def { - // loc_pattern: Located { - // region: Region::zero(), - // value: Pattern::Identifier(Symbol::ARG_1), - // }, - // pattern_vars: SendMap::default(), - // loc_expr: Located { - // region: Region::new(0, 0, 11, 12), - // value: Num(unsafe { Variable::unsafe_test_debug_variable(7) }, 5), - // }, - // expr_var: unsafe { Variable::unsafe_test_debug_variable(8) }, - // annotation: None, - // }), - // Box::new(Located { - // region: Region::zero(), - // value: Expr::Call( - // Box::new(( - // unsafe { Variable::unsafe_test_debug_variable(138) }, - // Located { - // region: Region::zero(), - // value: Expr::Var(Symbol::BOOL_EQ), - // }, - // unsafe { Variable::unsafe_test_debug_variable(139) }, - // )), - // vec![ - // ( - // unsafe { Variable::unsafe_test_debug_variable(140) }, - // Located { - // region: Region::zero(), - // value: Var(Symbol::ARG_1), - // }, - // ), - // ( - // unsafe { Variable::unsafe_test_debug_variable(141) }, - // Located { - // region: Region::zero(), - // value: Int( - // unsafe { Variable::unsafe_test_debug_variable(137) }, - // 0, - // ), - // }, - // ), - // ], - // CalledVia::Space, - // ), - // }), - // unsafe { Variable::unsafe_test_debug_variable(198) }, - // aliases, - // ), - // var_store, - // ) - } -} diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 83481c882b..838280f73d 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -20,11 +20,32 @@ mod test_can { use roc_region::all::{Position, Region}; use std::{f64, i64}; - fn assert_can(input: &str, expected: Expr) { + fn assert_can_runtime_error(input: &str, expected: RuntimeError) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); - assert_eq!(actual_out.loc_expr.value, expected); + match actual_out.loc_expr.value { + Expr::RuntimeError(actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } + } + + fn assert_can_string(input: &str, expected: &str) { + let arena = Bump::new(); + let actual_out = can_expr_with(&arena, test_home(), input); + + match actual_out.loc_expr.value { + Expr::Str(actual) => { + assert_eq!(expected, &*actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } } fn assert_can_float(input: &str, expected: f64) { @@ -69,10 +90,6 @@ mod test_can { } } - fn expr_str(contents: &str) -> Expr { - Expr::Str(contents.into()) - } - // NUMBER LITERALS #[test] @@ -81,14 +98,14 @@ mod test_can { let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidInt( + RuntimeError::InvalidInt( IntErrorKind::Overflow, Base::Decimal, Region::zero(), string.into_boxed_str(), - )), + ), ); } @@ -98,14 +115,14 @@ mod test_can { let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidInt( + RuntimeError::InvalidInt( IntErrorKind::Underflow, Base::Decimal, Region::zero(), string.into(), - )), + ), ); } @@ -114,13 +131,9 @@ mod test_can { let string = format!("{}1.0", f64::MAX); let region = Region::zero(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::PositiveInfinity, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::PositiveInfinity, region, string.into()), ); } @@ -129,13 +142,9 @@ mod test_can { let string = format!("{}1.0", f64::MIN); let region = Region::zero(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::NegativeInfinity, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::NegativeInfinity, region, string.into()), ); } @@ -144,13 +153,9 @@ mod test_can { let string = "1.1.1"; let region = Region::zero(); - assert_can( + assert_can_runtime_error( string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::Error, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::Error, region, string.into()), ); } @@ -1582,27 +1587,27 @@ mod test_can { #[test] fn string_with_valid_unicode_escapes() { - assert_can(r#""x\u(00A0)x""#, expr_str("x\u{00A0}x")); - assert_can(r#""x\u(101010)x""#, expr_str("x\u{101010}x")); + assert_can_string(r#""x\u(00A0)x""#, "x\u{00A0}x"); + assert_can_string(r#""x\u(101010)x""#, "x\u{101010}x"); } #[test] fn block_string() { - assert_can( + assert_can_string( r#" """foobar""" "#, - expr_str("foobar"), + "foobar", ); - assert_can( + assert_can_string( indoc!( r#" """foo bar""" "# ), - expr_str("foo\nbar"), + "foo\nbar", ); } diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index c5d3e2c31c..f0f98b5e64 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -4,7 +4,9 @@ pub mod all; pub mod soa; +mod vec_map; mod vec_set; pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +pub use vec_map::VecMap; pub use vec_set::VecSet; diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 5aaace9f19..0574311f0e 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -112,13 +112,11 @@ mod test_load { )); } - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty(),); Ok(loaded_module) } @@ -208,13 +206,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty()); let expected_name = loaded_module .interns @@ -261,13 +257,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty()); for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { match decl { @@ -365,13 +359,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty(),); let def_count: usize = loaded_module .declarations_by_id diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 5bb784d3c4..9a12b67ef5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -108,7 +108,7 @@ pub struct EntryPoint<'a> { #[derive(Clone, Copy, Debug)] pub struct PartialProcId(usize); -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PartialProcs<'a> { /// maps a function name (symbol) to an index symbols: Vec<'a, Symbol>, @@ -190,7 +190,7 @@ impl<'a> PartialProcs<'a> { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PartialProc<'a> { pub annotation: Variable, pub pattern_symbols: &'a [Symbol], diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index e30be26df4..dbe6f50c5d 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -79,7 +79,7 @@ pub struct IncompleteAbilityImplementation { pub missing_members: Vec>, } -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index bba051e6b6..23df54d27a 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -129,7 +129,7 @@ fn compiles_to_ir(test_name: &str, src: &str) { println!("Ignoring {} canonicalization problems", can_problems.len()); } - assert_eq!(type_problems, Vec::new()); + assert!(type_problems.is_empty()); assert_eq!(mono_problems, Vec::new()); debug_assert_eq!(exposed_to_host.values.len(), 1); From e740bbe529e2d181dc74fae91b34bba43a18dfb8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 17:25:22 +0200 Subject: [PATCH 04/89] make some of References' fields private --- compiler/can/src/annotation.rs | 2 +- compiler/can/src/def.rs | 12 ++++-------- compiler/can/src/expr.rs | 5 ++--- compiler/can/src/module.rs | 8 ++++---- compiler/can/src/pattern.rs | 3 +-- compiler/can/src/procedure.rs | 17 +++++++++++++++-- 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 87dc27f336..1d83835d38 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -125,7 +125,7 @@ impl IntroducedVariables { self.lambda_sets.extend(other.lambda_sets.iter().copied()); self.inferred.extend(other.inferred.iter().copied()); self.host_exposed_aliases - .extend(other.host_exposed_aliases.clone()); + .extend(other.host_exposed_aliases.iter().map(|(k, v)| (*k, *v))); self.named.extend(other.named.iter().cloned()); self.able.extend(other.able.iter().cloned()); diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 0fbe917e30..5d7b0ced91 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -334,8 +334,7 @@ pub fn canonicalize_defs<'a>( // Record all the annotation's references in output.references.lookups for symbol in can_ann.references { - output.references.type_lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + output.references.insert_type_lookup(symbol); } let mut can_vars: Vec> = Vec::with_capacity(vars.len()); @@ -445,8 +444,7 @@ pub fn canonicalize_defs<'a>( // Record all the annotation's references in output.references.lookups for symbol in member_annot.references { - output.references.type_lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + output.references.insert_type_lookup(symbol); } let name_region = member.name.region; @@ -1162,8 +1160,7 @@ fn canonicalize_pending_value_def<'a>( // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { - output.references.type_lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); @@ -1282,8 +1279,7 @@ fn canonicalize_pending_value_def<'a>( // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { - output.references.type_lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 9ec675185f..c916ffe2f7 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -487,8 +487,7 @@ pub fn canonicalize_expr<'a>( } Ok((name, opaque_def)) => { let argument = Box::new(args.pop().unwrap()); - output.references.referenced_type_defs.insert(name); - output.references.type_lookups.insert(name); + output.references.insert_type_lookup(name); let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); @@ -685,7 +684,7 @@ pub fn canonicalize_expr<'a>( // filter out aliases debug_assert!(captured_symbols .iter() - .all(|s| !output.references.referenced_type_defs.contains(s))); + .all(|s| !output.references.references_type_def(*s))); // captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s)); // filter out functions that don't close over anything diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index dbd4a30274..ba4ece29d5 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -329,8 +329,8 @@ pub fn canonicalize_module_defs<'a>( let mut referenced_types = VecSet::default(); // Gather up all the symbols that were referenced across all the defs' lookups. - referenced_values.extend(output.references.value_lookups); - referenced_types.extend(output.references.type_lookups); + referenced_values.extend(output.references.value_lookups.iter().copied()); + referenced_types.extend(output.references.type_lookups().copied()); // Gather up all the symbols that were referenced across all the defs' calls. referenced_values.extend(output.references.calls); @@ -528,8 +528,8 @@ pub fn canonicalize_module_defs<'a>( } // Incorporate any remaining output.lookups entries into references. - referenced_values.extend(output.references.value_lookups); - referenced_types.extend(output.references.type_lookups); + referenced_values.extend(output.references.value_lookups.iter().copied()); + referenced_types.extend(output.references.type_lookups().copied()); // Incorporate any remaining output.calls entries into references. referenced_values.extend(output.references.calls); diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index c47af17fb4..f6885c15d0 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -318,8 +318,7 @@ pub fn canonicalize_pattern<'a>( let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); - output.references.referenced_type_defs.insert(opaque); - output.references.type_lookups.insert(opaque); + output.references.insert_type_lookup(opaque); Pattern::UnwrappedOpaque { whole_var: var_store.fresh(), diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index fd0fd5d372..d893f1a1bb 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -45,10 +45,10 @@ impl Procedure { #[derive(Clone, Debug, Default, PartialEq)] pub struct References { pub bound_symbols: VecSet, - pub type_lookups: VecSet, + type_lookups: VecSet, pub value_lookups: VecSet, /// Aliases or opaque types referenced - pub referenced_type_defs: VecSet, + referenced_type_defs: VecSet, pub calls: VecSet, } @@ -75,4 +75,17 @@ impl References { pub fn has_type_lookup(&self, symbol: Symbol) -> bool { self.type_lookups.contains(&symbol) } + + pub fn references_type_def(&self, symbol: Symbol) -> bool { + self.referenced_type_defs.contains(&symbol) + } + + pub fn insert_type_lookup(&mut self, symbol: Symbol) { + self.type_lookups.insert(symbol); + self.referenced_type_defs.insert(symbol); + } + + pub fn type_lookups(&self) -> impl Iterator { + self.type_lookups.iter() + } } From c531191e4943850b736d432ebc909a9ce2a792a7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 17:34:08 +0200 Subject: [PATCH 05/89] make value_lookups private --- compiler/can/src/def.rs | 12 ++++++------ compiler/can/src/expr.rs | 18 +++++++----------- compiler/can/src/module.rs | 4 ++-- compiler/can/src/procedure.rs | 14 +++++++++++++- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 5d7b0ced91..231ecabc1f 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -691,10 +691,10 @@ pub fn sort_can_defs( let mut loc_succ = local_successors_with_duplicates(references, &env.closures); // if the current symbol is a closure, peek into its body - if let Some(References { value_lookups, .. }) = env.closures.get(symbol) { + if let Some(references) = env.closures.get(symbol) { let home = env.home; - for lookup in value_lookups.iter() { + for lookup in references.value_lookups() { if lookup != symbol && lookup.module_id() == home { // DO NOT register a self-call behind a lambda! // @@ -744,8 +744,8 @@ pub fn sort_can_defs( let mut loc_succ = local_successors_with_duplicates(references, &env.closures); // if the current symbol is a closure, peek into its body - if let Some(References { value_lookups, .. }) = env.closures.get(symbol) { - for lookup in value_lookups.iter() { + if let Some(references) = env.closures.get(symbol) { + for lookup in references.value_lookups() { loc_succ.push(*lookup); } } @@ -1358,7 +1358,7 @@ fn canonicalize_pending_value_def<'a>( // Recursion doesn't count as referencing. (If it did, all recursive functions // would result in circular def errors!) refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.value_lookups.remove(&symbol); + refs.remove_value_lookup(&symbol); }); // renamed_closure_def = Some(&symbol); @@ -1498,7 +1498,7 @@ fn canonicalize_pending_value_def<'a>( // Recursion doesn't count as referencing. (If it did, all recursive functions // would result in circular def errors!) refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.value_lookups.remove(&symbol); + refs.remove_value_lookup(&symbol); }); loc_can_expr.value = Closure(ClosureData { diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index c916ffe2f7..f808787a8c 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -655,12 +655,8 @@ pub fn canonicalize_expr<'a>( &loc_body_expr.value, ); - let mut captured_symbols: MutSet = new_output - .references - .value_lookups - .iter() - .copied() - .collect(); + let mut captured_symbols: MutSet = + new_output.references.value_lookups().copied().collect(); // filter out the closure's name itself captured_symbols.remove(&symbol); @@ -702,7 +698,7 @@ pub fn canonicalize_expr<'a>( // We shouldn't ultimately count arguments as referenced locals. Otherwise, // we end up with weird conclusions like the expression (\x -> x + 1) // references the (nonexistent) local variable x! - output.references.value_lookups.remove(sub_symbol); + output.references.remove_value_lookup(sub_symbol); } } @@ -1105,7 +1101,7 @@ pub fn local_successors_with_duplicates<'a>( references: &'a References, closures: &'a MutMap, ) -> Vec { - let mut answer: Vec<_> = references.value_lookups.iter().copied().collect(); + let mut answer: Vec<_> = references.value_lookups().copied().collect(); let mut stack: Vec<_> = references.calls.iter().copied().collect(); let mut seen = Vec::new(); @@ -1116,7 +1112,7 @@ pub fn local_successors_with_duplicates<'a>( } if let Some(references) = closures.get(&symbol) { - answer.extend(references.value_lookups.iter().copied()); + answer.extend(references.value_lookups().copied()); stack.extend(references.calls.iter().copied()); seen.push(symbol); @@ -1254,7 +1250,7 @@ fn canonicalize_var_lookup( // Look it up in scope! match scope.lookup(&(*ident).into(), region) { Ok(symbol) => { - output.references.value_lookups.insert(symbol); + output.references.insert_value_lookup(symbol); Var(symbol) } @@ -1269,7 +1265,7 @@ fn canonicalize_var_lookup( // Look it up in the env! match env.qualified_lookup(module_name, ident, region) { Ok(symbol) => { - output.references.value_lookups.insert(symbol); + output.references.insert_value_lookup(symbol); Var(symbol) } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index ba4ece29d5..4445fa6486 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -329,7 +329,7 @@ pub fn canonicalize_module_defs<'a>( let mut referenced_types = VecSet::default(); // Gather up all the symbols that were referenced across all the defs' lookups. - referenced_values.extend(output.references.value_lookups.iter().copied()); + referenced_values.extend(output.references.value_lookups().copied()); referenced_types.extend(output.references.type_lookups().copied()); // Gather up all the symbols that were referenced across all the defs' calls. @@ -528,7 +528,7 @@ pub fn canonicalize_module_defs<'a>( } // Incorporate any remaining output.lookups entries into references. - referenced_values.extend(output.references.value_lookups.iter().copied()); + referenced_values.extend(output.references.value_lookups().copied()); referenced_types.extend(output.references.type_lookups().copied()); // Incorporate any remaining output.calls entries into references. diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index d893f1a1bb..24028dab08 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -46,7 +46,7 @@ impl Procedure { pub struct References { pub bound_symbols: VecSet, type_lookups: VecSet, - pub value_lookups: VecSet, + value_lookups: VecSet, /// Aliases or opaque types referenced referenced_type_defs: VecSet, pub calls: VecSet, @@ -80,11 +80,23 @@ impl References { self.referenced_type_defs.contains(&symbol) } + pub fn insert_value_lookup(&mut self, symbol: Symbol) { + self.value_lookups.insert(symbol); + } + + pub fn remove_value_lookup(&mut self, symbol: &Symbol) { + self.value_lookups.remove(symbol); + } + pub fn insert_type_lookup(&mut self, symbol: Symbol) { self.type_lookups.insert(symbol); self.referenced_type_defs.insert(symbol); } + pub fn value_lookups(&self) -> impl Iterator { + self.value_lookups.iter() + } + pub fn type_lookups(&self) -> impl Iterator { self.type_lookups.iter() } From ab8ac2edadeba6ff4e03bdb2ecd020a92c069e0b Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 17:39:19 +0200 Subject: [PATCH 06/89] make bound_symbols private --- compiler/can/src/expr.rs | 5 ++--- compiler/can/src/pattern.rs | 12 ++++++------ compiler/can/src/procedure.rs | 10 +++++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index f808787a8c..282d981050 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -639,8 +639,7 @@ pub fn canonicalize_expr<'a>( loc_pattern.region, ); - bound_by_argument_patterns - .extend(new_output.references.bound_symbols.iter().copied()); + bound_by_argument_patterns.extend(new_output.references.bound_symbols().copied()); output.union(new_output); @@ -662,7 +661,7 @@ pub fn canonicalize_expr<'a>( captured_symbols.remove(&symbol); // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); + captured_symbols.retain(|s| !new_output.references.bound_symbols().any(|x| x == s)); captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); // filter out top-level symbols diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index f6885c15d0..dc8a11a5b0 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -172,7 +172,7 @@ pub fn canonicalize_def_header_pattern<'a>( region, ) { Ok((symbol, shadowing_ability_member)) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); let can_pattern = match shadowing_ability_member { // A fresh identifier. None => Pattern::Identifier(symbol), @@ -190,7 +190,7 @@ pub fn canonicalize_def_header_pattern<'a>( shadow: shadow.clone(), kind: ShadowKind::Variable, })); - output.references.bound_symbols.insert(new_symbol); + output.references.insert_bound(new_symbol); let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); (output, Loc::at(region, can_pattern)) @@ -220,7 +220,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); Pattern::Identifier(symbol) } @@ -230,7 +230,7 @@ pub fn canonicalize_pattern<'a>( shadow: shadow.clone(), kind: ShadowKind::Variable, })); - output.references.bound_symbols.insert(new_symbol); + output.references.insert_bound(new_symbol); Pattern::Shadowed(original_region, shadow, new_symbol) } @@ -460,7 +460,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); destructs.push(Loc { region: loc_pattern.region, @@ -531,7 +531,7 @@ pub fn canonicalize_pattern<'a>( ); // an optional field binds the symbol! - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); output.union(expr_output); diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 24028dab08..04a5efdb99 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -44,7 +44,7 @@ impl Procedure { /// so it's important that building the same code gives the same order every time! #[derive(Clone, Debug, Default, PartialEq)] pub struct References { - pub bound_symbols: VecSet, + bound_symbols: VecSet, type_lookups: VecSet, value_lookups: VecSet, /// Aliases or opaque types referenced @@ -84,6 +84,10 @@ impl References { self.value_lookups.insert(symbol); } + pub fn insert_bound(&mut self, symbol: Symbol) { + self.bound_symbols.insert(symbol); + } + pub fn remove_value_lookup(&mut self, symbol: &Symbol) { self.value_lookups.remove(symbol); } @@ -100,4 +104,8 @@ impl References { pub fn type_lookups(&self) -> impl Iterator { self.type_lookups.iter() } + + pub fn bound_symbols(&self) -> impl Iterator { + self.bound_symbols.iter() + } } From a47b3be9c0fd6ff03da0b60c08fef95dc6921b1e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 10:04:37 -0400 Subject: [PATCH 07/89] Revise some CLI flag descriptions --- cli/src/lib.rs | 19 ++++++++++--------- version.txt | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 6bb78f495a..e64aeb9d63 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -49,10 +49,12 @@ pub const ROC_DIR: &str = "ROC_DIR"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; +const VERSION: &str = include_str!("../../version.txt"); + pub fn build_app<'a>() -> App<'a> { let app = App::new("roc") - .version(concatcp!(include_str!("../../version.txt"), "\n")) - .about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!") + .version(concatcp!(VERSION, "\n")) + .about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!") .subcommand(App::new(CMD_BUILD) .about("Build a binary from the given .roc file, but don't run it") .arg( @@ -141,7 +143,7 @@ pub fn build_app<'a>() -> App<'a> { .about("Launch the interactive Read Eval Print Loop (REPL)") ) .subcommand(App::new(CMD_FORMAT) - .about("Format Roc code") + .about("Format a .roc file using standard Roc formatting") .arg( Arg::new(DIRECTORY_OR_FILES) .index(1) @@ -155,10 +157,9 @@ pub fn build_app<'a>() -> App<'a> { ) ) .subcommand(App::new(CMD_VERSION) - .about("Print version information") - ) + .about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION))) .subcommand(App::new(CMD_CHECK) - .about("When developing, it's recommended to run `check` before `build`. It may provide a useful error message in cases where `build` panics") + .about("Check the code for problems, but doesn’t build or run it") .arg( Arg::new(FLAG_TIME) .long(FLAG_TIME) @@ -193,19 +194,19 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_OPT_SIZE) .long(FLAG_OPT_SIZE) - .about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)") + .about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)") .required(false), ) .arg( Arg::new(FLAG_DEV) .long(FLAG_DEV) - .about("Make compilation as fast as possible. (Runtime performance may suffer)") + .about("Make compilation finish as soon as possible, at the expense of runtime performance.") .required(false), ) .arg( Arg::new(FLAG_DEBUG) .long(FLAG_DEBUG) - .about("Store LLVM debug information in the generated program") + .about("Store LLVM debug information in the generated program.") .requires(ROC_FILE) .required(false), ) diff --git a/version.txt b/version.txt index 27ffb11fc3..3fed276742 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ - \ No newline at end of file +(built from source) \ No newline at end of file From b92c28b237e5d77f838224ac15523f2d146ee989 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 17:43:18 +0200 Subject: [PATCH 08/89] make calls private --- compiler/can/src/def.rs | 4 ++-- compiler/can/src/expr.rs | 8 ++++---- compiler/can/src/module.rs | 4 ++-- compiler/can/src/procedure.rs | 10 +++++++++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 231ecabc1f..2b99f698d7 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1642,7 +1642,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> let mut stack = Vec::new(); if let Some(references) = closures.get(&symbol) { - for v in references.calls.iter() { + for v in references.calls() { stack.push(*v); } @@ -1658,7 +1658,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> // if it calls any functions if let Some(nested_references) = closures.get(&nested_symbol) { // add its called to the stack - for v in nested_references.calls.iter() { + for v in nested_references.calls() { stack.push(*v); } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 282d981050..e8db25e794 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -517,7 +517,7 @@ pub fn canonicalize_expr<'a>( let expr = match fn_expr.value { Var(symbol) => { - output.references.calls.insert(symbol); + output.references.insert_call(symbol); // we're tail-calling a symbol by name, check if it's the tail-callable symbol output.tail_call = match &env.tailcallable_symbol { @@ -1102,7 +1102,7 @@ pub fn local_successors_with_duplicates<'a>( ) -> Vec { let mut answer: Vec<_> = references.value_lookups().copied().collect(); - let mut stack: Vec<_> = references.calls.iter().copied().collect(); + let mut stack: Vec<_> = references.calls().copied().collect(); let mut seen = Vec::new(); while let Some(symbol) = stack.pop() { @@ -1112,7 +1112,7 @@ pub fn local_successors_with_duplicates<'a>( if let Some(references) = closures.get(&symbol) { answer.extend(references.value_lookups().copied()); - stack.extend(references.calls.iter().copied()); + stack.extend(references.calls().copied()); seen.push(symbol); } @@ -1720,7 +1720,7 @@ fn flatten_str_lines<'a>( Interpolated(loc_expr) => { if is_valid_interpolation(loc_expr.value) { // Interpolations desugar to Str.concat calls - output.references.calls.insert(Symbol::STR_CONCAT); + output.references.insert_call(Symbol::STR_CONCAT); if !buf.is_empty() { segments.push(StrSegment::Plaintext(buf.into())); diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 4445fa6486..fa901a6cec 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -333,7 +333,7 @@ pub fn canonicalize_module_defs<'a>( referenced_types.extend(output.references.type_lookups().copied()); // Gather up all the symbols that were referenced across all the defs' calls. - referenced_values.extend(output.references.calls); + referenced_values.extend(output.references.calls().copied()); // Gather up all the symbols that were referenced from other modules. referenced_values.extend(env.qualified_value_lookups.iter().copied()); @@ -532,7 +532,7 @@ pub fn canonicalize_module_defs<'a>( referenced_types.extend(output.references.type_lookups().copied()); // Incorporate any remaining output.calls entries into references. - referenced_values.extend(output.references.calls); + referenced_values.extend(output.references.calls().copied()); // Gather up all the symbols that were referenced from other modules. referenced_values.extend(env.qualified_value_lookups.iter().copied()); diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 04a5efdb99..1378a4a012 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -49,7 +49,7 @@ pub struct References { value_lookups: VecSet, /// Aliases or opaque types referenced referenced_type_defs: VecSet, - pub calls: VecSet, + calls: VecSet, } impl References { @@ -88,6 +88,10 @@ impl References { self.bound_symbols.insert(symbol); } + pub fn insert_call(&mut self, symbol: Symbol) { + self.calls.insert(symbol); + } + pub fn remove_value_lookup(&mut self, symbol: &Symbol) { self.value_lookups.remove(symbol); } @@ -108,4 +112,8 @@ impl References { pub fn bound_symbols(&self) -> impl Iterator { self.bound_symbols.iter() } + + pub fn calls(&self) -> impl Iterator { + self.calls.iter() + } } From 0267963e14bc5b83d8f00b0c794739afd86515ca Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 18:02:11 +0200 Subject: [PATCH 09/89] stop using referenced_type_defs --- compiler/can/src/procedure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 1378a4a012..34496bd09e 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -77,7 +77,7 @@ impl References { } pub fn references_type_def(&self, symbol: Symbol) -> bool { - self.referenced_type_defs.contains(&symbol) + self.type_lookups.contains(&symbol) } pub fn insert_value_lookup(&mut self, symbol: Symbol) { From ec43d7d770335b68b7bd4d3663a1986ac8351dbb Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 18:05:06 +0200 Subject: [PATCH 10/89] clippy --- compiler/can/src/annotation.rs | 2 +- compiler/can/src/def.rs | 1 + compiler/load_internal/src/file.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 1d83835d38..47f11353cf 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::{ImMap, MutMap, MutSet, SendMap, VecMap, VecSet}; +use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 2b99f698d7..9235cd32ca 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -97,6 +97,7 @@ enum PendingTypeDef<'a> { /// An invalid alias, that is ignored in the rest of the pipeline /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` /// with an incorrect pattern + #[allow(dead_code)] InvalidAlias { kind: AliasKind }, /// An invalid ability, that is ignored in the rest of the pipeline. diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index f8f60cfe4c..cdab508046 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -1110,7 +1110,7 @@ pub fn load<'a>( ) -> Result, LoadingProblem<'a>> { // When compiling to wasm, we cannot spawn extra threads // so we have a single-threaded implementation - if true || cfg!(target_family = "wasm") { + if cfg!(target_family = "wasm") { load_single_threaded( arena, load_start, From 9eddd23250e413fe20e498cb56667dde8caf0980 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 18:09:09 +0200 Subject: [PATCH 11/89] add vec_map --- compiler/collections/src/vec_map.rs | 133 ++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 compiler/collections/src/vec_map.rs diff --git a/compiler/collections/src/vec_map.rs b/compiler/collections/src/vec_map.rs new file mode 100644 index 0000000000..57ab5c31b8 --- /dev/null +++ b/compiler/collections/src/vec_map.rs @@ -0,0 +1,133 @@ +#[derive(Debug, Clone)] +pub struct VecMap { + keys: Vec, + values: Vec, +} + +impl Default for VecMap { + fn default() -> Self { + Self { + keys: Vec::new(), + values: Vec::new(), + } + } +} + +impl VecMap { + pub fn with_capacity(capacity: usize) -> Self { + Self { + keys: Vec::with_capacity(capacity), + values: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.len() + } + + pub fn is_empty(&self) -> bool { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.is_empty() + } + + pub fn swap_remove(&mut self, index: usize) -> (K, V) { + let k = self.keys.swap_remove(index); + let v = self.values.swap_remove(index); + + (k, v) + } + + pub fn insert(&mut self, key: K, mut value: V) -> Option { + match self.keys.iter().position(|x| x == &key) { + Some(index) => { + std::mem::swap(&mut value, &mut self.values[index]); + + Some(value) + } + None => { + self.keys.push(key); + self.values.push(value); + + None + } + } + } + + pub fn contains(&self, key: &K) -> bool { + self.keys.contains(key) + } + + pub fn remove(&mut self, key: &K) { + match self.keys.iter().position(|x| x == key) { + None => { + // just do nothing + } + Some(index) => { + self.swap_remove(index); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.keys.iter().zip(self.values.iter()) + } + + pub fn values(&self) -> impl Iterator { + self.values.iter() + } +} + +impl Extend<(K, V)> for VecMap { + #[inline(always)] + fn extend>(&mut self, iter: T) { + let it = iter.into_iter(); + let hint = it.size_hint(); + + match hint { + (0, Some(0)) => { + // done, do nothing + } + (1, Some(1)) | (2, Some(2)) => { + for (k, v) in it { + self.insert(k, v); + } + } + (_min, _opt_max) => { + // TODO do this with sorting and dedup? + for (k, v) in it { + self.insert(k, v); + } + } + } + } +} + +impl IntoIterator for VecMap { + type Item = (K, V); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + keys: self.keys.into_iter(), + values: self.values.into_iter(), + } + } +} + +pub struct IntoIter { + keys: std::vec::IntoIter, + values: std::vec::IntoIter, +} + +impl Iterator for IntoIter { + type Item = (K, V); + + fn next(&mut self) -> Option { + match (self.keys.next(), self.values.next()) { + (Some(k), Some(v)) => Some((k, v)), + _ => None, + } + } +} From 19dce40cd18b31e5a58db394716fa81c7b48e569 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 18:11:44 +0200 Subject: [PATCH 12/89] fix formatting --- compiler/can/src/annotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 47f11353cf..07b1faac64 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet}; +use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; From b557929276e65cb2163e93f594246a604481bbdf Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 20:22:02 +0200 Subject: [PATCH 13/89] compact References into just two allocations --- compiler/can/src/procedure.rs | 183 ++++++++++++++++++++++++---------- 1 file changed, 128 insertions(+), 55 deletions(-) diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 34496bd09e..97cf8f762b 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -1,6 +1,5 @@ use crate::expr::Expr; use crate::pattern::Pattern; -use roc_collections::VecSet; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; @@ -39,81 +38,155 @@ impl Procedure { } } -/// These are all ordered sets because they end up getting traversed in a graph search -/// to determine how defs should be ordered. We want builds to be reproducible, -/// so it's important that building the same code gives the same order every time! -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Debug, Default, Clone, Copy)] +struct ReferencesBitflags(u8); + +impl ReferencesBitflags { + const VALUE_LOOKUP: Self = ReferencesBitflags(1); + const TYPE_LOOKUP: Self = ReferencesBitflags(2); + const CALL: Self = ReferencesBitflags(4); + const BOUND: Self = ReferencesBitflags(8); +} + +#[derive(Clone, Debug, Default)] pub struct References { - bound_symbols: VecSet, - type_lookups: VecSet, - value_lookups: VecSet, - /// Aliases or opaque types referenced - referenced_type_defs: VecSet, - calls: VecSet, + symbols: Vec, + bitflags: Vec, } impl References { - pub fn new() -> References { + pub fn new() -> Self { Self::default() } - pub fn union_mut(&mut self, other: &References) { - self.value_lookups - .extend(other.value_lookups.iter().copied()); - self.type_lookups.extend(other.type_lookups.iter().copied()); - self.calls.extend(other.calls.iter().copied()); - self.bound_symbols - .extend(other.bound_symbols.iter().copied()); - self.referenced_type_defs - .extend(other.referenced_type_defs.iter().copied()); + pub fn union_mut(&mut self, other: &Self) { + for (k, v) in other.symbols.iter().zip(other.bitflags.iter()) { + self.insert(*k, *v); + } } - pub fn has_value_lookup(&self, symbol: Symbol) -> bool { - self.value_lookups.contains(&symbol) - } + // iterators - pub fn has_type_lookup(&self, symbol: Symbol) -> bool { - self.type_lookups.contains(&symbol) - } - - pub fn references_type_def(&self, symbol: Symbol) -> bool { - self.type_lookups.contains(&symbol) - } - - pub fn insert_value_lookup(&mut self, symbol: Symbol) { - self.value_lookups.insert(symbol); - } - - pub fn insert_bound(&mut self, symbol: Symbol) { - self.bound_symbols.insert(symbol); - } - - pub fn insert_call(&mut self, symbol: Symbol) { - self.calls.insert(symbol); - } - - pub fn remove_value_lookup(&mut self, symbol: &Symbol) { - self.value_lookups.remove(symbol); - } - - pub fn insert_type_lookup(&mut self, symbol: Symbol) { - self.type_lookups.insert(symbol); - self.referenced_type_defs.insert(symbol); + fn retain<'a, P: Fn(&'a ReferencesBitflags) -> bool>( + &'a self, + pred: P, + ) -> impl Iterator { + self.symbols + .iter() + .zip(self.bitflags.iter()) + .filter_map(move |(a, b)| if pred(b) { Some(a) } else { None }) } pub fn value_lookups(&self) -> impl Iterator { - self.value_lookups.iter() + self.retain(|b| b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0) } pub fn type_lookups(&self) -> impl Iterator { - self.type_lookups.iter() + self.retain(|b| b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0) } pub fn bound_symbols(&self) -> impl Iterator { - self.bound_symbols.iter() + self.retain(|b| b.0 & ReferencesBitflags::BOUND.0 > 0) } pub fn calls(&self) -> impl Iterator { - self.calls.iter() + self.retain(|b| b.0 & ReferencesBitflags::CALL.0 > 0) + } + + // insert + + fn insert(&mut self, symbol: Symbol, flags: ReferencesBitflags) { + match self.symbols.iter().position(|x| *x == symbol) { + None => { + self.symbols.push(symbol); + self.bitflags.push(flags); + } + Some(index) => { + // idea: put some debug_asserts in here? + self.bitflags[index].0 |= flags.0; + } + } + } + + pub fn insert_value_lookup(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::VALUE_LOOKUP); + } + + pub fn insert_type_lookup(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::TYPE_LOOKUP); + } + + pub fn insert_bound(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::BOUND); + } + + pub fn insert_call(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::CALL); + } + + // remove + + pub fn remove_value_lookup(&mut self, symbol: &Symbol) { + match self.symbols.iter().position(|x| x == symbol) { + None => { + // it's not in there; do nothing + } + Some(index) => { + // idea: put some debug_asserts in here? + self.bitflags[index].0 ^= ReferencesBitflags::VALUE_LOOKUP.0; + } + } + } + + // contains + + pub fn has_value_lookup(&self, symbol: Symbol) -> bool { + // println!("has a value lookup? {} {:?}", self.symbols.len(), symbol); + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0 { + return true; + } + } + + false + } + + pub fn has_type_lookup(&self, symbol: Symbol) -> bool { + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0 { + return true; + } + } + + false + } + + pub fn has_type_or_value_lookup(&self, symbol: Symbol) -> bool { + // if self.symbols.len() > 100 { + // panic!() + // } + // println!( + // "has a type or value lookup? {} {:?}", + // self.symbols.len(), + // symbol + // ); + let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0; + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & mask > 0 { + return true; + } + } + + false + } + + pub fn references_type_def(&self, symbol: Symbol) -> bool { + self.has_type_lookup(symbol) } } From 9d17a075d98f09b4852d4d239fe415aa8bba68d3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 20:22:52 +0200 Subject: [PATCH 14/89] halve the number of lookups into References --- compiler/can/src/def.rs | 3 +-- compiler/can/src/expr.rs | 6 ++---- compiler/can/src/module.rs | 3 +-- compiler/can/src/procedure.rs | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 9235cd32ca..3d18978806 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1594,8 +1594,7 @@ pub fn can_defs_with_return<'a>( // Now that we've collected all the references, check to see if any of the new idents // we defined went unused by the return expression. If any were unused, report it. for (symbol, region) in symbols_introduced { - if !output.references.has_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, region)); diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index e8db25e794..be8dd26648 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1072,10 +1072,8 @@ fn canonicalize_when_branch<'a>( for (symbol, region) in scope.symbols() { let symbol = *symbol; - if !output.references.has_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) - && !branch_output.references.has_value_lookup(symbol) - && !branch_output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) + && !branch_output.references.has_type_or_value_lookup(symbol) && !original_scope.contains_symbol(symbol) && !scope.abilities_store.is_specialization_name(symbol) { diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index fa901a6cec..b34aba4fc5 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -302,8 +302,7 @@ pub fn canonicalize_module_defs<'a>( // See if any of the new idents we defined went unused. // If any were unused and also not exposed, report it. for (symbol, region) in symbols_introduced { - if !output.references.has_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) && !exposed_symbols.contains(&symbol) && !scope.abilities_store.is_specialization_name(symbol) { diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 97cf8f762b..778688d74f 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -153,7 +153,7 @@ impl References { false } - pub fn has_type_lookup(&self, symbol: Symbol) -> bool { + fn has_type_lookup(&self, symbol: Symbol) -> bool { let it = self.symbols.iter().zip(self.bitflags.iter()); for (a, b) in it { From e87ca7e4b7f853e55f8778888df290caf1e8ceb0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 20 Apr 2022 21:20:59 +0200 Subject: [PATCH 15/89] create and union fewer Output's --- compiler/can/src/def.rs | 46 +++++++++++++++++++++++++------------ compiler/can/src/expr.rs | 19 +++++++-------- compiler/can/src/pattern.rs | 45 +++++++++++++++++++----------------- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 3d18978806..aca0206831 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -564,9 +564,17 @@ pub fn canonicalize_defs<'a>( // once we've finished assembling the entire scope. let mut pending_value_defs = Vec::with_capacity(value_defs.len()); for loc_def in value_defs.into_iter() { - match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) { + let mut new_output = Output::default(); + match to_pending_value_def( + env, + var_store, + loc_def.value, + &mut scope, + &mut new_output, + pattern_type, + ) { None => { /* skip */ } - Some((new_output, pending_def)) => { + Some(pending_def) => { // store the top-level defs, used to ensure that closures won't capture them if let PatternType::TopLevelDef = pattern_type { match &pending_def { @@ -1806,41 +1814,46 @@ fn to_pending_value_def<'a>( var_store: &mut VarStore, def: &'a ast::ValueDef<'a>, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, -) -> Option<(Output, PendingValueDef<'a>)> { +) -> Option> { use ast::ValueDef::*; match def { Annotation(loc_pattern, loc_ann) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - Some(( - output, - PendingValueDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), + Some(PendingValueDef::AnnotationOnly( + loc_pattern, + loc_can_pattern, + loc_ann, )) } Body(loc_pattern, loc_expr) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - Some(( - output, - PendingValueDef::Body(loc_pattern, loc_can_pattern, loc_expr), + Some(PendingValueDef::Body( + loc_pattern, + loc_can_pattern, + loc_expr, )) } @@ -1859,18 +1872,21 @@ fn to_pending_value_def<'a>( // { x, y ? False } = rec // // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &body_pattern.value, body_pattern.region, ); - Some(( - output, - PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr), + Some(PendingValueDef::TypedBody( + body_pattern, + loc_can_pattern, + ann_type, + body_expr, )) } else { // the pattern of the annotation does not match the pattern of the body direc diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index be8dd26648..10e9479f23 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -627,25 +627,23 @@ pub fn canonicalize_expr<'a>( let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); let mut output = Output::default(); - let mut bound_by_argument_patterns = MutSet::default(); - for loc_pattern in loc_arg_patterns.iter() { - let (new_output, can_arg) = canonicalize_pattern( + let can_argument_pattern = canonicalize_pattern( env, var_store, &mut scope, + &mut output, FunctionArg, &loc_pattern.value, loc_pattern.region, ); - bound_by_argument_patterns.extend(new_output.references.bound_symbols().copied()); - - output.union(new_output); - - can_args.push((var_store.fresh(), can_arg)); + can_args.push((var_store.fresh(), can_argument_pattern)); } + let bound_by_argument_patterns: Vec<_> = + output.references.bound_symbols().copied().collect(); + let (loc_body_expr, new_output) = canonicalize_expr( env, var_store, @@ -1034,17 +1032,16 @@ fn canonicalize_when_branch<'a>( // TODO report symbols not bound in all patterns for loc_pattern in branch.patterns.iter() { - let (new_output, can_pattern) = canonicalize_pattern( + let can_pattern = canonicalize_pattern( env, var_store, &mut scope, + output, WhenBranch, &loc_pattern.value, loc_pattern.region, ); - output.union(new_output); - patterns.push(can_pattern); } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index dc8a11a5b0..9804707b2c 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -156,13 +156,13 @@ pub fn canonicalize_def_header_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> (Output, Loc) { +) -> Loc { use roc_parse::ast::Pattern::*; - let mut output = Output::default(); match pattern { // Identifiers that shadow ability members may appear (and may only appear) at the header of a def. Identifier(name) => match scope.introduce_or_shadow_ability_member( @@ -182,7 +182,7 @@ pub fn canonicalize_def_header_pattern<'a>( specializes: ability_member_name, }, }; - (output, Loc::at(region, can_pattern)) + Loc::at(region, can_pattern) } Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { @@ -193,10 +193,10 @@ pub fn canonicalize_def_header_pattern<'a>( output.references.insert_bound(new_symbol); let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); - (output, Loc::at(region, can_pattern)) + Loc::at(region, can_pattern) } }, - _ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region), + _ => canonicalize_pattern(env, var_store, scope, output, pattern_type, pattern, region), } } @@ -204,14 +204,14 @@ pub fn canonicalize_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> (Output, Loc) { +) -> Loc { use roc_parse::ast::Pattern::*; use PatternType::*; - let mut output = Output::default(); let can_pattern = match pattern { Identifier(name) => match scope.introduce( (*name).into(), @@ -266,17 +266,16 @@ pub fn canonicalize_pattern<'a>( Apply(tag, patterns) => { let mut can_patterns = Vec::with_capacity(patterns.len()); for loc_pattern in *patterns { - let (new_output, can_pattern) = canonicalize_pattern( + let can_pattern = canonicalize_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - output.union(new_output); - can_patterns.push((var_store.fresh(), can_pattern)); } @@ -442,7 +441,15 @@ pub fn canonicalize_pattern<'a>( } SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { - return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region) + return canonicalize_pattern( + env, + var_store, + scope, + output, + pattern_type, + sub_pattern, + region, + ) } RecordDestructure(patterns) => { let ext_var = var_store.fresh(); @@ -492,17 +499,16 @@ pub fn canonicalize_pattern<'a>( RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! let symbol = scope.ignore(label.into(), &mut env.ident_ids); - let (new_output, can_guard) = canonicalize_pattern( + let can_guard = canonicalize_pattern( env, var_store, scope, + output, pattern_type, &loc_guard.value, loc_guard.region, ); - output.union(new_output); - destructs.push(Loc { region: loc_pattern.region, value: RecordDestruct { @@ -597,13 +603,10 @@ pub fn canonicalize_pattern<'a>( } }; - ( - output, - Loc { - region, - value: can_pattern, - }, - ) + Loc { + region, + value: can_pattern, + } } /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't From 62484d3890a62fc264c0643c56a9c20c7df46f5d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 13:50:00 -0400 Subject: [PATCH 16/89] Add `roc run` to run even if there are build errors. --- cli/src/build.rs | 46 ++++--------- cli/src/lib.rs | 126 +++++++++++++++++++++------------- cli/src/main.rs | 28 ++++++-- compiler/build/src/program.rs | 32 +++++++-- 4 files changed, 141 insertions(+), 91 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index 0ccf118178..a898d7eb6f 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -1,7 +1,7 @@ use bumpalo::Bump; use roc_build::{ link::{link, rebuild_host, LinkType}, - program, + program::{self, Problems}, }; use roc_builtins::bitcode; use roc_load::LoadingProblem; @@ -21,25 +21,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) { )); } -pub enum BuildOutcome { - NoProblems, - OnlyWarnings, - Errors, -} - -impl BuildOutcome { - pub fn status_code(&self) -> i32 { - match self { - Self::NoProblems => 0, - Self::OnlyWarnings => 1, - Self::Errors => 2, - } - } -} - pub struct BuiltFile { pub binary_path: PathBuf, - pub outcome: BuildOutcome, + pub problems: Problems, pub total_time: Duration, } @@ -184,7 +168,7 @@ pub fn build_file<'a>( // This only needs to be mutable for report_problems. This can't be done // inside a nested scope without causing a borrow error! let mut loaded = loaded; - program::report_problems_monomorphized(&mut loaded); + let problems = program::report_problems_monomorphized(&mut loaded); let loaded = loaded; let code_gen_timing = program::gen_from_mono_module( @@ -243,7 +227,7 @@ pub fn build_file<'a>( // Step 2: link the precompiled host and compiled app let link_start = SystemTime::now(); - let outcome = if surgically_link { + let problems = if surgically_link { roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path) .map_err(|err| { todo!( @@ -251,12 +235,12 @@ pub fn build_file<'a>( err ); })?; - BuildOutcome::NoProblems + problems } else if matches!(link_type, LinkType::None) { // Just copy the object file to the output folder. binary_path.set_extension(app_extension); std::fs::copy(app_o_file, &binary_path).unwrap(); - BuildOutcome::NoProblems + problems } else { let mut inputs = vec![ host_input_path.as_path().to_str().unwrap(), @@ -281,11 +265,15 @@ pub fn build_file<'a>( todo!("gracefully handle error after `ld` spawned"); })?; - // TODO change this to report whether there were errors or warnings! if exit_status.success() { - BuildOutcome::NoProblems + problems } else { - BuildOutcome::Errors + let mut problems = problems; + + // Add an error for `ld` failing + problems.errors += 1; + + problems } }; let linking_time = link_start.elapsed().unwrap(); @@ -298,7 +286,7 @@ pub fn build_file<'a>( Ok(BuiltFile { binary_path, - outcome, + problems, total_time, }) } @@ -350,10 +338,6 @@ fn spawn_rebuild_thread( } let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); - if !precompiled { - println!("Done!"); - } - rebuild_host_end.as_millis() }) } @@ -364,7 +348,7 @@ pub fn check_file( src_dir: PathBuf, roc_file_path: PathBuf, emit_timings: bool, -) -> Result { +) -> Result { let compilation_start = SystemTime::now(); // only used for generating errors. We don't do code generation, so hardcoding should be fine diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e64aeb9d63..6a503b80b5 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate const_format; -use build::{BuildOutcome, BuiltFile}; +use build::BuiltFile; use bumpalo::Bump; use clap::{App, AppSettings, Arg, ArgMatches}; use roc_build::link::LinkType; @@ -24,6 +24,7 @@ mod format; pub use format::format; pub const CMD_BUILD: &str = "build"; +pub const CMD_RUN: &str = "run"; pub const CMD_REPL: &str = "repl"; pub const CMD_EDIT: &str = "edit"; pub const CMD_DOCS: &str = "docs"; @@ -142,6 +143,14 @@ pub fn build_app<'a>() -> App<'a> { .subcommand(App::new(CMD_REPL) .about("Launch the interactive Read Eval Print Loop (REPL)") ) + .subcommand(App::new(CMD_RUN) + .about("Run a .roc file even if it has build errors") + .arg( + Arg::new(ROC_FILE) + .about("The .roc file of an app to run") + .required(true), + ) + ) .subcommand(App::new(CMD_FORMAT) .about("Format a .roc file using standard Roc formatting") .arg( @@ -168,7 +177,7 @@ pub fn build_app<'a>() -> App<'a> { ) .arg( Arg::new(ROC_FILE) - .about("The .roc file of an app to run") + .about("The .roc file of an app to check") .required(true), ) ) @@ -273,6 +282,7 @@ pub fn docs(files: Vec) { pub enum BuildConfig { BuildOnly, BuildAndRun { roc_file_arg_index: usize }, + BuildAndRunIfNoErrors { roc_file_arg_index: usize }, } pub enum FormatMode { @@ -380,7 +390,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { match res_binary_path { Ok(BuiltFile { binary_path, - outcome, + problems, total_time, }) => { match config { @@ -401,50 +411,26 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { ); // Return a nonzero exit code if there were problems - Ok(outcome.status_code()) + Ok(problems.exit_code()) } - BuildAndRun { roc_file_arg_index } => { - let mut cmd = match triple.architecture { - Architecture::Wasm32 => { - // If possible, report the generated executable name relative to the current dir. - let generated_filename = binary_path - .strip_prefix(env::current_dir().unwrap()) - .unwrap_or(&binary_path); - - // No need to waste time freeing this memory, - // since the process is about to exit anyway. - std::mem::forget(arena); - - let args = std::env::args() - .skip(roc_file_arg_index) - .collect::>(); - - run_with_wasmer(generated_filename, &args); - return Ok(0); - } - _ => Command::new(&binary_path), - }; - - if let Architecture::Wasm32 = triple.architecture { - cmd.arg(binary_path); - } - - // Forward all the arguments after the .roc file argument - // to the new process. This way, you can do things like: - // - // roc app.roc foo bar baz - // - // ...and have it so that app.roc will receive only `foo`, - // `bar`, and `baz` as its arguments. - for (index, arg) in std::env::args().enumerate() { - if index > roc_file_arg_index { - cmd.arg(arg); - } - } - - match outcome { - BuildOutcome::Errors => Ok(outcome.status_code()), - _ => roc_run(cmd.current_dir(original_cwd)), + BuildAndRun { roc_file_arg_index } => roc_run( + &arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ), + BuildAndRunIfNoErrors { roc_file_arg_index } => { + if problems.errors == 0 { + roc_run( + &arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ) + } else { + Ok(problems.exit_code()) } } } @@ -461,11 +447,55 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } #[cfg(target_family = "unix")] -fn roc_run(cmd: &mut Command) -> io::Result { +fn roc_run( + arena: &Bump, + cwd: &Path, + triple: Triple, + roc_file_arg_index: usize, + binary_path: &Path, +) -> io::Result { use std::os::unix::process::CommandExt; + let mut cmd = match triple.architecture { + Architecture::Wasm32 => { + // If possible, report the generated executable name relative to the current dir. + let generated_filename = binary_path + .strip_prefix(env::current_dir().unwrap()) + .unwrap_or(&binary_path); + + // No need to waste time freeing this memory, + // since the process is about to exit anyway. + std::mem::forget(arena); + + let args = std::env::args() + .skip(roc_file_arg_index) + .collect::>(); + + run_with_wasmer(generated_filename, &args); + return Ok(0); + } + _ => Command::new(&binary_path), + }; + + if let Architecture::Wasm32 = triple.architecture { + cmd.arg(binary_path); + } + + // Forward all the arguments after the .roc file argument + // to the new process. This way, you can do things like: + // + // roc app.roc foo bar baz + // + // ...and have it so that app.roc will receive only `foo`, + // `bar`, and `baz` as its arguments. + for (index, arg) in std::env::args().enumerate() { + if index > roc_file_arg_index { + cmd.arg(arg); + } + } + // This is much faster than spawning a subprocess if we're on a UNIX system! - let err = cmd.exec(); + let err = cmd.current_dir(cwd).exec(); // If exec actually returned, it was definitely an error! (Otherwise, // this process would have been replaced by the other one, and we'd diff --git a/cli/src/main.rs b/cli/src/main.rs index 6a5c984ef3..871e93af9a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,8 @@ use roc_cli::build::check_file; use roc_cli::{ build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, - CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, ROC_FILE, + CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, + ROC_FILE, }; use roc_load::LoadingProblem; use std::fs::{self, FileType}; @@ -27,7 +28,10 @@ fn main() -> io::Result<()> { Some(arg_index) => { let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! - build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + build( + &matches, + BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index }, + ) } None => { @@ -37,6 +41,21 @@ fn main() -> io::Result<()> { } } } + Some((CMD_RUN, matches)) => { + match matches.index_of(ROC_FILE) { + Some(arg_index) => { + let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! + + build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + } + + None => { + eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command."); + + Ok(1) + } + } + } Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?), Some((CMD_CHECK, matches)) => { let arena = bumpalo::Bump::new(); @@ -47,10 +66,7 @@ fn main() -> io::Result<()> { let src_dir = roc_file_path.parent().unwrap().to_owned(); match check_file(&arena, src_dir, roc_file_path, emit_timings) { - Ok(number_of_errors) => { - let exit_code = if number_of_errors != 0 { 1 } else { 0 }; - Ok(exit_code) - } + Ok(problems) => Ok(problems.exit_code()), Err(LoadingProblem::FormattedReport(report)) => { print!("{}", report); diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 518bc0c3e4..3d8ac5e456 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -28,7 +28,7 @@ const LLVM_VERSION: &str = "12"; // them after type checking (like Elm does) so we can complete the entire // `roc check` process without needing to monomorphize. /// Returns the number of problems reported. -pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { +pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Problems { report_problems_help( loaded.total_problems(), &loaded.sources, @@ -39,7 +39,7 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize ) } -pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { +pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> Problems { report_problems_help( loaded.total_problems(), &loaded.sources, @@ -50,6 +50,23 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { ) } +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Problems { + pub errors: usize, + pub warnings: usize, +} + +impl Problems { + pub fn exit_code(&self) -> i32 { + // 0 means no problems, 1 means errors, 2 means warnings + if self.errors > 0 { + 1 + } else { + self.warnings.min(1) as i32 + } + } +} + fn report_problems_help( total_problems: usize, sources: &MutMap)>, @@ -57,7 +74,7 @@ fn report_problems_help( can_problems: &mut MutMap>, type_problems: &mut MutMap>, mono_problems: &mut MutMap>, -) -> usize { +) -> Problems { use roc_reporting::report::{ can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*, DEFAULT_PALETTE, @@ -144,13 +161,13 @@ fn report_problems_help( if errors.is_empty() { problems_reported = warnings.len(); - for warning in warnings { + for warning in warnings.iter() { println!("\n{}\n", warning); } } else { problems_reported = errors.len(); - for error in errors { + for error in errors.iter() { println!("\n{}\n", error); } } @@ -165,7 +182,10 @@ fn report_problems_help( println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette)); } - problems_reported + Problems { + errors: errors.len(), + warnings: warnings.len(), + } } #[cfg(not(feature = "llvm"))] From 4d11e7cbe6580eb84f6063f813739b9d66ab7be9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 16:47:25 -0400 Subject: [PATCH 17/89] Revamp notes printed after compilation finishes. --- cli/src/build.rs | 9 ++-- cli/src/lib.rs | 116 +++++++++++++++++++++++++++++++++++++++++++---- cli/src/main.rs | 31 ++++++++++++- 3 files changed, 142 insertions(+), 14 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index a898d7eb6f..be2bd0736e 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -306,7 +306,7 @@ fn spawn_rebuild_thread( let thread_local_target = target.clone(); std::thread::spawn(move || { if !precompiled { - print!("🔨 Rebuilding host... "); + println!("🔨 Rebuilding host..."); } let rebuild_host_start = SystemTime::now(); @@ -348,7 +348,7 @@ pub fn check_file( src_dir: PathBuf, roc_file_path: PathBuf, emit_timings: bool, -) -> Result { +) -> Result<(program::Problems, Duration), LoadingProblem> { let compilation_start = SystemTime::now(); // only used for generating errors. We don't do code generation, so hardcoding should be fine @@ -421,5 +421,8 @@ pub fn check_file( println!("Finished checking in {} ms\n", compilation_end.as_millis(),); } - Ok(program::report_problems_typechecked(&mut loaded)) + Ok(( + program::report_problems_typechecked(&mut loaded), + compilation_end, + )) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 6a503b80b5..e92ff626a5 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -405,23 +405,91 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { std::mem::forget(arena); println!( - "🎉 Built {} in {} ms", - generated_filename.to_str().unwrap(), - total_time.as_millis() + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while sucessfully building:\n\n {}", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + generated_filename.to_str().unwrap() ); // Return a nonzero exit code if there were problems Ok(problems.exit_code()) } - BuildAndRun { roc_file_arg_index } => roc_run( - &arena, - &original_cwd, - triple, - roc_file_arg_index, - &binary_path, - ), + BuildAndRun { roc_file_arg_index } => { + if problems.errors > 0 || problems.warnings > 0 { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + "─".repeat(80) + ); + } + + roc_run( + &arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ) + } BuildAndRunIfNoErrors { roc_file_arg_index } => { if problems.errors == 0 { + if problems.warnings > 0 { + println!( + "\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m", + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + "─".repeat(80) + ); + } + roc_run( &arena, &original_cwd, @@ -430,6 +498,34 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { &binary_path, ) } else { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with: \x1B[32mroc run {}\x1B[39m", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + filename + ); + Ok(problems.exit_code()) } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 871e93af9a..a657aedc3d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -66,7 +66,36 @@ fn main() -> io::Result<()> { let src_dir = roc_file_path.parent().unwrap().to_owned(); match check_file(&arena, src_dir, roc_file_path, emit_timings) { - Ok(problems) => Ok(problems.exit_code()), + Ok((problems, total_time)) => { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + ); + + Ok(problems.exit_code()) + } Err(LoadingProblem::FormattedReport(report)) => { print!("{}", report); From 729aab21a1801c776d0c230d8803e79351d519e8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 17:06:17 -0400 Subject: [PATCH 18/89] Don't try to mem::forget a reference --- cli/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e92ff626a5..fce91935d9 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -467,7 +467,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } roc_run( - &arena, + arena, &original_cwd, triple, roc_file_arg_index, @@ -491,7 +491,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } roc_run( - &arena, + arena, &original_cwd, triple, roc_file_arg_index, @@ -544,7 +544,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { #[cfg(target_family = "unix")] fn roc_run( - arena: &Bump, + arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it! cwd: &Path, triple: Triple, roc_file_arg_index: usize, From 41fafd85fd7ec92a2a578cab575e2a076449c08a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 17:06:20 -0400 Subject: [PATCH 19/89] C L I P P Y --- cli/src/lib.rs | 2 +- cli/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index fce91935d9..ced6c61823 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -557,7 +557,7 @@ fn roc_run( // If possible, report the generated executable name relative to the current dir. let generated_filename = binary_path .strip_prefix(env::current_dir().unwrap()) - .unwrap_or(&binary_path); + .unwrap_or(binary_path); // No need to waste time freeing this memory, // since the process is about to exit anyway. diff --git a/cli/src/main.rs b/cli/src/main.rs index a657aedc3d..b38445980b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -46,7 +46,7 @@ fn main() -> io::Result<()> { Some(arg_index) => { let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! - build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + build(matches, BuildConfig::BuildAndRun { roc_file_arg_index }) } None => { From 6c1e8d3789d5860a65fe7dd051e9e64217730b29 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 17:14:22 -0400 Subject: [PATCH 20/89] fix typo --- cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ced6c61823..25f831d1f0 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -405,7 +405,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { std::mem::forget(arena); println!( - "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while sucessfully building:\n\n {}", + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while successfully building:\n\n {}", if problems.errors == 0 { 32 // green } else { From a07323fb40b7ffef3b33948bb4d4b9489da96db6 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 13:03:08 -0400 Subject: [PATCH 21/89] Typecheck annotations with able variables outside ability members --- compiler/can/src/annotation.rs | 34 ++---------------- compiler/can/src/def.rs | 35 +++++++++++++----- compiler/solve/tests/solve_expr.rs | 57 ++++++++++++++++++++++++++++++ compiler/types/src/pretty_print.rs | 2 ++ 4 files changed, 88 insertions(+), 40 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 97b74f2101..09395d3e3f 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -187,37 +187,8 @@ fn malformed(env: &mut Env, region: Region, name: &str) { env.problem(roc_problem::can::Problem::RuntimeError(problem)); } +/// Canonicalizes a top-level type annotation. pub fn canonicalize_annotation( - env: &mut Env, - scope: &mut Scope, - annotation: &roc_parse::ast::TypeAnnotation, - region: Region, - var_store: &mut VarStore, -) -> Annotation { - let mut introduced_variables = IntroducedVariables::default(); - let mut references = VecSet::default(); - let mut aliases = SendMap::default(); - - let typ = can_annotation_help( - env, - annotation, - region, - scope, - var_store, - &mut introduced_variables, - &mut aliases, - &mut references, - ); - - Annotation { - typ, - introduced_variables, - references, - aliases, - } -} - -pub fn canonicalize_annotation_with_possible_clauses( env: &mut Env, scope: &mut Scope, annotation: &TypeAnnotation, @@ -828,8 +799,7 @@ fn can_annotation_help( Where(_annotation, clauses) => { debug_assert!(!clauses.is_empty()); - // Has clauses are allowed only on the top level of an ability member signature (for - // now), which we handle elsewhere. + // Has clauses are allowed only on the top level of a signature, which we handle elsewhere. env.problem(roc_problem::can::Problem::IllegalHasClause { region: Region::across_all(clauses.iter().map(|clause| &clause.region)), }); diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 2cc1cd85ae..6b9c4450c5 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,6 +1,5 @@ use crate::abilities::MemberVariables; use crate::annotation::canonicalize_annotation; -use crate::annotation::canonicalize_annotation_with_possible_clauses; use crate::annotation::IntroducedVariables; use crate::env::Env; use crate::expr::ClosureData; @@ -318,8 +317,14 @@ pub fn canonicalize_defs<'a>( match type_defs.remove(&type_name).unwrap() { TypeDef::AliasLike(name, vars, ann, kind) => { let symbol = name.value; - let can_ann = - canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store); + let can_ann = canonicalize_annotation( + env, + &mut scope, + &ann.value, + ann.region, + var_store, + &abilities_in_scope, + ); // Does this alias reference any abilities? For now, we don't permit that. let ability_references = can_ann @@ -437,7 +442,7 @@ pub fn canonicalize_defs<'a>( let mut can_members = Vec::with_capacity(members.len()); for member in members { - let member_annot = canonicalize_annotation_with_possible_clauses( + let member_annot = canonicalize_annotation( env, &mut scope, &member.typ.value, @@ -605,6 +610,7 @@ pub fn canonicalize_defs<'a>( var_store, &mut refs_by_symbol, &mut aliases, + &abilities_in_scope, ); // TODO we should do something with these references; they include @@ -1148,6 +1154,7 @@ fn canonicalize_pending_value_def<'a>( var_store: &mut VarStore, refs_by_symbol: &mut MutMap, aliases: &mut ImMap, + abilities_in_scope: &[Symbol], ) -> Output { use PendingValueDef::*; @@ -1159,8 +1166,14 @@ fn canonicalize_pending_value_def<'a>( AnnotationOnly(_, loc_can_pattern, loc_ann) => { // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them - let type_annotation = - canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); // Record all the annotation's references in output.references.lookups @@ -1280,8 +1293,14 @@ fn canonicalize_pending_value_def<'a>( } TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - let type_annotation = - canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + &abilities_in_scope, + ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 54bd3626d6..b31b6502b7 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5951,4 +5951,61 @@ mod solve_expr { "{ tag : [ A, B ] }a -> { tag : [ A, B ] }a", ) } + + #[test] + fn ability_constrained_in_non_member_check() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hashEq ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq : a, a -> Bool | a has Hash + hashEq = \x, y -> hash x == hash y + "# + ), + "a, a -> Bool | a has Hash", + ) + } + + #[test] + fn ability_constrained_in_non_member_infer() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hashEq ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq = \x, y -> hash x == hash y + "# + ), + "a, a -> Bool | a has Hash", + ) + } + + #[test] + fn ability_constrained_in_non_member_infer_usage() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq = \x, y -> hash x == hash y + + Id := U64 + hash = \$Id n -> n + + result = hashEq ($Id 100) ($Id 101) + "# + ), + "Bool", + ) + } } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 998684d777..ddcb05895a 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -307,6 +307,8 @@ pub fn content_to_string( write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary); + ctx.able_variables.sort(); + ctx.able_variables.dedup(); for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() { buf.push_str(if i == 0 { " | " } else { ", " }); buf.push_str(var); From b9f79fdd31fa65be79dfe714d32d8cc75e666da2 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 13:09:40 -0400 Subject: [PATCH 22/89] Able variables through different functions compile --- compiler/can/src/def.rs | 4 ++++ compiler/test_gen/src/gen_abilities.rs | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 6b9c4450c5..2b981510f0 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -478,6 +478,10 @@ pub fn canonicalize_defs<'a>( } }; + if pattern_type == PatternType::TopLevelDef { + env.top_level_symbols.insert(member_sym); + } + // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 9b0b1f0ed5..40711c9baf 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -84,3 +84,26 @@ fn alias_member_specialization() { u64 ); } + +#[test] +fn ability_constrained_in_non_member_usage() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + result = mulHashes ($Id 5) ($Id 7) + "# + ), + 35, + u64 + ) +} From ff5ae67094a82ba3472f740c3bf3803004dca200 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 13:13:33 -0400 Subject: [PATCH 23/89] Multi-specializations of able variables through function compile --- compiler/solve/tests/solve_expr.rs | 25 +++++++++++++++++++++++ compiler/test_gen/src/gen_abilities.rs | 28 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index b31b6502b7..da03df77e2 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -6008,4 +6008,29 @@ mod solve_expr { "Bool", ) } + + #[test] + fn ability_constrained_in_non_member_multiple_specializations() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + "U64", + ) + } } diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 40711c9baf..384ba85b36 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -86,6 +86,7 @@ fn alias_member_specialization() { } #[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn ability_constrained_in_non_member_usage() { assert_evals_to!( indoc!( @@ -107,3 +108,30 @@ fn ability_constrained_in_non_member_usage() { u64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_multiple_specializations() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + 300, + u64 + ) +} From bd6078a34ae8834cc8cd78a90f817950da41b2fc Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 13:18:27 -0400 Subject: [PATCH 24/89] Fix reporting tests --- reporting/src/error/canonicalize.rs | 4 ++- reporting/tests/test_reporting.rs | 38 +++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 7facf2ec27..87ab93c885 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -643,7 +643,9 @@ pub fn can_problem<'b>( alloc.region(lines.convert_region(region)), alloc.concat([ alloc.keyword("has"), - alloc.reflow(" clauses can only be specified on the top-level type annotation of an ability member."), + alloc.reflow( + " clauses can only be specified on the top-level type annotations.", + ), ]), ]); title = ILLEGAL_HAS_CLAUSE.to_string(); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index d8a615cfe3..d7314f9082 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -9433,13 +9433,13 @@ I need all branches in an `if` to have the same type! } #[test] - fn has_clause_outside_of_ability() { + fn has_clause_not_on_toplevel() { new_report_problem_as( indoc!( r#" - app "test" provides [ hash, f ] to "./platform" + app "test" provides [ f ] to "./platform" - Hash has hash : a -> Num.U64 | a has Hash + Hash has hash : (a | a has Hash) -> Num.U64 f : a -> Num.U64 | a has Hash "# @@ -9450,11 +9450,35 @@ I need all branches in an `if` to have the same type! A `has` clause is not allowed here: - 5│ f : a -> Num.U64 | a has Hash - ^^^^^^^^^^ + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^^^^^^^ - `has` clauses can only be specified on the top-level type annotation of - an ability member. + `has` clauses can only be specified on the top-level type annotations. + + ── ABILITY MEMBER MISSING HAS CLAUSE ─────────────────────────────────────────── + + The definition of the ability member `hash` does not include a `has` + clause binding a type variable to the ability `Hash`: + + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ + + Ability members must include a `has` clause binding a type variable to + an ability, like + + a has Hash + + Otherwise, the function does not need to be part of the ability! + + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + + `hash` is not used anywhere in your code. + + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ + + If you didn't intend on using `hash` then remove it so future readers of + your code don't wonder why it is there. "# ), ) From 9d71a3d1ac539bd71a418050558104c5f64edf57 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 13:26:22 -0400 Subject: [PATCH 25/89] Generalizing ability type instance to ability is illegal Closes #2881 --- compiler/unify/src/unify.rs | 18 ++++++------- reporting/tests/test_reporting.rs | 44 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 794f9ad00b..cebebcca4f 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,5 +1,5 @@ use bitflags::bitflags; -use roc_error_macros::{internal_error, todo_abilities}; +use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; @@ -369,9 +369,12 @@ fn unify_ranged_number( // Ranged number wins merge(subs, ctx, RangedNumber(real_var, range_vars)) } - RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { - unify_pool(subs, pool, real_var, ctx.second, ctx.mode) - } + RecursionVar { .. } + | RigidVar(..) + | Alias(..) + | Structure(..) + | RigidAbleVar(..) + | FlexAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), &RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); if outcome.mismatches.is_empty() { @@ -382,9 +385,6 @@ fn unify_ranged_number( // TODO: We should probably check that "range_vars" and "other_range_vars" intersect } Error => merge(subs, ctx, Error), - FlexAbleVar(..) | RigidAbleVar(..) => { - todo_abilities!("I don't think this can be reached yet") - } }; if !outcome.mismatches.is_empty() { @@ -451,8 +451,8 @@ fn unify_alias( RecursionVar { structure, .. } if !either_is_opaque => { unify_pool(subs, pool, real_var, *structure, ctx.mode) } - RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { + RigidVar(_) | RigidAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { // Opaque type wins let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability }); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index d7314f9082..70f5d5bf2e 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -9828,4 +9828,48 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn expression_generalization_to_ability_is_an_error() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ hash, hashable ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + hash = \$Id n -> n + + hashable : a | a has Hash + hashable = $Id 15 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + Something is off with the body of the `hashable` definition: + + 9│ hashable : a | a has Hash + 10│ hashable = $Id 15 + ^^^^^^ + + This Id opaque wrapping has the type: + + Id + + But the type annotation on `hashable` says it should be: + + a | a has Hash + + Tip: Type comparisons between an opaque type are only ever equal if + both types are the same opaque type. Did you mean to create an opaque + type by wrapping it? If I have an opaque type Age := U32 I can create + an instance of this opaque type by doing @Age 23. + "# + ), + ) + } } From cbfd76380a170bcacb4493f17ba7d176d58a2881 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 13:36:42 -0400 Subject: [PATCH 26/89] Improve ability generalization error message --- reporting/src/error/type.rs | 46 +++++++++++++++++++++++++++---- reporting/tests/test_reporting.rs | 9 +++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 0351636408..c1640a9eef 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1767,7 +1767,7 @@ pub enum Problem { FieldsMissing(Vec), TagTypo(TagName, Vec), TagsMissing(Vec), - BadRigidVar(Lowercase, ErrorType), + BadRigidVar(Lowercase, ErrorType, Option), OptionalRequiredMismatch(Lowercase), OpaqueComparedToNonOpaque, } @@ -1872,7 +1872,7 @@ fn diff_is_wildcard_comparison<'b>( ) -> bool { let Comparison { problems, .. } = to_comparison(alloc, actual, expected); match problems.last() { - Some(Problem::BadRigidVar(v1, ErrorType::RigidVar(v2))) => { + Some(Problem::BadRigidVar(v1, ErrorType::RigidVar(v2), None)) => { v1.as_str() == WILDCARD && v2.as_str() == WILDCARD } _ => false, @@ -2143,6 +2143,32 @@ fn to_diff<'b>( same(alloc, parens, type1) } + (RigidVar(x), other) | (other, RigidVar(x)) => { + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); + + Diff { + left, + right, + status: Status::Different(vec![Problem::BadRigidVar(x, other, None)]), + left_able, + right_able, + } + } + + (RigidAbleVar(x, ab), other) | (other, RigidAbleVar(x, ab)) => { + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); + + Diff { + left, + right, + status: Status::Different(vec![Problem::BadRigidVar(x, other, Some(ab))]), + left_able, + right_able, + } + } + (Function(args1, _, ret1), Function(args2, _, ret2)) => { if args1.len() == args2.len() { let mut status = Status::Similar; @@ -2325,7 +2351,6 @@ fn to_diff<'b>( }; let problems = match pair { - (RigidVar(x), other) | (other, RigidVar(x)) => vec![Problem::BadRigidVar(x, other)], (a, b) if (is_int(&a) && is_float(&b)) || (is_float(&a) && is_int(&b)) => { vec![Problem::IntFloat] } @@ -2751,6 +2776,7 @@ fn ext_to_status(ext1: &TypeExt, ext2: &TypeExt) -> Status { Status::Different(vec![Problem::BadRigidVar( x.clone(), ErrorType::RigidVar(y.clone()), + None, )]) } } @@ -3128,15 +3154,25 @@ fn type_problem_to_pretty<'b>( alloc.tip().append(line) } - (BadRigidVar(x, tipe), expectation) => { + (BadRigidVar(x, tipe, opt_ability), expectation) => { use ErrorType::*; let bad_rigid_var = |name: Lowercase, a_thing| { + let kind_of_value = match opt_ability { + Some(ability) => alloc.concat(vec![ + alloc.reflow("any value implementing the "), + alloc.symbol_unqualified(ability), + alloc.reflow(" ability"), + ]), + None => alloc.reflow("any type of value"), + }; alloc .tip() .append(alloc.reflow("The type annotation uses the type variable ")) .append(alloc.type_variable(name)) - .append(alloc.reflow(" to say that this definition can produce any type of value. But in the body I see that it will only produce ")) + .append(alloc.reflow(" to say that this definition can produce ") + .append(kind_of_value) + .append(alloc.reflow(". But in the body I see that it will only produce "))) .append(a_thing) .append(alloc.reflow(" of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general?")) }; diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 70f5d5bf2e..6fa7e8c10e 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -9864,10 +9864,11 @@ I need all branches in an `if` to have the same type! a | a has Hash - Tip: Type comparisons between an opaque type are only ever equal if - both types are the same opaque type. Did you mean to create an opaque - type by wrapping it? If I have an opaque type Age := U32 I can create - an instance of this opaque type by doing @Age 23. + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any value implementing the `Hash` ability. But in + the body I see that it will only produce a `Id` value of a single + specific type. Maybe change the type annotation to be more specific? + Maybe change the code to be more general? "# ), ) From 0387eeed23420ced74d4840fa7950e2393a187b7 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 16:21:06 -0400 Subject: [PATCH 27/89] Make sure we're generating correct code with has annotations --- compiler/can/src/annotation.rs | 32 ++++++++++------ compiler/constrain/src/expr.rs | 10 ++--- compiler/mono/src/ir.rs | 2 + compiler/test_gen/src/gen_abilities.rs | 53 ++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 09395d3e3f..72f2f89f8f 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -32,6 +32,20 @@ impl<'a> NamedOrAbleVariable<'a> { NamedOrAbleVariable::Able(av) => av.first_seen, } } + + pub fn name(&self) -> &Lowercase { + match self { + NamedOrAbleVariable::Named(nv) => &nv.name, + NamedOrAbleVariable::Able(av) => &av.name, + } + } + + pub fn variable(&self) -> Variable { + match self { + NamedOrAbleVariable::Named(nv) => nv.variable, + NamedOrAbleVariable::Able(av) => av.variable, + } + } } /// A named type variable, not bound to an ability. @@ -148,19 +162,13 @@ impl IntroducedVariables { .map(|(_, var)| var) } + pub fn iter_named(&self) -> impl Iterator { + (self.named.iter().map(NamedOrAbleVariable::Named)) + .chain(self.able.iter().map(NamedOrAbleVariable::Able)) + } + pub fn named_var_by_name(&self, name: &Lowercase) -> Option { - if let Some(nav) = self - .named - .iter() - .find(|nv| &nv.name == name) - .map(NamedOrAbleVariable::Named) - { - return Some(nav); - } - self.able - .iter() - .find(|av| &av.name == name) - .map(NamedOrAbleVariable::Able) + self.iter_named().find(|v| v.name() == name) } pub fn collect_able(&self) -> Vec { diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c9b41c1da8..1b820bf762 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1684,18 +1684,18 @@ fn instantiate_rigids( let mut new_rigid_variables: Vec = Vec::new(); let mut rigid_substitution: MutMap = MutMap::default(); - for named in introduced_vars.named.iter() { + for named in introduced_vars.iter_named() { use std::collections::hash_map::Entry::*; - match ftv.entry(named.name.clone()) { + match ftv.entry(named.name().clone()) { Occupied(occupied) => { let existing_rigid = occupied.get(); - rigid_substitution.insert(named.variable, *existing_rigid); + rigid_substitution.insert(named.variable(), *existing_rigid); } Vacant(vacant) => { // It's possible to use this rigid in nested defs - vacant.insert(named.variable); - new_rigid_variables.push(named.variable); + vacant.insert(named.variable()); + new_rigid_variables.push(named.variable()); } } } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 5bb784d3c4..099f5f8dfc 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4750,6 +4750,7 @@ fn get_specialization<'a>( symbol: Symbol, ) -> Option { use roc_solve::ability::type_implementing_member; + use roc_solve::solve::instantiate_rigids; use roc_unify::unify::unify; match env.abilities_store.member_def(symbol) { @@ -4759,6 +4760,7 @@ fn get_specialization<'a>( } Some(member) => { let snapshot = env.subs.snapshot(); + instantiate_rigids(env.subs, member.signature_var); let (_, must_implement_ability) = unify( env.subs, symbol_var, diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 384ba85b36..0fa3f804ab 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -88,6 +88,31 @@ fn alias_member_specialization() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn ability_constrained_in_non_member_usage() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : a, a -> U64 | a has Hash + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + result = mulHashes ($Id 5) ($Id 7) + "# + ), + 35, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_usage_inferred() { assert_evals_to!( indoc!( r#" @@ -112,6 +137,34 @@ fn ability_constrained_in_non_member_usage() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn ability_constrained_in_non_member_multiple_specializations() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : a, b -> U64 | a has Hash, b has Hash + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + 300, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_multiple_specializations_inferred() { assert_evals_to!( indoc!( r#" From c0dec1d5bcd092e250105a96898706500d69d96b Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 16:21:46 -0400 Subject: [PATCH 28/89] Fix indent --- compiler/test_gen/src/gen_abilities.rs | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 0fa3f804ab..4434a77cf7 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -140,22 +140,22 @@ fn ability_constrained_in_non_member_multiple_specializations() { assert_evals_to!( indoc!( r#" - app "test" provides [ result ] to "./platform" + app "test" provides [ result ] to "./platform" - Hash has - hash : a -> U64 | a has Hash + Hash has + hash : a -> U64 | a has Hash - mulHashes : a, b -> U64 | a has Hash, b has Hash - mulHashes = \x, y -> hash x * hash y + mulHashes : a, b -> U64 | a has Hash, b has Hash + mulHashes = \x, y -> hash x * hash y - Id := U64 - hash = \$Id n -> n + Id := U64 + hash = \$Id n -> n - Three := {} - hash = \$Three _ -> 3 + Three := {} + hash = \$Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) - "# + result = mulHashes ($Id 100) ($Three {}) + "# ), 300, u64 @@ -168,21 +168,21 @@ fn ability_constrained_in_non_member_multiple_specializations_inferred() { assert_evals_to!( indoc!( r#" - app "test" provides [ result ] to "./platform" + app "test" provides [ result ] to "./platform" - Hash has - hash : a -> U64 | a has Hash + Hash has + hash : a -> U64 | a has Hash - mulHashes = \x, y -> hash x * hash y + mulHashes = \x, y -> hash x * hash y - Id := U64 - hash = \$Id n -> n + Id := U64 + hash = \$Id n -> n - Three := {} - hash = \$Three _ -> 3 + Three := {} + hash = \$Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) - "# + result = mulHashes ($Id 100) ($Three {}) + "# ), 300, u64 From 80dc50763e26ae7d138845e4c7bc58ae1dbce54c Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 16:41:18 -0400 Subject: [PATCH 29/89] Using abilities as types is illegal, but we can still compile them Closes #2881 --- compiler/can/src/abilities.rs | 4 ++ compiler/can/src/annotation.rs | 30 ++++++++++++- compiler/problem/src/can.rs | 1 + compiler/test_gen/src/gen_abilities.rs | 28 +++++++++++++ compiler/test_gen/src/helpers/llvm.rs | 5 ++- reporting/src/error/canonicalize.rs | 29 +++++++++++++ reporting/tests/test_reporting.rs | 58 +++++++++++++++++++++++++- 7 files changed, 151 insertions(+), 4 deletions(-) diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index b162a1355f..43f958eece 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -85,6 +85,10 @@ impl AbilitiesStore { ); } + pub fn is_ability(&self, ability: Symbol) -> bool { + self.members_of_ability.contains_key(&ability) + } + /// Records a specialization of `ability_member` with specialized type `implementing_type`. /// Entries via this function are considered a source of truth. It must be ensured that a /// specialization is validated before being registered here. diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 72f2f89f8f..ae3ef9864d 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -8,7 +8,8 @@ use roc_problem::can::ShadowKind; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{ - Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension, + name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, + TypeExtension, }; #[derive(Clone, Debug, PartialEq)] @@ -397,6 +398,16 @@ pub fn find_type_def_symbols( result } +/// Generates a fresh type variable name. PERF: not super performant, don't prefer using this in +/// non-degenerate compilation paths! +fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase { + let mut taken = introduced_variables + .iter_named() + .map(|v| v.name().clone()) + .collect(); + name_type_var(0, &mut taken).0 +} + #[allow(clippy::too_many_arguments)] fn can_annotation_help( env: &mut Env, @@ -418,7 +429,7 @@ fn can_annotation_help( let arg_ann = can_annotation_help( env, &arg.value, - region, + arg.region, scope, var_store, introduced_variables, @@ -456,6 +467,21 @@ fn can_annotation_help( references.insert(symbol); + if scope.abilities_store.is_ability(symbol) { + let fresh_ty_var = find_fresh_var_name(introduced_variables); + + env.problem(roc_problem::can::Problem::AbilityUsedAsType( + fresh_ty_var.clone(), + symbol, + region, + )); + + // Generate an variable bound to the ability so we can keep compiling. + let var = var_store.fresh(); + introduced_variables.insert_able(fresh_ty_var, Loc::at(region, var), symbol); + return Type::Variable(var); + } + for arg in *type_arguments { let arg_ann = can_annotation_help( env, diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index a12584c580..e28177345f 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -138,6 +138,7 @@ pub enum Problem { AbilityNotOnToplevel { region: Region, }, + AbilityUsedAsType(Lowercase, Symbol, Region), } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 4434a77cf7..f268f0ae2b 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -188,3 +188,31 @@ fn ability_constrained_in_non_member_multiple_specializations_inferred() { u64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_used_as_type_still_compiles() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + 300, + u64 + ) +} diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 03bc6b7a1e..cf77aba1e2 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -106,8 +106,9 @@ fn create_llvm_module<'a>( use roc_problem::can::Problem::*; for problem in can_problems.into_iter() { - // Ignore "unused" problems + dbg!(&problem); match problem { + // Ignore "unused" problems UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) @@ -122,6 +123,8 @@ fn create_llvm_module<'a>( delayed_errors.push(buf.clone()); lines.push(buf); } + // We should be able to compile even when abilities are used as types + AbilityUsedAsType(..) => {} _ => { let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 87ab93c885..9d755bee9c 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -46,6 +46,7 @@ const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAU const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE"; const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES"; const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL"; +const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -750,6 +751,34 @@ pub fn can_problem<'b>( title = ABILITY_NOT_ON_TOPLEVEL.to_string(); severity = Severity::RuntimeError; } + + Problem::AbilityUsedAsType(suggested_var_name, ability, region) => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("You are attempting to use the ability "), + alloc.symbol_unqualified(ability), + alloc.reflow(" as a type directly:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow( + "Abilities can only be used in type annotations to constrain type variables.", + ), + alloc + .hint("") + .append(alloc.reflow("Perhaps you meant to include a ")) + .append(alloc.keyword("has")) + .append(alloc.reflow(" annotation, like")), + alloc.type_block(alloc.concat(vec![ + alloc.type_variable(suggested_var_name), + alloc.space(), + alloc.keyword("has"), + alloc.space(), + alloc.symbol_unqualified(ability), + ])), + ]); + title = ABILITY_USED_AS_TYPE.to_string(); + severity = Severity::RuntimeError; + } }; Report { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 6fa7e8c10e..604f245089 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8877,7 +8877,7 @@ I need all branches in an `if` to have the same type! I cannot find a `UnknownType` value 3│ insertHelper : UnknownType, Type -> Type - ^^^^ + ^^^^^^^^^^^ Did you mean one of these? @@ -9873,4 +9873,60 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn ability_value_annotations_are_an_error() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + indoc!( + r#" + ── ABILITY USED AS TYPE ──────────────────────────────────────────────────────── + + You are attempting to use the ability `Hash` as a type directly: + + 6│ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + a has Hash + + ── ABILITY USED AS TYPE ──────────────────────────────────────────────────────── + + You are attempting to use the ability `Hash` as a type directly: + + 6│ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + b has Hash + "# + ), + ) + } } From 4bbc1d3a2bea21a30e15dda2ad882f91722c3d0a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 19 Apr 2022 16:42:33 -0400 Subject: [PATCH 30/89] Clippy --- compiler/can/src/def.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 2b981510f0..66b82bdb6a 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1303,7 +1303,7 @@ fn canonicalize_pending_value_def<'a>( &loc_ann.value, loc_ann.region, var_store, - &abilities_in_scope, + abilities_in_scope, ); // Record all the annotation's references in output.references.lookups From 35b560f14dc1d5878c95784936f8039e1397343f Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 21 Apr 2022 00:47:55 +0200 Subject: [PATCH 31/89] remove debug code --- compiler/can/src/procedure.rs | 8 -------- compiler/load_internal/src/file.rs | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 778688d74f..15ab82ff9f 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -166,14 +166,6 @@ impl References { } pub fn has_type_or_value_lookup(&self, symbol: Symbol) -> bool { - // if self.symbols.len() > 100 { - // panic!() - // } - // println!( - // "has a type or value lookup? {} {:?}", - // self.symbols.len(), - // symbol - // ); let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0; let it = self.symbols.iter().zip(self.bitflags.iter()); diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index cdab508046..f8f60cfe4c 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -1110,7 +1110,7 @@ pub 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 a1c0cdaeb1f6bd60ab86b1cfd34094236a6ae5b0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 21 Apr 2022 00:50:05 +0200 Subject: [PATCH 32/89] make multi-threaded the default again --- compiler/load_internal/src/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index f8f60cfe4c..cdab508046 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -1110,7 +1110,7 @@ pub fn load<'a>( ) -> Result, LoadingProblem<'a>> { // When compiling to wasm, we cannot spawn extra threads // so we have a single-threaded implementation - if true || cfg!(target_family = "wasm") { + if cfg!(target_family = "wasm") { load_single_threaded( arena, load_start, From 928f99957ac6f4374ec25b38301ff7ec1f6b0797 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 18:17:40 -0400 Subject: [PATCH 33/89] Fix warnings in Closure benchmark --- examples/benchmarks/Closure.roc | 58 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/examples/benchmarks/Closure.roc b/examples/benchmarks/Closure.roc index d6bf6b3ff8..1a78d99867 100644 --- a/examples/benchmarks/Closure.roc +++ b/examples/benchmarks/Closure.roc @@ -20,32 +20,32 @@ toUnitBorrowed = \x -> Str.countGraphemes x foo = \f, x -> f x # --- -closure2 : {} -> Task.Task {} [] -closure2 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.map (\_ -> x) - |> Task.map toUnit - -toUnit = \_ -> {} - -# --- -closure3 : {} -> Task.Task {} [] -closure3 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {})) - -# --- -closure4 : {} -> Task.Task {} [] -closure4 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.after (\_ -> Task.succeed x) - |> Task.map (\_ -> {}) +# closure2 : {} -> Task.Task {} [] +# closure2 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.map (\_ -> x) +# |> Task.map toUnit +# +# toUnit = \_ -> {} +# +# # --- +# closure3 : {} -> Task.Task {} [] +# closure3 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {})) +# +# # --- +# closure4 : {} -> Task.Task {} [] +# closure4 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.after (\_ -> Task.succeed x) +# |> Task.map (\_ -> {}) From e2ea66043a1c52d2a8d114d7f77c2066be1566a3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 18:13:18 -0400 Subject: [PATCH 34/89] Add color_reset to StyleCodes --- cli/tests/cli_run.rs | 1 + reporting/src/report.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 32ef62de18..989f0054c4 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -61,6 +61,7 @@ mod cli_run { .replace(ANSI_STYLE_CODES.bold, "") .replace(ANSI_STYLE_CODES.underline, "") .replace(ANSI_STYLE_CODES.reset, "") + .replace(ANSI_STYLE_CODES.color_reset, "") } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { diff --git a/reporting/src/report.rs b/reporting/src/report.rs index a97537e18c..c3d8204a78 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -215,6 +215,7 @@ pub struct StyleCodes { pub bold: &'static str, pub underline: &'static str, pub reset: &'static str, + pub color_reset: &'static str, } pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { @@ -228,6 +229,7 @@ pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { bold: "\u{001b}[1m", underline: "\u{001b}[4m", reset: "\u{001b}[0m", + color_reset: "\u{1b}[39m", }; macro_rules! html_color { @@ -247,6 +249,7 @@ pub const HTML_STYLE_CODES: StyleCodes = StyleCodes { bold: "", underline: "", reset: "", + color_reset: "", }; // define custom allocator struct so we can `impl RocDocAllocator` custom helpers From 4952f7e9d20062ec5e29114d6d1a412bc72e018f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 18:16:01 -0400 Subject: [PATCH 35/89] fix cli_run tests --- cli/tests/cli_run.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 989f0054c4..bec2eb7047 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -68,7 +68,12 @@ mod cli_run { let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat()); let err = compile_out.stdout.trim(); let err = strip_colors(err); - assert_multiline_str_eq!(err, expected.into()); + + // e.g. "1 error and 0 warnings found in 123 ms." + let (before_first_digit, _) = err.split_at(err.rfind("found in ").unwrap()); + let err = format!("{}found in ms.", before_first_digit); + + assert_multiline_str_eq!(err.as_str(), expected.into()); } fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { @@ -859,7 +864,9 @@ mod cli_run { I8 F64 - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } @@ -878,7 +885,9 @@ mod cli_run { You can fix this by adding a definition for bar, or by removing it from exposes. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } @@ -899,7 +908,9 @@ mod cli_run { Since Symbol isn't used, you don't need to import it. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 0 errors and 1 warning found in ms."# ), ); } @@ -921,7 +932,9 @@ mod cli_run { Only specific functions like `after` and `map` can be generated.Learn more about hosted modules at TODO. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } From 6fb8481ebd7a883c68f37d919233c42e8c84b688 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 18:34:38 -0400 Subject: [PATCH 36/89] Report contents of stderr when example test fails --- cli/tests/cli_run.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bec2eb7047..38a2460883 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -181,8 +181,8 @@ mod cli_run { }; if !&out.stdout.ends_with(expected_ending) { panic!( - "expected output to end with {:?} but instead got {:#?}", - expected_ending, out.stdout + "expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}", + expected_ending, out.stdout, out.stderr ); } assert!(out.status.success()); From d903ac59f33e457e01040ef8dac527ce232f8312 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 21:27:55 -0400 Subject: [PATCH 37/89] Make From for IdentStr reuse allocation --- compiler/ident/src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/compiler/ident/src/lib.rs b/compiler/ident/src/lib.rs index 85342346db..5b5dbefcdf 100644 --- a/compiler/ident/src/lib.rs +++ b/compiler/ident/src/lib.rs @@ -202,8 +202,19 @@ impl From<&str> for IdentStr { } impl From for IdentStr { - fn from(str: String) -> Self { - Self::from_str(&str) + fn from(string: String) -> Self { + if string.len() <= Self::SMALL_STR_BYTES { + Self::from_str(string.as_str()) + } else { + // Take over the string's heap allocation + let length = string.len(); + let elements = string.as_ptr(); + + // Make sure the existing string doesn't get dropped. + std::mem::forget(string); + + Self { elements, length } + } } } From 132213245d3eccddbab8ec4856b19667db740d89 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 21:28:08 -0400 Subject: [PATCH 38/89] Add a way to convert Lowercase to &str --- compiler/module/src/ident.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 1056f9eeaf..2b4347d3d9 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -204,6 +204,12 @@ impl<'a> From<&'a str> for Lowercase { } } +impl<'a> From<&'a Lowercase> for &'a str { + fn from(lowercase: &'a Lowercase) -> Self { + lowercase.as_str() + } +} + impl<'a> From for Lowercase { fn from(string: String) -> Self { Self(string.into()) From 058bfdb8d0ff6f7065ba17e87c9cfb3ce146ab39 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 20 Apr 2022 21:31:29 -0400 Subject: [PATCH 39/89] Improve perf of finding type new type variables --- compiler/can/src/annotation.rs | 11 ++++------ compiler/types/src/pretty_print.rs | 7 ++++++- compiler/types/src/subs.rs | 6 +++++- compiler/types/src/types.rs | 33 ++++++++++++++++++------------ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index ae3ef9864d..79d138c744 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -398,14 +398,11 @@ pub fn find_type_def_symbols( result } -/// Generates a fresh type variable name. PERF: not super performant, don't prefer using this in -/// non-degenerate compilation paths! fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase { - let mut taken = introduced_variables - .iter_named() - .map(|v| v.name().clone()) - .collect(); - name_type_var(0, &mut taken).0 + name_type_var(0, &mut introduced_variables.iter_named(), |var, str| { + var.name().as_str() == str + }) + .0 } #[allow(clippy::too_many_arguments)] diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index ddcb05895a..d3111cad1d 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -248,7 +248,12 @@ fn name_root( subs: &mut Subs, taken: &mut MutSet, ) -> u32 { - let (generated_name, new_letters_used) = name_type_var(letters_used, taken); + let (generated_name, new_letters_used) = + name_type_var(letters_used, &mut taken.iter(), |var, str| { + var.as_str() == str + }); + + taken.insert(generated_name.clone()); set_root_name(root, generated_name, subs); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 1aac8b45bd..1a327c85c3 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -3660,10 +3660,14 @@ fn flat_type_to_err_type( } fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase { - let (name, new_index) = name_type_var(state.normals, &mut state.taken); + let (name, new_index) = name_type_var(state.normals, &mut state.taken.iter(), |var, str| { + var.as_str() == str + }); state.normals = new_index; + state.taken.insert(name.clone()); + name } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 8f5f68636e..0f3536cd54 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -2328,26 +2328,33 @@ fn write_type_ext(ext: TypeExt, buf: &mut String) { static THE_LETTER_A: u32 = 'a' as u32; -pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lowercase, u32) { +pub fn name_type_var bool>( + letters_used: u32, + taken: &mut impl Iterator, + mut predicate: F, +) -> (Lowercase, u32) { // TODO we should arena-allocate this String, // so all the strings in the entire pass only require ~1 allocation. - let mut generated_name = String::with_capacity((letters_used as usize) / 26 + 1); + let mut buf = String::with_capacity((letters_used as usize) / 26 + 1); - let mut remaining = letters_used as i32; - while remaining >= 0 { - generated_name.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap()); - remaining -= 26; - } + let is_taken = { + let mut remaining = letters_used as i32; - let generated_name = generated_name.into(); + while remaining >= 0 { + buf.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap()); + remaining -= 26; + } - if taken.contains(&generated_name) { + let generated_name: &str = buf.as_str(); + + taken.any(|item| predicate(&item, generated_name)) + }; + + if is_taken { // If the generated name is already taken, try again. - name_type_var(letters_used + 1, taken) + name_type_var(letters_used + 1, taken, predicate) } else { - taken.insert(generated_name.clone()); - - (generated_name, letters_used + 1) + (buf.into(), letters_used + 1) } } From 78ce0f8f3e25ed8a75d0bf4dd8ae9544039ca8d6 Mon Sep 17 00:00:00 2001 From: Ayaz <20735482+ayazhafiz@users.noreply.github.com> Date: Wed, 20 Apr 2022 22:50:23 -0400 Subject: [PATCH 40/89] Update reporting/src/error/type.rs Co-authored-by: Richard Feldman --- reporting/src/error/type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index c1640a9eef..3e0a301298 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -3159,7 +3159,7 @@ fn type_problem_to_pretty<'b>( let bad_rigid_var = |name: Lowercase, a_thing| { let kind_of_value = match opt_ability { - Some(ability) => alloc.concat(vec![ + Some(ability) => alloc.concat([ alloc.reflow("any value implementing the "), alloc.symbol_unqualified(ability), alloc.reflow(" ability"), From e2a28347bb40163c3a59109d05a3c041936c6e5e Mon Sep 17 00:00:00 2001 From: Ayaz <20735482+ayazhafiz@users.noreply.github.com> Date: Wed, 20 Apr 2022 22:50:31 -0400 Subject: [PATCH 41/89] Update compiler/test_gen/src/helpers/llvm.rs Co-authored-by: Richard Feldman --- compiler/test_gen/src/helpers/llvm.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index cf77aba1e2..99252b15c5 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -106,7 +106,6 @@ fn create_llvm_module<'a>( use roc_problem::can::Problem::*; for problem in can_problems.into_iter() { - dbg!(&problem); match problem { // Ignore "unused" problems UnusedDef(_, _) From 3243765eb4bb24936d028287d1c4533a2e244157 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Fri, 15 Apr 2022 10:58:30 -0700 Subject: [PATCH 42/89] Include file path in report If the path is too long to fit in the 80 characters with everything else it will be truncated to '...foo/bar.roc' --- reporting/src/report.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/reporting/src/report.rs b/reporting/src/report.rs index a97537e18c..5eab494398 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -129,10 +129,30 @@ impl<'b> Report<'b> { if self.title.is_empty() { self.doc } else { + let header_width = 80; + let title_width = self.title.len() + 4; + let full_path = self.filename.to_str().unwrap(); + let full_path_width = full_path.len() + 3; + let available_path_width = header_width - title_width - 1; + + // If path is too long to fit in 80 characters with everything else then truncate it + let path_width = if full_path_width <= available_path_width { + full_path_width + } else { + available_path_width + }; + let path_trim = full_path_width - path_width; + let path = if path_trim > 0 { + format!("...{}", &full_path[(path_trim + 3)..]) + } else { + full_path.to_string() + }; + let header = format!( - "── {} {}", + "── {} {} {} ─", self.title, - "─".repeat(80 - (self.title.len() + 4)) + "─".repeat(header_width - (title_width + path_width)), + path ); alloc.stack([alloc.text(header).annotate(Annotation::Header), self.doc]) From 477055b0fb79286a8fa994f33b1977ec1d573c03 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Fri, 15 Apr 2022 22:28:19 -0700 Subject: [PATCH 43/89] Show relative path instead of full Relative path is more concise and full path would not work well with unit testing expected output --- reporting/src/report.rs | 62 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 5eab494398..a83194036b 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -60,6 +60,42 @@ pub fn cycle<'b>( .annotate(Annotation::TypeBlock) } +pub fn pretty_header(title : &str, path : &PathBuf) -> String +{ + let cwd = std::env::current_dir().unwrap(); + let relative_path = match path.strip_prefix(cwd) { + Ok(p) => p, + StripPrefixError => &path + }.to_str().unwrap(); + + let header_width = 80; + let title_width = title.len() + 4; + let relative_path_width = relative_path.len() + 3; + let available_path_width = header_width - title_width - 1; + + // If path is too long to fit in 80 characters with everything else then truncate it + let path_width = if relative_path_width <= available_path_width { + relative_path_width + } else { + available_path_width + }; + let path_trim = relative_path_width - path_width; + let path = if path_trim > 0 { + format!("...{}", &relative_path[(path_trim + 3)..]) + } else { + relative_path.to_string() + }; + + let header = format!( + "── {} {} {} ─", + title, + "─".repeat(header_width - (title_width + path_width)), + path + ); + + return header; +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Severity { /// This will cause a runtime error if some code get srun @@ -129,31 +165,7 @@ impl<'b> Report<'b> { if self.title.is_empty() { self.doc } else { - let header_width = 80; - let title_width = self.title.len() + 4; - let full_path = self.filename.to_str().unwrap(); - let full_path_width = full_path.len() + 3; - let available_path_width = header_width - title_width - 1; - - // If path is too long to fit in 80 characters with everything else then truncate it - let path_width = if full_path_width <= available_path_width { - full_path_width - } else { - available_path_width - }; - let path_trim = full_path_width - path_width; - let path = if path_trim > 0 { - format!("...{}", &full_path[(path_trim + 3)..]) - } else { - full_path.to_string() - }; - - let header = format!( - "── {} {} {} ─", - self.title, - "─".repeat(header_width - (title_width + path_width)), - path - ); + let header = crate::report::pretty_header(&self.title, &self.filename); alloc.stack([alloc.text(header).annotate(Annotation::Header), self.doc]) } From 66009c4b3cc717ef1583c652f2127a18f67fdd0d Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Fri, 15 Apr 2022 22:59:35 -0700 Subject: [PATCH 44/89] Updated unit tests --- cli/tests/cli_run.rs | 8 ++--- compiler/load_internal/tests/test_load.rs | 8 ++--- repl_test/src/tests.rs | 10 +++---- reporting/tests/test_reporting.rs | 36 +++++++++++------------ 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 32ef62de18..5af8c79d97 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -844,7 +844,7 @@ mod cli_run { &[], indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `d` value @@ -870,7 +870,7 @@ mod cli_run { &[], indoc!( r#" - ── MISSING DEFINITION ────────────────────────────────────────────────────────── + ── MISSING DEFINITION ────────────────── tests/known_bad/ExposedNotDefined.roc ─ bar is listed as exposed, but it isn't defined in this module. @@ -889,7 +889,7 @@ mod cli_run { &[], indoc!( r#" - ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + ── UNUSED IMPORT ──────────────────────────── tests/known_bad/UnusedImport.roc ─ Nothing from Symbol is used in this module. @@ -910,7 +910,7 @@ mod cli_run { &[], indoc!( r#" - ── UNKNOWN GENERATES FUNCTION ────────────────────────────────────────────────── + ── UNKNOWN GENERATES FUNCTION ─────── tests/known_bad/UnknownGeneratesWith.roc ─ I don't know how to generate the foobar function. diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 5aaace9f19..cf74b8b7b2 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -589,7 +589,7 @@ mod test_load { report, indoc!( " - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ────────────────────────────────────────────────────── Main ─ I cannot find the end of this list: @@ -773,7 +773,7 @@ mod test_load { err, indoc!( r#" - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ────────────────────────────────────────── + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─────────────────────────────────── Main ─ The unwrapped opaque type Age referenced here: @@ -787,7 +787,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ────────────────────────────────────────── + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─────────────────────────────────── Main ─ The unwrapped opaque type Age referenced here: @@ -801,7 +801,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + ── UNUSED IMPORT ──────────────────────────────────────────────────────── Main ─ Nothing from Age is used in this module. diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 53b692266c..b66fcd17aa 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -575,7 +575,7 @@ fn too_few_args() { "Num.add 2", indoc!( r#" - ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + ── TOO FEW ARGS ───────────────────────────────────────────────────── REPL.roc ─ The add function expects 2 arguments, but it got only 1: @@ -596,7 +596,7 @@ fn type_problem() { "1 + \"\"", indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ──────────────────────────────────────────────────── REPL.roc ─ The 2nd argument to add is not what I expect: @@ -882,7 +882,7 @@ fn parse_problem() { "add m n = m + n", indoc!( r#" - ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + ── ARGUMENTS BEFORE EQUALS ────────────────────────────────────────── REPL.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -912,7 +912,7 @@ fn mono_problem() { "#, indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ─────────────────────────────────────────────────── REPL.roc ─ This when does not cover all the possibilities: @@ -948,7 +948,7 @@ fn issue_2343_complete_mono_with_shadowed_vars() { ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ The b name is first defined here: diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 604f245089..54829bc130 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -41,7 +41,7 @@ mod test_reporting { Report { title: "".to_string(), doc, - filename: filename_from_string(r"\code\proj\Main.roc"), + filename: filename_from_string(r"/code/proj/Main.roc"), severity: Severity::RuntimeError, } } @@ -205,7 +205,7 @@ mod test_reporting { { use ven_pretty::DocAllocator; - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); let mut buf = String::new(); @@ -359,7 +359,7 @@ mod test_reporting { let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); match infer_expr_help(arena, src) { Err(parse_err) => { @@ -424,7 +424,7 @@ mod test_reporting { let state = State::new(src.as_bytes()); - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); @@ -618,7 +618,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ The `i` name is first defined here: @@ -655,7 +655,7 @@ mod test_reporting { // Booly is called a "variable" indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ The `Booly` name is first defined here: @@ -807,7 +807,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `bar` value @@ -835,7 +835,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `true` value @@ -1000,7 +1000,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `theAdmin` value @@ -1783,7 +1783,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `foo` value @@ -2240,7 +2240,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `ok` value @@ -6173,7 +6173,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `bar` value @@ -8858,7 +8858,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `UnknownType` value @@ -8872,8 +8872,8 @@ I need all branches in an `if` to have the same type! Box Ok - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── - + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + I cannot find a `UnknownType` value 3│ insertHelper : UnknownType, Type -> Type @@ -9205,7 +9205,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ The `a` name is first defined here: @@ -9282,8 +9282,8 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── - + ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ + The `Ability` name is first defined here: 3│ Ability has ab : a -> U64 | a has Ability From b48e63e931ab1b31c4612f559c791784402232ca Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Fri, 15 Apr 2022 23:07:37 -0700 Subject: [PATCH 45/89] Fixed formatting and clippy errors --- reporting/src/report.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/reporting/src/report.rs b/reporting/src/report.rs index a83194036b..7e8cbe4867 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -60,13 +60,14 @@ pub fn cycle<'b>( .annotate(Annotation::TypeBlock) } -pub fn pretty_header(title : &str, path : &PathBuf) -> String -{ +pub fn pretty_header(title: &str, path: &std::path::Path) -> String { let cwd = std::env::current_dir().unwrap(); let relative_path = match path.strip_prefix(cwd) { Ok(p) => p, - StripPrefixError => &path - }.to_str().unwrap(); + _ => path, + } + .to_str() + .unwrap(); let header_width = 80; let title_width = title.len() + 4; @@ -92,8 +93,8 @@ pub fn pretty_header(title : &str, path : &PathBuf) -> String "─".repeat(header_width - (title_width + path_width)), path ); - - return header; + + header } #[derive(Copy, Clone, Debug, PartialEq, Eq)] From ec203595c18db82d38aff5db871d9b77c90cddc1 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Wed, 20 Apr 2022 17:39:16 -0700 Subject: [PATCH 46/89] Util for temporary directory temdir() creates a random directory which isn't good for testing. Adding suppo rt for a temporary named directory that cleans itself up before and after use. Adds a dependency on 'remove_dir_all' crate, but that crate was already an implicit dependency through the 'tempfile' crate. --- test_utils/Cargo.toml | 3 +-- test_utils/src/lib.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index d71698cd2f..b1ce5e9843 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -8,5 +8,4 @@ description = "Utility functions used all over the code base." [dependencies] pretty_assertions = "1.0.0" - -[dev-dependencies] +remove_dir_all = "0.7.0" diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index bca9060a5d..f5e00b7817 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -16,3 +16,33 @@ macro_rules! assert_multiline_str_eq { $crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b)) }; } + +/** + * Creates a temporary empty directory that gets deleted when this goes out of scope. + */ +pub struct TmpDir { + path: std::path::PathBuf, +} + +impl TmpDir { + pub fn new(dir: &str) -> TmpDir { + let path = std::path::Path::new(dir); + // ensure_empty_dir will fail if the dir doesn't already exist + std::fs::create_dir_all(path).unwrap(); + remove_dir_all::ensure_empty_dir(&path).unwrap(); + + let mut pathbuf = std::path::PathBuf::new(); + pathbuf.push(path); + TmpDir { path: pathbuf } + } + + pub fn path(&self) -> &std::path::Path { + self.path.as_path() + } +} + +impl Drop for TmpDir { + fn drop(&mut self) { + remove_dir_all::remove_dir_all(&self.path).unwrap(); + } +} From d20542efaed85b0b4ac199735ccd98d45356caf9 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Wed, 20 Apr 2022 17:40:46 -0700 Subject: [PATCH 47/89] Updated tests to use deterministic tmp dir --- Cargo.lock | 17 +- compiler/load_internal/.gitignore | 1 + compiler/load_internal/Cargo.toml | 1 + compiler/load_internal/tests/test_load.rs | 44 +- reporting/.gitignore | 1 + reporting/tests/test_reporting.rs | 678 +++++++++++----------- 6 files changed, 394 insertions(+), 348 deletions(-) create mode 100644 compiler/load_internal/.gitignore create mode 100644 reporting/.gitignore diff --git a/Cargo.lock b/Cargo.lock index 71b9a3e355..bd46536149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3271,6 +3271,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "remove_dir_all" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40" +dependencies = [ + "libc", + "log", + "num_cpus", + "rayon", + "winapi", +] + [[package]] name = "renderdoc-sys" version = "0.7.1" @@ -3735,6 +3748,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_target", + "roc_test_utils", "roc_types", "roc_unify", "tempfile", @@ -3953,6 +3967,7 @@ name = "roc_test_utils" version = "0.1.0" dependencies = [ "pretty_assertions", + "remove_dir_all 0.7.0", ] [[package]] @@ -4463,7 +4478,7 @@ dependencies = [ "libc", "rand", "redox_syscall", - "remove_dir_all", + "remove_dir_all 0.5.3", "winapi", ] diff --git a/compiler/load_internal/.gitignore b/compiler/load_internal/.gitignore new file mode 100644 index 0000000000..cad2309100 --- /dev/null +++ b/compiler/load_internal/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 3d876623f5..28641d1c1b 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -33,3 +33,4 @@ tempfile = "3.2.0" pretty_assertions = "1.0.0" maplit = "1.0.2" indoc = "1.0.3" +roc_test_utils = { path = "../../test_utils" } \ No newline at end of file diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index cf74b8b7b2..72ebf8df50 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -89,11 +89,11 @@ mod test_load { buf } - fn multiple_modules(files: Vec<(&str, &str)>) -> Result { + fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result { let arena = Bump::new(); let arena = &arena; - match multiple_modules_help(arena, files) { + match multiple_modules_help(subdir, arena, files) { Err(io_error) => panic!("IO trouble: {:?}", io_error), Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf), Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)), @@ -126,18 +126,21 @@ mod test_load { } fn multiple_modules_help<'a>( + subdir: &str, arena: &'a Bump, mut files: Vec<(&str, &str)>, ) -> Result>, std::io::Error> { use std::fs::{self, File}; use std::io::Write; - use tempfile::tempdir; let mut file_handles: Vec<_> = Vec::new(); - // create a temporary directory - let dir = tempdir()?; + // Use a deterministic temporary directory. + // We can't have all tests use "tmp" because tests run in parallel, + // so append the test name to the tmp path. + let tmp = format!("tmp/{}", subdir); + let dir = roc_test_utils::TmpDir::new(&tmp); let app_module = files.pop().unwrap(); @@ -173,8 +176,6 @@ mod test_load { ) }; - dir.close()?; - Ok(result) } @@ -341,7 +342,7 @@ mod test_load { ), ]; - assert!(multiple_modules(modules).is_ok()); + assert!(multiple_modules("import_transitive_alias", modules).is_ok()); } #[test] @@ -584,12 +585,12 @@ mod test_load { ), )]; - match multiple_modules(modules) { + match multiple_modules("parse_problem", modules) { Err(report) => assert_eq!( report, indoc!( " - ── UNFINISHED LIST ────────────────────────────────────────────────────── Main ─ + ── UNFINISHED LIST ──────────────────────────────────── tmp/parse_problem/Main ─ I cannot find the end of this list: @@ -651,10 +652,14 @@ mod test_load { ), )]; - match multiple_modules(modules) { + match multiple_modules("platform_does_not_exist", modules) { Err(report) => { - assert!(report.contains("FILE NOT FOUND")); - assert!(report.contains("zzz-does-not-exist/Package-Config.roc")); + assert!(report.contains("FILE NOT FOUND"), "report=({})", report); + assert!( + report.contains("zzz-does-not-exist/Package-Config.roc"), + "report=({})", + report + ); } Ok(_) => unreachable!("we expect failure here"), } @@ -694,7 +699,7 @@ mod test_load { ), ]; - match multiple_modules(modules) { + match multiple_modules("platform_parse_error", modules) { Err(report) => { assert!(report.contains("NOT END OF FILE")); assert!(report.contains("blah 1 2 3 # causing a parse error on purpose")); @@ -738,7 +743,7 @@ mod test_load { ), ]; - assert!(multiple_modules(modules).is_ok()); + assert!(multiple_modules("platform_exposes_main_return_by_pointer_issue", modules).is_ok()); } #[test] @@ -768,12 +773,13 @@ mod test_load { ), ]; - let err = multiple_modules(modules).unwrap_err(); + let err = multiple_modules("opaque_wrapped_unwrapped_outside_defining_module", modules) + .unwrap_err(); assert_eq!( err, indoc!( r#" - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─────────────────────────────────── Main ─ + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─ ...rapped_outside_defining_module/Main ─ The unwrapped opaque type Age referenced here: @@ -787,7 +793,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─────────────────────────────────── Main ─ + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─ ...rapped_outside_defining_module/Main ─ The unwrapped opaque type Age referenced here: @@ -801,7 +807,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── UNUSED IMPORT ──────────────────────────────────────────────────────── Main ─ + ── UNUSED IMPORT ─── tmp/opaque_wrapped_unwrapped_outside_defining_module/Main ─ Nothing from Age is used in this module. diff --git a/reporting/.gitignore b/reporting/.gitignore new file mode 100644 index 0000000000..cad2309100 --- /dev/null +++ b/reporting/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 54829bc130..feea376e9c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -61,12 +61,12 @@ mod test_reporting { } fn run_load_and_infer<'a>( + subdir: &str, arena: &'a Bump, src: &'a str, ) -> (String, Result>) { use std::fs::File; use std::io::Write; - use tempfile::tempdir; let module_src = if src.starts_with("app") { // this is already a module @@ -78,7 +78,12 @@ mod test_reporting { let exposed_types = Default::default(); let loaded = { - let dir = tempdir().unwrap(); + // Use a deterministic temporary directory. + // We can't have all tests use "tmp" because tests run in parallel, + // so append the test name to the tmp path. + let tmp = format!("tmp/{}", subdir); + let dir = roc_test_utils::TmpDir::new(&tmp); + let filename = PathBuf::from("Test.roc"); let file_path = dir.path().join(filename); let full_file_path = file_path.clone(); @@ -94,8 +99,6 @@ mod test_reporting { ); drop(file); - dir.close().unwrap(); - result }; @@ -103,6 +106,7 @@ mod test_reporting { } fn infer_expr_help_new<'a>( + subdir: &str, arena: &'a Bump, expr_src: &'a str, ) -> Result< @@ -116,7 +120,7 @@ mod test_reporting { ), LoadingProblem<'a>, > { - let (module_src, result) = run_load_and_infer(arena, expr_src); + let (module_src, result) = run_load_and_infer(subdir, arena, expr_src); let LoadedModule { module_id: home, mut can_problems, @@ -199,7 +203,7 @@ mod test_reporting { )) } - fn list_reports_new(arena: &Bump, src: &str, finalize_render: F) -> String + fn list_reports_new(subdir: &str, arena: &Bump, src: &str, finalize_render: F) -> String where F: FnOnce(RocDocBuilder<'_>, &mut String), { @@ -209,7 +213,7 @@ mod test_reporting { let mut buf = String::new(); - match infer_expr_help_new(arena, src) { + match infer_expr_help_new(subdir, arena, src) { Err(LoadingProblem::FormattedReport(fail)) => fail, Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => { let lines = LineInfo::new(&module_src); @@ -514,7 +518,7 @@ mod test_reporting { assert_eq!(readable, expected_rendering); } - fn new_report_problem_as(src: &str, expected_rendering: &str) { + fn new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) { let arena = Bump::new(); let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| { @@ -523,7 +527,7 @@ mod test_reporting { .expect("list_reports") }; - let buf = list_reports_new(&arena, src, finalize_render); + let buf = list_reports_new(subdir, &arena, src, finalize_render); // convenient to copy-paste the generated message if buf != expected_rendering { @@ -558,7 +562,7 @@ mod test_reporting { ), indoc!( r#" - ── NOT EXPOSED ───────────────────────────────────────────────────────────────── + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ The List module does not expose `isempty`: @@ -589,7 +593,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `y` is not used anywhere in your code. @@ -618,7 +622,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `i` name is first defined here: @@ -655,7 +659,7 @@ mod test_reporting { // Booly is called a "variable" indoc!( r#" - ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `Booly` name is first defined here: @@ -670,7 +674,7 @@ mod test_reporting { Since these aliases have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Booly` is not used anywhere in your code. @@ -680,7 +684,7 @@ mod test_reporting { If you didn't intend on using `Booly` then remove it so future readers of your code don't wonder why it is there. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Booly` is not used anywhere in your code. @@ -775,7 +779,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Using != and == together requires parentheses, to clarify how they should be grouped. @@ -807,7 +811,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `bar` value @@ -835,7 +839,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `true` value @@ -871,7 +875,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Using more than one == like this requires parentheses, to clarify how things should be grouped. @@ -901,7 +905,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ `box` doesn't use `htmlChildren`. @@ -914,7 +918,7 @@ mod test_reporting { at the start of a variable name is a way of saying that the variable is not used. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `y` is not used anywhere in your code. @@ -1000,7 +1004,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `theAdmin` value @@ -1074,7 +1078,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` condition needs to be a Bool: @@ -1104,7 +1108,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` guard condition needs to be a Bool: @@ -1133,7 +1137,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -1164,7 +1168,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 3rd branch of this `if` does not match all the previous branches: @@ -1197,7 +1201,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd branch of this `when` does not match all the previous branches: @@ -1230,7 +1234,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This list contains elements with different types: @@ -1264,7 +1268,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ I cannot update the `.foo` field like this: @@ -1298,7 +1302,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR TYPE ─────────────────────────────────────────────────────────────── + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ I'm inferring a weird self-referential type for `g`: @@ -1327,7 +1331,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR TYPE ─────────────────────────────────────────────────────────────── + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ I'm inferring a weird self-referential type for `f`: @@ -1359,7 +1363,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1397,7 +1401,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1435,7 +1439,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1473,7 +1477,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the `then` branch of this `if` expression: @@ -1511,7 +1515,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -1548,7 +1552,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -1584,7 +1588,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ The `x` value is not a function, but it was given 1 argument: @@ -1610,7 +1614,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ The `f` function expects 1 argument, but it got 2 instead: @@ -1636,7 +1640,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + ── TOO FEW ARGS ────────────────────────────────────────── /code/proj/Main.roc ─ The `f` function expects 2 arguments, but it got only 1: @@ -1661,7 +1665,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1692,7 +1696,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern in this `when` does not match the previous ones: @@ -1722,7 +1726,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1752,7 +1756,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1783,7 +1787,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `foo` value @@ -1848,7 +1852,7 @@ mod test_reporting { // Just putting this here. We should probably handle or-patterns better indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1880,7 +1884,7 @@ mod test_reporting { // Maybe this should specifically say the pattern doesn't work? indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -1912,7 +1916,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: @@ -1947,7 +1951,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer pattern is malformed: @@ -1972,7 +1976,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float pattern is malformed: @@ -1997,7 +2001,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer pattern is malformed: @@ -2022,7 +2026,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer pattern is malformed: @@ -2047,7 +2051,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer pattern is malformed: @@ -2073,7 +2077,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -2123,7 +2127,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the `else` branch of this `if` expression: @@ -2161,7 +2165,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2200,7 +2204,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2240,7 +2244,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `ok` value @@ -2275,7 +2279,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ok` is not used anywhere in your code. @@ -2285,7 +2289,7 @@ mod test_reporting { If you didn't intend on using `ok` then remove it so future readers of your code don't wonder why it is there. - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2321,7 +2325,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR DEFINITION ───────────────────────────────────────────────────────── + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ The `f` value is defined directly in terms of itself, causing an infinite loop. @@ -2345,7 +2349,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR DEFINITION ───────────────────────────────────────────────────────── + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ The `foo` definition is causing a very tricky infinite loop: @@ -2377,7 +2381,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `x` record doesn’t have a `foo` field: @@ -2403,7 +2407,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `x` record doesn’t have a `foo` field: @@ -2440,7 +2444,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `r` record doesn’t have a `foo` field: @@ -2472,7 +2476,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `x` record doesn’t have a `foo` field: @@ -2506,7 +2510,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2535,7 +2539,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2567,7 +2571,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2599,7 +2603,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2637,7 +2641,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2680,7 +2684,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern does not cover all the possibilities: @@ -2715,7 +2719,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -2751,7 +2755,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2782,7 +2786,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2814,7 +2818,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2847,7 +2851,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2880,7 +2884,7 @@ mod test_reporting { // Tip: Looks like a record field guard is not exhaustive. Learn more about record pattern matches at TODO. indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2913,7 +2917,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2943,7 +2947,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2974,7 +2978,7 @@ mod test_reporting { ), indoc!( r#" - ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern is redundant: @@ -3006,7 +3010,7 @@ mod test_reporting { // de-aliases the alias to give a better error message indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -3048,7 +3052,7 @@ mod test_reporting { // should not report Bar as unused! indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is recursive in an invalid way: @@ -3067,7 +3071,7 @@ mod test_reporting { Recursion in aliases is only allowed if recursion happens behind a tag. - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ `Bar` is not used anywhere in your code. @@ -3097,7 +3101,7 @@ mod test_reporting { // should not report Bar as unused! indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is self-recursive in an invalid way: @@ -3121,7 +3125,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3149,7 +3153,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3181,7 +3185,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3220,7 +3224,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3257,8 +3261,8 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── - + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + This record type defines the `.foo` field twice! 1│ a : { foo : Num.I64, bar : {}, foo : Str } @@ -3289,8 +3293,8 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE TAG NAME ────────────────────────────────────────────────────────── - + ── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─ + This tag union type defines the `Foo` tag twice! 1│ a : [ Foo Num.I64, Bar {}, Foo Str ] @@ -3322,7 +3326,7 @@ mod test_reporting { ), indoc!( r#" - ── NAMING PROBLEM ────────────────────────────────────────────────────────────── + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This annotation does not match the definition immediately following it: @@ -3364,7 +3368,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern in the definition of `MyAlias` is not what I expect: @@ -3373,7 +3377,7 @@ mod test_reporting { Only type variables like `a` or `value` can occur in this position. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `MyAlias` is not used anywhere in your code. @@ -3400,7 +3404,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern in the definition of `Age` is not what I expect: @@ -3426,8 +3430,8 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── - + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ + The `Num` alias expects 1 type argument, but it got 2 instead: 1│ a : Num.Num Num.I64 Num.F64 @@ -3452,8 +3456,8 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── - + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ + The `Num` alias expects 1 type argument, but it got 2 instead: 1│ f : Str -> Num.Num Num.I64 Num.F64 @@ -3480,7 +3484,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ The `Pair` alias expects 2 type arguments, but it got 1 instead: @@ -3508,7 +3512,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Pair` alias expects 2 type arguments, but it got 3 instead: @@ -3535,7 +3539,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED TYPE ALIAS PARAMETER ───────────────────────────────────────────────── + ── UNUSED TYPE ALIAS PARAMETER ─────────────────────────── /code/proj/Main.roc ─ The `a` type parameter is not used in the `Foo` alias definition: @@ -3561,7 +3565,7 @@ mod test_reporting { ), indoc!( r#" - ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + ── ARGUMENTS BEFORE EQUALS ─────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -3590,7 +3594,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -3631,7 +3635,7 @@ mod test_reporting { // TODO do not show recursion var if the recursion var does not render on the surface of a type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -3673,7 +3677,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too big: @@ -3685,7 +3689,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too small: @@ -3697,7 +3701,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too big: @@ -3709,7 +3713,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too small: @@ -3739,7 +3743,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal is too big: @@ -3751,7 +3755,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal is too small: @@ -3788,7 +3792,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -3800,7 +3804,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer literal contains an invalid digit: @@ -3812,7 +3816,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer literal contains an invalid digit: @@ -3824,7 +3828,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer literal contains an invalid digit: @@ -3858,7 +3862,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer literal contains no digits: @@ -3870,7 +3874,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer literal contains no digits: @@ -3882,7 +3886,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer literal contains no digits: @@ -3910,7 +3914,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal contains an invalid digit: @@ -3945,7 +3949,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This expression cannot be updated: @@ -3968,7 +3972,7 @@ mod test_reporting { ), indoc!( r#" - ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ The `Foo` module is not imported: @@ -3997,7 +4001,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -4029,7 +4033,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is weird: @@ -4062,7 +4066,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: @@ -4097,7 +4101,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is weird: @@ -4134,7 +4138,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -4169,7 +4173,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -4204,7 +4208,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to this function is not what I expect: @@ -4242,7 +4246,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -4277,7 +4281,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -4306,7 +4310,7 @@ mod test_reporting { ), indoc!( r#" - ── BAD OPTIONAL VALUE ────────────────────────────────────────────────────────── + ── BAD OPTIONAL VALUE ──────────────────────────────────── /code/proj/Main.roc ─ This record uses an optional value for the `.y` field in an incorrect context! @@ -4348,7 +4352,7 @@ mod test_reporting { ), indoc!( r#" - ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ The 3rd pattern is redundant: @@ -4399,7 +4403,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ `f` doesn't use `foo`. @@ -4425,7 +4429,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -4450,7 +4454,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -4474,7 +4478,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -4497,7 +4501,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this expression: @@ -4524,7 +4528,7 @@ mod test_reporting { ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -4557,7 +4561,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 3 arguments: @@ -4580,7 +4584,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TAG UNION TYPE ─────────────────────────────────────────────────── + ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ I just started parsing a tag union type, but I got stuck here: @@ -4604,7 +4608,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TAG UNION TYPE ─────────────────────────────────────────────────── + ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4628,7 +4632,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4653,7 +4657,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4678,7 +4682,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I just started parsing a record type, but I got stuck here: @@ -4703,7 +4707,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4729,7 +4733,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4753,7 +4757,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I just started parsing a record type, but I got stuck on this field name: @@ -4779,7 +4783,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4800,7 +4804,7 @@ mod test_reporting { "f : { foo \t }", indoc!( r#" - ── TAB CHARACTER ─────────────────────────────────────────────────────────────── + ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ I encountered a tab character @@ -4819,7 +4823,7 @@ mod test_reporting { "# comment with a \t\n4", indoc!( " - ── TAB CHARACTER ─────────────────────────────────────────────────────────────── + ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ I encountered a tab character @@ -4843,7 +4847,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -4866,7 +4870,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type in parentheses, but I got stuck here: @@ -4895,7 +4899,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -4930,7 +4934,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -4964,7 +4968,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -4989,7 +4993,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -5025,7 +5029,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -5049,7 +5053,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING FINAL EXPRESSION ──────────────────────────────────────────────────── + ── MISSING FINAL EXPRESSION ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition's final expression, but I got stuck here: @@ -5082,7 +5086,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED INLINE ALIAS ───────────────────────────────────────────────────── + ── UNFINISHED INLINE ALIAS ─────────────────────────────── /code/proj/Main.roc ─ I just started parsing an inline type alias, but I got stuck here: @@ -5108,7 +5112,7 @@ mod test_reporting { ), indoc!( r#" - ── DOUBLE COMMA ──────────────────────────────────────────────────────────────── + ── DOUBLE COMMA ────────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a function argument type, but I encountered two commas in a row: @@ -5135,7 +5139,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type, but I got stuck here: @@ -5162,7 +5166,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -5189,7 +5193,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -5219,7 +5223,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `myDict` definition: @@ -5256,7 +5260,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `myDict` definition: @@ -5292,7 +5296,7 @@ mod test_reporting { ), indoc!( r#" - ── IF GUARD NO CONDITION ─────────────────────────────────────────────────────── + ── IF GUARD NO CONDITION ───────────────────────────────── /code/proj/Main.roc ─ I just started parsing an if guard, but there is no guard condition: @@ -5321,7 +5325,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a pattern, but I got stuck here: @@ -5350,7 +5354,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING EXPRESSION ────────────────────────────────────────────────────────── + ── MISSING EXPRESSION ──────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -5377,7 +5381,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING ARROW ─────────────────────────────────────────────────────────────── + ── MISSING ARROW ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a `when` expression, but got stuck here: @@ -5414,7 +5418,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── + ── UNFINISHED ARGUMENT LIST ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a function argument list, but I got stuck at this comma: @@ -5439,7 +5443,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── + ── UNFINISHED ARGUMENT LIST ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a function argument list, but I got stuck at this comma: @@ -5467,7 +5471,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I got stuck here: @@ -5518,7 +5522,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I got stuck here: @@ -5546,7 +5550,7 @@ mod test_reporting { ), indoc!( r#" - ── UNEXPECTED ARROW ──────────────────────────────────────────────────────────── + ── UNEXPECTED ARROW ────────────────────────────────────── /code/proj/Main.roc ─ I am parsing a `when` expression right now, but this arrow is confusing me: @@ -5589,7 +5593,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED IF ─────────────────────────────────────────────────────────────── + ── UNFINISHED IF ───────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an `if` expression, but I got stuck here: @@ -5613,7 +5617,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED IF ─────────────────────────────────────────────────────────────── + ── UNFINISHED IF ───────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an `if` expression, but I got stuck here: @@ -5636,7 +5640,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through started parsing a list, but I got stuck here: @@ -5660,7 +5664,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through started parsing a list, but I got stuck here: @@ -5688,7 +5692,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal contains an invalid digit: @@ -5710,7 +5714,7 @@ mod test_reporting { r#""abc\u(zzzz)def""#, indoc!( r#" - ── WEIRD CODE POINT ──────────────────────────────────────────────────────────── + ── WEIRD CODE POINT ────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a unicode code point, but I got stuck here: @@ -5732,7 +5736,7 @@ mod test_reporting { r#""abc\(32)def""#, indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This string interpolation is invalid: @@ -5754,7 +5758,7 @@ mod test_reporting { r#""abc\u(110000)def""#, indoc!( r#" - ── INVALID UNICODE ───────────────────────────────────────────────────────────── + ── INVALID UNICODE ─────────────────────────────────────── /code/proj/Main.roc ─ This unicode code point is invalid: @@ -5773,7 +5777,7 @@ mod test_reporting { r#""abc\qdef""#, indoc!( r#" - ── WEIRD ESCAPE ──────────────────────────────────────────────────────────────── + ── WEIRD ESCAPE ────────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing a string literal, but I got stuck here: @@ -5801,7 +5805,7 @@ mod test_reporting { r#""there is no end"#, indoc!( r#" - ── ENDLESS STRING ────────────────────────────────────────────────────────────── + ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ I cannot find the end of this string: @@ -5821,7 +5825,7 @@ mod test_reporting { r#""""there is no end"#, indoc!( r#" - ── ENDLESS STRING ────────────────────────────────────────────────────────────── + ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ I cannot find the end of this block string: @@ -5848,7 +5852,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -5877,7 +5881,7 @@ mod test_reporting { report_problem_as( &format!(r#"if True then "abc" else 1 {} 2"#, $op), &format!( -r#"── TYPE MISMATCH ─────────────────────────────────────────────────────────────── +r#"── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -5923,7 +5927,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `foo` record doesn’t have a `if` field: @@ -5946,7 +5950,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NOT EXPOSED ───────────────────────────────────────────────────────────────── + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ The Num module does not expose `if`: @@ -5974,7 +5978,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -5997,7 +6001,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a private tag here: @@ -6022,7 +6026,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access: @@ -6045,7 +6049,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access: @@ -6068,7 +6072,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access @@ -6093,7 +6097,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -6116,7 +6120,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NAMING PROBLEM ────────────────────────────────────────────────────────────── + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse an identifier here: @@ -6173,7 +6177,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `bar` value @@ -6202,7 +6206,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6228,7 +6232,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6256,7 +6260,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6286,7 +6290,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6316,7 +6320,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD PROVIDES ────────────────────────────────────────────────────────────── + ── WEIRD PROVIDES ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a provides list, but I got stuck here: @@ -6353,7 +6357,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── BAD REQUIRES ──────────────────────────────────────────────────────────────── + ── BAD REQUIRES ────────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but I got stuck here: @@ -6381,7 +6385,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD IMPORTS ─────────────────────────────────────────────────────────────── + ── WEIRD IMPORTS ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but I got stuck here: @@ -6408,7 +6412,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── + ── WEIRD EXPOSES ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing an `exposes` list, but I got stuck here: @@ -6436,7 +6440,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD MODULE NAME ─────────────────────────────────────────────────────────── + ── WEIRD MODULE NAME ───────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but got stuck here: @@ -6462,7 +6466,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD APP NAME ────────────────────────────────────────────────────────────── + ── WEIRD APP NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but got stuck here: @@ -6488,7 +6492,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 2 arguments: @@ -6513,7 +6517,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 2 arguments: @@ -6539,7 +6543,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -6569,7 +6573,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6594,7 +6598,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6619,7 +6623,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6645,7 +6649,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── + ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6671,7 +6675,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a pattern, but I got stuck here: @@ -6698,7 +6702,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── + ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type in parentheses, but I got stuck here: @@ -6727,7 +6731,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `map` is not what I expect: @@ -6759,7 +6763,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Underscore patterns are not allowed in definitions @@ -6782,7 +6786,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `expect` condition needs to be a Bool: @@ -6813,7 +6817,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `mul` is not what I expect: @@ -6828,7 +6832,7 @@ I need all branches in an `if` to have the same type! Num * - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `mult` definition: @@ -6861,7 +6865,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `mul` is not what I expect: @@ -6876,7 +6880,7 @@ I need all branches in an `if` to have the same type! Num a - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `mult` definition: @@ -6915,7 +6919,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ The `Result` alias expects 2 type arguments, but it got 1 instead: @@ -6947,7 +6951,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Result` alias expects 2 type arguments, but it got 3 instead: @@ -6973,7 +6977,7 @@ I need all branches in an `if` to have the same type! // TODO: We should tell the user that we inferred `_` as `a` indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7011,7 +7015,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7047,7 +7051,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7087,7 +7091,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7123,7 +7127,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NOT AN INLINE ALIAS ───────────────────────────────────────────────────────── + ── NOT AN INLINE ALIAS ─────────────────────────────────── /code/proj/Main.roc ─ The inline type after this `as` is not a type alias: @@ -7147,7 +7151,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── QUALIFIED ALIAS NAME ──────────────────────────────────────────────────────── + ── QUALIFIED ALIAS NAME ────────────────────────────────── /code/proj/Main.roc ─ This type alias has a qualified name: @@ -7171,7 +7175,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE ARGUMENT NOT LOWERCASE ───────────────────────────────────────────────── + ── TYPE ARGUMENT NOT LOWERCASE ─────────────────────────── /code/proj/Main.roc ─ This alias type argument is not lowercase: @@ -7199,7 +7203,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `isEmpty` is not what I expect: @@ -7239,7 +7243,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `c` is not what I expect: @@ -7276,7 +7280,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7303,7 +7307,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7329,7 +7333,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7358,7 +7362,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `job` is weird: @@ -7397,7 +7401,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `job` definition: @@ -7432,7 +7436,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ `Nested` is a nested datatype. Here is one recursive usage of it: @@ -7464,7 +7468,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ `Nested` is a nested datatype. Here is one recursive usage of it: @@ -7507,7 +7511,7 @@ I need all branches in an `if` to have the same type! ), bad_type, number, $suffix), &format!(indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `use` is not what I expect: @@ -7570,7 +7574,7 @@ I need all branches in an `if` to have the same type! ), number, bad_suffix, number, $suffix), &format!(indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -7619,7 +7623,7 @@ I need all branches in an `if` to have the same type! // TODO: link to number suffixes indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -7646,7 +7650,7 @@ I need all branches in an `if` to have the same type! // TODO: link to number suffixes indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -7672,7 +7676,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ This number literal is an integer, but it has a float suffix: @@ -7693,7 +7697,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ This number literal is a float, but it has an integer suffix: @@ -7710,7 +7714,7 @@ I need all branches in an `if` to have the same type! "256u8", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7730,7 +7734,7 @@ I need all branches in an `if` to have the same type! "-1u8", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7750,7 +7754,7 @@ I need all branches in an `if` to have the same type! "65536u16", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7770,7 +7774,7 @@ I need all branches in an `if` to have the same type! "-1u16", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7790,7 +7794,7 @@ I need all branches in an `if` to have the same type! "4_294_967_296u32", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7810,7 +7814,7 @@ I need all branches in an `if` to have the same type! "-1u32", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7830,7 +7834,7 @@ I need all branches in an `if` to have the same type! "18_446_744_073_709_551_616u64", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7850,7 +7854,7 @@ I need all branches in an `if` to have the same type! "-1u64", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7870,7 +7874,7 @@ I need all branches in an `if` to have the same type! "-1u128", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7890,7 +7894,7 @@ I need all branches in an `if` to have the same type! "128i8", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7910,7 +7914,7 @@ I need all branches in an `if` to have the same type! "-129i8", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7930,7 +7934,7 @@ I need all branches in an `if` to have the same type! "32768i16", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7950,7 +7954,7 @@ I need all branches in an `if` to have the same type! "-32769i16", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7970,7 +7974,7 @@ I need all branches in an `if` to have the same type! "2_147_483_648i32", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7990,7 +7994,7 @@ I need all branches in an `if` to have the same type! "-2_147_483_649i32", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -8010,7 +8014,7 @@ I need all branches in an `if` to have the same type! "9_223_372_036_854_775_808i64", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -8030,7 +8034,7 @@ I need all branches in an `if` to have the same type! "-9_223_372_036_854_775_809i64", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -8050,7 +8054,7 @@ I need all branches in an `if` to have the same type! "170_141_183_460_469_231_731_687_303_715_884_105_728i128", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -8076,7 +8080,7 @@ I need all branches in an `if` to have the same type! // be used as ... because of its literal value" indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8106,7 +8110,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8137,7 +8141,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8168,7 +8172,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -8200,7 +8204,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `R` alias is self-recursive in an invalid way: @@ -8227,7 +8231,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `R` alias is self-recursive in an invalid way: @@ -8255,7 +8259,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is recursive in an invalid way: @@ -8306,7 +8310,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: @@ -8331,7 +8335,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: @@ -8345,7 +8349,7 @@ I need all branches in an `if` to have the same type! Note: It looks like there are no opaque types declared in this scope yet! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Age` is not used anywhere in your code. @@ -8372,7 +8376,7 @@ I need all branches in an `if` to have the same type! // Apply(Error(OtherModule), [ $Age, 21 ]) indoc!( r#" - ── OPAQUE TYPE NOT APPLIED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ This opaque type is not applied to an argument: @@ -8381,7 +8385,7 @@ I need all branches in an `if` to have the same type! Note: Opaque types always wrap exactly one argument! - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -8412,7 +8416,7 @@ I need all branches in an `if` to have the same type! // raise that declaration to the outer scope. indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Age` is not used anywhere in your code. @@ -8422,7 +8426,7 @@ I need all branches in an `if` to have the same type! If you didn't intend on using `Age` then remove it so future readers of your code don't wonder why it is there. - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: @@ -8447,7 +8451,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ The `Task` module is not imported: @@ -8483,7 +8487,7 @@ I need all branches in an `if` to have the same type! // that the argument be a U8, and linking to the definitin! indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -8516,7 +8520,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -8550,7 +8554,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `v` definition: @@ -8592,7 +8596,7 @@ I need all branches in an `if` to have the same type! // probably wants to change "Age" to "@Age"! indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This pattern is being used in an unexpected way: @@ -8631,7 +8635,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern in this `when` does not match the previous ones: @@ -8666,7 +8670,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8700,7 +8704,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8737,7 +8741,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `y` is not what I expect: @@ -8774,7 +8778,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8802,7 +8806,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ This record extension type is invalid: @@ -8827,7 +8831,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ This tag union extension type is invalid: @@ -8858,7 +8862,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `UnknownType` value @@ -8872,7 +8876,7 @@ I need all branches in an `if` to have the same type! Box Ok - ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `UnknownType` value @@ -8903,7 +8907,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8921,6 +8925,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demands_not_indented_with_first() { new_report_problem_as( + "ability_demands_not_indented_with_first", indoc!( r#" Eq has @@ -8932,7 +8937,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8949,6 +8954,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demand_value_has_args() { new_report_problem_as( + "ability_demand_value_has_args", indoc!( r#" Eq has @@ -8959,7 +8965,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8986,7 +8992,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -9013,7 +9019,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9039,7 +9045,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9065,7 +9071,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has 2 unbound type variables. @@ -9093,7 +9099,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9119,7 +9125,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9136,6 +9142,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_bad_type_parameter() { new_report_problem_as( + "ability_bad_type_parameter", indoc!( r#" app "test" provides [] to "./platform" @@ -9146,7 +9153,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY HAS TYPE VARIABLES ────────────────────────────────────────────────── + ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ The definition of the `Hash` ability includes type variables: @@ -9156,7 +9163,7 @@ I need all branches in an `if` to have the same type! Abilities cannot depend on type variables, but their member values can! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Hash` is not used anywhere in your code. @@ -9173,6 +9180,7 @@ I need all branches in an `if` to have the same type! #[test] fn alias_in_has_clause() { new_report_problem_as( + "alias_in_has_clause", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9182,7 +9190,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── HAS CLAUSE IS NOT AN ABILITY ──────────────────────────────────────────────── + ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ The type referenced in this "has" clause is not an ability: @@ -9196,6 +9204,7 @@ I need all branches in an `if` to have the same type! #[test] fn shadowed_type_variable_in_has_clause() { new_report_problem_as( + "shadowed_type_variable_in_has_clause", indoc!( r#" app "test" provides [ ab1 ] to "./platform" @@ -9205,7 +9214,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `a` name is first defined here: @@ -9227,6 +9236,7 @@ I need all branches in an `if` to have the same type! #[test] fn alias_using_ability() { new_report_problem_as( + "alias_using_ability", indoc!( r#" app "test" provides [ a ] to "./platform" @@ -9240,7 +9250,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ALIAS USES ABILITY ────────────────────────────────────────────────────────── + ── ALIAS USES ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ The definition of the `Alias` aliases references the ability `Ability`: @@ -9254,7 +9264,7 @@ I need all branches in an `if` to have the same type! at the end of the type. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ab` is not used anywhere in your code. @@ -9271,6 +9281,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_shadows_ability() { new_report_problem_as( + "ability_shadows_ability", indoc!( r#" app "test" provides [ ab ] to "./platform" @@ -9282,7 +9293,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `Ability` name is first defined here: @@ -9304,6 +9315,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_does_not_bind_ability() { new_report_problem_as( + "ability_member_does_not_bind_ability", indoc!( r#" app "test" provides [ ] to "./platform" @@ -9313,7 +9325,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER MISSING HAS CLAUSE ─────────────────────────────────────────── + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ The definition of the ability member `ab` does not include a `has` clause binding a type variable to the ability `Ability`: @@ -9328,7 +9340,7 @@ I need all branches in an `if` to have the same type! Otherwise, the function does not need to be part of the ability! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Ability` is not used anywhere in your code. @@ -9338,7 +9350,7 @@ I need all branches in an `if` to have the same type! If you didn't intend on using `Ability` then remove it so future readers of your code don't wonder why it is there. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ab` is not used anywhere in your code. @@ -9355,6 +9367,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_extra_ability() { new_report_problem_as( + "ability_member_binds_extra_ability", indoc!( r#" app "test" provides [ eq ] to "./platform" @@ -9365,7 +9378,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ──────────────────────────────────── + ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ────────────── /code/proj/Main.roc ─ The definition of the ability member `hash` includes a has clause binding an ability it is not a part of: @@ -9378,7 +9391,7 @@ I need all branches in an `if` to have the same type! Hint: Did you mean to bind the `Hash` ability instead? - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `hash` is not used anywhere in your code. @@ -9395,6 +9408,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_parent_twice() { new_report_problem_as( + "ability_member_binds_parent_twice", indoc!( r#" app "test" provides [ ] to "./platform" @@ -9404,7 +9418,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ───────────────────────────────────── + ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ The definition of the ability member `eq` includes multiple variables bound to the `Eq`` ability:` @@ -9418,7 +9432,7 @@ I need all branches in an `if` to have the same type! Hint: Did you mean to only bind `a` to `Eq`? - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `eq` is not used anywhere in your code. @@ -9435,6 +9449,7 @@ I need all branches in an `if` to have the same type! #[test] fn has_clause_not_on_toplevel() { new_report_problem_as( + "has_clause_outside_of_ability", indoc!( r#" app "test" provides [ f ] to "./platform" @@ -9446,7 +9461,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ILLEGAL HAS CLAUSE ────────────────────────────────────────────────────────── + ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ A `has` clause is not allowed here: @@ -9487,6 +9502,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_with_non_implementing_type() { new_report_problem_as( + "ability_specialization_with_non_implementing_type", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9498,7 +9514,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9525,6 +9541,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_does_not_match_type() { new_report_problem_as( + "ability_specialization_does_not_match_type", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9538,7 +9555,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9560,6 +9577,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_is_incomplete() { new_report_problem_as( + "ability_specialization_is_incomplete", indoc!( r#" app "test" provides [ eq, le ] to "./platform" @@ -9575,7 +9593,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ─────────────────────────────────────────── + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ The type `Id` does not fully implement the ability `Eq`. The following specializations are missing: @@ -9599,6 +9617,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_overly_generalized() { new_report_problem_as( + "ability_specialization_overly_generalized", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9611,7 +9630,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This specialization of `hash` is overly general: @@ -9640,6 +9659,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_conflicting_specialization_types() { new_report_problem_as( + "ability_specialization_conflicting_specialization_types", indoc!( r#" app "test" provides [ eq ] to "./platform" @@ -9655,7 +9675,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `eq`: @@ -9682,6 +9702,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_checked_against_annotation() { new_report_problem_as( + "ability_specialization_checked_against_annotation", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9697,7 +9718,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: @@ -9713,7 +9734,7 @@ I need all branches in an `if` to have the same type! U32 - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9735,6 +9756,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_called_with_non_specializing() { new_report_problem_as( + "ability_specialization_called_with_non_specializing", indoc!( r#" app "test" provides [ noGoodVeryBadTerrible ] to "./platform" @@ -9757,7 +9779,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `hash` is not what I expect: @@ -9772,7 +9794,7 @@ I need all branches in an `if` to have the same type! a | a has Hash - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression has a type that does not implement the abilities it's expected to: From bfbd810ae666d4463ff5ca454b339ec1e1c7e3c5 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Wed, 20 Apr 2022 21:07:25 -0700 Subject: [PATCH 48/89] Updated new tests --- compiler/load_internal/tests/test_load.rs | 2 +- reporting/tests/test_reporting.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 72ebf8df50..cc4499c7f0 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -856,7 +856,7 @@ mod test_load { ), ]; - match multiple_modules(modules) { + match multiple_modules("issue_2863_module_type_does_not_exist", modules) { Err(report) => { assert_eq!( report, diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index feea376e9c..43879a808d 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -9825,6 +9825,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_not_on_toplevel() { new_report_problem_as( + "ability_not_on_toplevel", indoc!( r#" app "test" provides [ main ] to "./platform" From 497b8c114e5df91ff46545fd493ffe752020663d Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Wed, 20 Apr 2022 22:02:13 -0700 Subject: [PATCH 49/89] Updated new tests --- compiler/load_internal/tests/test_load.rs | 2 +- reporting/tests/test_reporting.rs | 2 ++ test_utils/Cargo.toml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index cc4499c7f0..8cfd332c99 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -862,7 +862,7 @@ mod test_load { report, indoc!( " - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ────────── tmp/issue_2863_module_type_does_not_exist/Main ─ I cannot find a `DoesNotExist` value diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 43879a808d..ed7f6703c4 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -9855,6 +9855,7 @@ I need all branches in an `if` to have the same type! #[test] fn expression_generalization_to_ability_is_an_error() { new_report_problem_as( + "expression_generalization_to_ability_is_an_error", indoc!( r#" app "test" provides [ hash, hashable ] to "./platform" @@ -9900,6 +9901,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_value_annotations_are_an_error() { new_report_problem_as( + "ability_value_annotations_are_an_error", indoc!( r#" app "test" provides [ result ] to "./platform" diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index b1ce5e9843..1a2efaceb1 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -9,3 +9,5 @@ description = "Utility functions used all over the code base." [dependencies] pretty_assertions = "1.0.0" remove_dir_all = "0.7.0" + +[dev-dependencies] From f097a4ffdb03a6261818fd627138673f0a484d35 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Wed, 20 Apr 2022 22:59:28 -0700 Subject: [PATCH 50/89] Updated new tests --- reporting/tests/test_reporting.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index ed7f6703c4..8d25660f64 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -9470,7 +9470,7 @@ I need all branches in an `if` to have the same type! `has` clauses can only be specified on the top-level type annotations. - ── ABILITY MEMBER MISSING HAS CLAUSE ─────────────────────────────────────────── + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ The definition of the ability member `hash` does not include a `has` clause binding a type variable to the ability `Hash`: @@ -9485,7 +9485,7 @@ I need all branches in an `if` to have the same type! Otherwise, the function does not need to be part of the ability! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `hash` is not used anywhere in your code. @@ -9839,7 +9839,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY NOT ON TOP-LEVEL ──────────────────────────────────────────────────── + ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ This ability definition is not on the top-level of a module: @@ -9872,7 +9872,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `hashable` definition: @@ -9923,7 +9923,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY USED AS TYPE ──────────────────────────────────────────────────────── + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ You are attempting to use the ability `Hash` as a type directly: @@ -9937,7 +9937,7 @@ I need all branches in an `if` to have the same type! a has Hash - ── ABILITY USED AS TYPE ──────────────────────────────────────────────────────── + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ You are attempting to use the ability `Hash` as a type directly: From 21661275d8818e7f58afdfdf885a0a2034f09659 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 20 Apr 2022 14:18:12 -0400 Subject: [PATCH 51/89] Show unified variables as a tree --- compiler/unify/src/unify.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index cebebcca4f..a8ceea4cd6 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -275,11 +275,24 @@ pub fn unify_pool( } #[cfg(debug_assertions)] -fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: bool) { +fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option<&Outcome>) { + static mut UNIFICATION_DEPTH: usize = 0; + if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() { - let time = if before_unified { "START" } else { "END" }; - // if true, print the types that are unified. - // + let prefix = match opt_outcome { + None => "âť”", + Some(outcome) if outcome.mismatches.is_empty() => "âś…", + Some(_) => "❌", + }; + + let depth = unsafe { UNIFICATION_DEPTH }; + let indent = 2; + let (use_depth, new_depth) = if opt_outcome.is_none() { + (depth, depth + indent) + } else { + (depth - indent, depth - indent) + }; + // NOTE: names are generated here (when creating an error type) and that modifies names // generated by pretty_print.rs. So many test will fail with changes in variable names when // this block runs. @@ -294,8 +307,9 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: boo let content_2 = subs.get(ctx.second).content; let mode = if ctx.mode.is_eq() { "~" } else { "+=" }; eprintln!( - "{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", - time, + "{}{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", + " ".repeat(use_depth), + prefix, ctx.first, ctx.second, ctx.first, @@ -304,12 +318,14 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: boo ctx.second, roc_types::subs::SubsFmtContent(&content_2, subs), ); + + unsafe { UNIFICATION_DEPTH = new_depth }; } } fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { #[cfg(debug_assertions)] - debug_print_unified_types(subs, &ctx, true); + debug_print_unified_types(subs, &ctx, None); let result = match &ctx.first_desc.content { FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content), @@ -349,7 +365,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { }; #[cfg(debug_assertions)] - debug_print_unified_types(subs, &ctx, false); + debug_print_unified_types(subs, &ctx, Some(&result)); result } From 11f29baf25df2cfb4ca30cc20b67d06f1ccaea64 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Thu, 21 Apr 2022 09:34:40 -0400 Subject: [PATCH 52/89] Add note about output --- compiler/unify/src/unify.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index a8ceea4cd6..10738f6382 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -274,6 +274,9 @@ pub fn unify_pool( } } +/// Set `ROC_PRINT_UNIFICATIONS` in debug runs to print unifications as they start and complete as +/// a tree to stderr. +/// NOTE: Only run this on individual tests! Run on multiple threads, this would clobber each others' output. #[cfg(debug_assertions)] fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option<&Outcome>) { static mut UNIFICATION_DEPTH: usize = 0; From 6942216474a8ae873804c1a441a4d5493e91b2b9 Mon Sep 17 00:00:00 2001 From: Tommy Graves Date: Thu, 21 Apr 2022 21:12:30 -0400 Subject: [PATCH 53/89] Adds rwx to the Sponsors section of the README --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2b094900b3..c0c5f98702 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,13 @@ For NQueens, input 10 in the terminal and press enter. **Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic. -## Sponsor +## Sponsors -We are very grateful for our sponsor [NoRedInk](https://www.noredink.com/). +We are very grateful for our sponsors [NoRedInk](https://www.noredink.com/) and [rwx](https://www.rwx.com). -NoRedInk logo +[NoRedInk logo](https://www.noredink.com/) +     +[rwx logo](https://www.rwx.com) ## Applications and Platforms From 1ee2f85f9ee07c3bcdd05a72ce0a02072abb5e08 Mon Sep 17 00:00:00 2001 From: Jared Cone Date: Thu, 21 Apr 2022 22:44:07 -0700 Subject: [PATCH 54/89] Exclude filename from repl reports. --- repl_eval/src/gen.rs | 2 +- repl_test/src/tests.rs | 10 +++++----- reporting/src/report.rs | 29 ++++++++++++++++++----------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index 4e8f698c29..efe32c4ca1 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -47,7 +47,7 @@ pub fn compile_to_mono<'a>( target_info: TargetInfo, palette: Palette, ) -> Result, Vec> { - let filename = PathBuf::from("REPL.roc"); + let filename = PathBuf::from(""); let src_dir = Path::new("fake/test/path"); let module_src = arena.alloc(promote_expr_to_module(src)); diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index b66fcd17aa..53b692266c 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -575,7 +575,7 @@ fn too_few_args() { "Num.add 2", indoc!( r#" - ── TOO FEW ARGS ───────────────────────────────────────────────────── REPL.roc ─ + ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── The add function expects 2 arguments, but it got only 1: @@ -596,7 +596,7 @@ fn type_problem() { "1 + \"\"", indoc!( r#" - ── TYPE MISMATCH ──────────────────────────────────────────────────── REPL.roc ─ + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 2nd argument to add is not what I expect: @@ -882,7 +882,7 @@ fn parse_problem() { "add m n = m + n", indoc!( r#" - ── ARGUMENTS BEFORE EQUALS ────────────────────────────────────────── REPL.roc ─ + ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── I am partway through parsing a definition, but I got stuck here: @@ -912,7 +912,7 @@ fn mono_problem() { "#, indoc!( r#" - ── UNSAFE PATTERN ─────────────────────────────────────────────────── REPL.roc ─ + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This when does not cover all the possibilities: @@ -948,7 +948,7 @@ fn issue_2343_complete_mono_with_shadowed_vars() { ), indoc!( r#" - ── DUPLICATE NAME ─────────────────────────────────────────────────── REPL.roc ─ + ── DUPLICATE NAME ────────────────────────────────────────────────────────────── The b name is first defined here: diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 448a82162f..2f26a35c75 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -3,7 +3,7 @@ use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_region::all::LineColumnRegion; use std::fmt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; pub use crate::error::canonicalize::can_problem; @@ -60,7 +60,15 @@ pub fn cycle<'b>( .annotate(Annotation::TypeBlock) } -pub fn pretty_header(title: &str, path: &std::path::Path) -> String { +const HEADER_WIDTH: usize = 80; + +pub fn pretty_header(title: &str) -> String { + let title_width = title.len() + 4; + let header = format!("── {} {}", title, "─".repeat(HEADER_WIDTH - title_width)); + header +} + +pub fn pretty_header_with_path(title: &str, path: &Path) -> String { let cwd = std::env::current_dir().unwrap(); let relative_path = match path.strip_prefix(cwd) { Ok(p) => p, @@ -69,17 +77,12 @@ pub fn pretty_header(title: &str, path: &std::path::Path) -> String { .to_str() .unwrap(); - let header_width = 80; let title_width = title.len() + 4; let relative_path_width = relative_path.len() + 3; - let available_path_width = header_width - title_width - 1; + let available_path_width = HEADER_WIDTH - title_width - 1; // If path is too long to fit in 80 characters with everything else then truncate it - let path_width = if relative_path_width <= available_path_width { - relative_path_width - } else { - available_path_width - }; + let path_width = relative_path_width.min(available_path_width); let path_trim = relative_path_width - path_width; let path = if path_trim > 0 { format!("...{}", &relative_path[(path_trim + 3)..]) @@ -90,7 +93,7 @@ pub fn pretty_header(title: &str, path: &std::path::Path) -> String { let header = format!( "── {} {} {} ─", title, - "─".repeat(header_width - (title_width + path_width)), + "─".repeat(HEADER_WIDTH - (title_width + path_width)), path ); @@ -166,7 +169,11 @@ impl<'b> Report<'b> { if self.title.is_empty() { self.doc } else { - let header = crate::report::pretty_header(&self.title, &self.filename); + let header = if self.filename == PathBuf::from("") { + crate::report::pretty_header(&self.title) + } else { + crate::report::pretty_header_with_path(&self.title, &self.filename) + }; alloc.stack([alloc.text(header).annotate(Annotation::Header), self.doc]) } From a1faa0ad9d670faade92f8e2d80ae42565562311 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 22 Apr 2022 08:57:47 +0200 Subject: [PATCH 55/89] recommend using legacy linker for hello C and Zig for now --- getting_started/linux_x86.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86.md index f499209607..bbc672377a 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86.md @@ -24,9 +24,9 @@ # Rust. If you installed rust in this terminal you'll need to open a new one first! ./roc examples/hello-world/rust-platform/helloRust.roc # Zig - ./roc examples/hello-world/zig-platform/helloZig.roc + ./roc examples/hello-world/zig-platform/helloZig.roc --linker=legacy # C - ./roc examples/hello-world/c-platform/helloC.roc + ./roc examples/hello-world/c-platform/helloC.roc --linker=legacy ``` 0. See [here](../README.md#examples) for the other examples. From 705564508525ab2b932637184c50c5ae9e28dc91 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 11:34:02 +0200 Subject: [PATCH 56/89] add bitvec dependency --- Cargo.lock | 42 +++++++++++++++++++++++++++++++++++++---- compiler/can/Cargo.toml | 1 + 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd46536149..0690cc0fb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,10 +242,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.0", ] [[package]] @@ -1393,6 +1405,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.17" @@ -2645,7 +2663,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1" dependencies = [ - "bitvec", + "bitvec 0.22.3", "packed_struct_codegen", "serde", ] @@ -3092,6 +3110,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -3422,6 +3446,7 @@ dependencies = [ name = "roc_can" version = "0.1.0" dependencies = [ + "bitvec 1.0.0", "bumpalo", "indoc", "pretty_assertions", @@ -5595,6 +5620,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "x11-clipboard" version = "0.5.3" diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 159b8429a3..b1d092053e 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -17,6 +17,7 @@ roc_builtins = { path = "../builtins" } ven_graph = { path = "../../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" +bitvec = "1" [dev-dependencies] pretty_assertions = "1.0.0" From b6ccd9c8fb2edf2c34b631d2606e1ce2cfa46ba7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 11:35:08 +0200 Subject: [PATCH 57/89] use bitvec-based topological sort --- compiler/can/src/def.rs | 496 ++++++++++++++++++++++++++++++++++++- compiler/can/src/module.rs | 6 +- 2 files changed, 496 insertions(+), 6 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1c0254d315..886d5129a7 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -11,6 +11,8 @@ use crate::scope::create_alias; use crate::scope::Scope; use roc_collections::{default_hasher, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Lowercase; +use roc_module::symbol::IdentId; +use roc_module::symbol::ModuleId; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::ast::AbilityMember; @@ -659,6 +661,407 @@ pub fn canonicalize_defs<'a>( ) } +#[derive(Clone, Copy)] +struct DefId(u32); + +#[derive(Debug)] +struct DefIds { + home: ModuleId, + symbol_to_id: Vec<(IdentId, u32)>, + + // an length x length matrix indicating who references who + references: bitvec::vec::BitVec, + length: u32, +} + +impl DefIds { + fn with_capacity(home: ModuleId, capacity: usize) -> Self { + use bitvec::vec::BitVec; + + // makes each new row start at a multiple of 8 + // let hack = capacity + (8 - capacity % 8); + let references = BitVec::repeat(false, capacity * capacity); + + Self { + home, + symbol_to_id: Vec::with_capacity(capacity), + references, + length: capacity as u32, + } + } + + fn from_defs_by_symbol( + env: &Env, + can_defs_by_symbol: &MutMap, + refs_by_symbol: &MutMap, + ) -> Self { + let mut this = Self::with_capacity(env.home, can_defs_by_symbol.len()); + + for (i, symbol) in can_defs_by_symbol.keys().enumerate() { + debug_assert_eq!(env.home, symbol.module_id()); + + this.symbol_to_id.push((symbol.ident_id(), i as u32)); + } + + for (symbol, (_, references)) in refs_by_symbol.iter() { + let def_id = DefId(this.get_id(*symbol).unwrap()); + + for referenced in references.value_lookups() { + this.register_reference(def_id, *referenced); + } + + for referenced in references.calls() { + this.register_reference(def_id, *referenced); + } + + if let Some(references) = env.closures.get(symbol) { + for referenced in references.value_lookups() { + this.register_reference(def_id, *referenced); + } + + for referenced in references.calls() { + this.register_reference(def_id, *referenced); + } + } + } + + this + } + + fn get_id(&self, symbol: Symbol) -> Option { + self.symbol_to_id + .iter() + .find(|(id, _)| *id == symbol.ident_id()) + .map(|t| t.1) + } + + fn get_symbol(&self, id: u32) -> Option { + self.symbol_to_id + .iter() + .find(|(_, def_id)| id == *def_id) + .map(|t| Symbol::new(self.home, t.0)) + } + + fn register_reference(&mut self, id: DefId, referenced: Symbol) -> bool { + if referenced.module_id() != self.home { + return false; + } + + match self.get_id(referenced) { + None => { + // this symbol is not defined within the let-block that this DefIds represents + false + } + Some(referenced_id) => { + let row = id.0; + let column = referenced_id; + + let index = row * self.length + column; + + self.references.set(index as usize, true); + + true + } + } + } + + fn calls_itself_directly(&self, id: u32) -> bool { + let row = &self.references[(id * self.length) as usize..][..self.length as usize]; + + row[id as usize] + } + + #[inline(always)] + fn successors(&self, id: u32) -> impl Iterator + '_ { + let row = &self.references[(id * self.length) as usize..][..self.length as usize]; + + row.iter_ones().map(|x| x as u32) + } + + #[inline(always)] + fn successors_without_self(&self, id: u32) -> impl Iterator + '_ { + self.successors(id).filter(move |x| *x != id) + } + + #[allow(clippy::type_complexity)] + fn topological_sort_into_groups(&self) -> Result>, (Vec>, Vec)> { + let length = self.length as usize; + let bitvec = &self.references; + + if length == 0 { + return Ok(Vec::new()); + } + + let mut preds_map: Vec = vec![0; length]; + + // this is basically summing the columns, I don't see a better way to do it + for row in bitvec.chunks(length) { + for succ in row.iter_ones() { + preds_map[succ] += 1; + } + } + + let mut groups = Vec::>::new(); + + // the initial group contains all symbols with no predecessors + let mut prev_group: Vec = preds_map + .iter() + .enumerate() + .filter_map(|(node, &num_preds)| { + if num_preds == 0 { + Some(node as u32) + } else { + None + } + }) + .collect(); + + if prev_group.is_empty() { + let remaining: Vec = (0u32..length as u32).collect(); + return Err((Vec::new(), remaining)); + } + + // NOTE: the original now removes elements from the preds_map if they have count 0 + // for node in &prev_group { + // preds_map.remove(node); + // } + + while preds_map.iter().any(|x| *x > 0) { + let mut next_group = Vec::::new(); + for node in &prev_group { + let row = &bitvec[length * (*node as usize)..][..length]; + for succ in row.iter_ones() { + { + let num_preds = preds_map.get_mut(succ).unwrap(); + *num_preds = num_preds.saturating_sub(1); + if *num_preds > 0 { + continue; + } + } + + let count = preds_map[succ]; + preds_map[succ] = -1; + + if count > -1 { + next_group.push(succ as u32); + } + } + } + groups.push(std::mem::replace(&mut prev_group, next_group)); + if prev_group.is_empty() { + let remaining: Vec = (0u32..length as u32) + .filter(|i| preds_map[*i as usize] > 0) + .collect(); + return Err((groups, remaining)); + } + } + groups.push(prev_group); + Ok(groups) + } + + #[allow(dead_code)] + fn debug_relations(&self) { + for id in 0u32..self.length as u32 { + let row = &self.references[(id * self.length) as usize..][..self.length as usize]; + + let matches = row + .iter() + .enumerate() + .filter(move |t| *t.1) + .map(|t| t.0 as u32); + + for m in matches { + let a = self.get_symbol(id).unwrap(); + let b = self.get_symbol(m).unwrap(); + + println!("{:?} <- {:?}", a, b); + } + } + } +} + +#[inline(always)] +pub fn sort_can_defs_improved( + env: &mut Env<'_>, + defs: CanDefs, + mut output: Output, +) -> (Result, RuntimeError>, Output) { + let def_ids = DefIds::from_defs_by_symbol(env, &defs.can_defs_by_symbol, &defs.refs_by_symbol); + + let CanDefs { + refs_by_symbol, + mut can_defs_by_symbol, + aliases, + } = defs; + + for (symbol, alias) in aliases.into_iter() { + output.aliases.insert(symbol, alias); + } + + // TODO also do the same `addDirects` check elm/compiler does, so we can + // report an error if a recursive definition can't possibly terminate! + match def_ids.topological_sort_into_groups() { + Ok(groups) => { + let mut declarations = Vec::new(); + + // groups are in reversed order + for group in groups.into_iter().rev() { + group_to_declaration_improved( + &def_ids, + &group, + &env.closures, + &mut can_defs_by_symbol, + &mut declarations, + ); + } + + (Ok(declarations), output) + } + Err((mut groups, nodes_in_cycle)) => { + let mut declarations = Vec::new(); + let mut problems = Vec::new(); + + // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, + // and in general it's impossible to decide whether it is. So we use a crude heuristic: + // + // Definitions where the cycle occurs behind a lambda are OK + // + // boom = \_ -> boom {} + // + // But otherwise we report an error, e.g. + // + // foo = if b then foo else bar + + let all_successors_without_self = |id: &u32| { + let id = *id; + def_ids.successors_without_self(id) + }; + + for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) + { + // check whether the cycle is faulty, which is when it has + // a direct successor in the current cycle. This catches things like: + // + // x = x + // + // or + // + // p = q + // q = p + let is_invalid_cycle = match cycle.get(0) { + Some(def_id) => def_ids.successors(*def_id).any(|key| cycle.contains(&key)), + None => false, + }; + + if is_invalid_cycle { + // We want to show the entire cycle in the error message, so expand it out. + let mut entries = Vec::new(); + + for def_id in &cycle { + let symbol = def_ids.get_symbol(*def_id).unwrap(); + match refs_by_symbol.get(&symbol) { + None => unreachable!( + r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, + symbol, refs_by_symbol + ), + Some((region, _)) => { + let expr_region = + can_defs_by_symbol.get(&symbol).unwrap().loc_expr.region; + + let entry = CycleEntry { + symbol, + symbol_region: *region, + expr_region, + }; + + entries.push(entry); + } + } + } + + // Sort them by line number to make the report more helpful. + entries.sort_by_key(|entry| entry.symbol_region); + + problems.push(Problem::RuntimeError(RuntimeError::CircularDef( + entries.clone(), + ))); + + declarations.push(Declaration::InvalidCycle(entries)); + } + + // if it's an invalid cycle, other groups may depend on the + // symbols defined here, so also push this cycle onto the groups + // + // if it's not an invalid cycle, this is slightly inefficient, + // because we know this becomes exactly one DeclareRec already + groups.push(cycle); + } + + // now we have a collection of groups whose dependencies are not cyclic. + // They are however not yet topologically sorted. Here we have to get a bit + // creative to get all the definitions in the correct sorted order. + + let mut group_ids = Vec::with_capacity(groups.len()); + let mut symbol_to_group_index = MutMap::default(); + for (i, group) in groups.iter().enumerate() { + for symbol in group { + symbol_to_group_index.insert(*symbol, i); + } + + group_ids.push(i); + } + + let successors_of_group = |group_id: &usize| { + let mut result = MutSet::default(); + + // for each symbol in this group + for symbol in &groups[*group_id] { + // find its successors + for succ in all_successors_without_self(symbol) { + // and add its group to the result + match symbol_to_group_index.get(&succ) { + Some(index) => { + result.insert(*index); + } + None => unreachable!("no index for symbol {:?}", succ), + } + } + } + + // don't introduce any cycles to self + result.remove(group_id); + + result + }; + + match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { + Ok(sorted_group_ids) => { + for sorted_group in sorted_group_ids.iter().rev() { + for group_id in sorted_group.iter().rev() { + let group = &groups[*group_id]; + + group_to_declaration_improved( + &def_ids, + group, + &env.closures, + &mut can_defs_by_symbol, + &mut declarations, + ); + } + } + } + Err(_) => unreachable!("there should be no cycles now!"), + } + + for problem in problems { + env.problem(problem); + } + + (Ok(declarations), output) + } + } +} + #[inline(always)] pub fn sort_can_defs( env: &mut Env<'_>, @@ -807,12 +1210,14 @@ pub fn sort_can_defs( } }; - // TODO also do the same `addDirects` check elm/compiler does, so we can - // report an error if a recursive definition can't possibly terminate! - match ven_graph::topological_sort_into_groups( + let grouped2 = ven_graph::topological_sort_into_groups( defined_symbols.as_slice(), all_successors_without_self, - ) { + ); + + // TODO also do the same `addDirects` check elm/compiler does, so we can + // report an error if a recursive definition can't possibly terminate! + match grouped2 { Ok(groups) => { let mut declarations = Vec::new(); @@ -1060,6 +1465,89 @@ fn group_to_declaration( } } +fn group_to_declaration_improved( + def_ids: &DefIds, + group: &[u32], + closures: &MutMap, + can_defs_by_symbol: &mut MutMap, + declarations: &mut Vec, +) { + use Declaration::*; + + // We want only successors in the current group, otherwise definitions get duplicated + let filtered_successors = |id: &u32| def_ids.successors(*id).filter(|key| group.contains(key)); + + // Patterns like + // + // { x, y } = someDef + // + // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), + // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key + // for a definition, so every definition is only inserted (thus typechecked and emitted) once + let mut seen_pattern_regions: Vec = Vec::with_capacity(2); + + for cycle in strongly_connected_components(group, filtered_successors) { + if cycle.len() == 1 { + let def_id = cycle[0]; + let symbol = def_ids.get_symbol(def_id).unwrap(); + + match can_defs_by_symbol.remove(&symbol) { + Some(mut new_def) => { + // Determine recursivity of closures that are not tail-recursive + if let Closure(ClosureData { + recursive: recursive @ Recursive::NotRecursive, + .. + }) = &mut new_def.loc_expr.value + { + *recursive = closure_recursivity(symbol, closures); + } + + let is_recursive = def_ids.calls_itself_directly(def_id); + + if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { + seen_pattern_regions.push(new_def.loc_pattern.region); + + if is_recursive { + declarations.push(DeclareRec(vec![new_def])); + } else { + declarations.push(Declare(new_def)); + } + } + } + None => roc_error_macros::internal_error!("def not available {:?}", symbol), + } + } else { + let mut can_defs = Vec::new(); + + // Topological sort gives us the reverse of the sorting we want! + for def_id in cycle.into_iter().rev() { + let symbol = def_ids.get_symbol(def_id).unwrap(); + match can_defs_by_symbol.remove(&symbol) { + Some(mut new_def) => { + // Determine recursivity of closures that are not tail-recursive + if let Closure(ClosureData { + recursive: recursive @ Recursive::NotRecursive, + .. + }) = &mut new_def.loc_expr.value + { + *recursive = closure_recursivity(symbol, closures); + } + + if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { + seen_pattern_regions.push(new_def.loc_pattern.region); + + can_defs.push(new_def); + } + } + None => roc_error_macros::internal_error!("def not available {:?}", symbol), + } + } + + declarations.push(DeclareRec(can_defs)); + } + } +} + fn pattern_to_vars_by_symbol( vars_by_symbol: &mut SendMap, pattern: &Pattern, diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index b34aba4fc5..345d6905af 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,5 +1,5 @@ use crate::abilities::AbilitiesStore; -use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; +use crate::def::{canonicalize_defs, sort_can_defs_improved, Declaration, Def}; use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; use crate::expr::{ClosureData, Expr, Output}; @@ -361,7 +361,9 @@ pub fn canonicalize_module_defs<'a>( ..Default::default() }; - match sort_can_defs(&mut env, defs, new_output) { + let sorted = sort_can_defs_improved(&mut env, defs, new_output); + + match sorted { (Ok(mut declarations), output) => { use crate::def::Declaration::*; From 141f88365df330cdebff52f90f5c08af862dedf0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 12:56:50 +0200 Subject: [PATCH 58/89] use extra bitvec to spot faulty recursion --- compiler/can/src/def.rs | 80 ++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 886d5129a7..d568abf4f2 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -671,6 +671,11 @@ struct DefIds { // an length x length matrix indicating who references who references: bitvec::vec::BitVec, + + // references without looking into closure bodies. + // Used to spot definitely-wrong recursion + direct_references: bitvec::vec::BitVec, + length: u32, } @@ -678,14 +683,14 @@ impl DefIds { fn with_capacity(home: ModuleId, capacity: usize) -> Self { use bitvec::vec::BitVec; - // makes each new row start at a multiple of 8 - // let hack = capacity + (8 - capacity % 8); let references = BitVec::repeat(false, capacity * capacity); + let direct_references = BitVec::repeat(false, capacity * capacity); Self { home, symbol_to_id: Vec::with_capacity(capacity), references, + direct_references, length: capacity as u32, } } @@ -708,10 +713,12 @@ impl DefIds { for referenced in references.value_lookups() { this.register_reference(def_id, *referenced); + this.register_direct_reference(def_id, *referenced); } for referenced in references.calls() { this.register_reference(def_id, *referenced); + this.register_direct_reference(def_id, *referenced); } if let Some(references) = env.closures.get(symbol) { @@ -742,33 +749,70 @@ impl DefIds { .map(|t| Symbol::new(self.home, t.0)) } - fn register_reference(&mut self, id: DefId, referenced: Symbol) -> bool { - if referenced.module_id() != self.home { - return false; - } - - match self.get_id(referenced) { + fn register_help( + id: DefId, + referenced: Option, + length: usize, + bitvec: &mut bitvec::vec::BitVec, + ) -> bool { + match referenced { None => { // this symbol is not defined within the let-block that this DefIds represents false } Some(referenced_id) => { - let row = id.0; - let column = referenced_id; + let row = id.0 as usize; + let column = referenced_id as usize; - let index = row * self.length + column; + let index = row * length + column; - self.references.set(index as usize, true); + bitvec.set(index, true); true } } } - fn calls_itself_directly(&self, id: u32) -> bool { - let row = &self.references[(id * self.length) as usize..][..self.length as usize]; + fn register_direct_reference(&mut self, id: DefId, referenced: Symbol) -> bool { + if referenced.module_id() != self.home { + return false; + } - row[id as usize] + Self::register_help( + id, + self.get_id(referenced), + self.length as usize, + &mut self.direct_references, + ) + } + + fn register_reference(&mut self, id: DefId, referenced: Symbol) -> bool { + if referenced.module_id() != self.home { + return false; + } + + Self::register_help( + id, + self.get_id(referenced), + self.length as usize, + &mut self.references, + ) + } + + fn calls_itself(&self, id: u32) -> bool { + debug_assert!(id < self.length); + + // id'th row, id'th column + let index = (id * self.length) + id; + + self.references[index as usize] + } + + #[inline(always)] + fn direct_successors(&self, id: u32) -> impl Iterator + '_ { + let row = &self.direct_references[(id * self.length) as usize..][..self.length as usize]; + + row.iter_ones().map(|x| x as u32) } #[inline(always)] @@ -949,7 +993,9 @@ pub fn sort_can_defs_improved( // p = q // q = p let is_invalid_cycle = match cycle.get(0) { - Some(def_id) => def_ids.successors(*def_id).any(|key| cycle.contains(&key)), + Some(def_id) => def_ids + .direct_successors(*def_id) + .any(|key| cycle.contains(&key)), None => false, }; @@ -1502,7 +1548,7 @@ fn group_to_declaration_improved( *recursive = closure_recursivity(symbol, closures); } - let is_recursive = def_ids.calls_itself_directly(def_id); + let is_recursive = def_ids.calls_itself(def_id); if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { seen_pattern_regions.push(new_def.loc_pattern.region); From 45567a591c10f321e52b3c04d56b50bdd972dae6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 13:42:57 +0200 Subject: [PATCH 59/89] rename --- compiler/can/src/def.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index d568abf4f2..3cf7f0904c 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -799,7 +799,7 @@ impl DefIds { ) } - fn calls_itself(&self, id: u32) -> bool { + fn is_self_recursive(&self, id: u32) -> bool { debug_assert!(id < self.length); // id'th row, id'th column @@ -1548,7 +1548,7 @@ fn group_to_declaration_improved( *recursive = closure_recursivity(symbol, closures); } - let is_recursive = def_ids.calls_itself(def_id); + let is_recursive = def_ids.is_self_recursive(def_id); if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { seen_pattern_regions.push(new_def.loc_pattern.region); From 8c08c6315166892a098ebbd3a01c3ccb8867ed14 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 14:10:46 +0200 Subject: [PATCH 60/89] faster strongly-connected components --- compiler/can/src/def.rs | 114 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 3cf7f0904c..700332b9c8 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1511,6 +1511,111 @@ fn group_to_declaration( } } +fn strongly_connected_components_improved( + length: usize, + bitvec: &bitvec::vec::BitVec, + group: &[u32], +) -> Vec> { + let mut params = Params::new(length, group); + + 'outer: loop { + for (node, value) in params.preorders.iter().enumerate() { + if let Preorder::Removed = value { + continue; + } + + recurse_onto(length, bitvec, node, &mut params); + + continue 'outer; + } + + break params.scc; + } +} + +#[derive(Clone, Copy)] +enum Preorder { + Empty, + Filled(usize), + Removed, +} + +struct Params { + preorders: Vec, + c: usize, + p: Vec, + s: Vec, + scc: Vec>, + scca: Vec, +} + +impl Params { + fn new(length: usize, group: &[u32]) -> Self { + let mut preorders = vec![Preorder::Removed; length]; + + for value in group { + preorders[*value as usize] = Preorder::Empty; + } + + Self { + preorders, + c: 0, + s: Vec::new(), + p: Vec::new(), + scc: Vec::new(), + scca: Vec::new(), + } + } +} + +fn recurse_onto(length: usize, bitvec: &bitvec::vec::BitVec, v: usize, params: &mut Params) { + params.preorders[v] = Preorder::Filled(params.c); + + params.c += 1; + + params.s.push(v as u32); + params.p.push(v as u32); + + for w in bitvec[v * length..][..length].iter_ones() { + if !params.scca.contains(&(w as u32)) { + match params.preorders[w] { + Preorder::Filled(pw) => loop { + let index = *params.p.last().unwrap(); + + match params.preorders[index as usize] { + Preorder::Empty => unreachable!(), + Preorder::Filled(current) => { + if current > pw { + params.p.pop(); + } else { + break; + } + } + Preorder::Removed => {} + } + }, + Preorder::Empty => recurse_onto(length, bitvec, w, params), + Preorder::Removed => {} + } + } + } + + if params.p.last() == Some(&(v as u32)) { + params.p.pop(); + + let mut component = Vec::new(); + while let Some(node) = params.s.pop() { + component.push(node); + params.scca.push(node); + params.preorders[node as usize] = Preorder::Removed; + if node as usize == v { + break; + } + } + params.scc.push(component); + } +} + fn group_to_declaration_improved( def_ids: &DefIds, group: &[u32], @@ -1520,9 +1625,6 @@ fn group_to_declaration_improved( ) { use Declaration::*; - // We want only successors in the current group, otherwise definitions get duplicated - let filtered_successors = |id: &u32| def_ids.successors(*id).filter(|key| group.contains(key)); - // Patterns like // // { x, y } = someDef @@ -1532,7 +1634,11 @@ fn group_to_declaration_improved( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - for cycle in strongly_connected_components(group, filtered_successors) { + let bitvec = def_ids.references.clone(); + + let sccs = strongly_connected_components_improved(def_ids.length as usize, &bitvec, group); + + for cycle in sccs { if cycle.len() == 1 { let def_id = cycle[0]; let symbol = def_ids.get_symbol(def_id).unwrap(); From 887acb751917f24f1547d7023be0af8767603c75 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 14:41:42 +0200 Subject: [PATCH 61/89] be smarter about when a function is recursive --- compiler/can/src/def.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 700332b9c8..815f7a13e7 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1634,9 +1634,8 @@ fn group_to_declaration_improved( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - let bitvec = def_ids.references.clone(); - - let sccs = strongly_connected_components_improved(def_ids.length as usize, &bitvec, group); + let sccs = + strongly_connected_components_improved(def_ids.length as usize, &def_ids.references, group); for cycle in sccs { if cycle.len() == 1 { @@ -1645,21 +1644,25 @@ fn group_to_declaration_improved( match can_defs_by_symbol.remove(&symbol) { Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive + // there is only one definition in this cycle, so we only have + // to check whether it recurses with itself; there is nobody else + // to recurse with, or they would also be in this cycle. + let is_self_recursive = def_ids.is_self_recursive(def_id); + if let Closure(ClosureData { recursive: recursive @ Recursive::NotRecursive, .. }) = &mut new_def.loc_expr.value { - *recursive = closure_recursivity(symbol, closures); + if is_self_recursive { + *recursive = Recursive::Recursive + } } - let is_recursive = def_ids.is_self_recursive(def_id); - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { seen_pattern_regions.push(new_def.loc_pattern.region); - if is_recursive { + if is_self_recursive { declarations.push(DeclareRec(vec![new_def])); } else { declarations.push(Declare(new_def)); From c763d515511bb336d8ebbcf82a8cda161e2fc310 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 15:01:53 +0200 Subject: [PATCH 62/89] use new sccs in error case too --- compiler/can/src/def.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 815f7a13e7..424fcd8fbd 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -976,13 +976,13 @@ pub fn sort_can_defs_improved( // // foo = if b then foo else bar - let all_successors_without_self = |id: &u32| { - let id = *id; - def_ids.successors_without_self(id) - }; + let sccs = strongly_connected_components_improved( + def_ids.length as usize, + &def_ids.references, + &nodes_in_cycle, + ); - for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) - { + for cycle in sccs { // check whether the cycle is faulty, which is when it has // a direct successor in the current cycle. This catches things like: // @@ -1063,7 +1063,7 @@ pub fn sort_can_defs_improved( // for each symbol in this group for symbol in &groups[*group_id] { // find its successors - for succ in all_successors_without_self(symbol) { + for succ in def_ids.successors_without_self(*symbol) { // and add its group to the result match symbol_to_group_index.get(&succ) { Some(index) => { From 8837eb2c19edf7023cfce993c0b9f76bf198bb65 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 15:09:45 +0200 Subject: [PATCH 63/89] remove old def sorting code --- compiler/can/src/def.rs | 407 +-------------------------------------- compiler/can/src/expr.rs | 30 +-- 2 files changed, 3 insertions(+), 434 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 424fcd8fbd..c0f22484a7 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -4,7 +4,7 @@ use crate::annotation::IntroducedVariables; use crate::env::Env; use crate::expr::ClosureData; use crate::expr::Expr::{self, *}; -use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive}; +use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; use crate::procedure::References; use crate::scope::create_alias; @@ -1108,409 +1108,6 @@ pub fn sort_can_defs_improved( } } -#[inline(always)] -pub fn sort_can_defs( - env: &mut Env<'_>, - defs: CanDefs, - mut output: Output, -) -> (Result, RuntimeError>, Output) { - let CanDefs { - refs_by_symbol, - mut can_defs_by_symbol, - aliases, - } = defs; - - for (symbol, alias) in aliases.into_iter() { - output.aliases.insert(symbol, alias); - } - - let mut defined_symbols: Vec = Vec::new(); - - for symbol in can_defs_by_symbol.keys() { - defined_symbols.push(*symbol); - } - - // Use topological sort to reorder the defs based on their dependencies to one another. - // This way, during code gen, no def will refer to a value that hasn't been initialized yet. - // As a bonus, the topological sort also reveals any cycles between the defs, allowing - // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually - // recursive definitions. - - // All successors that occur in the body of a symbol. - let all_successors_without_self = |symbol: &Symbol| -> Vec { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(references) = env.closures.get(symbol) { - let home = env.home; - - for lookup in references.value_lookups() { - if lookup != symbol && lookup.module_id() == home { - // DO NOT register a self-call behind a lambda! - // - // We allow `boom = \_ -> boom {}`, but not `x = x` - loc_succ.push(*lookup); - } - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - // All successors that occur in the body of a symbol, including the symbol itself - // This is required to determine whether a symbol is recursive. Recursive symbols - // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the - // group - let mut all_successors_with_self = |symbol: &Symbol| -> Vec { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(references) = env.closures.get(symbol) { - for lookup in references.value_lookups() { - loc_succ.push(*lookup); - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - // If a symbol is a direct successor of itself, there is an invalid cycle. - // The difference with the function above is that this one does not look behind lambdas, - // but does consider direct self-recursion. - let direct_successors = |symbol: &Symbol| -> Vec { - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // NOTE: if the symbol is a closure we DONT look into its body - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - // NOTE: direct recursion does matter here: `x = x` is invalid recursion! - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - let grouped2 = ven_graph::topological_sort_into_groups( - defined_symbols.as_slice(), - all_successors_without_self, - ); - - // TODO also do the same `addDirects` check elm/compiler does, so we can - // report an error if a recursive definition can't possibly terminate! - match grouped2 { - Ok(groups) => { - let mut declarations = Vec::new(); - - // groups are in reversed order - for group in groups.into_iter().rev() { - group_to_declaration( - &group, - &env.closures, - &mut all_successors_with_self, - &mut can_defs_by_symbol, - &mut declarations, - ); - } - - (Ok(declarations), output) - } - Err((mut groups, nodes_in_cycle)) => { - let mut declarations = Vec::new(); - let mut problems = Vec::new(); - - // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, - // and in general it's impossible to decide whether it is. So we use a crude heuristic: - // - // Definitions where the cycle occurs behind a lambda are OK - // - // boom = \_ -> boom {} - // - // But otherwise we report an error, e.g. - // - // foo = if b then foo else bar - - for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) - { - // check whether the cycle is faulty, which is when it has - // a direct successor in the current cycle. This catches things like: - // - // x = x - // - // or - // - // p = q - // q = p - let is_invalid_cycle = match cycle.get(0) { - Some(symbol) => { - let mut succs = direct_successors(symbol); - - succs.retain(|key| cycle.contains(key)); - - !succs.is_empty() - } - None => false, - }; - - if is_invalid_cycle { - // We want to show the entire cycle in the error message, so expand it out. - let mut entries = Vec::new(); - - for symbol in &cycle { - match refs_by_symbol.get(symbol) { - None => unreachable!( - r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, - symbol, refs_by_symbol - ), - Some((region, _)) => { - let expr_region = - can_defs_by_symbol.get(symbol).unwrap().loc_expr.region; - - let entry = CycleEntry { - symbol: *symbol, - symbol_region: *region, - expr_region, - }; - - entries.push(entry); - } - } - } - - // Sort them by line number to make the report more helpful. - entries.sort_by_key(|entry| entry.symbol_region); - - problems.push(Problem::RuntimeError(RuntimeError::CircularDef( - entries.clone(), - ))); - - declarations.push(Declaration::InvalidCycle(entries)); - } - - // if it's an invalid cycle, other groups may depend on the - // symbols defined here, so also push this cycle onto the groups - // - // if it's not an invalid cycle, this is slightly inefficient, - // because we know this becomes exactly one DeclareRec already - groups.push(cycle); - } - - // now we have a collection of groups whose dependencies are not cyclic. - // They are however not yet topologically sorted. Here we have to get a bit - // creative to get all the definitions in the correct sorted order. - - let mut group_ids = Vec::with_capacity(groups.len()); - let mut symbol_to_group_index = MutMap::default(); - for (i, group) in groups.iter().enumerate() { - for symbol in group { - symbol_to_group_index.insert(*symbol, i); - } - - group_ids.push(i); - } - - let successors_of_group = |group_id: &usize| { - let mut result = MutSet::default(); - - // for each symbol in this group - for symbol in &groups[*group_id] { - // find its successors - for succ in all_successors_without_self(symbol) { - // and add its group to the result - match symbol_to_group_index.get(&succ) { - Some(index) => { - result.insert(*index); - } - None => unreachable!("no index for symbol {:?}", succ), - } - } - } - - // don't introduce any cycles to self - result.remove(group_id); - - result - }; - - match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { - Ok(sorted_group_ids) => { - for sorted_group in sorted_group_ids.iter().rev() { - for group_id in sorted_group.iter().rev() { - let group = &groups[*group_id]; - - group_to_declaration( - group, - &env.closures, - &mut all_successors_with_self, - &mut can_defs_by_symbol, - &mut declarations, - ); - } - } - } - Err(_) => unreachable!("there should be no cycles now!"), - } - - for problem in problems { - env.problem(problem); - } - - (Ok(declarations), output) - } - } -} - -fn group_to_declaration( - group: &[Symbol], - closures: &MutMap, - successors: &mut dyn FnMut(&Symbol) -> Vec, - can_defs_by_symbol: &mut MutMap, - declarations: &mut Vec, -) { - use Declaration::*; - - // We want only successors in the current group, otherwise definitions get duplicated - let filtered_successors = |symbol: &Symbol| -> Vec { - let mut result = successors(symbol); - - result.retain(|key| group.contains(key)); - result - }; - - // Patterns like - // - // { x, y } = someDef - // - // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), - // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key - // for a definition, so every definition is only inserted (thus typechecked and emitted) once - let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - - for cycle in strongly_connected_components(group, filtered_successors) { - if cycle.len() == 1 { - let symbol = &cycle[0]; - - match can_defs_by_symbol.remove(symbol) { - Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive - if let Closure(ClosureData { - recursive: recursive @ Recursive::NotRecursive, - .. - }) = &mut new_def.loc_expr.value - { - *recursive = closure_recursivity(*symbol, closures); - } - - let is_recursive = successors(symbol).contains(symbol); - - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - seen_pattern_regions.push(new_def.loc_pattern.region); - - if is_recursive { - declarations.push(DeclareRec(vec![new_def])); - } else { - declarations.push(Declare(new_def)); - } - } - } - None => roc_error_macros::internal_error!("def not available {:?}", symbol), - } - } else { - let mut can_defs = Vec::new(); - - // Topological sort gives us the reverse of the sorting we want! - for symbol in cycle.into_iter().rev() { - match can_defs_by_symbol.remove(&symbol) { - Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive - if let Closure(ClosureData { - recursive: recursive @ Recursive::NotRecursive, - .. - }) = &mut new_def.loc_expr.value - { - *recursive = closure_recursivity(symbol, closures); - } - - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - seen_pattern_regions.push(new_def.loc_pattern.region); - - can_defs.push(new_def); - } - } - None => roc_error_macros::internal_error!("def not available {:?}", symbol), - } - } - - declarations.push(DeclareRec(can_defs)); - } - } -} - fn strongly_connected_components_improved( length: usize, bitvec: &bitvec::vec::BitVec, @@ -2277,7 +1874,7 @@ pub fn can_defs_with_return<'a>( } } - let (can_defs, output) = sort_can_defs(env, unsorted, output); + let (can_defs, output) = sort_can_defs_improved(env, unsorted, output); match can_defs { Ok(decls) => { diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 10e9479f23..2dade79cd6 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -9,7 +9,7 @@ use crate::num::{ use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use roc_collections::{MutMap, MutSet, SendMap, VecMap, VecSet}; +use roc_collections::{MutSet, SendMap, VecMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -1091,34 +1091,6 @@ fn canonicalize_when_branch<'a>( ) } -pub fn local_successors_with_duplicates<'a>( - references: &'a References, - closures: &'a MutMap, -) -> Vec { - let mut answer: Vec<_> = references.value_lookups().copied().collect(); - - let mut stack: Vec<_> = references.calls().copied().collect(); - let mut seen = Vec::new(); - - while let Some(symbol) = stack.pop() { - if seen.contains(&symbol) { - continue; - } - - if let Some(references) = closures.get(&symbol) { - answer.extend(references.value_lookups().copied()); - stack.extend(references.calls().copied()); - - seen.push(symbol); - } - } - - answer.sort(); - answer.dedup(); - - answer -} - enum CanonicalizeRecordProblem { InvalidOptionalValue { field_name: Lowercase, From e40e4ae846d30932c3ca2d8d3a56f6b6337f0ac1 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 15:11:02 +0200 Subject: [PATCH 64/89] rename --- compiler/can/src/def.rs | 24 ++++++++++++------------ compiler/can/src/module.rs | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index c0f22484a7..8491103d26 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -28,7 +28,7 @@ use roc_types::types::LambdaSet; use roc_types::types::{Alias, Type}; use std::collections::HashMap; use std::fmt::Debug; -use ven_graph::{strongly_connected_components, topological_sort}; +use ven_graph::topological_sort; #[derive(Clone, Debug)] pub struct Def { @@ -143,7 +143,7 @@ fn sort_type_defs_before_introduction( let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied(); - strongly_connected_components(&defined_symbols, all_successors_with_self) + ven_graph::strongly_connected_components(&defined_symbols, all_successors_with_self) }; // then sort the strongly connected components @@ -925,7 +925,7 @@ impl DefIds { } #[inline(always)] -pub fn sort_can_defs_improved( +pub fn sort_can_defs( env: &mut Env<'_>, defs: CanDefs, mut output: Output, @@ -950,7 +950,7 @@ pub fn sort_can_defs_improved( // groups are in reversed order for group in groups.into_iter().rev() { - group_to_declaration_improved( + group_to_declaration( &def_ids, &group, &env.closures, @@ -976,7 +976,7 @@ pub fn sort_can_defs_improved( // // foo = if b then foo else bar - let sccs = strongly_connected_components_improved( + let sccs = strongly_connected_components( def_ids.length as usize, &def_ids.references, &nodes_in_cycle, @@ -1086,7 +1086,7 @@ pub fn sort_can_defs_improved( for group_id in sorted_group.iter().rev() { let group = &groups[*group_id]; - group_to_declaration_improved( + group_to_declaration( &def_ids, group, &env.closures, @@ -1108,7 +1108,7 @@ pub fn sort_can_defs_improved( } } -fn strongly_connected_components_improved( +fn strongly_connected_components( length: usize, bitvec: &bitvec::vec::BitVec, group: &[u32], @@ -1213,7 +1213,7 @@ fn recurse_onto(length: usize, bitvec: &bitvec::vec::BitVec, v: usize, param } } -fn group_to_declaration_improved( +fn group_to_declaration( def_ids: &DefIds, group: &[u32], closures: &MutMap, @@ -1231,8 +1231,7 @@ fn group_to_declaration_improved( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - let sccs = - strongly_connected_components_improved(def_ids.length as usize, &def_ids.references, group); + let sccs = strongly_connected_components(def_ids.length as usize, &def_ids.references, group); for cycle in sccs { if cycle.len() == 1 { @@ -1874,7 +1873,7 @@ pub fn can_defs_with_return<'a>( } } - let (can_defs, output) = sort_can_defs_improved(env, unsorted, output); + let (can_defs, output) = sort_can_defs(env, unsorted, output); match can_defs { Ok(decls) => { @@ -2209,7 +2208,8 @@ fn correct_mutual_recursive_type_alias<'a>( // TODO investigate should this be in a loop? let defined_symbols: Vec = original_aliases.keys().copied().collect(); - let cycles = strongly_connected_components(&defined_symbols, all_successors_with_self); + let cycles = + ven_graph::strongly_connected_components(&defined_symbols, all_successors_with_self); let mut solved_aliases = ImMap::default(); for cycle in cycles { diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 345d6905af..317b00578a 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,5 +1,5 @@ use crate::abilities::AbilitiesStore; -use crate::def::{canonicalize_defs, sort_can_defs_improved, Declaration, Def}; +use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; use crate::expr::{ClosureData, Expr, Output}; @@ -361,7 +361,7 @@ pub fn canonicalize_module_defs<'a>( ..Default::default() }; - let sorted = sort_can_defs_improved(&mut env, defs, new_output); + let sorted = sort_can_defs(&mut env, defs, new_output); match sorted { (Ok(mut declarations), output) => { From 5d68a00d1cb92ee2120cc9a129f6e5bf111c2984 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 15:40:43 +0200 Subject: [PATCH 65/89] make ReferenceMatrix type/module --- compiler/can/src/lib.rs | 1 + compiler/can/src/reference_matrix.rs | 224 +++++++++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 compiler/can/src/reference_matrix.rs diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index f22d1a1fe6..4e90bf4d4d 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -15,5 +15,6 @@ pub mod num; pub mod operator; pub mod pattern; pub mod procedure; +mod reference_matrix; pub mod scope; pub mod string; diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs new file mode 100644 index 0000000000..db8eff5eaa --- /dev/null +++ b/compiler/can/src/reference_matrix.rs @@ -0,0 +1,224 @@ +// see if we get better performance with different integer types +pub(crate) type Element = u8; +pub(crate) type BitVec = bitvec::vec::BitVec; +pub(crate) type BitSlice = bitvec::prelude::BitSlice; + +pub(crate) struct ReferenceMatrix { + bitvec: BitVec, + length: usize, +} + +impl ReferenceMatrix { + pub fn new(length: usize) -> Self { + Self { + bitvec: BitVec::repeat(false, length * length), + length, + } + } + + pub fn references_for(&self, row: usize) -> impl Iterator + '_ { + self.row_slice(row).iter_ones() + } + + #[inline(always)] + fn row_slice(&self, row: usize) -> &BitSlice { + &self.bitvec[row * self.length..][..self.length] + } + + pub const fn len(&self) -> usize { + self.length + } + + pub const fn is_empty(&self) -> bool { + self.length == 0 + } + + #[inline(always)] + fn set(&mut self, index: usize, value: bool) { + self.bitvec.set(index, value) + } + + #[inline(always)] + fn get(&self, index: usize) -> bool { + self.bitvec[index] + } +} + +impl ReferenceMatrix { + pub fn topological_sort_into_groups(&self) -> Result>, (Vec>, Vec)> { + let length = self.length; + let bitvec = &self.bitvec; + + if length == 0 { + return Ok(Vec::new()); + } + + let mut preds_map: Vec = vec![0; length]; + + // this is basically summing the columns, I don't see a better way to do it + for row in bitvec.chunks(length) { + for succ in row.iter_ones() { + preds_map[succ] += 1; + } + } + + let mut groups = Vec::>::new(); + + // the initial group contains all symbols with no predecessors + let mut prev_group: Vec = preds_map + .iter() + .enumerate() + .filter_map(|(node, &num_preds)| { + if num_preds == 0 { + Some(node as u32) + } else { + None + } + }) + .collect(); + + if prev_group.is_empty() { + let remaining: Vec = (0u32..length as u32).collect(); + return Err((Vec::new(), remaining)); + } + + // NOTE: the original now removes elements from the preds_map if they have count 0 + // for node in &prev_group { + // preds_map.remove(node); + // } + + while preds_map.iter().any(|x| *x > 0) { + let mut next_group = Vec::::new(); + for node in &prev_group { + let row = &bitvec[length * (*node as usize)..][..length]; + for succ in row.iter_ones() { + { + let num_preds = preds_map.get_mut(succ).unwrap(); + *num_preds = num_preds.saturating_sub(1); + if *num_preds > 0 { + continue; + } + } + + let count = preds_map[succ]; + preds_map[succ] = -1; + + if count > -1 { + next_group.push(succ as u32); + } + } + } + groups.push(std::mem::replace(&mut prev_group, next_group)); + if prev_group.is_empty() { + let remaining: Vec = (0u32..length as u32) + .filter(|i| preds_map[*i as usize] > 0) + .collect(); + return Err((groups, remaining)); + } + } + groups.push(prev_group); + + Ok(groups) + } + + pub fn strongly_connected_components(&self, group: &[u32]) -> Vec> { + let mut params = Params::new(self.length, group); + + 'outer: loop { + for (node, value) in params.preorders.iter().enumerate() { + if let Preorder::Removed = value { + continue; + } + + recurse_onto(self.length, &self.bitvec, node, &mut params); + + continue 'outer; + } + + break params.scc; + } + } +} + +#[derive(Clone, Copy)] +enum Preorder { + Empty, + Filled(usize), + Removed, +} + +struct Params { + preorders: Vec, + c: usize, + p: Vec, + s: Vec, + scc: Vec>, + scca: Vec, +} + +impl Params { + fn new(length: usize, group: &[u32]) -> Self { + let mut preorders = vec![Preorder::Removed; length]; + + for value in group { + preorders[*value as usize] = Preorder::Empty; + } + + Self { + preorders, + c: 0, + s: Vec::new(), + p: Vec::new(), + scc: Vec::new(), + scca: Vec::new(), + } + } +} + +fn recurse_onto(length: usize, bitvec: &bitvec::vec::BitVec, v: usize, params: &mut Params) { + params.preorders[v] = Preorder::Filled(params.c); + + params.c += 1; + + params.s.push(v as u32); + params.p.push(v as u32); + + for w in bitvec[v * length..][..length].iter_ones() { + if !params.scca.contains(&(w as u32)) { + match params.preorders[w] { + Preorder::Filled(pw) => loop { + let index = *params.p.last().unwrap(); + + match params.preorders[index as usize] { + Preorder::Empty => unreachable!(), + Preorder::Filled(current) => { + if current > pw { + params.p.pop(); + } else { + break; + } + } + Preorder::Removed => {} + } + }, + Preorder::Empty => recurse_onto(length, bitvec, w, params), + Preorder::Removed => {} + } + } + } + + if params.p.last() == Some(&(v as u32)) { + params.p.pop(); + + let mut component = Vec::new(); + while let Some(node) = params.s.pop() { + component.push(node); + params.scca.push(node); + params.preorders[node as usize] = Preorder::Removed; + if node as usize == v { + break; + } + } + params.scc.push(component); + } +} From 0c10fa31f5913f683a5418795a0166b3c38440c3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 15:41:21 +0200 Subject: [PATCH 66/89] rename --- compiler/can/src/def.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 8491103d26..dd812ae050 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -665,7 +665,7 @@ pub fn canonicalize_defs<'a>( struct DefId(u32); #[derive(Debug)] -struct DefIds { +struct DefOrdering { home: ModuleId, symbol_to_id: Vec<(IdentId, u32)>, @@ -679,7 +679,7 @@ struct DefIds { length: u32, } -impl DefIds { +impl DefOrdering { fn with_capacity(home: ModuleId, capacity: usize) -> Self { use bitvec::vec::BitVec; @@ -930,7 +930,8 @@ pub fn sort_can_defs( defs: CanDefs, mut output: Output, ) -> (Result, RuntimeError>, Output) { - let def_ids = DefIds::from_defs_by_symbol(env, &defs.can_defs_by_symbol, &defs.refs_by_symbol); + let def_ids = + DefOrdering::from_defs_by_symbol(env, &defs.can_defs_by_symbol, &defs.refs_by_symbol); let CanDefs { refs_by_symbol, @@ -1214,7 +1215,7 @@ fn recurse_onto(length: usize, bitvec: &bitvec::vec::BitVec, v: usize, param } fn group_to_declaration( - def_ids: &DefIds, + def_ids: &DefOrdering, group: &[u32], closures: &MutMap, can_defs_by_symbol: &mut MutMap, From 9f7c7b56a1126356990490eaf9e6005037968328 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 15:49:32 +0200 Subject: [PATCH 67/89] use ReferenceMatrix in DefOrdering --- compiler/can/src/def.rs | 242 +++------------------------ compiler/can/src/reference_matrix.rs | 14 +- 2 files changed, 23 insertions(+), 233 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index dd812ae050..43c8ad862b 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -7,6 +7,7 @@ use crate::expr::Expr::{self, *}; use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; use crate::procedure::References; +use crate::reference_matrix::ReferenceMatrix; use crate::scope::create_alias; use crate::scope::Scope; use roc_collections::{default_hasher, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; @@ -670,21 +671,19 @@ struct DefOrdering { symbol_to_id: Vec<(IdentId, u32)>, // an length x length matrix indicating who references who - references: bitvec::vec::BitVec, + references: ReferenceMatrix, // references without looking into closure bodies. // Used to spot definitely-wrong recursion - direct_references: bitvec::vec::BitVec, + direct_references: ReferenceMatrix, length: u32, } impl DefOrdering { fn with_capacity(home: ModuleId, capacity: usize) -> Self { - use bitvec::vec::BitVec; - - let references = BitVec::repeat(false, capacity * capacity); - let direct_references = BitVec::repeat(false, capacity * capacity); + let references = ReferenceMatrix::new(capacity); + let direct_references = ReferenceMatrix::new(capacity); Self { home, @@ -753,7 +752,7 @@ impl DefOrdering { id: DefId, referenced: Option, length: usize, - bitvec: &mut bitvec::vec::BitVec, + refmatrix: &mut ReferenceMatrix, ) -> bool { match referenced { None => { @@ -766,7 +765,7 @@ impl DefOrdering { let index = row * length + column; - bitvec.set(index, true); + refmatrix.set(index, true); true } @@ -805,123 +804,27 @@ impl DefOrdering { // id'th row, id'th column let index = (id * self.length) + id; - self.references[index as usize] + self.references.get(index as usize) } #[inline(always)] fn direct_successors(&self, id: u32) -> impl Iterator + '_ { - let row = &self.direct_references[(id * self.length) as usize..][..self.length as usize]; - - row.iter_ones().map(|x| x as u32) + self.direct_references + .references_for(id as usize) + .map(|x| x as u32) } #[inline(always)] fn successors(&self, id: u32) -> impl Iterator + '_ { - let row = &self.references[(id * self.length) as usize..][..self.length as usize]; - - row.iter_ones().map(|x| x as u32) + self.references + .references_for(id as usize) + .map(|x| x as u32) } #[inline(always)] fn successors_without_self(&self, id: u32) -> impl Iterator + '_ { self.successors(id).filter(move |x| *x != id) } - - #[allow(clippy::type_complexity)] - fn topological_sort_into_groups(&self) -> Result>, (Vec>, Vec)> { - let length = self.length as usize; - let bitvec = &self.references; - - if length == 0 { - return Ok(Vec::new()); - } - - let mut preds_map: Vec = vec![0; length]; - - // this is basically summing the columns, I don't see a better way to do it - for row in bitvec.chunks(length) { - for succ in row.iter_ones() { - preds_map[succ] += 1; - } - } - - let mut groups = Vec::>::new(); - - // the initial group contains all symbols with no predecessors - let mut prev_group: Vec = preds_map - .iter() - .enumerate() - .filter_map(|(node, &num_preds)| { - if num_preds == 0 { - Some(node as u32) - } else { - None - } - }) - .collect(); - - if prev_group.is_empty() { - let remaining: Vec = (0u32..length as u32).collect(); - return Err((Vec::new(), remaining)); - } - - // NOTE: the original now removes elements from the preds_map if they have count 0 - // for node in &prev_group { - // preds_map.remove(node); - // } - - while preds_map.iter().any(|x| *x > 0) { - let mut next_group = Vec::::new(); - for node in &prev_group { - let row = &bitvec[length * (*node as usize)..][..length]; - for succ in row.iter_ones() { - { - let num_preds = preds_map.get_mut(succ).unwrap(); - *num_preds = num_preds.saturating_sub(1); - if *num_preds > 0 { - continue; - } - } - - let count = preds_map[succ]; - preds_map[succ] = -1; - - if count > -1 { - next_group.push(succ as u32); - } - } - } - groups.push(std::mem::replace(&mut prev_group, next_group)); - if prev_group.is_empty() { - let remaining: Vec = (0u32..length as u32) - .filter(|i| preds_map[*i as usize] > 0) - .collect(); - return Err((groups, remaining)); - } - } - groups.push(prev_group); - Ok(groups) - } - - #[allow(dead_code)] - fn debug_relations(&self) { - for id in 0u32..self.length as u32 { - let row = &self.references[(id * self.length) as usize..][..self.length as usize]; - - let matches = row - .iter() - .enumerate() - .filter(move |t| *t.1) - .map(|t| t.0 as u32); - - for m in matches { - let a = self.get_symbol(id).unwrap(); - let b = self.get_symbol(m).unwrap(); - - println!("{:?} <- {:?}", a, b); - } - } - } } #[inline(always)] @@ -945,7 +848,7 @@ pub fn sort_can_defs( // TODO also do the same `addDirects` check elm/compiler does, so we can // report an error if a recursive definition can't possibly terminate! - match def_ids.topological_sort_into_groups() { + match def_ids.references.topological_sort_into_groups() { Ok(groups) => { let mut declarations = Vec::new(); @@ -977,11 +880,9 @@ pub fn sort_can_defs( // // foo = if b then foo else bar - let sccs = strongly_connected_components( - def_ids.length as usize, - &def_ids.references, - &nodes_in_cycle, - ); + let sccs = def_ids + .references + .strongly_connected_components(&nodes_in_cycle); for cycle in sccs { // check whether the cycle is faulty, which is when it has @@ -1109,111 +1010,6 @@ pub fn sort_can_defs( } } -fn strongly_connected_components( - length: usize, - bitvec: &bitvec::vec::BitVec, - group: &[u32], -) -> Vec> { - let mut params = Params::new(length, group); - - 'outer: loop { - for (node, value) in params.preorders.iter().enumerate() { - if let Preorder::Removed = value { - continue; - } - - recurse_onto(length, bitvec, node, &mut params); - - continue 'outer; - } - - break params.scc; - } -} - -#[derive(Clone, Copy)] -enum Preorder { - Empty, - Filled(usize), - Removed, -} - -struct Params { - preorders: Vec, - c: usize, - p: Vec, - s: Vec, - scc: Vec>, - scca: Vec, -} - -impl Params { - fn new(length: usize, group: &[u32]) -> Self { - let mut preorders = vec![Preorder::Removed; length]; - - for value in group { - preorders[*value as usize] = Preorder::Empty; - } - - Self { - preorders, - c: 0, - s: Vec::new(), - p: Vec::new(), - scc: Vec::new(), - scca: Vec::new(), - } - } -} - -fn recurse_onto(length: usize, bitvec: &bitvec::vec::BitVec, v: usize, params: &mut Params) { - params.preorders[v] = Preorder::Filled(params.c); - - params.c += 1; - - params.s.push(v as u32); - params.p.push(v as u32); - - for w in bitvec[v * length..][..length].iter_ones() { - if !params.scca.contains(&(w as u32)) { - match params.preorders[w] { - Preorder::Filled(pw) => loop { - let index = *params.p.last().unwrap(); - - match params.preorders[index as usize] { - Preorder::Empty => unreachable!(), - Preorder::Filled(current) => { - if current > pw { - params.p.pop(); - } else { - break; - } - } - Preorder::Removed => {} - } - }, - Preorder::Empty => recurse_onto(length, bitvec, w, params), - Preorder::Removed => {} - } - } - } - - if params.p.last() == Some(&(v as u32)) { - params.p.pop(); - - let mut component = Vec::new(); - while let Some(node) = params.s.pop() { - component.push(node); - params.scca.push(node); - params.preorders[node as usize] = Preorder::Removed; - if node as usize == v { - break; - } - } - params.scc.push(component); - } -} - fn group_to_declaration( def_ids: &DefOrdering, group: &[u32], @@ -1232,7 +1028,7 @@ fn group_to_declaration( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - let sccs = strongly_connected_components(def_ids.length as usize, &def_ids.references, group); + let sccs = def_ids.references.strongly_connected_components(group); for cycle in sccs { if cycle.len() == 1 { diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index db8eff5eaa..d1aa5a5766 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -3,6 +3,7 @@ pub(crate) type Element = u8; pub(crate) type BitVec = bitvec::vec::BitVec; pub(crate) type BitSlice = bitvec::prelude::BitSlice; +#[derive(Debug)] pub(crate) struct ReferenceMatrix { bitvec: BitVec, length: usize, @@ -25,26 +26,19 @@ impl ReferenceMatrix { &self.bitvec[row * self.length..][..self.length] } - pub const fn len(&self) -> usize { - self.length - } - - pub const fn is_empty(&self) -> bool { - self.length == 0 - } - #[inline(always)] - fn set(&mut self, index: usize, value: bool) { + pub fn set(&mut self, index: usize, value: bool) { self.bitvec.set(index, value) } #[inline(always)] - fn get(&self, index: usize) -> bool { + pub fn get(&self, index: usize) -> bool { self.bitvec[index] } } impl ReferenceMatrix { + #[allow(clippy::type_complexity)] pub fn topological_sort_into_groups(&self) -> Result>, (Vec>, Vec)> { let length = self.length; let bitvec = &self.bitvec; From a40483a2ecea819bda52823b4e6b011ee612880e Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 16:05:06 +0200 Subject: [PATCH 68/89] cleanup --- compiler/can/src/def.rs | 100 +++++++++------------------ compiler/can/src/reference_matrix.rs | 4 +- 2 files changed, 34 insertions(+), 70 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 43c8ad862b..e5827d40f9 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -662,9 +662,6 @@ pub fn canonicalize_defs<'a>( ) } -#[derive(Clone, Copy)] -struct DefId(u32); - #[derive(Debug)] struct DefOrdering { home: ModuleId, @@ -682,14 +679,11 @@ struct DefOrdering { impl DefOrdering { fn with_capacity(home: ModuleId, capacity: usize) -> Self { - let references = ReferenceMatrix::new(capacity); - let direct_references = ReferenceMatrix::new(capacity); - Self { home, symbol_to_id: Vec::with_capacity(capacity), - references, - direct_references, + references: ReferenceMatrix::new(capacity), + direct_references: ReferenceMatrix::new(capacity), length: capacity as u32, } } @@ -708,7 +702,7 @@ impl DefOrdering { } for (symbol, (_, references)) in refs_by_symbol.iter() { - let def_id = DefId(this.get_id(*symbol).unwrap()); + let def_id = this.get_id(*symbol).unwrap(); for referenced in references.value_lookups() { this.register_reference(def_id, *referenced); @@ -735,67 +729,43 @@ impl DefOrdering { } fn get_id(&self, symbol: Symbol) -> Option { - self.symbol_to_id - .iter() - .find(|(id, _)| *id == symbol.ident_id()) - .map(|t| t.1) + if symbol.module_id() != self.home { + return None; + } + + let target = symbol.ident_id(); + + for (ident_id, def_id) in self.symbol_to_id.iter() { + if target == *ident_id { + return Some(*def_id); + } + } + + None } fn get_symbol(&self, id: u32) -> Option { - self.symbol_to_id - .iter() - .find(|(_, def_id)| id == *def_id) - .map(|t| Symbol::new(self.home, t.0)) - } - - fn register_help( - id: DefId, - referenced: Option, - length: usize, - refmatrix: &mut ReferenceMatrix, - ) -> bool { - match referenced { - None => { - // this symbol is not defined within the let-block that this DefIds represents - false - } - Some(referenced_id) => { - let row = id.0 as usize; - let column = referenced_id as usize; - - let index = row * length + column; - - refmatrix.set(index, true); - - true + for (ident_id, def_id) in self.symbol_to_id.iter() { + if id == *def_id { + return Some(Symbol::new(self.home, *ident_id)); } } + + None } - fn register_direct_reference(&mut self, id: DefId, referenced: Symbol) -> bool { - if referenced.module_id() != self.home { - return false; + fn register_direct_reference(&mut self, id: u32, referenced: Symbol) { + if let Some(ref_id) = self.get_id(referenced) { + self.direct_references + .set_row_col(id as usize, ref_id as usize, true); } - - Self::register_help( - id, - self.get_id(referenced), - self.length as usize, - &mut self.direct_references, - ) } - fn register_reference(&mut self, id: DefId, referenced: Symbol) -> bool { - if referenced.module_id() != self.home { - return false; + fn register_reference(&mut self, id: u32, referenced: Symbol) { + if let Some(ref_id) = self.get_id(referenced) { + self.references + .set_row_col(id as usize, ref_id as usize, true); } - - Self::register_help( - id, - self.get_id(referenced), - self.length as usize, - &mut self.references, - ) } fn is_self_recursive(&self, id: u32) -> bool { @@ -807,13 +777,6 @@ impl DefOrdering { self.references.get(index as usize) } - #[inline(always)] - fn direct_successors(&self, id: u32) -> impl Iterator + '_ { - self.direct_references - .references_for(id as usize) - .map(|x| x as u32) - } - #[inline(always)] fn successors(&self, id: u32) -> impl Iterator + '_ { self.references @@ -896,8 +859,9 @@ pub fn sort_can_defs( // q = p let is_invalid_cycle = match cycle.get(0) { Some(def_id) => def_ids - .direct_successors(*def_id) - .any(|key| cycle.contains(&key)), + .direct_references + .references_for(*def_id as usize) + .any(|key| cycle.contains(&(key as u32))), None => false, }; diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index d1aa5a5766..4865e95c7e 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -27,8 +27,8 @@ impl ReferenceMatrix { } #[inline(always)] - pub fn set(&mut self, index: usize, value: bool) { - self.bitvec.set(index, value) + pub fn set_row_col(&mut self, row: usize, col: usize, value: bool) { + self.bitvec.set(row * self.length + col, value) } #[inline(always)] From 09869ac6457c6cddb2be32257ad7c9c0f2301bce Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 16:21:28 +0200 Subject: [PATCH 69/89] pick usize as the underlying integer type --- compiler/can/src/reference_matrix.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index 4865e95c7e..12db131e37 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -1,5 +1,5 @@ // see if we get better performance with different integer types -pub(crate) type Element = u8; +pub(crate) type Element = usize; pub(crate) type BitVec = bitvec::vec::BitVec; pub(crate) type BitSlice = bitvec::prelude::BitSlice; @@ -169,7 +169,7 @@ impl Params { } } -fn recurse_onto(length: usize, bitvec: &bitvec::vec::BitVec, v: usize, params: &mut Params) { +fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) { params.preorders[v] = Preorder::Filled(params.c); params.c += 1; From 55749e7470f7d8c045b4971850d611bf2d3cb2bc Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 16:22:27 +0200 Subject: [PATCH 70/89] make diff a bit smaller --- compiler/can/src/module.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 317b00578a..b34aba4fc5 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -361,9 +361,7 @@ pub fn canonicalize_module_defs<'a>( ..Default::default() }; - let sorted = sort_can_defs(&mut env, defs, new_output); - - match sorted { + match sort_can_defs(&mut env, defs, new_output) { (Ok(mut declarations), output) => { use crate::def::Declaration::*; From 6306923e0f72535164527bc763a3e9045c0e0ba7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 16:24:47 +0200 Subject: [PATCH 71/89] add thanks comment --- compiler/can/src/reference_matrix.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index 12db131e37..bafd37cbcb 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -37,6 +37,14 @@ impl ReferenceMatrix { } } +// Topological sort and strongly-connected components +// +// Adapted from the Pathfinding crate v2.0.3 by Samuel Tardieu , +// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 +// +// The original source code can be found at: https://github.com/samueltardieu/pathfinding +// +// Thank you, Samuel! impl ReferenceMatrix { #[allow(clippy::type_complexity)] pub fn topological_sort_into_groups(&self) -> Result>, (Vec>, Vec)> { @@ -76,11 +84,6 @@ impl ReferenceMatrix { return Err((Vec::new(), remaining)); } - // NOTE: the original now removes elements from the preds_map if they have count 0 - // for node in &prev_group { - // preds_map.remove(node); - // } - while preds_map.iter().any(|x| *x > 0) { let mut next_group = Vec::::new(); for node in &prev_group { From 7196e7cdac1118cab79336cfc1b4a9d9ee6c13c8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 16:26:52 +0200 Subject: [PATCH 72/89] add further comment --- compiler/can/src/reference_matrix.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index bafd37cbcb..0c2d67cda4 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -3,6 +3,10 @@ pub(crate) type Element = usize; pub(crate) type BitVec = bitvec::vec::BitVec; pub(crate) type BitSlice = bitvec::prelude::BitSlice; +/// A square boolean matrix used to store relations +/// +/// We use this for sorting definitions so every definition is defined before it is used. +/// This functionality is also used to spot and report invalid recursion. #[derive(Debug)] pub(crate) struct ReferenceMatrix { bitvec: BitVec, From 48ce1c14bfed738770e7238f6c5c1ea30cce5029 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 17:10:27 +0200 Subject: [PATCH 73/89] ordering changed in a test --- compiler/test_gen/src/gen_refcount.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 5b857a1fbc..25ade5e934 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -301,8 +301,8 @@ fn refcount_different_rosetrees_inc() { (Pointer, Pointer), &[ Live(2), // s - Live(2), // s1 Live(3), // i1 + Live(2), // s1 Live(1), // [i1, i1] Live(1), // i2 Live(1), // [s1, s1] From 28cb9bf36e30f0f15fee07f430a6f4dadbf78c86 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 19:28:46 +0200 Subject: [PATCH 74/89] use custom type for TopologicalSort result --- compiler/can/src/def.rs | 8 +++++-- compiler/can/src/reference_matrix.rs | 32 ++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index e5827d40f9..4d764d337e 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -8,6 +8,7 @@ use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; use crate::procedure::References; use crate::reference_matrix::ReferenceMatrix; +use crate::reference_matrix::TopologicalSort; use crate::scope::create_alias; use crate::scope::Scope; use roc_collections::{default_hasher, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; @@ -812,7 +813,7 @@ pub fn sort_can_defs( // TODO also do the same `addDirects` check elm/compiler does, so we can // report an error if a recursive definition can't possibly terminate! match def_ids.references.topological_sort_into_groups() { - Ok(groups) => { + TopologicalSort::Groups { groups } => { let mut declarations = Vec::new(); // groups are in reversed order @@ -828,7 +829,10 @@ pub fn sort_can_defs( (Ok(declarations), output) } - Err((mut groups, nodes_in_cycle)) => { + TopologicalSort::HasCycles { + mut groups, + nodes_in_cycle, + } => { let mut declarations = Vec::new(); let mut problems = Vec::new(); diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index 0c2d67cda4..d0052993e4 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -50,13 +50,12 @@ impl ReferenceMatrix { // // Thank you, Samuel! impl ReferenceMatrix { - #[allow(clippy::type_complexity)] - pub fn topological_sort_into_groups(&self) -> Result>, (Vec>, Vec)> { + pub fn topological_sort_into_groups(&self) -> TopologicalSort { let length = self.length; let bitvec = &self.bitvec; if length == 0 { - return Ok(Vec::new()); + return TopologicalSort::Groups { groups: Vec::new() }; } let mut preds_map: Vec = vec![0; length]; @@ -85,7 +84,11 @@ impl ReferenceMatrix { if prev_group.is_empty() { let remaining: Vec = (0u32..length as u32).collect(); - return Err((Vec::new(), remaining)); + + return TopologicalSort::HasCycles { + groups: Vec::new(), + nodes_in_cycle: remaining, + }; } while preds_map.iter().any(|x| *x > 0) { @@ -114,12 +117,16 @@ impl ReferenceMatrix { let remaining: Vec = (0u32..length as u32) .filter(|i| preds_map[*i as usize] > 0) .collect(); - return Err((groups, remaining)); + + return TopologicalSort::HasCycles { + groups, + nodes_in_cycle: remaining, + }; } } groups.push(prev_group); - Ok(groups) + TopologicalSort::Groups { groups } } pub fn strongly_connected_components(&self, group: &[u32]) -> Vec> { @@ -141,6 +148,19 @@ impl ReferenceMatrix { } } +pub(crate) enum TopologicalSort { + /// There were no cycles, all nodes have been partitioned into groups + Groups { groups: Vec> }, + /// Cycles were found. All nodes that are not part of a cycle have been partitioned + /// into groups. The other elements are in the `cyclic` vector. However, there may be + /// many cycles, or just one big one. Use strongly-connected components to find out + /// exactly what the cycles are and how they fit into the groups. + HasCycles { + groups: Vec>, + nodes_in_cycle: Vec, + }, +} + #[derive(Clone, Copy)] enum Preorder { Empty, From 18ba49c694ab67bc3d7c7b112fecb8e9778f70af Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 19:29:52 +0200 Subject: [PATCH 75/89] add comment --- compiler/can/src/reference_matrix.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index d0052993e4..7db76dfd26 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -104,6 +104,8 @@ impl ReferenceMatrix { } } + // NOTE: we use -1 to mark nodes that have no predecessors, but are already + // part of an earlier group. That ensures nodes are added to just 1 group let count = preds_map[succ]; preds_map[succ] = -1; From fdd0910ba05a73f35d505505f3da9c7df917d948 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 19:32:47 +0200 Subject: [PATCH 76/89] refactor --- compiler/can/src/reference_matrix.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index 7db76dfd26..524c044fd4 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -51,17 +51,14 @@ impl ReferenceMatrix { // Thank you, Samuel! impl ReferenceMatrix { pub fn topological_sort_into_groups(&self) -> TopologicalSort { - let length = self.length; - let bitvec = &self.bitvec; - - if length == 0 { + if self.length == 0 { return TopologicalSort::Groups { groups: Vec::new() }; } - let mut preds_map: Vec = vec![0; length]; + let mut preds_map: Vec = vec![0; self.length]; // this is basically summing the columns, I don't see a better way to do it - for row in bitvec.chunks(length) { + for row in self.bitvec.chunks(self.length) { for succ in row.iter_ones() { preds_map[succ] += 1; } @@ -83,7 +80,7 @@ impl ReferenceMatrix { .collect(); if prev_group.is_empty() { - let remaining: Vec = (0u32..length as u32).collect(); + let remaining: Vec = (0u32..self.length as u32).collect(); return TopologicalSort::HasCycles { groups: Vec::new(), @@ -94,8 +91,7 @@ impl ReferenceMatrix { while preds_map.iter().any(|x| *x > 0) { let mut next_group = Vec::::new(); for node in &prev_group { - let row = &bitvec[length * (*node as usize)..][..length]; - for succ in row.iter_ones() { + for succ in self.references_for(*node as usize) { { let num_preds = preds_map.get_mut(succ).unwrap(); *num_preds = num_preds.saturating_sub(1); @@ -116,7 +112,7 @@ impl ReferenceMatrix { } groups.push(std::mem::replace(&mut prev_group, next_group)); if prev_group.is_empty() { - let remaining: Vec = (0u32..length as u32) + let remaining: Vec = (0u32..self.length as u32) .filter(|i| preds_map[*i as usize] > 0) .collect(); From 285e4f05ec3f3d02c493b72a78bd5923fdb61e8b Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 19:35:01 +0200 Subject: [PATCH 77/89] add comment --- compiler/can/src/reference_matrix.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs index 524c044fd4..eaadee556c 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/can/src/reference_matrix.rs @@ -127,8 +127,9 @@ impl ReferenceMatrix { TopologicalSort::Groups { groups } } - pub fn strongly_connected_components(&self, group: &[u32]) -> Vec> { - let mut params = Params::new(self.length, group); + /// Get the strongly-connected components of the set of input nodes. + pub fn strongly_connected_components(&self, nodes: &[u32]) -> Vec> { + let mut params = Params::new(self.length, nodes); 'outer: loop { for (node, value) in params.preorders.iter().enumerate() { From 6ffe14809f2e946605e5a9870f345015e1835691 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 22 Apr 2022 20:07:28 +0200 Subject: [PATCH 78/89] don't populate Scope with aliases these are now properly imported. For testing reporting we still need a way to provide them without resolving imports (just for speed) --- compiler/can/src/scope.rs | 72 +++++++++++++++++++++------------- reporting/tests/helpers/mod.rs | 2 +- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index b71061f7b2..76a08d276a 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -29,44 +29,60 @@ pub struct Scope { home: ModuleId, } -impl Scope { - pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope { - use roc_types::solved_types::{BuiltinAlias, FreeVars}; - let solved_aliases = roc_types::builtin_aliases::aliases(); - let mut aliases = SendMap::default(); +fn add_aliases(var_store: &mut VarStore) -> SendMap { + use roc_types::solved_types::{BuiltinAlias, FreeVars}; - for (symbol, builtin_alias) in solved_aliases { - let BuiltinAlias { region, vars, typ } = builtin_alias; + let solved_aliases = roc_types::builtin_aliases::aliases(); + let mut aliases = SendMap::default(); - let mut free_vars = FreeVars::default(); - let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + for (symbol, builtin_alias) in solved_aliases { + let BuiltinAlias { region, vars, typ } = builtin_alias; - let mut variables = Vec::new(); - // make sure to sort these variables to make them line up with the type arguments - let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - type_variables.sort(); - for (loc_name, (_, var)) in vars.iter().zip(type_variables) { - variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); - } + let mut free_vars = FreeVars::default(); + let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); - let alias = Alias { - region, - typ, - lambda_set_variables: Vec::new(), - recursion_variables: MutSet::default(), - type_variables: variables, - // TODO(opaques): replace when opaques are included in the stdlib - kind: AliasKind::Structural, - }; - - aliases.insert(symbol, alias); + let mut variables = Vec::new(); + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); + for (loc_name, (_, var)) in vars.iter().zip(type_variables) { + variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); } + let alias = Alias { + region, + typ, + lambda_set_variables: Vec::new(), + recursion_variables: MutSet::default(), + type_variables: variables, + // TODO(opaques): replace when opaques are included in the stdlib + kind: AliasKind::Structural, + }; + + aliases.insert(symbol, alias); + } + + aliases +} + +impl Scope { + pub fn new(home: ModuleId, _var_store: &mut VarStore) -> Scope { Scope { home, idents: Symbol::default_in_scope(), symbols: SendMap::default(), - aliases, + aliases: SendMap::default(), + // TODO(abilities): default abilities in scope + abilities_store: AbilitiesStore::default(), + } + } + + pub fn new_with_aliases(home: ModuleId, var_store: &mut VarStore) -> Scope { + Scope { + home, + idents: Symbol::default_in_scope(), + symbols: SendMap::default(), + aliases: add_aliases(var_store), // TODO(abilities): default abilities in scope abilities_store: AbilitiesStore::default(), } diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 34aa95bf8f..269e0598ff 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -151,7 +151,7 @@ pub fn can_expr_with<'a>( // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, &mut var_store); + let mut scope = Scope::new_with_aliases(home, &mut var_store); let dep_idents = IdentIds::exposed_builtins(0); let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); let (loc_expr, output) = canonicalize_expr( From b0aafd762a2630dbb43d17ab84287f941fba630e Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 8 Apr 2022 11:59:29 -0400 Subject: [PATCH 79/89] Show more type information in pretty printing --- compiler/types/src/pretty_print.rs | 4 +++- compiler/types/src/subs.rs | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index d3111cad1d..f9e11471f3 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -450,7 +450,9 @@ fn write_content<'a>( } // useful for debugging - if false { + if cfg!(debug_assertions) + && std::env::var("ROC_PRETTY_PRINT_ALIAS_CONTENTS").is_ok() + { buf.push_str("[[ but really "); let content = subs.get_content_without_compacting(*_actual); write_content(env, ctx, content, subs, buf, parens); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 1a327c85c3..a94cca1be6 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -772,7 +772,15 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: AliasKind::Opaque => "Opaque", }; - write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual) + write!( + f, + "{}({:?}, {:?}, <{:?}>{:?})", + wrap, + name, + slice, + actual, + SubsFmtContent(subs.get_content_without_compacting(*actual), subs) + ) } Content::RangedNumber(typ, range) => { let slice = subs.get_subs_slice(*range); @@ -833,7 +841,16 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext); for (name, slice) in it { - write!(f, "{:?} {:?}, ", name, slice)?; + write!(f, "{:?} ", name)?; + for var in slice { + write!( + f, + "<{:?}>{:?} ", + var, + SubsFmtContent(subs.get_content_without_compacting(*var), subs) + )?; + } + write!(f, ", ")?; } write!(f, "]<{:?}>", new_ext) From 92dfccedffa925cc2e11489ca3290baeb133e38a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 8 Apr 2022 11:59:37 -0400 Subject: [PATCH 80/89] Document debugging env variables --- compiler/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/README.md b/compiler/README.md index e57984562d..d26c7e862e 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -167,6 +167,20 @@ For a more detailed understanding of the compilation phases, see the `Phase`, `B ## Debugging intermediate representations +### Debugging the typechecker + +Setting the following environment variables: + +- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done, + before and after the unification. +- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification. +- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during + pretty-printing of types. + +Note that this is only relevant during debug builds. Eventually we should have +some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486 +for one. + ### The mono IR If you observe a miscomplication, you may first want to check the generated mono From 02d5cd78856b3fe2ece69f139069d4305b9dc34b Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 8 Apr 2022 12:21:53 -0400 Subject: [PATCH 81/89] Deal with recursive pointers that pass through non-recursive layouts --- compiler/alias_analysis/src/lib.rs | 127 +++++++++++++------- compiler/gen_llvm/src/llvm/refcounting.rs | 34 +++++- compiler/mono/src/layout.rs | 104 +++++++--------- compiler/test_mono/generated/issue_2810.txt | 6 + compiler/test_mono/src/tests.rs | 17 +++ repl_test/src/tests.rs | 20 +++ 6 files changed, 203 insertions(+), 105 deletions(-) create mode 100644 compiler/test_mono/generated/issue_2810.txt diff --git a/compiler/alias_analysis/src/lib.rs b/compiler/alias_analysis/src/lib.rs index d492f3a12a..572a438847 100644 --- a/compiler/alias_analysis/src/lib.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -279,7 +279,8 @@ fn build_entry_point( let block = builder.add_block(); // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + let argument_type = + build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?; // does not make any assumptions about the input // let argument = builder.add_unknown_with(block, &[], argument_type)?; @@ -308,7 +309,11 @@ fn build_entry_point( let block = builder.add_block(); - let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?; + let type_id = layout_spec( + &mut builder, + &Layout::struct_no_name_order(layouts), + &WhenRecursive::Unreachable, + )?; let argument = builder.add_unknown_with(block, &[], type_id)?; @@ -352,8 +357,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> let arg_type_id = layout_spec( &mut builder, &Layout::struct_no_name_order(&argument_layouts), + &WhenRecursive::Unreachable, )?; - let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?; + let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?; let spec = builder.build(arg_type_id, ret_type_id, root)?; @@ -457,10 +463,14 @@ fn stmt_spec<'a>( let mut type_ids = Vec::new(); for p in parameters.iter() { - type_ids.push(layout_spec(builder, &p.layout)?); + type_ids.push(layout_spec( + builder, + &p.layout, + &WhenRecursive::Unreachable, + )?); } - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let jp_arg_type_id = builder.add_tuple_type(&type_ids)?; @@ -500,14 +510,14 @@ fn stmt_spec<'a>( builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id)) } Jump(id, symbols) => { - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let argument = build_tuple_value(builder, env, block, symbols)?; let jpid = env.join_points[id]; builder.add_jump(block, jpid, argument, ret_type_id) } RuntimeError(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -556,11 +566,15 @@ fn build_recursive_tuple_type( builder.add_tuple_type(&field_types) } -fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result { +fn build_tuple_type( + builder: &mut impl TypeContext, + layouts: &[Layout], + when_recursive: &WhenRecursive, +) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { - field_types.push(layout_spec(builder, field)?); + field_types.push(layout_spec(builder, field, when_recursive)?); } builder.add_tuple_type(&field_types) @@ -691,7 +705,7 @@ fn call_spec( .map(|symbol| env.symbols[symbol]) .collect(); - let result_type = layout_spec(builder, ret_layout)?; + let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -761,7 +775,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -782,7 +797,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -806,7 +822,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -828,10 +845,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -851,10 +870,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -879,7 +900,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -903,10 +925,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -936,10 +960,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -975,10 +1001,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1010,7 +1038,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -1087,11 +1116,13 @@ fn call_spec( ) }; - let output_element_type = layout_spec(builder, &output_element_layout)?; + let output_element_type = + layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; let state_layout = Layout::Builtin(Builtin::List(&output_element_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; add_loop(builder, block, state_type, init_state, loop_body) } @@ -1108,7 +1139,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1127,7 +1159,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1139,7 +1172,8 @@ fn call_spec( // ListFindUnsafe returns { value: v, found: Bool=Int1 } let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)]; let output_layout = Layout::struct_no_name_order(&output_layouts); - let output_type = layout_spec(builder, &output_layout)?; + let output_type = + layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?; let loop_body = |builder: &mut FuncDefBuilder, block, output| { let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; @@ -1201,7 +1235,7 @@ fn lowlevel_spec( ) -> Result { use LowLevel::*; - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); @@ -1323,8 +1357,8 @@ fn lowlevel_spec( } DictEmpty => match layout { Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { - let key_id = layout_spec(builder, key_layout)?; - let value_id = layout_spec(builder, value_layout)?; + let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?; + let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?; new_dict(builder, block, key_id, value_id) } _ => unreachable!("empty array does not have a list layout"), @@ -1367,7 +1401,7 @@ fn lowlevel_spec( // TODO overly pessimstic let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); - let result_type = layout_spec(builder, layout)?; + let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -1478,7 +1512,8 @@ fn expr_spec<'a>( let value_id = match tag_layout { UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = + non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?; let value_id = build_tuple_value(builder, env, block, arguments)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); } @@ -1592,7 +1627,7 @@ fn expr_spec<'a>( builder.add_get_tuple_field(block, value_id, *index as u32) } Array { elem_layout, elems } => { - let type_id = layout_spec(builder, elem_layout)?; + let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?; let list = new_list(builder, block, type_id)?; @@ -1619,19 +1654,19 @@ fn expr_spec<'a>( EmptyArray => match layout { Layout::Builtin(Builtin::List(element_layout)) => { - let type_id = layout_spec(builder, element_layout)?; + let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?; new_list(builder, block, type_id) } _ => unreachable!("empty array does not have a list layout"), }, Reset { symbol, .. } => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let value_id = env.symbols[symbol]; builder.add_unknown_with(block, &[value_id], type_id) } RuntimeErrorFunction(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -1658,18 +1693,24 @@ fn literal_spec( } } -fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result { - layout_spec_help(builder, layout, &WhenRecursive::Unreachable) +fn layout_spec( + builder: &mut impl TypeContext, + layout: &Layout, + when_recursive: &WhenRecursive, +) -> Result { + layout_spec_help(builder, layout, when_recursive) } fn non_recursive_variant_types( builder: &mut impl TypeContext, tags: &[&[Layout]], + // If there is a recursive pointer latent within this layout, coming from a containing layout. + when_recursive: &WhenRecursive, ) -> Result> { let mut result = Vec::with_capacity(tags.len()); for tag in tags.iter() { - result.push(build_tuple_type(builder, tag)?); + result.push(build_tuple_type(builder, tag, when_recursive)?); } Ok(result) @@ -1701,7 +1742,7 @@ fn layout_spec_help( builder.add_tuple_type(&[]) } UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?; builder.add_union_type(&variant_types) } UnionLayout::Recursive(_) diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index ce79180f75..504d59b96a 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( for (i, field_layout) in field_layouts.iter().enumerate() { if let Layout::RecursivePointer = field_layout { - panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + let recursive_union_layout = match when_recursive { + WhenRecursive::Unreachable => { + panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + } + WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout, + }; + + // This field is a pointer to the recursive pointer. + let field_ptr = env + .builder + .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field") + .unwrap(); + + // This is the actual pointer to the recursive data. + let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer"); + + debug_assert!(field_value.is_pointer_value()); + + // therefore we must cast it to our desired type + let union_type = + basic_type_from_layout(env, &Layout::Union(*recursive_union_layout)); + let recursive_ptr_field_value = + cast_basic_basic(env.builder, field_value, union_type); + + modify_refcount_layout_help( + env, + parent, + layout_ids, + mode.to_call_mode(fn_val), + when_recursive, + recursive_ptr_field_value, + &Layout::RecursivePointer, + ) } else if field_layout.contains_refcounted() { let field_ptr = env .builder diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index eef312cab3..e21d054ed0 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -78,7 +78,7 @@ impl<'a> RawFunctionLayout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), + Structure(flat_type) => Self::layout_from_flat_type(env, var, flat_type), RangedNumber(typ, _) => Self::from_var(env, typ), // Ints @@ -152,6 +152,7 @@ impl<'a> RawFunctionLayout<'a> { fn layout_from_flat_type( env: &mut Env<'a, '_>, + var: Variable, flat_type: FlatType, ) -> Result { use roc_types::subs::FlatType::*; @@ -195,7 +196,7 @@ impl<'a> RawFunctionLayout<'a> { Self::from_var(env, var) } _ => { - let layout = layout_from_flat_type(env, flat_type)?; + let layout = layout_from_flat_type(env, var, flat_type)?; Ok(Self::ZeroArgumentThunk(layout)) } } @@ -959,7 +960,7 @@ impl<'a> Layout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - Structure(flat_type) => layout_from_flat_type(env, flat_type), + Structure(flat_type) => layout_from_flat_type(env, var, flat_type), Alias(symbol, _args, actual_var, _) => { if let Some(int_width) = IntWidth::try_from_symbol(symbol) { @@ -1297,6 +1298,8 @@ impl<'a> LayoutCache<'a> { target_info: self.target_info, }; + //if true {panic!()} + Layout::from_var(&mut env, var) } @@ -1570,6 +1573,7 @@ impl<'a> Builtin<'a> { fn layout_from_flat_type<'a>( env: &mut Env<'a, '_>, + var: Variable, flat_type: FlatType, ) -> Result, LayoutProblem> { use roc_types::subs::FlatType::*; @@ -1731,7 +1735,7 @@ fn layout_from_flat_type<'a>( debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!( @@ -1742,7 +1746,7 @@ fn layout_from_flat_type<'a>( let union_tags = UnionTags::from_tag_name_index(tag_name); let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } RecursiveTagUnion(rec_var, tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); @@ -1772,6 +1776,7 @@ fn layout_from_flat_type<'a>( } } + env.insert_seen(var); env.insert_seen(rec_var); for (index, &(_name, variables)) in tags_vec.iter().enumerate() { if matches!(nullable, Some(i) if i == index as TagIdIntType) { @@ -1801,6 +1806,7 @@ fn layout_from_flat_type<'a>( tag_layouts.push(tag_layout.into_bump_slice()); } env.remove_seen(rec_var); + env.insert_seen(var); let union_layout = if let Some(tag_id) = nullable { match tag_layouts.into_bump_slice() { @@ -2071,23 +2077,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { } fn union_sorted_tags_help_new<'a>( - arena: &'a Bump, + env: &mut Env<'a, '_>, tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, - subs: &Subs, - target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); + let mut tags_list = Vec::from_iter_in(tags_list.iter(), env.arena); tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - match tags_list.len() { 0 => { // trying to instantiate a type with no values @@ -2098,18 +2095,19 @@ fn union_sorted_tags_help_new<'a>( let tag_name = tag_name.clone(); // just one tag in the union (but with arguments) can be a struct - let mut layouts = Vec::with_capacity_in(tags_list.len(), arena); + let mut layouts = Vec::with_capacity_in(tags_list.len(), env.arena); // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { let var = arguments[0]; - layouts - .push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout")); + layouts.push( + unwrap_num_tag(env.subs, var, env.target_info).expect("invalid num layout"), + ); } _ => { for &var in arguments { - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => { layouts.push(layout); } @@ -2129,8 +2127,8 @@ fn union_sorted_tags_help_new<'a>( } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2151,7 +2149,7 @@ fn union_sorted_tags_help_new<'a>( } num_tags => { // default path - let mut answer = Vec::with_capacity_in(tags_list.len(), arena); + let mut answer = Vec::with_capacity_in(tags_list.len(), env.arena); let mut has_any_arguments = false; let mut nullable: Option<(TagIdIntType, TagName)> = None; @@ -2174,17 +2172,19 @@ fn union_sorted_tags_help_new<'a>( continue; } - let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); + let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena); for &var in arguments { - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => { has_any_arguments = true; // make sure to not unroll recursive types! let self_recursion = opt_rec_var.is_some() - && subs.get_root_key_without_compacting(var) - == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) + && env.subs.get_root_key_without_compacting(var) + == env + .subs + .get_root_key_without_compacting(opt_rec_var.unwrap()) && is_recursive_tag_union(&layout); if self_recursion { @@ -2207,8 +2207,8 @@ fn union_sorted_tags_help_new<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2229,7 +2229,7 @@ fn union_sorted_tags_help_new<'a>( 3..=MAX_ENUM_SIZE if !has_any_arguments => { // type can be stored in a byte // needs the sorted tag names to determine the tag_id - let mut tag_names = Vec::with_capacity_in(answer.len(), arena); + let mut tag_names = Vec::with_capacity_in(answer.len(), env.arena); for (tag_name, _) in answer { tag_names.push(tag_name); @@ -2488,27 +2488,15 @@ pub fn union_sorted_tags_help<'a>( } } -fn layout_from_newtype<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { - debug_assert!(tags.is_newtype_wrapper(subs)); +fn layout_from_newtype<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { + debug_assert!(tags.is_newtype_wrapper(env.subs)); - let (tag_name, var) = tags.get_newtype(subs); + let (tag_name, var) = tags.get_newtype(env.subs); if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") + unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument") } else { - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => layout, Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) @@ -2525,16 +2513,11 @@ fn layout_from_newtype<'a>( } } -fn layout_from_tag_union<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { +fn layout_from_tag_union<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { use UnionVariant::*; - if tags.is_newtype_wrapper(subs) { - return layout_from_newtype(arena, tags, subs, target_info); + if tags.is_newtype_wrapper(env.subs) { + return layout_from_newtype(env, tags); } let tags_vec = &tags.tags; @@ -2545,12 +2528,11 @@ fn layout_from_tag_union<'a>( let &var = arguments.iter().next().unwrap(); - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") + unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument") } _ => { let opt_rec_var = None; - let variant = - union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info); + let variant = union_sorted_tags_help_new(env, tags_vec, opt_rec_var); match variant { Never => Layout::VOID, @@ -2576,7 +2558,7 @@ fn layout_from_tag_union<'a>( NonRecursive { sorted_tag_layouts: tags, } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); tag_layouts.extend(tags.iter().map(|r| r.1)); Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice())) @@ -2585,7 +2567,7 @@ fn layout_from_tag_union<'a>( Recursive { sorted_tag_layouts: tags, } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); tag_layouts.extend(tags.iter().map(|r| r.1)); debug_assert!(tag_layouts.len() > 1); @@ -2597,7 +2579,7 @@ fn layout_from_tag_union<'a>( nullable_name: _, sorted_tag_layouts: tags, } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); tag_layouts.extend(tags.iter().map(|r| r.1)); Layout::Union(UnionLayout::NullableWrapped { diff --git a/compiler/test_mono/generated/issue_2810.txt b/compiler/test_mono/generated/issue_2810.txt new file mode 100644 index 0000000000..d5b11bcac2 --- /dev/null +++ b/compiler/test_mono/generated/issue_2810.txt @@ -0,0 +1,6 @@ +procedure Test.0 (): + let Test.16 : [C TODO, C ] = SystemTool ; + let Test.14 : TODO = Job Test.16; + let Test.13 : [C TODO, C ] = FromJob Test.14; + let Test.4 : TODO = Job Test.13; + ret Test.4; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 86e93346f1..115b5a8ca3 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -1283,6 +1283,23 @@ fn issue_2583_specialize_errors_behind_unified_branches() { ) } +#[mono_test] +fn issue_2810() { + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ) +} + #[mono_test] fn issue_2811() { indoc!( diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 53b692266c..fe2b90d2ea 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1138,3 +1138,23 @@ fn issue_2818() { r" : {} -> List Str", ) } + +#[test] +fn issue_2810_recursive_layout_inside_nonrecursive() { + expect_success( + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ), + "Job (Command (FromJob (Job (Command SystemTool)))) : Job", + ) +} From b61481c6e771f6c3a1969cf00d0d904a9ca852b5 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 8 Apr 2022 17:05:57 -0400 Subject: [PATCH 82/89] Try another strategy - fix recursion vars during typechecking --- compiler/mono/src/layout.rs | 8 ++-- compiler/types/src/subs.rs | 74 +++++++++++++++++++++++++++--------- compiler/unify/src/unify.rs | 76 ++++++++++++++++++++++++++++++++++--- 3 files changed, 131 insertions(+), 27 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index e21d054ed0..58fe798d23 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -196,7 +196,7 @@ impl<'a> RawFunctionLayout<'a> { Self::from_var(env, var) } _ => { - let layout = layout_from_flat_type(env, var, flat_type)?; + let layout = layout_from_flat_type(env, flat_type)?; Ok(Self::ZeroArgumentThunk(layout)) } } @@ -960,7 +960,7 @@ impl<'a> Layout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - Structure(flat_type) => layout_from_flat_type(env, var, flat_type), + Structure(flat_type) => layout_from_flat_type(env, flat_type), Alias(symbol, _args, actual_var, _) => { if let Some(int_width) = IntWidth::try_from_symbol(symbol) { @@ -1573,7 +1573,6 @@ impl<'a> Builtin<'a> { fn layout_from_flat_type<'a>( env: &mut Env<'a, '_>, - var: Variable, flat_type: FlatType, ) -> Result, LayoutProblem> { use roc_types::subs::FlatType::*; @@ -1776,7 +1775,6 @@ fn layout_from_flat_type<'a>( } } - env.insert_seen(var); env.insert_seen(rec_var); for (index, &(_name, variables)) in tags_vec.iter().enumerate() { if matches!(nullable, Some(i) if i == index as TagIdIntType) { @@ -1793,6 +1791,7 @@ fn layout_from_flat_type<'a>( continue; } + let content = subs.get_content_without_compacting(var); tag_layout.push(Layout::from_var(env, var)?); } @@ -1806,7 +1805,6 @@ fn layout_from_flat_type<'a>( tag_layouts.push(tag_layout.into_bump_slice()); } env.remove_seen(rec_var); - env.insert_seen(var); let union_layout = if let Some(tag_id) = nullable { match tag_layouts.into_bump_slice() { diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index a94cca1be6..256e1b7cef 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1906,7 +1906,14 @@ impl Subs { } pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { - occurs(self, &[], var) + occurs(self, &[], var, false) + } + + pub fn occurs_including_recursion_vars( + &self, + var: Variable, + ) -> Result<(), (Variable, Vec)> { + occurs(self, &[], var, true) } pub fn mark_tag_union_recursive( @@ -2876,6 +2883,7 @@ fn occurs( subs: &Subs, seen: &[Variable], input_var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { use self::Content::*; use self::FlatType::*; @@ -2899,47 +2907,77 @@ fn occurs( new_seen.push(root_var); match flat_type { - Apply(_, args) => { - short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter()) - } + Apply(_, args) => short_circuit( + subs, + root_var, + &new_seen, + subs.get_subs_slice(*args).iter(), + include_recursion_var, + ), Func(arg_vars, closure_var, ret_var) => { let it = once(ret_var) .chain(once(closure_var)) .chain(subs.get_subs_slice(*arg_vars).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } Record(vars_by_field, ext_var) => { let slice = SubsSlice::new(vars_by_field.variables_start, vars_by_field.length); let it = once(ext_var).chain(subs.get_subs_slice(slice).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } TagUnion(tags, ext_var) => { for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } FunctionOrTagUnion(_, _, ext_var) => { let it = once(ext_var); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } - RecursiveTagUnion(_rec_var, tags, ext_var) => { - // TODO rec_var is excluded here, verify that this is correct + RecursiveTagUnion(rec_var, tags, ext_var) => { + if include_recursion_var { + new_seen.push(*rec_var); + } for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()), } @@ -2950,7 +2988,7 @@ fn occurs( for var_index in args.into_iter() { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help(subs, root_var, &new_seen, var, include_recursion_var)?; } Ok(()) @@ -2959,7 +2997,7 @@ fn occurs( let mut new_seen = seen.to_owned(); new_seen.push(root_var); - short_circuit_help(subs, root_var, &new_seen, *typ)?; + short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?; // _range_vars excluded because they are not explicitly part of the type. Ok(()) @@ -2974,12 +3012,13 @@ fn short_circuit<'a, T>( root_key: Variable, seen: &[Variable], iter: T, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> where T: Iterator, { for var in iter { - short_circuit_help(subs, root_key, seen, *var)?; + short_circuit_help(subs, root_key, seen, *var, include_recursion_var)?; } Ok(()) @@ -2991,8 +3030,9 @@ fn short_circuit_help( root_key: Variable, seen: &[Variable], var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { - if let Err((v, mut vec)) = occurs(subs, seen, var) { + if let Err((v, mut vec)) = occurs(subs, seen, var, include_recursion_var) { vec.push(root_key); return Err((v, vec)); } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 10738f6382..530db674c8 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -5,7 +5,8 @@ use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, - RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, + RecordFields, Subs, SubsFmtContent, SubsIndex, SubsSlice, UnionTags, Variable, + VariableSubsSlice, }; use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField}; @@ -316,10 +317,10 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option ctx.first, ctx.second, ctx.first, - roc_types::subs::SubsFmtContent(&content_1, subs), + SubsFmtContent(&content_1, subs), mode, ctx.second, - roc_types::subs::SubsFmtContent(&content_2, subs), + SubsFmtContent(&content_2, subs), ); unsafe { UNIFICATION_DEPTH = new_depth }; @@ -576,7 +577,13 @@ fn unify_structure( RecursionVar { structure, .. } => match flat_type { FlatType::TagUnion(_, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut problems = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + problems.extend(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + + problems } FlatType::RecursiveTagUnion(rec, _, _) => { debug_assert!(is_recursion_var(subs, *rec)); @@ -585,7 +592,13 @@ fn unify_structure( } FlatType::FunctionOrTagUnion(_, _, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut problems = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + problems.extend(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + + problems } // Only tag unions can be recursive; everything else is an error. _ => mismatch!( @@ -643,6 +656,59 @@ fn unify_structure( } } +/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive +/// tag union, properly contains a recursion variable that recurses on itself. +// +// When might this not be the case? For example, in the code +// +// Indirect : [ Indirect ConsList ] +// +// ConsList : [ Nil, Cons Indirect ] +// +// l : ConsList +// l = Cons (Indirect (Cons (Indirect Nil))) +// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a +// # ~~~~~~~~~~~~~~~~~~~~~ region-b +// l +// +// Suppose `ConsList` has the expanded type `[ Nil, Cons [ Indirect ] ] as `. +// After unifying the tag application annotated "region-b" with the recursion variable ``, +// the tentative total-type of the application annotated "region-a" would be +// ` = [ Nil, Cons [ Indirect ] ] as `. That is, the type of the recursive tag union +// would be inlined at the site "v", rather than passing through the correct recursion variable +// "rec" first. +// +// This is not incorrect from a type perspective, but causes problems later on for e.g. layout +// determination, which expects recursion variables to be placed correctly. Attempting to detect +// this during layout generation does not work so well because it may be that there *are* recursive +// tag unions that should be inlined, and not pass through recursion variables. So instead, try to +// resolve these cases here. +// +// See tests labeled "issue_2810" for more examples. +fn fix_tag_union_recursion_variable( + subs: &mut Subs, + ctx: &Context, + tag_union_promoted_to_recursive: Variable, + recursion_var: &Content, +) -> Outcome { + debug_assert!(matches!( + subs.get_content_without_compacting(tag_union_promoted_to_recursive), + Structure(FlatType::RecursiveTagUnion(..)) + )); + + let f = subs.get_content_without_compacting(tag_union_promoted_to_recursive); + + let has_recursing_recursive_variable = subs + .occurs_including_recursion_vars(tag_union_promoted_to_recursive) + .is_err(); + + if !has_recursing_recursive_variable { + merge(subs, ctx, recursion_var.clone()) + } else { + vec![] + } +} + fn unify_record( subs: &mut Subs, pool: &mut Pool, From 3906c5c2003ba07c8bb0752fc3da47df85e9b37e Mon Sep 17 00:00:00 2001 From: Ayaz <20735482+ayazhafiz@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:31:41 -0400 Subject: [PATCH 83/89] Clippy --- compiler/unify/src/unify.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 530db674c8..407974fc4d 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -696,14 +696,12 @@ fn fix_tag_union_recursion_variable( Structure(FlatType::RecursiveTagUnion(..)) )); - let f = subs.get_content_without_compacting(tag_union_promoted_to_recursive); - let has_recursing_recursive_variable = subs .occurs_including_recursion_vars(tag_union_promoted_to_recursive) .is_err(); if !has_recursing_recursive_variable { - merge(subs, ctx, recursion_var.clone()) + merge(subs, ctx, *recursion_var) } else { vec![] } From 5dfe44c72d4c317077708ecd4b5a634559760e70 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 8 Apr 2022 17:56:04 -0400 Subject: [PATCH 84/89] Render nullable wrapped tag unions that pass through aliases --- compiler/mono/src/layout.rs | 50 +++++++++++++++-- repl_eval/src/eval.rs | 104 +++++++++++++++++++++--------------- repl_test/src/tests.rs | 19 +++++++ 3 files changed, 128 insertions(+), 45 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 58fe798d23..77d6a2bc30 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -196,7 +196,7 @@ impl<'a> RawFunctionLayout<'a> { Self::from_var(env, var) } _ => { - let layout = layout_from_flat_type(env, flat_type)?; + let layout = layout_from_flat_type(env, var, flat_type)?; Ok(Self::ZeroArgumentThunk(layout)) } } @@ -312,6 +312,50 @@ impl<'a> UnionLayout<'a> { .append(alloc.intersperse(tags_doc, ", ")) .append(alloc.text("]")) } + Recursive(tags) => { + let tags_doc = tags.iter().map(|fields| { + alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )) + }); + alloc + .text("[") + .append(alloc.intersperse(tags_doc, ", ")) + .append(alloc.text("]")) + } + NonNullableUnwrapped(fields) => { + let fields_doc = alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )); + alloc + .text("[") + .append(fields_doc) + .append(alloc.text("]")) + } + NullableUnwrapped { + nullable_id, + other_fields, + } => { + let fields_doc = alloc.text("C ").append( + alloc.intersperse( + other_fields + .iter() + .map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + ), + ); + let tags_doc = if nullable_id { + alloc.concat(vec![alloc.text(", "), fields_doc]) + } else { + alloc.concat(vec![fields_doc, alloc.text(", ")]) + }; + alloc + .text("[") + .append(tags_doc) + .append(alloc.text("]")) + } _ => alloc.text("TODO"), } } @@ -960,7 +1004,7 @@ impl<'a> Layout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - Structure(flat_type) => layout_from_flat_type(env, flat_type), + Structure(flat_type) => layout_from_flat_type(env, var, flat_type), Alias(symbol, _args, actual_var, _) => { if let Some(int_width) = IntWidth::try_from_symbol(symbol) { @@ -1573,6 +1617,7 @@ impl<'a> Builtin<'a> { fn layout_from_flat_type<'a>( env: &mut Env<'a, '_>, + _var: Variable, flat_type: FlatType, ) -> Result, LayoutProblem> { use roc_types::subs::FlatType::*; @@ -1791,7 +1836,6 @@ fn layout_from_flat_type<'a>( continue; } - let content = subs.get_content_without_compacting(var); tag_layout.push(Layout::from_var(env, var)?); } diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index bc718a7c6c..b1825c7288 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -86,12 +86,17 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a>( +/// +/// If we pass through aliases, the top-level alias that should be displayed to the user is passed +/// back as an option. +/// +/// Returns (new type containers, optional alias content, real content). +fn unroll_newtypes_and_aliases<'a>( env: &Env<'a, 'a>, mut content: &'a Content, -) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { +) -> (Vec<'a, NewtypeKind<'a>>, Option<&'a Content>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); - let mut force_alias_content = None; + let mut alias_content = None; loop { match content { Content::Structure(FlatType::TagUnion(tags, _)) @@ -118,18 +123,19 @@ fn unroll_newtypes<'a>( } Content::Alias(_, _, real_var, _) => { // We need to pass through aliases too, because their underlying types may have - // unrolled newtypes. In such cases return the list of unrolled newtypes, but keep - // the content as the alias for readability. For example, + // unrolled newtypes. For example, // T : { a : Str } // v : T // v = { a : "value" } // v - // Here we need the newtype container to be `[RecordField(a)]`, but the content to - // remain as the alias `T`. - force_alias_content = Some(content); + // Here we need the newtype container to be `[RecordField(a)]`. + // + // At the end of the day what we should show to the user is the alias content, not + // what's inside, so keep that around too. + alias_content = Some(content); content = env.subs.get_content_without_compacting(*real_var); } - _ => return (newtype_containers, force_alias_content.unwrap_or(content)), + _ => return (newtype_containers, alias_content, content), } } } @@ -140,8 +146,8 @@ fn apply_newtypes<'a>( mut expr: Expr<'a>, ) -> Expr<'a> { let arena = env.arena; - // Reverse order of what we receieve from `unroll_newtypes` since we want the deepest - // container applied first. + // Reverse order of what we receieve from `unroll_newtypes_and_aliases` since + // we want the deepest container applied first. for container in newtype_containers.into_iter().rev() { match container { NewtypeKind::Tag(tag_name) => { @@ -162,13 +168,6 @@ fn apply_newtypes<'a>( expr } -fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { - while let Content::Alias(_, _, real, _) = content { - content = env.subs.get_content_without_compacting(*real); - } - content -} - fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); @@ -278,13 +277,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); + let (newtype_containers, alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); macro_rules! num_helper { ($ty:ty) => { app.call_function(main_fn_name, |_, num: $ty| { - num_to_ast(env, number_literal_to_ast(env.arena, num), content) + num_to_ast( + env, + number_literal_to_ast(env.arena, num), + // We determine the number from what the alias looks like. + alias_content.unwrap_or(raw_content), + ) }) }; } @@ -292,17 +296,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(app .call_function(main_fn_name, |mem: &A::Memory, num: bool| { - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) })), Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; - let result = match (content, int_width) { + let result = match (raw_content, int_width) { (Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8), (_, U8) => { // This is not a number, it's a tag union or something else app.call_function(main_fn_name, |mem: &A::Memory, num: u8| { - byte_to_ast(env, mem, num, content) + byte_to_ast(env, mem, num, raw_content) }) } // The rest are numbers... for now @@ -344,14 +348,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( main_fn_name, |mem: &A::Memory, (addr, len): (usize, usize)| { - list_to_ast(env, mem, addr, len, elem_layout, content) + list_to_ast(env, mem, addr, len, elem_layout, raw_content) }, )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } Layout::Struct { field_layouts, .. } => { - let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { + let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content { Content::Structure(FlatType::Record(fields, _)) => { Ok(struct_to_ast(env, mem, addr, *fields)) } @@ -413,7 +417,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &'a A::Memory, addr: usize| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -432,7 +443,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( addr, layout, WhenRecursive::Loop(*layout), - content, + raw_content, ) }, )) @@ -447,7 +458,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &A::Memory, addr| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -493,9 +511,10 @@ fn addr_to_ast<'a, M: ReplAppMemory>( }}; } - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); - let expr = match (content, layout) { + let (newtype_containers, _alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); + + let expr = match (raw_content, layout) { (Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, (_, Layout::Builtin(Builtin::Bool)) => { @@ -503,7 +522,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( // num is always false at the moment. let num: bool = mem.deref_bool(addr); - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) } (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; @@ -534,14 +553,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let elem_addr = mem.deref_usize(addr); let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); - list_to_ast(env, mem, elem_addr, len, elem_layout, content) + list_to_ast(env, mem, elem_addr, len, elem_layout, raw_content) } (_, Layout::Builtin(Builtin::Str)) => { let string = mem.deref_str(addr); let arena_str = env.arena.alloc_str(string); Expr::Str(StrLiteral::PlainLine(arena_str)) } - (_, Layout::Struct{field_layouts, ..}) => match content { + (_, Layout::Struct{field_layouts, ..}) => match raw_content { Content::Structure(FlatType::Record(fields, _)) => { struct_to_ast(env, mem, addr, *fields) } @@ -566,7 +585,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } }, (_, Layout::RecursivePointer) => { - match (content, when_recursive) { + match (raw_content, when_recursive) { (Content::RecursionVar { structure, opt_name: _, @@ -580,7 +599,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { let union_layout = UnionLayout::NonRecursive(union_layouts); - let tags = match content { + let tags = match raw_content { Content::Structure(FlatType::TagUnion(tags, _)) => tags, other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), }; @@ -614,7 +633,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { - let (rec_var, tags) = match content { + let (rec_var, tags) = match raw_content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), _ => unreachable!("any other content would have a different layout"), }; @@ -644,7 +663,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -672,7 +691,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -706,7 +725,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } } (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -803,7 +822,8 @@ fn list_to_ast<'a, M: ReplAppMemory>( for index in 0..len { let offset_bytes = index * elem_size; let elem_addr = addr + offset_bytes; - let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); + let (newtype_containers, _alias_content, elem_content) = + unroll_newtypes_and_aliases(env, elem_content); let expr = addr_to_ast( env, mem, diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index fe2b90d2ea..17e3418d3c 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1158,3 +1158,22 @@ fn issue_2810_recursive_layout_inside_nonrecursive() { "Job (Command (FromJob (Job (Command SystemTool)))) : Job", ) } + +#[test] +fn render_nullable_unwrapped_passing_through_alias() { + expect_success( + indoc!( + r#" + Deep : [ L DeepList ] + + DeepList : [ Nil, Cons Deep ] + + v : DeepList + v = (Cons (L (Cons (L (Cons (L Nil)))))) + + v + "# + ), + "Cons (L (Cons (L (Cons (L Nil))))) : DeepList", + ) +} From 1e1ffb2f626d43cb934dd5780c4cab9e0d67c810 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Sat, 9 Apr 2022 18:09:48 -0400 Subject: [PATCH 85/89] Bugfix use root var --- compiler/types/src/subs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 256e1b7cef..a909fde93d 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -2955,7 +2955,7 @@ fn occurs( } RecursiveTagUnion(rec_var, tags, ext_var) => { if include_recursion_var { - new_seen.push(*rec_var); + new_seen.push(subs.get_root_key_without_compacting(*rec_var)); } for slice_index in tags.variables() { let slice = subs[slice_index]; From 939f4135692442714952cdd5fbd3c7951bf225fe Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 9 Apr 2022 22:42:51 -0400 Subject: [PATCH 86/89] Don't try to fix recursion vars if there are other errors --- compiler/unify/src/unify.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 407974fc4d..0c8f8562a5 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -579,9 +579,11 @@ fn unify_structure( // unify the structure with this unrecursive tag union let mut problems = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); - problems.extend(fix_tag_union_recursion_variable( - subs, ctx, ctx.first, other, - )); + if problems.is_empty() { + problems.extend(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } problems } @@ -594,9 +596,11 @@ fn unify_structure( // unify the structure with this unrecursive tag union let mut problems = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); - problems.extend(fix_tag_union_recursion_variable( - subs, ctx, ctx.first, other, - )); + if problems.is_empty() { + problems.extend(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } problems } From bb06bcd7f1ffcab75e034570d918cf048463181c Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Thu, 21 Apr 2022 10:26:16 -0400 Subject: [PATCH 87/89] Fix compile error --- compiler/unify/src/unify.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 0c8f8562a5..88bf2456ca 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -577,15 +577,15 @@ fn unify_structure( RecursionVar { structure, .. } => match flat_type { FlatType::TagUnion(_, _) => { // unify the structure with this unrecursive tag union - let mut problems = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); - if problems.is_empty() { - problems.extend(fix_tag_union_recursion_variable( + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( subs, ctx, ctx.first, other, )); } - problems + outcome } FlatType::RecursiveTagUnion(rec, _, _) => { debug_assert!(is_recursion_var(subs, *rec)); @@ -594,15 +594,15 @@ fn unify_structure( } FlatType::FunctionOrTagUnion(_, _, _) => { // unify the structure with this unrecursive tag union - let mut problems = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); - if problems.is_empty() { - problems.extend(fix_tag_union_recursion_variable( + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( subs, ctx, ctx.first, other, )); } - problems + outcome } // Only tag unions can be recursive; everything else is an error. _ => mismatch!( @@ -707,7 +707,7 @@ fn fix_tag_union_recursion_variable( if !has_recursing_recursive_variable { merge(subs, ctx, *recursion_var) } else { - vec![] + Outcome::default() } } From 3eb7824c7afdf52ef242a4e717ca72f00c255cab Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 22 Apr 2022 17:56:54 -0400 Subject: [PATCH 88/89] Typos --- compiler/mono/src/layout.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 77d6a2bc30..fd764f069d 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -78,7 +78,7 @@ impl<'a> RawFunctionLayout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - Structure(flat_type) => Self::layout_from_flat_type(env, var, flat_type), + Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), RangedNumber(typ, _) => Self::from_var(env, typ), // Ints @@ -152,7 +152,6 @@ impl<'a> RawFunctionLayout<'a> { fn layout_from_flat_type( env: &mut Env<'a, '_>, - var: Variable, flat_type: FlatType, ) -> Result { use roc_types::subs::FlatType::*; @@ -196,7 +195,7 @@ impl<'a> RawFunctionLayout<'a> { Self::from_var(env, var) } _ => { - let layout = layout_from_flat_type(env, var, flat_type)?; + let layout = layout_from_flat_type(env, flat_type)?; Ok(Self::ZeroArgumentThunk(layout)) } } @@ -1004,7 +1003,7 @@ impl<'a> Layout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - Structure(flat_type) => layout_from_flat_type(env, var, flat_type), + Structure(flat_type) => layout_from_flat_type(env, flat_type), Alias(symbol, _args, actual_var, _) => { if let Some(int_width) = IntWidth::try_from_symbol(symbol) { @@ -1342,8 +1341,6 @@ impl<'a> LayoutCache<'a> { target_info: self.target_info, }; - //if true {panic!()} - Layout::from_var(&mut env, var) } @@ -1617,7 +1614,6 @@ impl<'a> Builtin<'a> { fn layout_from_flat_type<'a>( env: &mut Env<'a, '_>, - _var: Variable, flat_type: FlatType, ) -> Result, LayoutProblem> { use roc_types::subs::FlatType::*; From 5d1dd81e93fa87331f06892a347e2fb1e60ec722 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Sat, 23 Apr 2022 14:37:52 -0400 Subject: [PATCH 89/89] Reuse symbol when opaque type wraps a known symbol Opaques decay immediately into their argument during codegen, so we need to handle something that's effectively variable aliasing correctly. This bug popped up while migrating all current private tags to opaques. --- compiler/mono/src/ir.rs | 27 ++++++++++++------- compiler/test_gen/src/gen_tags.rs | 25 +++++++++++++++++ .../generated/opaque_assign_to_symbol.txt | 10 +++++++ compiler/test_mono/src/tests.rs | 17 ++++++++++++ 4 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 compiler/test_mono/generated/opaque_assign_to_symbol.txt diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 69d1a080f8..72b10cc7f5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3499,15 +3499,24 @@ pub fn with_hole<'a>( OpaqueRef { argument, .. } => { let (arg_var, loc_arg_expr) = *argument; - with_hole( - env, - loc_arg_expr.value, - arg_var, - procs, - layout_cache, - assigned, - hole, - ) + + match can_reuse_symbol(env, procs, &loc_arg_expr.value) { + // Opaques decay to their argument. + ReuseSymbol::Value(real_name) => { + let mut result = hole.clone(); + substitute_in_exprs(arena, &mut result, assigned, real_name); + result + } + _ => with_hole( + env, + loc_arg_expr.value, + arg_var, + procs, + layout_cache, + assigned, + hole, + ), + } } Record { diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index eefcb272cf..48dc0880d2 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1580,3 +1580,28 @@ fn issue_2725_alias_polymorphic_lambda() { i64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn opaque_assign_to_symbol() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok ($Variable char) + + out = + when fromUtf8 98 is + Ok ($Variable n) -> n + _ -> 1 + "# + ), + 98, + u8 + ) +} diff --git a/compiler/test_mono/generated/opaque_assign_to_symbol.txt b/compiler/test_mono/generated/opaque_assign_to_symbol.txt new file mode 100644 index 0000000000..8e0c7fe63b --- /dev/null +++ b/compiler/test_mono/generated/opaque_assign_to_symbol.txt @@ -0,0 +1,10 @@ +procedure : `#UserApp.fromUtf8` [C {}, C U8] +procedure = `#UserApp.fromUtf8` (`#UserApp.char`): + let `#UserApp.3` : [C {}, C U8] = Ok `#UserApp.4`; + ret `#UserApp.3`; + +procedure : `#UserApp.out` [C {}, C U8] +procedure = `#UserApp.out` (): + let `#UserApp.2` : U8 = 98i64; + let `#UserApp.1` : [C {}, C U8] = CallByName `#UserApp.fromUtf8` `#UserApp.2`; + ret `#UserApp.1`; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 115b5a8ca3..fbb2612793 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -1330,6 +1330,23 @@ fn specialize_ability_call() { ) } +#[mono_test] +fn opaque_assign_to_symbol() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok ($Variable char) + + out = fromUtf8 98 + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() {