diff --git a/.cargo/config b/.cargo/config index 7f9d89b452..584d158f1c 100644 --- a/.cargo/config +++ b/.cargo/config @@ -2,3 +2,9 @@ test-gen-llvm = "test -p test_gen" test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev" test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm" + +[target.wasm32-unknown-unknown] +# Rust compiler flags for minimum-sized .wasm binary in the web REPL +# opt-level=s Optimizations should focus more on size than speed +# lto=fat Spend extra effort on link-time optimization across crates +rustflags = ["-Copt-level=s", "-Clto=fat"] diff --git a/Cargo.lock b/Cargo.lock index 1353f499e2..f1e283f2c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3762,6 +3762,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_types", ] [[package]] diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 16219d1179..9fa5385640 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -7,7 +7,9 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type}; +use roc_types::types::{ + Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension, +}; #[derive(Clone, Debug, PartialEq)] pub struct Annotation { @@ -29,25 +31,27 @@ pub struct IntroducedVariables { // But then between annotations, the same name can occur multiple times, // but a variable can only have one name. Therefore // `ftv : SendMap`. - pub wildcards: Vec, + pub wildcards: Vec>, pub lambda_sets: Vec, - pub inferred: Vec, - pub var_by_name: SendMap, + pub inferred: Vec>, + // NB: A mapping of a -> Loc in this map has the region of the first-seen var, but there + // may be multiple occurrences of it! + pub var_by_name: SendMap>, pub name_by_var: SendMap, pub host_exposed_aliases: MutMap, } impl IntroducedVariables { - pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + pub fn insert_named(&mut self, name: Lowercase, var: Loc) { self.var_by_name.insert(name.clone(), var); - self.name_by_var.insert(var, name); + self.name_by_var.insert(var.value, name); } - pub fn insert_wildcard(&mut self, var: Variable) { + pub fn insert_wildcard(&mut self, var: Loc) { self.wildcards.push(var); } - pub fn insert_inferred(&mut self, var: Variable) { + pub fn insert_inferred(&mut self, var: Loc) { self.inferred.push(var); } @@ -70,7 +74,7 @@ impl IntroducedVariables { } pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { - self.var_by_name.get(name) + self.var_by_name.get(name).map(|v| &v.value) } pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { @@ -286,7 +290,7 @@ fn can_annotation_help( let ret = can_annotation_help( env, &return_type.value, - region, + return_type.region, scope, var_store, introduced_variables, @@ -314,7 +318,7 @@ fn can_annotation_help( let arg_ann = can_annotation_help( env, &arg.value, - region, + arg.region, scope, var_store, introduced_variables, @@ -389,7 +393,7 @@ fn can_annotation_help( None => { let var = var_store.fresh(); - introduced_variables.insert_named(name, var); + introduced_variables.insert_named(name, Loc::at(region, var)); Type::Variable(var) } @@ -453,7 +457,8 @@ fn can_annotation_help( } else { let var = var_store.fresh(); - introduced_variables.insert_named(var_name.clone(), var); + introduced_variables + .insert_named(var_name.clone(), Loc::at(loc_var.region, var)); vars.push((var_name.clone(), Type::Variable(var))); lowercase_vars.push(Loc::at(loc_var.region, (var_name, var))); @@ -560,7 +565,7 @@ fn can_annotation_help( // just `a` does not mean the same as `{}a`, so even // if there are no fields, still make this a `Record`, // not an EmptyRec - Type::Record(Default::default(), Box::new(ext_type)) + Type::Record(Default::default(), TypeExtension::from_type(ext_type)) } None => Type::EmptyRec, @@ -577,7 +582,7 @@ fn can_annotation_help( references, ); - Type::Record(field_types, Box::new(ext_type)) + Type::Record(field_types, TypeExtension::from_type(ext_type)) } } TagUnion { tags, ext, .. } => { @@ -598,7 +603,7 @@ fn can_annotation_help( // just `a` does not mean the same as `{}a`, so even // if there are no fields, still make this a `Record`, // not an EmptyRec - Type::TagUnion(Default::default(), Box::new(ext_type)) + Type::TagUnion(Default::default(), TypeExtension::from_type(ext_type)) } None => Type::EmptyTagUnion, @@ -620,7 +625,7 @@ fn can_annotation_help( // in theory we save a lot of time by sorting once here insertion_sort_by(&mut tag_types, |a, b| a.0.cmp(&b.0)); - Type::TagUnion(tag_types, Box::new(ext_type)) + Type::TagUnion(tag_types, TypeExtension::from_type(ext_type)) } } SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_annotation_help( @@ -636,7 +641,7 @@ fn can_annotation_help( Wildcard => { let var = var_store.fresh(); - introduced_variables.insert_wildcard(var); + introduced_variables.insert_wildcard(Loc::at(region, var)); Type::Variable(var) } @@ -645,7 +650,7 @@ fn can_annotation_help( // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠 let var = var_store.fresh(); - introduced_variables.insert_inferred(var); + introduced_variables.insert_inferred(Loc::at(region, var)); Type::Variable(var) } @@ -655,7 +660,7 @@ fn can_annotation_help( let var = var_store.fresh(); - introduced_variables.insert_wildcard(var); + introduced_variables.insert_wildcard(Loc::at(region, var)); Type::Variable(var) } @@ -721,7 +726,7 @@ fn can_extension_type<'a>( let var = var_store.fresh(); - introduced_variables.insert_inferred(var); + introduced_variables.insert_inferred(Loc::at_zero(var)); Type::Variable(var) } @@ -913,7 +918,10 @@ fn can_assigned_fields<'a>( Type::Variable(*var) } else { let field_var = var_store.fresh(); - introduced_variables.insert_named(field_name.clone(), field_var); + introduced_variables.insert_named( + field_name.clone(), + Loc::at(loc_field_name.region, field_var), + ); Type::Variable(field_var) } }; diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 762b417129..7d1c03d58e 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -294,14 +294,12 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); let mut is_phantom = false; + let mut var_by_name = can_ann.introduced_variables.var_by_name.clone(); for loc_lowercase in vars.iter() { - if let Some(var) = can_ann - .introduced_variables - .var_by_name(&loc_lowercase.value) - { + if let Some(var) = var_by_name.remove(&loc_lowercase.value) { // This is a valid lowercase rigid var for the type def. can_vars.push(Loc { - value: (loc_lowercase.value.clone(), *var), + value: (loc_lowercase.value.clone(), var.value), region: loc_lowercase.region, }); } else { @@ -320,6 +318,33 @@ pub fn canonicalize_defs<'a>( continue; } + let IntroducedVariables { + wildcards, + inferred, + .. + } = can_ann.introduced_variables; + let num_unbound = var_by_name.len() + wildcards.len() + inferred.len(); + if num_unbound > 0 { + let one_occurrence = var_by_name + .iter() + .map(|(_, v)| v) + .chain(wildcards.iter()) + .chain(inferred.iter()) + .next() + .unwrap() + .region; + + env.problems.push(Problem::UnboundTypeVariable { + typ: symbol, + num_unbound, + one_occurrence, + kind, + }); + + // Bail out + continue; + } + let alias = create_alias( symbol, name.region, diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 6c04645b14..1c2f76d951 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -10,7 +10,7 @@ use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{AliasKind, Type}; +use roc_types::types::{AliasKind, Type, TypeExtension}; #[derive(Default, Clone, Copy)] pub(crate) struct HostedGeneratedFunctions { @@ -210,7 +210,7 @@ fn build_effect_always( let signature = { // Effect.always : a -> Effect a let var_a = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); let effect_a = build_effect_alias( effect_symbol, @@ -223,7 +223,7 @@ fn build_effect_always( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![Type::Variable(var_a)], @@ -402,8 +402,8 @@ fn build_effect_map( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_a = build_effect_alias( effect_symbol, @@ -426,7 +426,7 @@ fn build_effect_map( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let a_to_b = { Type::Function( vec![Type::Variable(var_a)], @@ -436,7 +436,7 @@ fn build_effect_map( }; let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![effect_a, a_to_b], Box::new(Type::Variable(closure_var)), @@ -571,8 +571,8 @@ fn build_effect_after( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_a = build_effect_alias( effect_symbol, @@ -595,7 +595,7 @@ fn build_effect_after( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let a_to_effect_b = Type::Function( vec![Type::Variable(var_a)], Box::new(Type::Variable(closure_var)), @@ -603,7 +603,7 @@ fn build_effect_after( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![effect_a, a_to_effect_b], Box::new(Type::Variable(closure_var)), @@ -831,8 +831,8 @@ fn build_effect_forever( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_a = build_effect_alias( effect_symbol, @@ -855,7 +855,7 @@ fn build_effect_forever( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![effect_a], @@ -1089,8 +1089,8 @@ fn build_effect_loop( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_b = build_effect_alias( effect_symbol, @@ -1111,13 +1111,13 @@ fn build_effect_loop( (step_tag_name, vec![Type::Variable(var_a)]), (done_tag_name, vec![Type::Variable(var_b)]), ], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ) }; let effect_state_type = { let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let actual = { Type::TagUnion( @@ -1129,7 +1129,7 @@ fn build_effect_loop( Box::new(state_type.clone()), )], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ) }; @@ -1145,7 +1145,7 @@ fn build_effect_loop( }; let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let step_type = Type::Function( vec![Type::Variable(var_a)], @@ -1154,7 +1154,7 @@ fn build_effect_loop( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![Type::Variable(var_a), step_type], @@ -1559,7 +1559,7 @@ fn build_effect_alias( introduced_variables: &mut IntroducedVariables, ) -> Type { let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let actual = { Type::TagUnion( @@ -1571,7 +1571,7 @@ fn build_effect_alias( Box::new(a_type), )], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ) }; @@ -1600,7 +1600,7 @@ pub fn build_effect_actual( Box::new(a_type), )], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ) } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 74a40f34f1..14f739a2bc 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -259,7 +259,7 @@ pub fn canonicalize_module_defs<'a>( } for var in output.introduced_variables.wildcards { - rigid_variables.wildcards.insert(var); + rigid_variables.wildcards.insert(var.value); } let mut referenced_values = MutSet::default(); diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 16cb4b25da..86a812aa64 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -6,9 +6,9 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; -use roc_types::types::Reason; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; +use roc_types::types::{Reason, TypeExtension}; #[must_use] #[inline(always)] @@ -190,7 +190,7 @@ pub fn num_floatingpoint(range: Type) -> Type { TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), vec![range.clone()], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); builtin_alias( @@ -209,7 +209,7 @@ pub fn num_u32() -> Type { fn num_unsigned32() -> Type { let alias_content = Type::TagUnion( vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content)) @@ -219,7 +219,7 @@ fn num_unsigned32() -> Type { pub fn num_binary64() -> Type { let alias_content = Type::TagUnion( vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) @@ -238,7 +238,7 @@ pub fn num_int(range: Type) -> Type { pub fn num_signed64() -> Type { let alias_content = Type::TagUnion( vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) @@ -251,7 +251,7 @@ pub fn num_integer(range: Type) -> Type { TagName::Private(Symbol::NUM_AT_INTEGER), vec![range.clone()], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); builtin_alias( @@ -265,7 +265,7 @@ pub fn num_integer(range: Type) -> Type { pub fn num_num(typ: Type) -> Type { let alias_content = Type::TagUnion( vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); builtin_alias( diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 6af602a30f..e933062308 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -16,7 +16,9 @@ use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; -use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField}; +use roc_types::types::{ + AliasKind, AnnotationSource, Category, PReason, Reason, RecordField, TypeExtension, +}; /// This is for constraining Defs #[derive(Default, Debug)] @@ -119,13 +121,7 @@ pub fn constrain_expr( rec_constraints.push(field_con); } - let record_type = Type::Record( - field_types, - // TODO can we avoid doing Box::new on every single one of these? - // We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a - // lifetime parameter on `Type` - Box::new(Type::EmptyRec), - ); + let record_type = Type::Record(field_types, TypeExtension::Closed); let record_con = constraints.equal_types_with_storage( record_type, @@ -165,7 +161,8 @@ pub fn constrain_expr( cons.push(con); } - let fields_type = Type::Record(fields, Box::new(Type::Variable(*ext_var))); + let fields_type = + Type::Record(fields, TypeExtension::from_type(Type::Variable(*ext_var))); let record_type = Type::Variable(*record_var); // NOTE from elm compiler: fields_type is separate so that Error propagates better @@ -720,7 +717,7 @@ pub fn constrain_expr( let label = field.clone(); rec_field_types.insert(label, RecordField::Demanded(field_type)); - let record_type = Type::Record(rec_field_types, Box::new(ext_type)); + let record_type = Type::Record(rec_field_types, TypeExtension::from_type(ext_type)); let record_expected = Expected::NoExpectation(record_type); let category = Category::Access(field.clone()); @@ -767,7 +764,7 @@ pub fn constrain_expr( let mut field_types = SendMap::default(); let label = field.clone(); field_types.insert(label, RecordField::Demanded(field_type.clone())); - let record_type = Type::Record(field_types, Box::new(ext_type)); + let record_type = Type::Record(field_types, TypeExtension::from_type(ext_type)); let category = Category::Accessor(field.clone()); @@ -905,7 +902,7 @@ pub fn constrain_expr( let union_con = constraints.equal_types_with_storage( Type::TagUnion( vec![(name.clone(), types)], - Box::new(Type::Variable(*ext_var)), + TypeExtension::from_type(Type::Variable(*ext_var)), ), expected.clone(), Category::TagApply { @@ -951,7 +948,7 @@ pub fn constrain_expr( Type::FunctionOrTagUnion( name.clone(), *closure_name, - Box::new(Type::Variable(*ext_var)), + TypeExtension::from_type(Type::Variable(*ext_var)), ), expected.clone(), Category::TagApply { @@ -1621,7 +1618,7 @@ fn constrain_closure_size( let tag_name = TagName::Closure(name); Type::TagUnion( vec![(tag_name, tag_arguments)], - Box::new(Type::Variable(closure_ext_var)), + TypeExtension::from_type(Type::Variable(closure_ext_var)), ) }; @@ -1651,7 +1648,7 @@ fn instantiate_rigids( headers: &mut SendMap>, ) -> InstantiateRigids { let mut annotation = annotation.clone(); - let mut new_rigid_variables = Vec::new(); + let mut new_rigid_variables: Vec = Vec::new(); let mut rigid_substitution: ImMap = ImMap::default(); for (name, var) in introduced_vars.var_by_name.iter() { @@ -1660,23 +1657,24 @@ fn instantiate_rigids( match ftv.entry(name.clone()) { Occupied(occupied) => { let existing_rigid = occupied.get(); - rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); + rigid_substitution.insert(var.value, Type::Variable(*existing_rigid)); } Vacant(vacant) => { // It's possible to use this rigid in nested defs - vacant.insert(*var); - new_rigid_variables.push(*var); + vacant.insert(var.value); + new_rigid_variables.push(var.value); } } } // wildcards are always freshly introduced in this annotation - new_rigid_variables.extend(introduced_vars.wildcards.iter().copied()); + new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value)); // lambda set vars are always freshly introduced in this annotation new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied()); - let new_infer_variables = introduced_vars.inferred.clone(); + let new_infer_variables: Vec = + introduced_vars.inferred.iter().map(|v| v.value).collect(); // Instantiate rigid variables if !rigid_substitution.is_empty() { diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index def61bc01e..c6652908cb 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -9,7 +9,9 @@ use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; -use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type}; +use roc_types::types::{ + AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type, TypeExtension, +}; #[derive(Default)] pub struct PatternState { @@ -391,7 +393,7 @@ pub fn constrain_pattern( state.vars.push(*var); } - let record_type = Type::Record(field_types, Box::new(ext_type)); + let record_type = Type::Record(field_types, TypeExtension::from_type(ext_type)); let whole_con = constraints.equal_types( Type::Variable(*whole_var), diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 36e56fa7e6..863e1ab7ec 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -36,7 +36,7 @@ use roc_solve::solve; use roc_target::TargetInfo; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; -use roc_types::types::{Alias, AliasCommon}; +use roc_types::types::{Alias, AliasCommon, TypeExtension}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; use std::io; @@ -4123,7 +4123,7 @@ fn default_aliases() -> roc_solve::solve::Aliases { TagName::Private(Symbol::NUM_AT_NUM), vec![Type::Variable(tvar)], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); let alias = Alias { @@ -4148,7 +4148,7 @@ fn default_aliases() -> roc_solve::solve::Aliases { TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), vec![Type::Variable(tvar)], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); let alias = Alias { @@ -4231,7 +4231,7 @@ fn default_aliases() -> roc_solve::solve::Aliases { TagName::Private(Symbol::NUM_AT_INTEGER), vec![Type::Variable(tvar)], )], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); let alias = Alias { @@ -4256,7 +4256,7 @@ fn default_aliases() -> roc_solve::solve::Aliases { (TagName::Global("Ok".into()), vec![Type::Variable(tvar1)]), (TagName::Global("Err".into()), vec![Type::Variable(tvar2)]), ], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); let alias = Alias { @@ -4277,7 +4277,7 @@ fn default_aliases() -> roc_solve::solve::Aliases { let mut unit_function = |alias_name: Symbol, at_tag_name: Symbol| { let typ = Type::TagUnion( vec![(TagName::Private(at_tag_name), vec![])], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); let alias = Alias { diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d2199b32e3..14d684c222 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -112,6 +112,11 @@ pub struct PartialProcs<'a> { /// maps a function name (symbol) to an index symbols: Vec<'a, Symbol>, + /// An entry (a, b) means `a` directly references the lambda value of `b`, + /// i.e. this came from a `let a = b in ...` where `b` was defined as a + /// lambda earlier. + references: Vec<'a, (Symbol, Symbol)>, + partial_procs: Vec<'a, PartialProc<'a>>, } @@ -119,6 +124,7 @@ impl<'a> PartialProcs<'a> { fn new_in(arena: &'a Bump) -> Self { Self { symbols: Vec::new_in(arena), + references: Vec::new_in(arena), partial_procs: Vec::new_in(arena), } } @@ -126,7 +132,16 @@ impl<'a> PartialProcs<'a> { self.symbol_to_id(symbol).is_some() } - fn symbol_to_id(&self, symbol: Symbol) -> Option { + fn symbol_to_id(&self, mut symbol: Symbol) -> Option { + while let Some(real_symbol) = self + .references + .iter() + .find(|(alias, _)| *alias == symbol) + .map(|(_, real)| real) + { + symbol = *real_symbol; + } + self.symbols .iter() .position(|s| *s == symbol) @@ -157,6 +172,21 @@ impl<'a> PartialProcs<'a> { id } + + pub fn insert_alias(&mut self, alias: Symbol, real_symbol: Symbol) { + debug_assert!( + !self.contains_key(alias), + "{:?} is inserted as a partial proc twice: that's a bug!", + alias, + ); + debug_assert!( + self.contains_key(real_symbol), + "{:?} is not a partial proc or another alias: that's a bug!", + real_symbol, + ); + + self.references.push((alias, real_symbol)); + } } #[derive(Clone, Debug, PartialEq)] @@ -6680,10 +6710,10 @@ where } // Otherwise we're dealing with an alias to something that doesn't need to be specialized, or - // whose usages will already be specialized in the rest of the program. Let's just build the - // rest of the program now to get our hole. - let mut result = build_rest(env, procs, layout_cache); + // whose usages will already be specialized in the rest of the program. if procs.is_imported_module_thunk(right) { + let result = build_rest(env, procs, layout_cache); + // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. add_needed_external(procs, env, variable, right); @@ -6693,25 +6723,25 @@ where force_thunk(env, right, layout, left, env.arena.alloc(result)) } else if env.is_imported_symbol(right) { + let result = build_rest(env, procs, layout_cache); + // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. add_needed_external(procs, env, variable, right); // then we must construct its closure; since imported symbols have no closure, we use the empty struct let_empty_struct(left, env.arena.alloc(result)) + } else if procs.partial_procs.contains_key(right) { + // This is an alias to a function defined in this module. + // Attach the alias, then build the rest of the module, so that we reference and specialize + // the correct proc. + procs.partial_procs.insert_alias(left, right); + build_rest(env, procs, layout_cache) } else { + // This should be a fully specialized value. Replace the alias with the original symbol. + let mut result = build_rest(env, procs, layout_cache); substitute_in_exprs(env.arena, &mut result, left, right); - - // if the substituted variable is a function, make sure we specialize it - reuse_function_symbol( - env, - procs, - layout_cache, - Some(variable), - right, - result, - right, - ) + result } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index ab3524af7d..5730de27fa 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1994,7 +1994,15 @@ pub fn union_sorted_tags<'a>( let mut tags_vec = std::vec::Vec::new(); let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) { - Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => { + Ok(()) + // Admit type variables in the extension for now. This may come from things that never got + // monomorphized, like in + // x : [ A ]* + // x = A + // x + // In such cases it's fine to drop the variable. We may be proven wrong in the future... + | Err((_, Content::FlexVar(_) | Content::RigidVar(_))) + | Err((_, Content::RecursionVar { .. })) => { let opt_rec_var = get_recursion_var(subs, var); union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, target_info) } @@ -2592,7 +2600,7 @@ pub fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), + Ok(()) | Err((_, Content::FlexVar(_) | Content::RigidVar(_))) => ext_fields.is_empty(), Err(content) => panic!("invalid content in ext_var: {:?}", content), } } diff --git a/compiler/problem/Cargo.toml b/compiler/problem/Cargo.toml index 56874ddecf..af0f1108a0 100644 --- a/compiler/problem/Cargo.toml +++ b/compiler/problem/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_types = { path = "../types" } roc_parse = { path = "../parse" } diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index e5d23518f9..daa20c0778 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -5,6 +5,7 @@ use roc_module::symbol::{ModuleId, Symbol}; use roc_parse::ast::Base; use roc_parse::pattern::PatternType; use roc_region::all::{Loc, Region}; +use roc_types::types::AliasKind; #[derive(Clone, Copy, Debug, PartialEq)] pub struct CycleEntry { @@ -43,6 +44,12 @@ pub enum Problem { variable_region: Region, variable_name: Lowercase, }, + UnboundTypeVariable { + typ: Symbol, + num_unbound: usize, + one_occurrence: Region, + kind: AliasKind, + }, DuplicateRecordFieldValue { field_name: Lowercase, record_region: Region, diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 8998fbb011..6afb812a78 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -14,6 +14,7 @@ use roc_types::subs::{ use roc_types::types::Type::{self, *}; use roc_types::types::{ gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory, + TypeExtension, }; use roc_unify::unify::{unify, Mode, Unified::*}; @@ -1111,7 +1112,7 @@ fn solve( let actual = type_to_var(subs, rank, pools, aliases, typ); let tag_ty = Type::TagUnion( vec![(tag_name.clone(), tys.to_vec())], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); let includes = type_to_var(subs, rank, pools, aliases, &tag_ty); @@ -1425,7 +1426,7 @@ fn type_to_variable<'a>( Record(fields, ext) => { // An empty fields is inefficient (but would be correct) // If hit, try to turn the value into an EmptyRecord in canonicalization - debug_assert!(!fields.is_empty() || !ext.is_empty_record()); + debug_assert!(!fields.is_empty() || !ext.is_closed()); let mut field_vars = Vec::with_capacity_in(fields.len(), arena); @@ -1442,7 +1443,10 @@ fn type_to_variable<'a>( field_vars.push((field.clone(), field_var)); } - let temp_ext_var = helper!(ext); + let temp_ext_var = match ext { + TypeExtension::Open(ext) => helper!(ext), + TypeExtension::Closed => Variable::EMPTY_RECORD, + }; let (it, new_ext_var) = gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) @@ -1465,7 +1469,7 @@ fn type_to_variable<'a>( TagUnion(tags, ext) => { // An empty tags is inefficient (but would be correct) // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + debug_assert!(!tags.is_empty() || !ext.is_closed()); let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); @@ -1474,7 +1478,10 @@ fn type_to_variable<'a>( register_with_known_var(subs, destination, rank, pools, content) } FunctionOrTagUnion(tag_name, symbol, ext) => { - let temp_ext_var = helper!(ext); + let temp_ext_var = match ext { + TypeExtension::Open(ext) => helper!(ext), + TypeExtension::Closed => Variable::EMPTY_TAG_UNION, + }; let (it, ext) = roc_types::types::gather_tags_unsorted_iter( subs, @@ -1496,7 +1503,7 @@ fn type_to_variable<'a>( RecursiveTagUnion(rec_var, tags, ext) => { // An empty tags is inefficient (but would be correct) // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + debug_assert!(!tags.is_empty() || !ext.is_closed()); let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); @@ -1681,7 +1688,7 @@ fn roc_result_to_var<'a>( ) -> Variable { match result_type { Type::TagUnion(tags, ext) => { - debug_assert!(ext.is_empty_tag_union()); + debug_assert!(ext.is_closed()); debug_assert!(tags.len() == 2); if let [(err, err_args), (ok, ok_args)] = &tags[..] { @@ -1925,40 +1932,46 @@ fn type_to_union_tags<'a>( pools: &mut Pools, arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - ext: &'a Type, + ext: &'a TypeExtension, stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; let sorted = tags.len() == 1 || sorted_no_duplicates(tags); - if ext.is_empty_tag_union() { - let ext = Variable::EMPTY_TAG_UNION; + match ext { + TypeExtension::Closed => { + let ext = Variable::EMPTY_TAG_UNION; - let union_tags = if sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags, stack) - } else { - let tag_vars = Vec::with_capacity_in(tags.len(), arena); - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) - }; + let union_tags = if sorted { + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) + } else { + let tag_vars = Vec::with_capacity_in(tags.len(), arena); + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) + }; - (union_tags, ext) - } else { - let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); + (union_tags, ext) + } + TypeExtension::Open(ext) => { + let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); - let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); - let (it, ext) = - roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + subs, + UnionTags::default(), + temp_ext_var, + ); - tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); - let union_tags = if tag_vars.is_empty() && sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags, stack) - } else { - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) - }; + let union_tags = if tag_vars.is_empty() && sorted { + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) + } else { + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) + }; - (union_tags, ext) + (union_tags, ext) + } } } diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 4cab64ec87..eefcb272cf 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1548,3 +1548,35 @@ fn issue_1162() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn polymorphic_tag() { + assert_evals_to!( + indoc!( + r#" + x : [ Y U8 ]* + x = Y 3 + x + "# + ), + 3, // Y is a newtype, it gets unwrapped + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2725_alias_polymorphic_lambda() { + assert_evals_to!( + indoc!( + r#" + wrap = \value -> Tag value + wrapIt = wrap + wrapIt 42 + "# + ), + 42, // Tag is a newtype, it gets unwrapped + i64 + ) +} diff --git a/compiler/test_mono/generated/issue_2725_alias_polymorphic_lambda.txt b/compiler/test_mono/generated/issue_2725_alias_polymorphic_lambda.txt new file mode 100644 index 0000000000..1eba5a003d --- /dev/null +++ b/compiler/test_mono/generated/issue_2725_alias_polymorphic_lambda.txt @@ -0,0 +1,7 @@ +procedure Test.2 (Test.3): + ret Test.3; + +procedure Test.0 (): + let Test.6 : I64 = 42i64; + let Test.5 : I64 = CallByName Test.2 Test.6; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt index c55c6b4f07..b461c2a04e 100644 --- a/compiler/test_mono/generated/monomorphized_ints_aliased.txt +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -2,22 +2,20 @@ procedure Num.22 (#Attr.2, #Attr.3): let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.12; -procedure Test.4 (Test.7, Test.8): - let Test.17 : U64 = 1i64; - ret Test.17; - -procedure Test.4 (Test.7, Test.8): +procedure Test.5 (Test.7, Test.8): let Test.18 : U64 = 1i64; ret Test.18; +procedure Test.6 (Test.7, Test.8): + let Test.15 : U64 = 1i64; + ret Test.15; + procedure Test.0 (): - let Test.4 : {} = Struct {}; - let Test.4 : {} = Struct {}; - let Test.15 : U8 = 100i64; - let Test.16 : U32 = 100i64; - let Test.10 : U64 = CallByName Test.4 Test.15 Test.16; + let Test.16 : U8 = 100i64; + let Test.17 : U32 = 100i64; + let Test.10 : U64 = CallByName Test.5 Test.16 Test.17; let Test.13 : U32 = 100i64; let Test.14 : U8 = 100i64; - let Test.11 : U64 = CallByName Test.4 Test.13 Test.14; + let Test.11 : U64 = CallByName Test.6 Test.13 Test.14; let Test.9 : U64 = CallByName Num.22 Test.10 Test.11; ret Test.9; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 8d39d6b960..1d6423ffb7 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -36,9 +36,9 @@ procedure Test.8 (Test.11, #Attr.12): ret Test.11; procedure Test.0 (): + let Test.5 : I64 = 2i64; let Test.6 : Int1 = true; let Test.4 : I64 = 1i64; - let Test.5 : I64 = 2i64; joinpoint Test.22 Test.14: let Test.15 : I64 = 42i64; let Test.13 : I64 = CallByName Test.1 Test.14 Test.15; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 9706cdd1bf..8a15d461e3 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -17,8 +17,8 @@ procedure Test.7 (Test.9, #Attr.12): ret Test.20; procedure Test.0 (): - let Test.4 : I64 = 1i64; let Test.5 : I64 = 2i64; + let Test.4 : I64 = 1i64; let Test.12 : I64 = 42i64; joinpoint Test.19 Test.13: let Test.14 : U8 = GetTagId Test.13; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 4722a52053..9e12dcb901 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -1265,6 +1265,17 @@ fn issue_2535_polymorphic_fields_referenced_in_list() { ) } +#[mono_test] +fn issue_2725_alias_polymorphic_lambda() { + indoc!( + r#" + wrap = \value -> Tag value + wrapIt = wrap + wrapIt 42 + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 7fefdeb1cd..6516dace02 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -1,5 +1,5 @@ use crate::subs::{FlatType, GetSubsSlice, Subs, VarId, VarStore, Variable}; -use crate::types::{AliasCommon, AliasKind, Problem, RecordField, Type}; +use crate::types::{AliasCommon, AliasKind, Problem, RecordField, Type, TypeExtension}; use roc_collections::all::{ImMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -113,7 +113,11 @@ impl SolvedType { SolvedType::Func(solved_args, Box::new(solved_closure), Box::new(solved_ret)) } Record(fields, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyRecord, + }; + let mut solved_fields = Vec::with_capacity(fields.len()); for (label, field) in fields { @@ -139,7 +143,11 @@ impl SolvedType { SolvedType::TagUnion(solved_tags, Box::new(solved_ext)) } TagUnion(tags, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyTagUnion, + }; + let mut solved_tags = Vec::with_capacity(tags.len()); for (tag_name, types) in tags { let mut solved_types = Vec::with_capacity(types.len()); @@ -155,11 +163,19 @@ impl SolvedType { SolvedType::TagUnion(solved_tags, Box::new(solved_ext)) } FunctionOrTagUnion(tag_name, symbol, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyTagUnion, + }; + SolvedType::FunctionOrTagUnion(tag_name.clone(), *symbol, Box::new(solved_ext)) } RecursiveTagUnion(rec_var, tags, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyTagUnion, + }; + let mut solved_tags = Vec::with_capacity(tags.len()); for (tag_name, types) in tags { let mut solved_types = Vec::with_capacity(types.len()); @@ -523,7 +539,12 @@ pub fn to_type( new_fields.insert(label.clone(), field_val); } - Type::Record(new_fields, Box::new(to_type(ext, free_vars, var_store))) + let ext = match ext.as_ref() { + SolvedType::EmptyRecord => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + + Type::Record(new_fields, ext) } EmptyRecord => Type::EmptyRec, EmptyTagUnion => Type::EmptyTagUnion, @@ -540,13 +561,21 @@ pub fn to_type( new_tags.push((tag_name.clone(), new_args)); } - Type::TagUnion(new_tags, Box::new(to_type(ext, free_vars, var_store))) + let ext = match ext.as_ref() { + SolvedType::EmptyTagUnion => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + + Type::TagUnion(new_tags, ext) + } + FunctionOrTagUnion(tag_name, symbol, ext) => { + let ext = match ext.as_ref() { + SolvedType::EmptyTagUnion => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + + Type::FunctionOrTagUnion(tag_name.clone(), *symbol, ext) } - FunctionOrTagUnion(tag_name, symbol, ext) => Type::FunctionOrTagUnion( - tag_name.clone(), - *symbol, - Box::new(to_type(ext, free_vars, var_store)), - ), RecursiveTagUnion(rec_var_id, tags, ext) => { let mut new_tags = Vec::with_capacity(tags.len()); @@ -560,16 +589,17 @@ pub fn to_type( new_tags.push((tag_name.clone(), new_args)); } + let ext = match ext.as_ref() { + SolvedType::EmptyTagUnion => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + let rec_var = free_vars .unnamed_vars .get(rec_var_id) .expect("rec var not in unnamed vars"); - Type::RecursiveTagUnion( - *rec_var, - new_tags, - Box::new(to_type(ext, free_vars, var_store)), - ) + Type::RecursiveTagUnion(*rec_var, new_tags, ext) } DelayedAlias(symbol, solved_type_variables, solved_lambda_sets) => { let mut type_variables = Vec::with_capacity(solved_type_variables.len()); diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 288c5f0fa2..fd8d9e12f0 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -193,9 +193,9 @@ pub enum Type { EmptyTagUnion, /// A function. The types of its arguments, size of its closure, then the type of its return value. Function(Vec, Box, Box), - Record(SendMap>, Box), - TagUnion(Vec<(TagName, Vec)>, Box), - FunctionOrTagUnion(TagName, Symbol, Box), + Record(SendMap>, TypeExtension), + TagUnion(Vec<(TagName, Vec)>, TypeExtension), + FunctionOrTagUnion(TagName, Symbol, TypeExtension), /// A function name that is used in our defunctionalization algorithm ClosureTag { name: Symbol, @@ -216,7 +216,7 @@ pub enum Type { actual_var: Variable, actual: Box, }, - RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, Box), + RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, TypeExtension), /// Applying a type to some arguments (e.g. Dict.Dict String Int) Apply(Symbol, Vec, Region), Variable(Variable), @@ -288,6 +288,43 @@ impl Clone for Type { } } +#[derive(PartialEq, Eq, Clone)] +pub enum TypeExtension { + Open(Box), + Closed, +} + +impl TypeExtension { + #[inline(always)] + pub fn from_type(typ: Type) -> Self { + match typ { + Type::EmptyTagUnion | Type::EmptyRec => Self::Closed, + _ => Self::Open(Box::new(typ)), + } + } + + #[inline(always)] + pub fn is_closed(&self) -> bool { + match self { + TypeExtension::Open(_) => false, + TypeExtension::Closed => true, + } + } +} + +impl<'a> IntoIterator for &'a TypeExtension { + type Item = &'a Type; + + type IntoIter = std::option::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + TypeExtension::Open(ext) => Some(ext.as_ref()).into_iter(), + TypeExtension::Closed => None.into_iter(), + } + } +} + impl fmt::Debug for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -422,12 +459,12 @@ impl fmt::Debug for Type { write!(f, "}}")?; - match *ext.clone() { - Type::EmptyRec => { + match ext { + TypeExtension::Closed => { // This is a closed record. We're done! Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open record, so print the variable // right after the '}' // @@ -463,12 +500,12 @@ impl fmt::Debug for Type { write!(f, "]")?; - match *ext.clone() { - Type::EmptyTagUnion => { + match ext { + TypeExtension::Closed => { // This is a closed variant. We're done! Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open tag union, so print the variable // right after the ']' // @@ -483,12 +520,12 @@ impl fmt::Debug for Type { write!(f, "{:?}", tag_name)?; write!(f, "]")?; - match *ext.clone() { - Type::EmptyTagUnion => { + match ext { + TypeExtension::Closed => { // This is a closed variant. We're done! Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open tag union, so print the variable // right after the ']' // @@ -533,19 +570,20 @@ impl fmt::Debug for Type { write!(f, "]")?; - match *ext.clone() { - Type::EmptyTagUnion => { + match ext { + TypeExtension::Closed => { // This is a closed variant. We're done! + Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open tag union, so print the variable // right after the ']' // // e.g. the "*" at the end of `[ Foo ]*` // or the "r" at the end of `[ DivByZero ]r` - other.fmt(f)?; + other.fmt(f) } - } + }?; write!(f, " as <{:?}>", rec) } @@ -611,22 +649,33 @@ impl Type { for (_, args) in tags { stack.extend(args.iter_mut()); } - stack.push(ext); + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } FunctionOrTagUnion(_, _, ext) => { - stack.push(ext); + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } RecursiveTagUnion(_, tags, ext) => { for (_, args) in tags { stack.extend(args.iter_mut()); } - stack.push(ext); + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } Record(fields, ext) => { for (_, x) in fields.iter_mut() { stack.push(x.as_inner_mut()); } - stack.push(ext); + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } Type::DelayedAlias(AliasCommon { type_arguments, @@ -650,6 +699,7 @@ impl Type { for (_, value) in type_arguments.iter_mut() { stack.push(value); } + for lambda_set in lambda_set_variables.iter_mut() { stack.push(lambda_set.as_inner_mut()); } @@ -705,10 +755,15 @@ impl Type { for (_, args) in tags { stack.extend(args.iter_mut()); } - stack.push(ext); + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } FunctionOrTagUnion(_, _, ext) => { - stack.push(ext); + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } RecursiveTagUnion(rec_var, tags, ext) => { if let Some(replacement) = substitutions.get(rec_var) { @@ -718,13 +773,18 @@ impl Type { for (_, args) in tags { stack.extend(args.iter_mut()); } - stack.push(ext); + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } Record(fields, ext) => { for (_, x) in fields.iter_mut() { stack.push(x.as_inner_mut()); } - stack.push(ext); + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } Type::DelayedAlias(AliasCommon { type_arguments, @@ -800,20 +860,31 @@ impl Type { closure.substitute_alias(rep_symbol, rep_args, actual)?; ret.substitute_alias(rep_symbol, rep_args, actual) } - FunctionOrTagUnion(_, _, ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + FunctionOrTagUnion(_, _, ext) => match ext { + TypeExtension::Open(ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + TypeExtension::Closed => Ok(()), + }, RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { for (_, args) in tags { for x in args { x.substitute_alias(rep_symbol, rep_args, actual)?; } } - ext.substitute_alias(rep_symbol, rep_args, actual) + + match ext { + TypeExtension::Open(ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + TypeExtension::Closed => Ok(()), + } } Record(fields, ext) => { for (_, x) in fields.iter_mut() { x.substitute_alias(rep_symbol, rep_args, actual)?; } - ext.substitute_alias(rep_symbol, rep_args, actual) + + match ext { + TypeExtension::Open(ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + TypeExtension::Closed => Ok(()), + } } DelayedAlias(AliasCommon { type_arguments, .. }) => { for (_, ta) in type_arguments { @@ -862,6 +933,13 @@ impl Type { } } + fn contains_symbol_ext(ext: &TypeExtension, rep_symbol: Symbol) -> bool { + match ext { + TypeExtension::Open(ext) => ext.contains_symbol(rep_symbol), + TypeExtension::Closed => false, + } + } + pub fn contains_symbol(&self, rep_symbol: Symbol) -> bool { use Type::*; @@ -871,9 +949,9 @@ impl Type { || closure.contains_symbol(rep_symbol) || args.iter().any(|arg| arg.contains_symbol(rep_symbol)) } - FunctionOrTagUnion(_, _, ext) => ext.contains_symbol(rep_symbol), + FunctionOrTagUnion(_, _, ext) => Self::contains_symbol_ext(ext, rep_symbol), RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - ext.contains_symbol(rep_symbol) + Self::contains_symbol_ext(ext, rep_symbol) || tags .iter() .map(|v| v.1.iter()) @@ -882,7 +960,7 @@ impl Type { } Record(fields, ext) => { - ext.contains_symbol(rep_symbol) + Self::contains_symbol_ext(ext, rep_symbol) || fields.values().any(|arg| arg.contains_symbol(rep_symbol)) } DelayedAlias(AliasCommon { @@ -910,6 +988,13 @@ impl Type { } } + fn contains_variable_ext(ext: &TypeExtension, rep_variable: Variable) -> bool { + match ext { + TypeExtension::Open(ext) => ext.contains_variable(rep_variable), + TypeExtension::Closed => false, + } + } + pub fn contains_variable(&self, rep_variable: Variable) -> bool { use Type::*; @@ -920,9 +1005,9 @@ impl Type { || closure.contains_variable(rep_variable) || args.iter().any(|arg| arg.contains_variable(rep_variable)) } - FunctionOrTagUnion(_, _, ext) => ext.contains_variable(rep_variable), + FunctionOrTagUnion(_, _, ext) => Self::contains_variable_ext(ext, rep_variable), RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - ext.contains_variable(rep_variable) + Self::contains_variable_ext(ext, rep_variable) || tags .iter() .map(|v| v.1.iter()) @@ -931,7 +1016,7 @@ impl Type { } Record(fields, ext) => { - ext.contains_variable(rep_variable) + Self::contains_variable_ext(ext, rep_variable) || fields .values() .any(|arg| arg.contains_variable(rep_variable)) @@ -983,7 +1068,9 @@ impl Type { ret.instantiate_aliases(region, aliases, var_store, introduced); } FunctionOrTagUnion(_, _, ext) => { - ext.instantiate_aliases(region, aliases, var_store, introduced); + if let TypeExtension::Open(ext) = ext { + ext.instantiate_aliases(region, aliases, var_store, introduced); + } } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { for (_, args) in tags { @@ -991,13 +1078,19 @@ impl Type { x.instantiate_aliases(region, aliases, var_store, introduced); } } - ext.instantiate_aliases(region, aliases, var_store, introduced); + + if let TypeExtension::Open(ext) = ext { + ext.instantiate_aliases(region, aliases, var_store, introduced); + } } Record(fields, ext) => { for (_, x) in fields.iter_mut() { x.instantiate_aliases(region, aliases, var_store, introduced); } - ext.instantiate_aliases(region, aliases, var_store, introduced); + + if let TypeExtension::Open(ext) = ext { + ext.instantiate_aliases(region, aliases, var_store, introduced); + } } DelayedAlias(AliasCommon { .. }) => { // do nothing, yay @@ -1084,7 +1177,10 @@ impl Type { for typ in tags.iter_mut().map(|v| v.1.iter_mut()).flatten() { typ.substitute(&substitution); } - ext.substitute(&substitution); + + if let TypeExtension::Open(ext) = &mut ext { + ext.substitute(&substitution); + } actual = Type::RecursiveTagUnion(new_rec_var, tags, ext); } @@ -1145,14 +1241,17 @@ impl Type { pub fn is_narrow(&self) -> bool { match self.shallow_dealias() { Type::TagUnion(tags, ext) | Type::RecursiveTagUnion(_, tags, ext) => { - ext.is_empty_tag_union() + matches!(ext, TypeExtension::Closed) && tags.len() == 1 && tags[0].1.len() == 1 && tags[0].1[0].is_narrow() } - Type::Record(fields, ext) => { - fields.values().all(|field| field.as_inner().is_narrow()) && ext.is_narrow() - } + Type::Record(fields, ext) => match ext { + TypeExtension::Open(ext) => { + fields.values().all(|field| field.as_inner().is_narrow()) && ext.is_narrow() + } + TypeExtension::Closed => fields.values().all(|field| field.as_inner().is_narrow()), + }, Type::Function(args, clos, ret) => { args.iter().all(|a| a.is_narrow()) && clos.is_narrow() && ret.is_narrow() } @@ -1187,15 +1286,15 @@ fn symbols_help(initial: &Type) -> Vec { stack.extend(args); } FunctionOrTagUnion(_, _, ext) => { - stack.push(ext); + stack.extend(ext); } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - stack.push(ext); + stack.extend(ext); stack.extend(tags.iter().map(|v| v.1.iter()).flatten()); } Record(fields, ext) => { - stack.push(ext); + stack.extend(ext); stack.extend(fields.values().map(|field| field.as_inner())); } DelayedAlias(AliasCommon { @@ -1269,7 +1368,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { Demanded(x) => variables_help(x, accum), }; } - variables_help(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } } TagUnion(tags, ext) => { for (_, args) in tags { @@ -1277,10 +1379,15 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { variables_help(x, accum); } } - variables_help(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } } FunctionOrTagUnion(_, _, ext) => { - variables_help(ext, accum); + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } } RecursiveTagUnion(rec, tags, ext) => { for (_, args) in tags { @@ -1288,7 +1395,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { variables_help(x, accum); } } - variables_help(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } // just check that this is actually a recursive type debug_assert!(accum.contains(rec)); @@ -1380,7 +1490,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { Demanded(x) => variables_help_detailed(x, accum), }; } - variables_help_detailed(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } } TagUnion(tags, ext) => { for (_, args) in tags { @@ -1388,10 +1501,15 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { variables_help_detailed(x, accum); } } - variables_help_detailed(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } } FunctionOrTagUnion(_, _, ext) => { - variables_help_detailed(ext, accum); + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } } RecursiveTagUnion(rec, tags, ext) => { for (_, args) in tags { @@ -1399,7 +1517,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { variables_help_detailed(x, accum); } } - variables_help_detailed(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } // just check that this is actually a recursive type // debug_assert!(accum.type_variables.contains(rec)); diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 944d550955..61dad966be 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -26,7 +26,6 @@ roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} [features] -default = ["console_error_panic_hook"] wasmer = ["futures"] [package.metadata.wasm-pack.profile.profiling] diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 0d1c6d9461..a1d66e6f8d 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -3,7 +3,7 @@ mod repl; // // Interface with external JS in the browser // -#[cfg(not(feature = "wasmer"))] +#[cfg(feature = "console_error_panic_hook")] extern crate console_error_panic_hook; #[cfg(not(feature = "wasmer"))] mod externs_js; diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs index 9bbd2891af..95cb77be11 100644 --- a/repl_wasm/src/repl.rs +++ b/repl_wasm/src/repl.rs @@ -155,7 +155,7 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { } pub async fn entrypoint_from_js(src: String) -> Result { - #[cfg(not(feature = "wasmer"))] + #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); let arena = &Bump::new(); diff --git a/repl_www/build.sh b/repl_www/build.sh index 52a2121631..d9e157271c 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -26,11 +26,11 @@ cp -r repl_www/public/* $WWW_ROOT if [ -n "${REPL_DEBUG:-}" ] then # Leave out wasm-opt since it takes too long when debugging, and provide some debug options - cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release + cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release --features console_error_panic_hook wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ else # A `--profiling` build is optimized and has debug info, so we get stack traces for compiler `todo!()` - wasm-pack build --profiling --target web repl_wasm + wasm-pack build --profiling --target web repl_wasm -- --features console_error_panic_hook fi cp repl_wasm/pkg/*.wasm $WWW_ROOT diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 6473db7388..83e35f6ade 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -5,6 +5,7 @@ use roc_problem::can::{ BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, }; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; +use roc_types::types::AliasKind; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -17,6 +18,7 @@ const UNRECOGNIZED_NAME: &str = "UNRECOGNIZED NAME"; const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; +const UNBOUND_TYPE_VARIABLE: &str = "UNBOUND TYPE VARIABLE"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const MISSING_DEFINITION: &str = "MISSING DEFINITION"; const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; @@ -252,6 +254,43 @@ pub fn can_problem<'b>( title = UNUSED_ALIAS_PARAM.to_string(); severity = Severity::RuntimeError; } + Problem::UnboundTypeVariable { + typ: alias, + num_unbound, + one_occurrence, + kind, + } => { + let mut stack = Vec::with_capacity(4); + if num_unbound == 1 { + stack.push(alloc.concat(vec![ + alloc.reflow("The definition of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" has an unbound type variable:"), + ])); + } else { + stack.push(alloc.concat(vec![ + alloc.reflow("The definition of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" has "), + alloc.text(format!("{}", num_unbound)), + alloc.reflow(" unbound type variables."), + ])); + stack.push(alloc.reflow("Here is one occurrence:")); + } + stack.push(alloc.region(lines.convert_region(one_occurrence))); + stack.push(alloc.tip().append(alloc.concat(vec![ + alloc.reflow("Type variables must be bound before the "), + alloc.keyword(match kind { + AliasKind::Structural => ":", + AliasKind::Opaque => ":=", + }), + alloc.reflow(". Perhaps you intended to add a type parameter to this type?"), + ]))); + doc = alloc.stack(stack); + + title = UNBOUND_TYPE_VARIABLE.to_string(); + severity = Severity::RuntimeError; + } Problem::BadRecursion(entries) => { doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF.to_string(); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 70f8d00de0..6a5179a778 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -76,7 +76,7 @@ mod test_reporting { } for var in output.introduced_variables.wildcards { - subs.rigid_var(var, "*".into()); + subs.rigid_var(var.value, "*".into()); } let mut solve_aliases = roc_solve::solve::Aliases::default(); @@ -8739,4 +8739,136 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn wildcard_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int * + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int * + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn wildcard_in_opaque() { + report_problem_as( + indoc!( + r#" + I := Int * + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I := Int * + ^ + + Tip: Type variables must be bound before the `:=`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn multiple_wildcards_in_alias() { + report_problem_as( + indoc!( + r#" + I : [ A (Int *), B (Int *) ] + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has 2 unbound type variables. + + Here is one occurrence: + + 1│ I : [ A (Int *), B (Int *) ] + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn inference_var_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int _ + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int _ + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn unbound_var_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int a + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int a + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } }