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-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"]

1
Cargo.lock generated
View file

@ -3762,6 +3762,7 @@ dependencies = [
"roc_module",
"roc_parse",
"roc_region",
"roc_types",
]
[[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_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<Variable, Lowercase>`.
pub wildcards: Vec<Variable>,
pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>,
pub inferred: Vec<Loc<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 host_exposed_aliases: MutMap<Symbol, Variable>,
}
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.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);
}
pub fn insert_inferred(&mut self, var: Variable) {
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
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)
}
};

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 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,

View file

@ -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,
)
}

View file

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

View file

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

View file

@ -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<Symbol, Loc<Type>>,
) -> InstantiateRigids {
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();
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<Variable> =
introduced_vars.inferred.iter().map(|v| v.value).collect();
// Instantiate rigid variables
if !rigid_substitution.is_empty() {

View file

@ -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),

View file

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

View file

@ -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<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
.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
}
}

View file

@ -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),
}
}

View file

@ -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" }

View file

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

View file

@ -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,14 +1932,15 @@ fn type_to_union_tags<'a>(
pools: &mut Pools,
arena: &'_ bumpalo::Bump,
tags: &'a [(TagName, Vec<Type>)],
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() {
match ext {
TypeExtension::Closed => {
let ext = Variable::EMPTY_TAG_UNION;
let union_tags = if sorted {
@ -1943,12 +1951,16 @@ fn type_to_union_tags<'a>(
};
(union_tags, ext)
} else {
}
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 (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)));
@ -1961,6 +1973,7 @@ fn type_to_union_tags<'a>(
(union_tags, ext)
}
}
}
fn check_for_infinite_type(
subs: &mut Subs,

View file

@ -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
)
}

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

View file

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

View file

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

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

View file

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

View file

@ -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<Type>, Box<Type>, Box<Type>),
Record(SendMap<Lowercase, RecordField<Type>>, Box<Type>),
TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>),
FunctionOrTagUnion(TagName, Symbol, Box<Type>),
Record(SendMap<Lowercase, RecordField<Type>>, TypeExtension),
TagUnion(Vec<(TagName, Vec<Type>)>, 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<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)
Apply(Symbol, Vec<Type>, Region),
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 {
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,23 +649,34 @@ impl Type {
for (_, args) in tags {
stack.extend(args.iter_mut());
}
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
RecursiveTagUnion(_, tags, ext) => {
for (_, args) in tags {
stack.extend(args.iter_mut());
}
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
Record(fields, ext) => {
for (_, x) in fields.iter_mut() {
stack.push(x.as_inner_mut());
}
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
Type::DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
@ -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,11 +755,16 @@ impl Type {
for (_, args) in tags {
stack.extend(args.iter_mut());
}
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
RecursiveTagUnion(rec_var, tags, ext) => {
if let Some(replacement) = substitutions.get(rec_var) {
*rec_var = *replacement;
@ -718,14 +773,19 @@ impl Type {
for (_, args) in tags {
stack.extend(args.iter_mut());
}
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
Record(fields, ext) => {
for (_, x) in fields.iter_mut() {
stack.push(x.as_inner_mut());
}
if let TypeExtension::Open(ext) = ext {
stack.push(ext);
}
}
Type::DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
@ -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,22 +1068,30 @@ impl Type {
ret.instantiate_aliases(region, aliases, var_store, introduced);
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
ext.instantiate_aliases(region, aliases, var_store, introduced);
}
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
x.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);
}
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);
}
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) => {
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<Symbol> {
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,26 +1368,37 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
Demanded(x) => variables_help(x, accum),
};
}
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum);
}
}
TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
variables_help(x, accum);
}
}
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum);
}
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
variables_help(ext, accum);
}
}
RecursiveTagUnion(rec, tags, ext) => {
for (_, args) in tags {
for x in args {
variables_help(x, 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,26 +1490,37 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
Demanded(x) => variables_help_detailed(x, accum),
};
}
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum);
}
}
TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
variables_help_detailed(x, accum);
}
}
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum);
}
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext) = ext {
variables_help_detailed(ext, accum);
}
}
RecursiveTagUnion(rec, tags, ext) => {
for (_, args) in tags {
for x in args {
variables_help_detailed(x, 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));

View file

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

View file

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

View file

@ -155,7 +155,7 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> {
}
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();
let arena = &Bump::new();

View file

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

View file

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

View file

@ -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?
"#
),
)
}
}