Merge branch 'explicit-closed-tag-record' into delay-instantiating-aliases

This commit is contained in:
Folkert 2022-03-17 20:34:52 +01:00
commit a3b00fbf55
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
29 changed files with 691 additions and 223 deletions

View file

@ -2,3 +2,9 @@
test-gen-llvm = "test -p test_gen" 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-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" 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"]

1
Cargo.lock generated
View file

@ -3762,6 +3762,7 @@ dependencies = [
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_types",
] ]
[[package]] [[package]]

View file

@ -7,7 +7,9 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader}; use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; 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)] #[derive(Clone, Debug, PartialEq)]
pub struct Annotation { pub struct Annotation {
@ -29,25 +31,27 @@ pub struct IntroducedVariables {
// But then between annotations, the same name can occur multiple times, // But then between annotations, the same name can occur multiple times,
// but a variable can only have one name. Therefore // but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`. // `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>, pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>, pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Variable>, pub inferred: Vec<Loc<Variable>>,
pub var_by_name: SendMap<Lowercase, Variable>, // NB: A mapping of a -> Loc<v1> in this map has the region of the first-seen var, but there
// may be multiple occurrences of it!
pub var_by_name: SendMap<Lowercase, Loc<Variable>>,
pub name_by_var: SendMap<Variable, Lowercase>, pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>, pub host_exposed_aliases: MutMap<Symbol, Variable>,
} }
impl IntroducedVariables { impl IntroducedVariables {
pub fn insert_named(&mut self, name: Lowercase, var: Variable) { pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
self.var_by_name.insert(name.clone(), var); 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<Variable>) {
self.wildcards.push(var); self.wildcards.push(var);
} }
pub fn insert_inferred(&mut self, var: Variable) { pub fn insert_inferred(&mut self, var: Loc<Variable>) {
self.inferred.push(var); self.inferred.push(var);
} }
@ -70,7 +74,7 @@ impl IntroducedVariables {
} }
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { 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> { pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
@ -286,7 +290,7 @@ fn can_annotation_help(
let ret = can_annotation_help( let ret = can_annotation_help(
env, env,
&return_type.value, &return_type.value,
region, return_type.region,
scope, scope,
var_store, var_store,
introduced_variables, introduced_variables,
@ -314,7 +318,7 @@ fn can_annotation_help(
let arg_ann = can_annotation_help( let arg_ann = can_annotation_help(
env, env,
&arg.value, &arg.value,
region, arg.region,
scope, scope,
var_store, var_store,
introduced_variables, introduced_variables,
@ -389,7 +393,7 @@ fn can_annotation_help(
None => { None => {
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_named(name, var); introduced_variables.insert_named(name, Loc::at(region, var));
Type::Variable(var) Type::Variable(var)
} }
@ -453,7 +457,8 @@ fn can_annotation_help(
} else { } else {
let var = var_store.fresh(); 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))); vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, 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 // just `a` does not mean the same as `{}a`, so even
// if there are no fields, still make this a `Record`, // if there are no fields, still make this a `Record`,
// not an EmptyRec // not an EmptyRec
Type::Record(Default::default(), Box::new(ext_type)) Type::Record(Default::default(), TypeExtension::from_type(ext_type))
} }
None => Type::EmptyRec, None => Type::EmptyRec,
@ -577,7 +582,7 @@ fn can_annotation_help(
references, references,
); );
Type::Record(field_types, Box::new(ext_type)) Type::Record(field_types, TypeExtension::from_type(ext_type))
} }
} }
TagUnion { tags, ext, .. } => { TagUnion { tags, ext, .. } => {
@ -598,7 +603,7 @@ fn can_annotation_help(
// just `a` does not mean the same as `{}a`, so even // just `a` does not mean the same as `{}a`, so even
// if there are no fields, still make this a `Record`, // if there are no fields, still make this a `Record`,
// not an EmptyRec // not an EmptyRec
Type::TagUnion(Default::default(), Box::new(ext_type)) Type::TagUnion(Default::default(), TypeExtension::from_type(ext_type))
} }
None => Type::EmptyTagUnion, None => Type::EmptyTagUnion,
@ -620,7 +625,7 @@ fn can_annotation_help(
// in theory we save a lot of time by sorting once here // 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)); 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( SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_annotation_help(
@ -636,7 +641,7 @@ fn can_annotation_help(
Wildcard => { Wildcard => {
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_wildcard(var); introduced_variables.insert_wildcard(Loc::at(region, var));
Type::Variable(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 🤠 // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_inferred(var); introduced_variables.insert_inferred(Loc::at(region, var));
Type::Variable(var) Type::Variable(var)
} }
@ -655,7 +660,7 @@ fn can_annotation_help(
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_wildcard(var); introduced_variables.insert_wildcard(Loc::at(region, var));
Type::Variable(var) Type::Variable(var)
} }
@ -721,7 +726,7 @@ fn can_extension_type<'a>(
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_inferred(var); introduced_variables.insert_inferred(Loc::at_zero(var));
Type::Variable(var) Type::Variable(var)
} }
@ -913,7 +918,10 @@ fn can_assigned_fields<'a>(
Type::Variable(*var) Type::Variable(*var)
} else { } else {
let field_var = var_store.fresh(); 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) Type::Variable(field_var)
} }
}; };

View file

@ -294,14 +294,12 @@ pub fn canonicalize_defs<'a>(
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len()); let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false; let mut is_phantom = false;
let mut var_by_name = can_ann.introduced_variables.var_by_name.clone();
for loc_lowercase in vars.iter() { for loc_lowercase in vars.iter() {
if let Some(var) = can_ann if let Some(var) = var_by_name.remove(&loc_lowercase.value) {
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the type def. // This is a valid lowercase rigid var for the type def.
can_vars.push(Loc { can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var), value: (loc_lowercase.value.clone(), var.value),
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
} else { } else {
@ -320,6 +318,33 @@ pub fn canonicalize_defs<'a>(
continue; 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( let alias = create_alias(
symbol, symbol,
name.region, name.region,

View file

@ -10,7 +10,7 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{AliasKind, Type}; use roc_types::types::{AliasKind, Type, TypeExtension};
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
pub(crate) struct HostedGeneratedFunctions { pub(crate) struct HostedGeneratedFunctions {
@ -210,7 +210,7 @@ fn build_effect_always(
let signature = { let signature = {
// Effect.always : a -> Effect a // Effect.always : a -> Effect a
let var_a = var_store.fresh(); 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( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -223,7 +223,7 @@ fn build_effect_always(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
@ -402,8 +402,8 @@ fn build_effect_map(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_a = build_effect_alias( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -426,7 +426,7 @@ fn build_effect_map(
); );
let closure_var = var_store.fresh(); 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 = { let a_to_b = {
Type::Function( Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
@ -436,7 +436,7 @@ fn build_effect_map(
}; };
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![effect_a, a_to_b], vec![effect_a, a_to_b],
Box::new(Type::Variable(closure_var)), Box::new(Type::Variable(closure_var)),
@ -571,8 +571,8 @@ fn build_effect_after(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_a = build_effect_alias( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -595,7 +595,7 @@ fn build_effect_after(
); );
let closure_var = var_store.fresh(); 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( let a_to_effect_b = Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
Box::new(Type::Variable(closure_var)), Box::new(Type::Variable(closure_var)),
@ -603,7 +603,7 @@ fn build_effect_after(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![effect_a, a_to_effect_b], vec![effect_a, a_to_effect_b],
Box::new(Type::Variable(closure_var)), Box::new(Type::Variable(closure_var)),
@ -831,8 +831,8 @@ fn build_effect_forever(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_a = build_effect_alias( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -855,7 +855,7 @@ fn build_effect_forever(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![effect_a], vec![effect_a],
@ -1089,8 +1089,8 @@ fn build_effect_loop(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_b = build_effect_alias( let effect_b = build_effect_alias(
effect_symbol, effect_symbol,
@ -1111,13 +1111,13 @@ fn build_effect_loop(
(step_tag_name, vec![Type::Variable(var_a)]), (step_tag_name, vec![Type::Variable(var_a)]),
(done_tag_name, vec![Type::Variable(var_b)]), (done_tag_name, vec![Type::Variable(var_b)]),
], ],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
) )
}; };
let effect_state_type = { let effect_state_type = {
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
let actual = { let actual = {
Type::TagUnion( Type::TagUnion(
@ -1129,7 +1129,7 @@ fn build_effect_loop(
Box::new(state_type.clone()), 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(); 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( let step_type = Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
@ -1154,7 +1154,7 @@ fn build_effect_loop(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![Type::Variable(var_a), step_type], vec![Type::Variable(var_a), step_type],
@ -1559,7 +1559,7 @@ fn build_effect_alias(
introduced_variables: &mut IntroducedVariables, introduced_variables: &mut IntroducedVariables,
) -> Type { ) -> Type {
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
let actual = { let actual = {
Type::TagUnion( Type::TagUnion(
@ -1571,7 +1571,7 @@ fn build_effect_alias(
Box::new(a_type), 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(a_type),
)], )],
)], )],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
) )
} }

View file

@ -259,7 +259,7 @@ pub fn canonicalize_module_defs<'a>(
} }
for var in output.introduced_variables.wildcards { for var in output.introduced_variables.wildcards {
rigid_variables.wildcards.insert(var); rigid_variables.wildcards.insert(var.value);
} }
let mut referenced_values = MutSet::default(); let mut referenced_values = MutSet::default();

View file

@ -6,9 +6,9 @@ use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::subs::Variable; use roc_types::subs::Variable;
use roc_types::types::Reason;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category}; use roc_types::types::{AliasKind, Category};
use roc_types::types::{Reason, TypeExtension};
#[must_use] #[must_use]
#[inline(always)] #[inline(always)]
@ -190,7 +190,7 @@ pub fn num_floatingpoint(range: Type) -> Type {
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
vec![range.clone()], vec![range.clone()],
)], )],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
builtin_alias( builtin_alias(
@ -209,7 +209,7 @@ pub fn num_u32() -> Type {
fn num_unsigned32() -> Type { fn num_unsigned32() -> Type {
let alias_content = Type::TagUnion( let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])], vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content)) builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
@ -219,7 +219,7 @@ fn num_unsigned32() -> Type {
pub fn num_binary64() -> Type { pub fn num_binary64() -> Type {
let alias_content = Type::TagUnion( let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])], vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) 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 { pub fn num_signed64() -> Type {
let alias_content = Type::TagUnion( let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])], vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) 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), TagName::Private(Symbol::NUM_AT_INTEGER),
vec![range.clone()], vec![range.clone()],
)], )],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
builtin_alias( builtin_alias(
@ -265,7 +265,7 @@ pub fn num_integer(range: Type) -> Type {
pub fn num_num(typ: Type) -> Type { pub fn num_num(typ: Type) -> Type {
let alias_content = Type::TagUnion( let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])], vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
builtin_alias( builtin_alias(

View file

@ -16,7 +16,9 @@ use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
use roc_types::types::Type::{self, *}; 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 /// This is for constraining Defs
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -119,13 +121,7 @@ pub fn constrain_expr(
rec_constraints.push(field_con); rec_constraints.push(field_con);
} }
let record_type = Type::Record( let record_type = Type::Record(field_types, TypeExtension::Closed);
field_types,
// TODO can we avoid doing Box::new on every single one of these?
// We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a
// lifetime parameter on `Type`
Box::new(Type::EmptyRec),
);
let record_con = constraints.equal_types_with_storage( let record_con = constraints.equal_types_with_storage(
record_type, record_type,
@ -165,7 +161,8 @@ pub fn constrain_expr(
cons.push(con); 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); let record_type = Type::Variable(*record_var);
// NOTE from elm compiler: fields_type is separate so that Error propagates better // 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(); let label = field.clone();
rec_field_types.insert(label, RecordField::Demanded(field_type)); 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 record_expected = Expected::NoExpectation(record_type);
let category = Category::Access(field.clone()); let category = Category::Access(field.clone());
@ -767,7 +764,7 @@ pub fn constrain_expr(
let mut field_types = SendMap::default(); let mut field_types = SendMap::default();
let label = field.clone(); let label = field.clone();
field_types.insert(label, RecordField::Demanded(field_type.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()); let category = Category::Accessor(field.clone());
@ -905,7 +902,7 @@ pub fn constrain_expr(
let union_con = constraints.equal_types_with_storage( let union_con = constraints.equal_types_with_storage(
Type::TagUnion( Type::TagUnion(
vec![(name.clone(), types)], vec![(name.clone(), types)],
Box::new(Type::Variable(*ext_var)), TypeExtension::from_type(Type::Variable(*ext_var)),
), ),
expected.clone(), expected.clone(),
Category::TagApply { Category::TagApply {
@ -951,7 +948,7 @@ pub fn constrain_expr(
Type::FunctionOrTagUnion( Type::FunctionOrTagUnion(
name.clone(), name.clone(),
*closure_name, *closure_name,
Box::new(Type::Variable(*ext_var)), TypeExtension::from_type(Type::Variable(*ext_var)),
), ),
expected.clone(), expected.clone(),
Category::TagApply { Category::TagApply {
@ -1621,7 +1618,7 @@ fn constrain_closure_size(
let tag_name = TagName::Closure(name); let tag_name = TagName::Closure(name);
Type::TagUnion( Type::TagUnion(
vec![(tag_name, tag_arguments)], 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<Symbol, Loc<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> InstantiateRigids { ) -> InstantiateRigids {
let mut annotation = annotation.clone(); let mut annotation = annotation.clone();
let mut new_rigid_variables = Vec::new(); let mut new_rigid_variables: Vec<Variable> = Vec::new();
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default(); let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
for (name, var) in introduced_vars.var_by_name.iter() { for (name, var) in introduced_vars.var_by_name.iter() {
@ -1660,23 +1657,24 @@ fn instantiate_rigids(
match ftv.entry(name.clone()) { match ftv.entry(name.clone()) {
Occupied(occupied) => { Occupied(occupied) => {
let existing_rigid = occupied.get(); let existing_rigid = occupied.get();
rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); rigid_substitution.insert(var.value, Type::Variable(*existing_rigid));
} }
Vacant(vacant) => { Vacant(vacant) => {
// It's possible to use this rigid in nested defs // It's possible to use this rigid in nested defs
vacant.insert(*var); vacant.insert(var.value);
new_rigid_variables.push(*var); new_rigid_variables.push(var.value);
} }
} }
} }
// wildcards are always freshly introduced in this annotation // 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 // lambda set vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied()); new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
let new_infer_variables = introduced_vars.inferred.clone(); let new_infer_variables: Vec<Variable> =
introduced_vars.inferred.iter().map(|v| v.value).collect();
// Instantiate rigid variables // Instantiate rigid variables
if !rigid_substitution.is_empty() { if !rigid_substitution.is_empty() {

View file

@ -9,7 +9,9 @@ use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable; 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)] #[derive(Default)]
pub struct PatternState { pub struct PatternState {
@ -391,7 +393,7 @@ pub fn constrain_pattern(
state.vars.push(*var); 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( let whole_con = constraints.equal_types(
Type::Variable(*whole_var), Type::Variable(*whole_var),

View file

@ -36,7 +36,7 @@ use roc_solve::solve;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::solved_types::Solved; use roc_types::solved_types::Solved;
use roc_types::subs::{Subs, VarStore, Variable}; 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::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::io; use std::io;
@ -4123,7 +4123,7 @@ fn default_aliases() -> roc_solve::solve::Aliases {
TagName::Private(Symbol::NUM_AT_NUM), TagName::Private(Symbol::NUM_AT_NUM),
vec![Type::Variable(tvar)], vec![Type::Variable(tvar)],
)], )],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
let alias = Alias { let alias = Alias {
@ -4148,7 +4148,7 @@ fn default_aliases() -> roc_solve::solve::Aliases {
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
vec![Type::Variable(tvar)], vec![Type::Variable(tvar)],
)], )],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
let alias = Alias { let alias = Alias {
@ -4231,7 +4231,7 @@ fn default_aliases() -> roc_solve::solve::Aliases {
TagName::Private(Symbol::NUM_AT_INTEGER), TagName::Private(Symbol::NUM_AT_INTEGER),
vec![Type::Variable(tvar)], vec![Type::Variable(tvar)],
)], )],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
let alias = Alias { let alias = Alias {
@ -4256,7 +4256,7 @@ fn default_aliases() -> roc_solve::solve::Aliases {
(TagName::Global("Ok".into()), vec![Type::Variable(tvar1)]), (TagName::Global("Ok".into()), vec![Type::Variable(tvar1)]),
(TagName::Global("Err".into()), vec![Type::Variable(tvar2)]), (TagName::Global("Err".into()), vec![Type::Variable(tvar2)]),
], ],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
let alias = Alias { 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 mut unit_function = |alias_name: Symbol, at_tag_name: Symbol| {
let typ = Type::TagUnion( let typ = Type::TagUnion(
vec![(TagName::Private(at_tag_name), vec![])], vec![(TagName::Private(at_tag_name), vec![])],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
let alias = Alias { let alias = Alias {

View file

@ -112,6 +112,11 @@ pub struct PartialProcs<'a> {
/// maps a function name (symbol) to an index /// maps a function name (symbol) to an index
symbols: Vec<'a, Symbol>, 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>>, partial_procs: Vec<'a, PartialProc<'a>>,
} }
@ -119,6 +124,7 @@ impl<'a> PartialProcs<'a> {
fn new_in(arena: &'a Bump) -> Self { fn new_in(arena: &'a Bump) -> Self {
Self { Self {
symbols: Vec::new_in(arena), symbols: Vec::new_in(arena),
references: Vec::new_in(arena),
partial_procs: 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() self.symbol_to_id(symbol).is_some()
} }
fn symbol_to_id(&self, symbol: Symbol) -> Option<PartialProcId> { fn symbol_to_id(&self, mut symbol: Symbol) -> Option<PartialProcId> {
while let Some(real_symbol) = self
.references
.iter()
.find(|(alias, _)| *alias == symbol)
.map(|(_, real)| real)
{
symbol = *real_symbol;
}
self.symbols self.symbols
.iter() .iter()
.position(|s| *s == symbol) .position(|s| *s == symbol)
@ -157,6 +172,21 @@ impl<'a> PartialProcs<'a> {
id 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)] #[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 // 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 // whose usages will already be specialized in the rest of the program.
// rest of the program now to get our hole.
let mut result = build_rest(env, procs, layout_cache);
if procs.is_imported_module_thunk(right) { 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 // if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer. // specialized, and wrap the original in a function pointer.
add_needed_external(procs, env, variable, right); add_needed_external(procs, env, variable, right);
@ -6693,25 +6723,25 @@ where
force_thunk(env, right, layout, left, env.arena.alloc(result)) force_thunk(env, right, layout, left, env.arena.alloc(result))
} else if env.is_imported_symbol(right) { } 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 // if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer. // specialized, and wrap the original in a function pointer.
add_needed_external(procs, env, variable, right); add_needed_external(procs, env, variable, right);
// then we must construct its closure; since imported symbols have no closure, we use the empty struct // 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)) 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 { } 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); substitute_in_exprs(env.arena, &mut result, left, right);
result
// if the substituted variable is a function, make sure we specialize it
reuse_function_symbol(
env,
procs,
layout_cache,
Some(variable),
right,
result,
right,
)
} }
} }

View file

@ -1994,7 +1994,15 @@ pub fn union_sorted_tags<'a>(
let mut tags_vec = std::vec::Vec::new(); let mut tags_vec = std::vec::Vec::new();
let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) { 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); let opt_rec_var = get_recursion_var(subs, var);
union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, target_info) 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 // the ext_var is empty
let mut ext_fields = std::vec::Vec::new(); let mut ext_fields = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { 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), Err(content) => panic!("invalid content in ext_var: {:?}", content),
} }
} }

View file

@ -9,4 +9,5 @@ edition = "2018"
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }

View file

@ -5,6 +5,7 @@ use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::ast::Base; use roc_parse::ast::Base;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::types::AliasKind;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct CycleEntry { pub struct CycleEntry {
@ -43,6 +44,12 @@ pub enum Problem {
variable_region: Region, variable_region: Region,
variable_name: Lowercase, variable_name: Lowercase,
}, },
UnboundTypeVariable {
typ: Symbol,
num_unbound: usize,
one_occurrence: Region,
kind: AliasKind,
},
DuplicateRecordFieldValue { DuplicateRecordFieldValue {
field_name: Lowercase, field_name: Lowercase,
record_region: Region, record_region: Region,

View file

@ -14,6 +14,7 @@ use roc_types::subs::{
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{ use roc_types::types::{
gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory, gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory,
TypeExtension,
}; };
use roc_unify::unify::{unify, Mode, Unified::*}; use roc_unify::unify::{unify, Mode, Unified::*};
@ -1111,7 +1112,7 @@ fn solve(
let actual = type_to_var(subs, rank, pools, aliases, typ); let actual = type_to_var(subs, rank, pools, aliases, typ);
let tag_ty = Type::TagUnion( let tag_ty = Type::TagUnion(
vec![(tag_name.clone(), tys.to_vec())], vec![(tag_name.clone(), tys.to_vec())],
Box::new(Type::EmptyTagUnion), TypeExtension::Closed,
); );
let includes = type_to_var(subs, rank, pools, aliases, &tag_ty); let includes = type_to_var(subs, rank, pools, aliases, &tag_ty);
@ -1425,7 +1426,7 @@ fn type_to_variable<'a>(
Record(fields, ext) => { Record(fields, ext) => {
// An empty fields is inefficient (but would be correct) // An empty fields is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyRecord in canonicalization // 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); 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)); 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) = let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
@ -1465,7 +1469,7 @@ fn type_to_variable<'a>(
TagUnion(tags, ext) => { TagUnion(tags, ext) => {
// An empty tags is inefficient (but would be correct) // An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization // 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) = let (union_tags, ext) =
type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); 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) register_with_known_var(subs, destination, rank, pools, content)
} }
FunctionOrTagUnion(tag_name, symbol, ext) => { 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( let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
subs, subs,
@ -1496,7 +1503,7 @@ fn type_to_variable<'a>(
RecursiveTagUnion(rec_var, tags, ext) => { RecursiveTagUnion(rec_var, tags, ext) => {
// An empty tags is inefficient (but would be correct) // An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization // 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) = let (union_tags, ext) =
type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack);
@ -1681,7 +1688,7 @@ fn roc_result_to_var<'a>(
) -> Variable { ) -> Variable {
match result_type { match result_type {
Type::TagUnion(tags, ext) => { Type::TagUnion(tags, ext) => {
debug_assert!(ext.is_empty_tag_union()); debug_assert!(ext.is_closed());
debug_assert!(tags.len() == 2); debug_assert!(tags.len() == 2);
if let [(err, err_args), (ok, ok_args)] = &tags[..] { if let [(err, err_args), (ok, ok_args)] = &tags[..] {
@ -1925,14 +1932,15 @@ fn type_to_union_tags<'a>(
pools: &mut Pools, pools: &mut Pools,
arena: &'_ bumpalo::Bump, arena: &'_ bumpalo::Bump,
tags: &'a [(TagName, Vec<Type>)], tags: &'a [(TagName, Vec<Type>)],
ext: &'a Type, ext: &'a TypeExtension,
stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>,
) -> (UnionTags, Variable) { ) -> (UnionTags, Variable) {
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
let sorted = tags.len() == 1 || sorted_no_duplicates(tags); let sorted = tags.len() == 1 || sorted_no_duplicates(tags);
if ext.is_empty_tag_union() { match ext {
TypeExtension::Closed => {
let ext = Variable::EMPTY_TAG_UNION; let ext = Variable::EMPTY_TAG_UNION;
let union_tags = if sorted { let union_tags = if sorted {
@ -1943,12 +1951,16 @@ fn type_to_union_tags<'a>(
}; };
(union_tags, ext) (union_tags, ext)
} else { }
TypeExtension::Open(ext) => {
let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); 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 temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack);
let (it, ext) = let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); 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)));
@ -1960,6 +1972,7 @@ fn type_to_union_tags<'a>(
(union_tags, ext) (union_tags, ext)
} }
}
} }
fn check_for_infinite_type( fn check_for_infinite_type(

View file

@ -1548,3 +1548,35 @@ fn issue_1162() {
u8 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
)
}

View file

@ -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;

View file

@ -2,22 +2,20 @@ procedure Num.22 (#Attr.2, #Attr.3):
let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.12; ret Test.12;
procedure Test.4 (Test.7, Test.8): procedure Test.5 (Test.7, Test.8):
let Test.17 : U64 = 1i64;
ret Test.17;
procedure Test.4 (Test.7, Test.8):
let Test.18 : U64 = 1i64; let Test.18 : U64 = 1i64;
ret Test.18; ret Test.18;
procedure Test.6 (Test.7, Test.8):
let Test.15 : U64 = 1i64;
ret Test.15;
procedure Test.0 (): procedure Test.0 ():
let Test.4 : {} = Struct {}; let Test.16 : U8 = 100i64;
let Test.4 : {} = Struct {}; let Test.17 : U32 = 100i64;
let Test.15 : U8 = 100i64; let Test.10 : U64 = CallByName Test.5 Test.16 Test.17;
let Test.16 : U32 = 100i64;
let Test.10 : U64 = CallByName Test.4 Test.15 Test.16;
let Test.13 : U32 = 100i64; let Test.13 : U32 = 100i64;
let Test.14 : U8 = 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; let Test.9 : U64 = CallByName Num.22 Test.10 Test.11;
ret Test.9; ret Test.9;

View file

@ -36,9 +36,9 @@ procedure Test.8 (Test.11, #Attr.12):
ret Test.11; ret Test.11;
procedure Test.0 (): procedure Test.0 ():
let Test.5 : I64 = 2i64;
let Test.6 : Int1 = true; let Test.6 : Int1 = true;
let Test.4 : I64 = 1i64; let Test.4 : I64 = 1i64;
let Test.5 : I64 = 2i64;
joinpoint Test.22 Test.14: joinpoint Test.22 Test.14:
let Test.15 : I64 = 42i64; let Test.15 : I64 = 42i64;
let Test.13 : I64 = CallByName Test.1 Test.14 Test.15; let Test.13 : I64 = CallByName Test.1 Test.14 Test.15;

View file

@ -17,8 +17,8 @@ procedure Test.7 (Test.9, #Attr.12):
ret Test.20; ret Test.20;
procedure Test.0 (): procedure Test.0 ():
let Test.4 : I64 = 1i64;
let Test.5 : I64 = 2i64; let Test.5 : I64 = 2i64;
let Test.4 : I64 = 1i64;
let Test.12 : I64 = 42i64; let Test.12 : I64 = 42i64;
joinpoint Test.19 Test.13: joinpoint Test.19 Test.13:
let Test.14 : U8 = GetTagId Test.13; let Test.14 : U8 = GetTagId Test.13;

View file

@ -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] // #[ignore]
// #[mono_test] // #[mono_test]
// fn static_str_closure() { // fn static_str_closure() {

View file

@ -1,5 +1,5 @@
use crate::subs::{FlatType, GetSubsSlice, Subs, VarId, VarStore, Variable}; 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_collections::all::{ImMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -113,7 +113,11 @@ impl SolvedType {
SolvedType::Func(solved_args, Box::new(solved_closure), Box::new(solved_ret)) SolvedType::Func(solved_args, Box::new(solved_closure), Box::new(solved_ret))
} }
Record(fields, box_ext) => { 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()); let mut solved_fields = Vec::with_capacity(fields.len());
for (label, field) in fields { for (label, field) in fields {
@ -139,7 +143,11 @@ impl SolvedType {
SolvedType::TagUnion(solved_tags, Box::new(solved_ext)) SolvedType::TagUnion(solved_tags, Box::new(solved_ext))
} }
TagUnion(tags, box_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()); let mut solved_tags = Vec::with_capacity(tags.len());
for (tag_name, types) in tags { for (tag_name, types) in tags {
let mut solved_types = Vec::with_capacity(types.len()); let mut solved_types = Vec::with_capacity(types.len());
@ -155,11 +163,19 @@ impl SolvedType {
SolvedType::TagUnion(solved_tags, Box::new(solved_ext)) SolvedType::TagUnion(solved_tags, Box::new(solved_ext))
} }
FunctionOrTagUnion(tag_name, symbol, box_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)) SolvedType::FunctionOrTagUnion(tag_name.clone(), *symbol, Box::new(solved_ext))
} }
RecursiveTagUnion(rec_var, tags, box_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()); let mut solved_tags = Vec::with_capacity(tags.len());
for (tag_name, types) in tags { for (tag_name, types) in tags {
let mut solved_types = Vec::with_capacity(types.len()); let mut solved_types = Vec::with_capacity(types.len());
@ -523,7 +539,12 @@ pub fn to_type(
new_fields.insert(label.clone(), field_val); 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, EmptyRecord => Type::EmptyRec,
EmptyTagUnion => Type::EmptyTagUnion, EmptyTagUnion => Type::EmptyTagUnion,
@ -540,13 +561,21 @@ pub fn to_type(
new_tags.push((tag_name.clone(), new_args)); 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) => { RecursiveTagUnion(rec_var_id, tags, ext) => {
let mut new_tags = Vec::with_capacity(tags.len()); 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)); 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 let rec_var = free_vars
.unnamed_vars .unnamed_vars
.get(rec_var_id) .get(rec_var_id)
.expect("rec var not in unnamed vars"); .expect("rec var not in unnamed vars");
Type::RecursiveTagUnion( Type::RecursiveTagUnion(*rec_var, new_tags, ext)
*rec_var,
new_tags,
Box::new(to_type(ext, free_vars, var_store)),
)
} }
DelayedAlias(symbol, solved_type_variables, solved_lambda_sets) => { DelayedAlias(symbol, solved_type_variables, solved_lambda_sets) => {
let mut type_variables = Vec::with_capacity(solved_type_variables.len()); let mut type_variables = Vec::with_capacity(solved_type_variables.len());

View file

@ -193,9 +193,9 @@ pub enum Type {
EmptyTagUnion, EmptyTagUnion,
/// A function. The types of its arguments, size of its closure, then the type of its return value. /// A function. The types of its arguments, size of its closure, then the type of its return value.
Function(Vec<Type>, Box<Type>, Box<Type>), Function(Vec<Type>, Box<Type>, Box<Type>),
Record(SendMap<Lowercase, RecordField<Type>>, Box<Type>), Record(SendMap<Lowercase, RecordField<Type>>, TypeExtension),
TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>), TagUnion(Vec<(TagName, Vec<Type>)>, TypeExtension),
FunctionOrTagUnion(TagName, Symbol, Box<Type>), FunctionOrTagUnion(TagName, Symbol, TypeExtension),
/// A function name that is used in our defunctionalization algorithm /// A function name that is used in our defunctionalization algorithm
ClosureTag { ClosureTag {
name: Symbol, name: Symbol,
@ -216,7 +216,7 @@ pub enum Type {
actual_var: Variable, actual_var: Variable,
actual: Box<Type>, actual: Box<Type>,
}, },
RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, Box<Type>), RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, TypeExtension),
/// Applying a type to some arguments (e.g. Dict.Dict String Int) /// Applying a type to some arguments (e.g. Dict.Dict String Int)
Apply(Symbol, Vec<Type>, Region), Apply(Symbol, Vec<Type>, Region),
Variable(Variable), Variable(Variable),
@ -288,6 +288,43 @@ impl Clone for Type {
} }
} }
#[derive(PartialEq, Eq, Clone)]
pub enum TypeExtension {
Open(Box<Type>),
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<Self::Item>;
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 { impl fmt::Debug for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
@ -422,12 +459,12 @@ impl fmt::Debug for Type {
write!(f, "}}")?; write!(f, "}}")?;
match *ext.clone() { match ext {
Type::EmptyRec => { TypeExtension::Closed => {
// This is a closed record. We're done! // This is a closed record. We're done!
Ok(()) Ok(())
} }
other => { TypeExtension::Open(other) => {
// This is an open record, so print the variable // This is an open record, so print the variable
// right after the '}' // right after the '}'
// //
@ -463,12 +500,12 @@ impl fmt::Debug for Type {
write!(f, "]")?; write!(f, "]")?;
match *ext.clone() { match ext {
Type::EmptyTagUnion => { TypeExtension::Closed => {
// This is a closed variant. We're done! // This is a closed variant. We're done!
Ok(()) Ok(())
} }
other => { TypeExtension::Open(other) => {
// This is an open tag union, so print the variable // This is an open tag union, so print the variable
// right after the ']' // right after the ']'
// //
@ -483,12 +520,12 @@ impl fmt::Debug for Type {
write!(f, "{:?}", tag_name)?; write!(f, "{:?}", tag_name)?;
write!(f, "]")?; write!(f, "]")?;
match *ext.clone() { match ext {
Type::EmptyTagUnion => { TypeExtension::Closed => {
// This is a closed variant. We're done! // This is a closed variant. We're done!
Ok(()) Ok(())
} }
other => { TypeExtension::Open(other) => {
// This is an open tag union, so print the variable // This is an open tag union, so print the variable
// right after the ']' // right after the ']'
// //
@ -533,19 +570,20 @@ impl fmt::Debug for Type {
write!(f, "]")?; write!(f, "]")?;
match *ext.clone() { match ext {
Type::EmptyTagUnion => { TypeExtension::Closed => {
// This is a closed variant. We're done! // This is a closed variant. We're done!
Ok(())
} }
other => { TypeExtension::Open(other) => {
// This is an open tag union, so print the variable // This is an open tag union, so print the variable
// right after the ']' // right after the ']'
// //
// e.g. the "*" at the end of `[ Foo ]*` // e.g. the "*" at the end of `[ Foo ]*`
// or the "r" at the end of `[ DivByZero ]r` // or the "r" at the end of `[ DivByZero ]r`
other.fmt(f)?; other.fmt(f)
}
} }
}?;
write!(f, " as <{:?}>", rec) write!(f, " as <{:?}>", rec)
} }
@ -611,23 +649,34 @@ impl Type {
for (_, args) in tags { for (_, args) in tags {
stack.extend(args.iter_mut()); stack.extend(args.iter_mut());
} }
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
RecursiveTagUnion(_, tags, ext) => { RecursiveTagUnion(_, tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
stack.extend(args.iter_mut()); stack.extend(args.iter_mut());
} }
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
Record(fields, ext) => { Record(fields, ext) => {
for (_, x) in fields.iter_mut() { for (_, x) in fields.iter_mut() {
stack.push(x.as_inner_mut()); stack.push(x.as_inner_mut());
} }
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
Type::DelayedAlias(AliasCommon { Type::DelayedAlias(AliasCommon {
type_arguments, type_arguments,
lambda_set_variables, lambda_set_variables,
@ -650,6 +699,7 @@ impl Type {
for (_, value) in type_arguments.iter_mut() { for (_, value) in type_arguments.iter_mut() {
stack.push(value); stack.push(value);
} }
for lambda_set in lambda_set_variables.iter_mut() { for lambda_set in lambda_set_variables.iter_mut() {
stack.push(lambda_set.as_inner_mut()); stack.push(lambda_set.as_inner_mut());
} }
@ -705,11 +755,16 @@ impl Type {
for (_, args) in tags { for (_, args) in tags {
stack.extend(args.iter_mut()); stack.extend(args.iter_mut());
} }
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
RecursiveTagUnion(rec_var, tags, ext) => { RecursiveTagUnion(rec_var, tags, ext) => {
if let Some(replacement) = substitutions.get(rec_var) { if let Some(replacement) = substitutions.get(rec_var) {
*rec_var = *replacement; *rec_var = *replacement;
@ -718,14 +773,19 @@ impl Type {
for (_, args) in tags { for (_, args) in tags {
stack.extend(args.iter_mut()); stack.extend(args.iter_mut());
} }
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
Record(fields, ext) => { Record(fields, ext) => {
for (_, x) in fields.iter_mut() { for (_, x) in fields.iter_mut() {
stack.push(x.as_inner_mut()); stack.push(x.as_inner_mut());
} }
if let TypeExtension::Open(ext) = ext {
stack.push(ext); stack.push(ext);
} }
}
Type::DelayedAlias(AliasCommon { Type::DelayedAlias(AliasCommon {
type_arguments, type_arguments,
lambda_set_variables, lambda_set_variables,
@ -800,20 +860,31 @@ impl Type {
closure.substitute_alias(rep_symbol, rep_args, actual)?; closure.substitute_alias(rep_symbol, rep_args, actual)?;
ret.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) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
x.substitute_alias(rep_symbol, rep_args, actual)?; 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) => { Record(fields, ext) => {
for (_, x) in fields.iter_mut() { for (_, x) in fields.iter_mut() {
x.substitute_alias(rep_symbol, rep_args, actual)?; 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, .. }) => { DelayedAlias(AliasCommon { type_arguments, .. }) => {
for (_, ta) in 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 { pub fn contains_symbol(&self, rep_symbol: Symbol) -> bool {
use Type::*; use Type::*;
@ -871,9 +949,9 @@ impl Type {
|| closure.contains_symbol(rep_symbol) || closure.contains_symbol(rep_symbol)
|| args.iter().any(|arg| arg.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) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
ext.contains_symbol(rep_symbol) Self::contains_symbol_ext(ext, rep_symbol)
|| tags || tags
.iter() .iter()
.map(|v| v.1.iter()) .map(|v| v.1.iter())
@ -882,7 +960,7 @@ impl Type {
} }
Record(fields, ext) => { Record(fields, ext) => {
ext.contains_symbol(rep_symbol) Self::contains_symbol_ext(ext, rep_symbol)
|| fields.values().any(|arg| arg.contains_symbol(rep_symbol)) || fields.values().any(|arg| arg.contains_symbol(rep_symbol))
} }
DelayedAlias(AliasCommon { 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 { pub fn contains_variable(&self, rep_variable: Variable) -> bool {
use Type::*; use Type::*;
@ -920,9 +1005,9 @@ impl Type {
|| closure.contains_variable(rep_variable) || closure.contains_variable(rep_variable)
|| args.iter().any(|arg| arg.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) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
ext.contains_variable(rep_variable) Self::contains_variable_ext(ext, rep_variable)
|| tags || tags
.iter() .iter()
.map(|v| v.1.iter()) .map(|v| v.1.iter())
@ -931,7 +1016,7 @@ impl Type {
} }
Record(fields, ext) => { Record(fields, ext) => {
ext.contains_variable(rep_variable) Self::contains_variable_ext(ext, rep_variable)
|| fields || fields
.values() .values()
.any(|arg| arg.contains_variable(rep_variable)) .any(|arg| arg.contains_variable(rep_variable))
@ -983,22 +1068,30 @@ impl Type {
ret.instantiate_aliases(region, aliases, var_store, introduced); ret.instantiate_aliases(region, aliases, var_store, introduced);
} }
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
ext.instantiate_aliases(region, aliases, var_store, introduced); ext.instantiate_aliases(region, aliases, var_store, introduced);
} }
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
x.instantiate_aliases(region, aliases, var_store, introduced); x.instantiate_aliases(region, aliases, var_store, introduced);
} }
} }
if let TypeExtension::Open(ext) = ext {
ext.instantiate_aliases(region, aliases, var_store, introduced); ext.instantiate_aliases(region, aliases, var_store, introduced);
} }
}
Record(fields, ext) => { Record(fields, ext) => {
for (_, x) in fields.iter_mut() { for (_, x) in fields.iter_mut() {
x.instantiate_aliases(region, aliases, var_store, introduced); x.instantiate_aliases(region, aliases, var_store, introduced);
} }
if let TypeExtension::Open(ext) = ext {
ext.instantiate_aliases(region, aliases, var_store, introduced); ext.instantiate_aliases(region, aliases, var_store, introduced);
} }
}
DelayedAlias(AliasCommon { .. }) => { DelayedAlias(AliasCommon { .. }) => {
// do nothing, yay // do nothing, yay
} }
@ -1084,7 +1177,10 @@ impl Type {
for typ in tags.iter_mut().map(|v| v.1.iter_mut()).flatten() { for typ in tags.iter_mut().map(|v| v.1.iter_mut()).flatten() {
typ.substitute(&substitution); typ.substitute(&substitution);
} }
if let TypeExtension::Open(ext) = &mut ext {
ext.substitute(&substitution); ext.substitute(&substitution);
}
actual = Type::RecursiveTagUnion(new_rec_var, tags, ext); actual = Type::RecursiveTagUnion(new_rec_var, tags, ext);
} }
@ -1145,14 +1241,17 @@ impl Type {
pub fn is_narrow(&self) -> bool { pub fn is_narrow(&self) -> bool {
match self.shallow_dealias() { match self.shallow_dealias() {
Type::TagUnion(tags, ext) | Type::RecursiveTagUnion(_, tags, ext) => { Type::TagUnion(tags, ext) | Type::RecursiveTagUnion(_, tags, ext) => {
ext.is_empty_tag_union() matches!(ext, TypeExtension::Closed)
&& tags.len() == 1 && tags.len() == 1
&& tags[0].1.len() == 1 && tags[0].1.len() == 1
&& tags[0].1[0].is_narrow() && tags[0].1[0].is_narrow()
} }
Type::Record(fields, ext) => { Type::Record(fields, ext) => match ext {
TypeExtension::Open(ext) => {
fields.values().all(|field| field.as_inner().is_narrow()) && ext.is_narrow() 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) => { Type::Function(args, clos, ret) => {
args.iter().all(|a| a.is_narrow()) && clos.is_narrow() && ret.is_narrow() args.iter().all(|a| a.is_narrow()) && clos.is_narrow() && ret.is_narrow()
} }
@ -1187,15 +1286,15 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
stack.extend(args); stack.extend(args);
} }
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
stack.push(ext); stack.extend(ext);
} }
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
stack.push(ext); stack.extend(ext);
stack.extend(tags.iter().map(|v| v.1.iter()).flatten()); stack.extend(tags.iter().map(|v| v.1.iter()).flatten());
} }
Record(fields, ext) => { Record(fields, ext) => {
stack.push(ext); stack.extend(ext);
stack.extend(fields.values().map(|field| field.as_inner())); stack.extend(fields.values().map(|field| field.as_inner()));
} }
DelayedAlias(AliasCommon { DelayedAlias(AliasCommon {
@ -1269,26 +1368,37 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
Demanded(x) => variables_help(x, accum), Demanded(x) => variables_help(x, accum),
}; };
} }
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum); variables_help(ext, accum);
} }
}
TagUnion(tags, ext) => { TagUnion(tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
variables_help(x, accum); variables_help(x, accum);
} }
} }
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum); variables_help(ext, accum);
} }
}
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum); variables_help(ext, accum);
} }
}
RecursiveTagUnion(rec, tags, ext) => { RecursiveTagUnion(rec, tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
variables_help(x, accum); variables_help(x, accum);
} }
} }
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum); variables_help(ext, accum);
}
// just check that this is actually a recursive type // just check that this is actually a recursive type
debug_assert!(accum.contains(rec)); debug_assert!(accum.contains(rec));
@ -1380,26 +1490,37 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
Demanded(x) => variables_help_detailed(x, accum), Demanded(x) => variables_help_detailed(x, accum),
}; };
} }
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum); variables_help_detailed(ext, accum);
} }
}
TagUnion(tags, ext) => { TagUnion(tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
variables_help_detailed(x, accum); variables_help_detailed(x, accum);
} }
} }
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum); variables_help_detailed(ext, accum);
} }
}
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum); variables_help_detailed(ext, accum);
} }
}
RecursiveTagUnion(rec, tags, ext) => { RecursiveTagUnion(rec, tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
variables_help_detailed(x, accum); variables_help_detailed(x, accum);
} }
} }
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum); variables_help_detailed(ext, accum);
}
// just check that this is actually a recursive type // just check that this is actually a recursive type
// debug_assert!(accum.type_variables.contains(rec)); // debug_assert!(accum.type_variables.contains(rec));

View file

@ -26,7 +26,6 @@ roc_target = {path = "../compiler/roc_target"}
roc_types = {path = "../compiler/types"} roc_types = {path = "../compiler/types"}
[features] [features]
default = ["console_error_panic_hook"]
wasmer = ["futures"] wasmer = ["futures"]
[package.metadata.wasm-pack.profile.profiling] [package.metadata.wasm-pack.profile.profiling]

View file

@ -3,7 +3,7 @@ mod repl;
// //
// Interface with external JS in the browser // Interface with external JS in the browser
// //
#[cfg(not(feature = "wasmer"))] #[cfg(feature = "console_error_panic_hook")]
extern crate console_error_panic_hook; extern crate console_error_panic_hook;
#[cfg(not(feature = "wasmer"))] #[cfg(not(feature = "wasmer"))]
mod externs_js; mod externs_js;

View file

@ -155,7 +155,7 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> {
} }
pub async fn entrypoint_from_js(src: String) -> Result<String, String> { pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
#[cfg(not(feature = "wasmer"))] #[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
let arena = &Bump::new(); let arena = &Bump::new();

View file

@ -26,11 +26,11 @@ cp -r repl_www/public/* $WWW_ROOT
if [ -n "${REPL_DEBUG:-}" ] if [ -n "${REPL_DEBUG:-}" ]
then then
# Leave out wasm-opt since it takes too long when debugging, and provide some debug options # 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/ wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/
else else
# A `--profiling` build is optimized and has debug info, so we get stack traces for compiler `todo!()` # 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 fi
cp repl_wasm/pkg/*.wasm $WWW_ROOT cp repl_wasm/pkg/*.wasm $WWW_ROOT

View file

@ -5,6 +5,7 @@ use roc_problem::can::{
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError,
}; };
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
use roc_types::types::AliasKind;
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::r#type::suggest; use crate::error::r#type::suggest;
@ -17,6 +18,7 @@ const UNRECOGNIZED_NAME: &str = "UNRECOGNIZED NAME";
const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_DEF: &str = "UNUSED DEFINITION";
const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_IMPORT: &str = "UNUSED IMPORT";
const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER";
const UNBOUND_TYPE_VARIABLE: &str = "UNBOUND TYPE VARIABLE";
const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const UNUSED_ARG: &str = "UNUSED ARGUMENT";
const MISSING_DEFINITION: &str = "MISSING DEFINITION"; const MISSING_DEFINITION: &str = "MISSING DEFINITION";
const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION";
@ -252,6 +254,43 @@ pub fn can_problem<'b>(
title = UNUSED_ALIAS_PARAM.to_string(); title = UNUSED_ALIAS_PARAM.to_string();
severity = Severity::RuntimeError; 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) => { Problem::BadRecursion(entries) => {
doc = to_circular_def_doc(alloc, lines, &entries); doc = to_circular_def_doc(alloc, lines, &entries);
title = CIRCULAR_DEF.to_string(); title = CIRCULAR_DEF.to_string();

View file

@ -76,7 +76,7 @@ mod test_reporting {
} }
for var in output.introduced_variables.wildcards { 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(); 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?
"#
),
)
}
} }