mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Merge remote-tracking branch 'origin/trunk' into content-flattype-copy
This commit is contained in:
commit
ba2f9ba779
8 changed files with 564 additions and 331 deletions
|
@ -1085,7 +1085,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
//
|
//
|
||||||
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||||
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
||||||
if let &Closure(ClosureData {
|
if let Closure(ClosureData {
|
||||||
function_type,
|
function_type,
|
||||||
closure_type,
|
closure_type,
|
||||||
closure_ext_var,
|
closure_ext_var,
|
||||||
|
@ -1095,7 +1095,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
loc_body: ref body,
|
loc_body: ref body,
|
||||||
ref captured_symbols,
|
ref captured_symbols,
|
||||||
..
|
..
|
||||||
}) = &loc_can_expr.value
|
}) = loc_can_expr.value
|
||||||
{
|
{
|
||||||
// Since everywhere in the code it'll be referred to by its defined name,
|
// Since everywhere in the code it'll be referred to by its defined name,
|
||||||
// remove its generated name from the closure map. (We'll re-insert it later.)
|
// remove its generated name from the closure map. (We'll re-insert it later.)
|
||||||
|
@ -1225,7 +1225,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
//
|
//
|
||||||
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||||
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
||||||
if let &Closure(ClosureData {
|
if let Closure(ClosureData {
|
||||||
function_type,
|
function_type,
|
||||||
closure_type,
|
closure_type,
|
||||||
closure_ext_var,
|
closure_ext_var,
|
||||||
|
@ -1235,7 +1235,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
loc_body: ref body,
|
loc_body: ref body,
|
||||||
ref captured_symbols,
|
ref captured_symbols,
|
||||||
..
|
..
|
||||||
}) = &loc_can_expr.value
|
}) = loc_can_expr.value
|
||||||
{
|
{
|
||||||
// Since everywhere in the code it'll be referred to by its defined name,
|
// Since everywhere in the code it'll be referred to by its defined name,
|
||||||
// remove its generated name from the closure map. (We'll re-insert it later.)
|
// remove its generated name from the closure map. (We'll re-insert it later.)
|
||||||
|
|
|
@ -138,17 +138,7 @@ pub enum Expr {
|
||||||
field: Lowercase,
|
field: Lowercase,
|
||||||
},
|
},
|
||||||
/// field accessor as a function, e.g. (.foo) expr
|
/// field accessor as a function, e.g. (.foo) expr
|
||||||
Accessor {
|
Accessor(AccessorData),
|
||||||
/// accessors are desugared to closures; they need to have a name
|
|
||||||
/// so the closure can have a correct lambda set
|
|
||||||
name: Symbol,
|
|
||||||
function_var: Variable,
|
|
||||||
record_var: Variable,
|
|
||||||
closure_ext_var: Variable,
|
|
||||||
ext_var: Variable,
|
|
||||||
field_var: Variable,
|
|
||||||
field: Lowercase,
|
|
||||||
},
|
|
||||||
|
|
||||||
Update {
|
Update {
|
||||||
record_var: Variable,
|
record_var: Variable,
|
||||||
|
@ -217,6 +207,70 @@ pub struct ClosureData {
|
||||||
pub loc_body: Box<Loc<Expr>>,
|
pub loc_body: Box<Loc<Expr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
|
||||||
|
/// Accessors are desugared to closures; they need to have a name
|
||||||
|
/// so the closure can have a correct lambda set.
|
||||||
|
///
|
||||||
|
/// We distinguish them from closures so we can have better error messages
|
||||||
|
/// during constraint generation.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct AccessorData {
|
||||||
|
pub name: Symbol,
|
||||||
|
pub function_var: Variable,
|
||||||
|
pub record_var: Variable,
|
||||||
|
pub closure_var: Variable,
|
||||||
|
pub closure_ext_var: Variable,
|
||||||
|
pub ext_var: Variable,
|
||||||
|
pub field_var: Variable,
|
||||||
|
pub field: Lowercase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccessorData {
|
||||||
|
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
|
||||||
|
let AccessorData {
|
||||||
|
name,
|
||||||
|
function_var,
|
||||||
|
record_var,
|
||||||
|
closure_var,
|
||||||
|
closure_ext_var,
|
||||||
|
ext_var,
|
||||||
|
field_var,
|
||||||
|
field,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
// IDEA: convert accessor from
|
||||||
|
//
|
||||||
|
// .foo
|
||||||
|
//
|
||||||
|
// into
|
||||||
|
//
|
||||||
|
// (\r -> r.foo)
|
||||||
|
let body = Expr::Access {
|
||||||
|
record_var,
|
||||||
|
ext_var,
|
||||||
|
field_var,
|
||||||
|
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol))),
|
||||||
|
field,
|
||||||
|
};
|
||||||
|
|
||||||
|
let loc_body = Loc::at_zero(body);
|
||||||
|
|
||||||
|
let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))];
|
||||||
|
|
||||||
|
ClosureData {
|
||||||
|
function_type: function_var,
|
||||||
|
closure_type: closure_var,
|
||||||
|
closure_ext_var,
|
||||||
|
return_type: field_var,
|
||||||
|
name,
|
||||||
|
captured_symbols: vec![],
|
||||||
|
recursive: Recursive::NotRecursive,
|
||||||
|
arguments,
|
||||||
|
loc_body: Box::new(loc_body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub var: Variable,
|
pub var: Variable,
|
||||||
|
@ -735,15 +789,16 @@ pub fn canonicalize_expr<'a>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::AccessorFunction(field) => (
|
ast::Expr::AccessorFunction(field) => (
|
||||||
Accessor {
|
Accessor(AccessorData {
|
||||||
name: env.gen_unique_symbol(),
|
name: env.gen_unique_symbol(),
|
||||||
function_var: var_store.fresh(),
|
function_var: var_store.fresh(),
|
||||||
record_var: var_store.fresh(),
|
record_var: var_store.fresh(),
|
||||||
ext_var: var_store.fresh(),
|
ext_var: var_store.fresh(),
|
||||||
|
closure_var: var_store.fresh(),
|
||||||
closure_ext_var: var_store.fresh(),
|
closure_ext_var: var_store.fresh(),
|
||||||
field_var: var_store.fresh(),
|
field_var: var_store.fresh(),
|
||||||
field: (*field).into(),
|
field: (*field).into(),
|
||||||
},
|
}),
|
||||||
Output::default(),
|
Output::default(),
|
||||||
),
|
),
|
||||||
ast::Expr::GlobalTag(tag) => {
|
ast::Expr::GlobalTag(tag) => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use roc_can::def::{Declaration, Def};
|
||||||
use roc_can::expected::Expected::{self, *};
|
use roc_can::expected::Expected::{self, *};
|
||||||
use roc_can::expected::PExpected;
|
use roc_can::expected::PExpected;
|
||||||
use roc_can::expr::Expr::{self, *};
|
use roc_can::expr::Expr::{self, *};
|
||||||
use roc_can::expr::{ClosureData, Field, WhenBranch};
|
use roc_can::expr::{AccessorData, ClosureData, Field, WhenBranch};
|
||||||
use roc_can::pattern::Pattern;
|
use roc_can::pattern::Pattern;
|
||||||
use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap};
|
use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
|
@ -762,15 +762,16 @@ pub fn constrain_expr(
|
||||||
[constraint, eq, record_con],
|
[constraint, eq, record_con],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Accessor {
|
Accessor(AccessorData {
|
||||||
name: closure_name,
|
name: closure_name,
|
||||||
function_var,
|
function_var,
|
||||||
field,
|
field,
|
||||||
record_var,
|
record_var,
|
||||||
closure_ext_var: closure_var,
|
closure_var,
|
||||||
|
closure_ext_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
field_var,
|
field_var,
|
||||||
} => {
|
}) => {
|
||||||
let ext_var = *ext_var;
|
let ext_var = *ext_var;
|
||||||
let ext_type = Variable(ext_var);
|
let ext_type = Variable(ext_var);
|
||||||
let field_var = *field_var;
|
let field_var = *field_var;
|
||||||
|
@ -793,16 +794,24 @@ pub fn constrain_expr(
|
||||||
|
|
||||||
let lambda_set = Type::ClosureTag {
|
let lambda_set = Type::ClosureTag {
|
||||||
name: *closure_name,
|
name: *closure_name,
|
||||||
ext: *closure_var,
|
ext: *closure_ext_var,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let closure_type = Type::Variable(*closure_var);
|
||||||
|
|
||||||
let function_type = Type::Function(
|
let function_type = Type::Function(
|
||||||
vec![record_type],
|
vec![record_type],
|
||||||
Box::new(lambda_set),
|
Box::new(closure_type.clone()),
|
||||||
Box::new(field_type),
|
Box::new(field_type),
|
||||||
);
|
);
|
||||||
|
|
||||||
let cons = [
|
let cons = [
|
||||||
|
constraints.equal_types(
|
||||||
|
closure_type,
|
||||||
|
NoExpectation(lambda_set),
|
||||||
|
category.clone(),
|
||||||
|
region,
|
||||||
|
),
|
||||||
constraints.equal_types(function_type.clone(), expected, category.clone(), region),
|
constraints.equal_types(function_type.clone(), expected, category.clone(), region),
|
||||||
constraints.equal_types(
|
constraints.equal_types(
|
||||||
function_type,
|
function_type,
|
||||||
|
@ -814,7 +823,14 @@ pub fn constrain_expr(
|
||||||
];
|
];
|
||||||
|
|
||||||
constraints.exists_many(
|
constraints.exists_many(
|
||||||
[*record_var, *function_var, *closure_var, field_var, ext_var],
|
[
|
||||||
|
*record_var,
|
||||||
|
*function_var,
|
||||||
|
*closure_var,
|
||||||
|
*closure_ext_var,
|
||||||
|
field_var,
|
||||||
|
ext_var,
|
||||||
|
],
|
||||||
cons,
|
cons,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3882,44 +3882,24 @@ pub fn with_hole<'a>(
|
||||||
stmt
|
stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
Accessor {
|
Accessor(accessor_data) => {
|
||||||
name,
|
let field_var = accessor_data.field_var;
|
||||||
function_var,
|
let fresh_record_symbol = env.unique_symbol();
|
||||||
record_var,
|
|
||||||
closure_ext_var: _,
|
|
||||||
ext_var,
|
|
||||||
field_var,
|
|
||||||
field,
|
|
||||||
} => {
|
|
||||||
// IDEA: convert accessor fromt
|
|
||||||
//
|
|
||||||
// .foo
|
|
||||||
//
|
|
||||||
// into
|
|
||||||
//
|
|
||||||
// (\r -> r.foo)
|
|
||||||
let record_symbol = env.unique_symbol();
|
|
||||||
let body = roc_can::expr::Expr::Access {
|
|
||||||
record_var,
|
|
||||||
ext_var,
|
|
||||||
field_var,
|
|
||||||
loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))),
|
|
||||||
field,
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc_body = Loc::at_zero(body);
|
let ClosureData {
|
||||||
|
name,
|
||||||
let arguments = vec![(
|
function_type,
|
||||||
record_var,
|
arguments,
|
||||||
Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)),
|
loc_body,
|
||||||
)];
|
..
|
||||||
|
} = accessor_data.to_closure_data(fresh_record_symbol);
|
||||||
|
|
||||||
match procs.insert_anonymous(
|
match procs.insert_anonymous(
|
||||||
env,
|
env,
|
||||||
name,
|
name,
|
||||||
function_var,
|
function_type,
|
||||||
arguments,
|
arguments,
|
||||||
loc_body,
|
*loc_body,
|
||||||
CapturedSymbols::None,
|
CapturedSymbols::None,
|
||||||
field_var,
|
field_var,
|
||||||
layout_cache,
|
layout_cache,
|
||||||
|
@ -3927,7 +3907,7 @@ pub fn with_hole<'a>(
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let raw_layout = return_on_layout_error!(
|
let raw_layout = return_on_layout_error!(
|
||||||
env,
|
env,
|
||||||
layout_cache.raw_from_var(env.arena, function_var, env.subs)
|
layout_cache.raw_from_var(env.arena, function_type, env.subs)
|
||||||
);
|
);
|
||||||
|
|
||||||
match raw_layout {
|
match raw_layout {
|
||||||
|
@ -5445,6 +5425,18 @@ pub fn from_can<'a>(
|
||||||
|
|
||||||
return from_can(env, variable, cont.value, procs, layout_cache);
|
return from_can(env, variable, cont.value, procs, layout_cache);
|
||||||
}
|
}
|
||||||
|
roc_can::expr::Expr::Accessor(accessor_data) => {
|
||||||
|
let fresh_record_symbol = env.unique_symbol();
|
||||||
|
register_noncapturing_closure(
|
||||||
|
env,
|
||||||
|
procs,
|
||||||
|
layout_cache,
|
||||||
|
*symbol,
|
||||||
|
accessor_data.to_closure_data(fresh_record_symbol),
|
||||||
|
);
|
||||||
|
|
||||||
|
return from_can(env, variable, cont.value, procs, layout_cache);
|
||||||
|
}
|
||||||
roc_can::expr::Expr::Var(original) => {
|
roc_can::expr::Expr::Var(original) => {
|
||||||
// a variable is aliased, e.g.
|
// a variable is aliased, e.g.
|
||||||
//
|
//
|
||||||
|
|
|
@ -16,7 +16,6 @@ use roc_types::types::{
|
||||||
gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory,
|
gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory,
|
||||||
};
|
};
|
||||||
use roc_unify::unify::{unify, Mode, Unified::*};
|
use roc_unify::unify::{unify, Mode, Unified::*};
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
|
|
||||||
// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed
|
// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed
|
||||||
// https://github.com/elm/compiler
|
// https://github.com/elm/compiler
|
||||||
|
@ -858,14 +857,84 @@ fn type_to_var(
|
||||||
_: &mut MutMap<Symbol, Variable>,
|
_: &mut MutMap<Symbol, Variable>,
|
||||||
typ: &Type,
|
typ: &Type,
|
||||||
) -> Variable {
|
) -> Variable {
|
||||||
let mut arena = take_scratchpad();
|
if let Type::Variable(var) = typ {
|
||||||
|
*var
|
||||||
|
} else {
|
||||||
|
let mut arena = take_scratchpad();
|
||||||
|
|
||||||
let var = type_to_variable(subs, rank, pools, &arena, typ);
|
// let var = type_to_variable(subs, rank, pools, &arena, typ);
|
||||||
|
let var = type_to_variable(subs, rank, pools, &arena, typ);
|
||||||
|
|
||||||
arena.reset();
|
arena.reset();
|
||||||
put_scratchpad(arena);
|
put_scratchpad(arena);
|
||||||
|
|
||||||
var
|
var
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RegisterVariable {
|
||||||
|
/// Based on the Type, we already know what variable this will be
|
||||||
|
Direct(Variable),
|
||||||
|
/// This Type needs more complicated Content. We reserve a Variable
|
||||||
|
/// for it, but put a placeholder Content in subs
|
||||||
|
Deferred,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterVariable {
|
||||||
|
fn from_type(
|
||||||
|
subs: &mut Subs,
|
||||||
|
rank: Rank,
|
||||||
|
pools: &mut Pools,
|
||||||
|
arena: &'_ bumpalo::Bump,
|
||||||
|
typ: &Type,
|
||||||
|
) -> Self {
|
||||||
|
use RegisterVariable::*;
|
||||||
|
|
||||||
|
match typ {
|
||||||
|
Variable(var) => Direct(*var),
|
||||||
|
EmptyRec => Direct(Variable::EMPTY_RECORD),
|
||||||
|
EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION),
|
||||||
|
Type::Alias { symbol, .. } => {
|
||||||
|
if let Some(reserved) = Variable::get_reserved(*symbol) {
|
||||||
|
if rank.is_none() {
|
||||||
|
// reserved variables are stored with rank NONE
|
||||||
|
return Direct(reserved);
|
||||||
|
} else {
|
||||||
|
// for any other rank, we need to copy; it takes care of adjusting the rank
|
||||||
|
let copied = deep_copy_var_in(subs, rank, pools, reserved, arena);
|
||||||
|
return Direct(copied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deferred
|
||||||
|
}
|
||||||
|
_ => Deferred,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn with_stack<'a>(
|
||||||
|
subs: &mut Subs,
|
||||||
|
rank: Rank,
|
||||||
|
pools: &mut Pools,
|
||||||
|
arena: &'_ bumpalo::Bump,
|
||||||
|
typ: &'a Type,
|
||||||
|
stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>,
|
||||||
|
) -> Variable {
|
||||||
|
match Self::from_type(subs, rank, pools, arena, typ) {
|
||||||
|
Self::Direct(var) => var,
|
||||||
|
Self::Deferred => {
|
||||||
|
let var = subs.fresh_unnamed_flex_var();
|
||||||
|
stack.push(TypeToVar::Defer(typ, var));
|
||||||
|
var
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum TypeToVar<'a> {
|
||||||
|
Defer(&'a Type, Variable),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_to_variable<'a>(
|
fn type_to_variable<'a>(
|
||||||
|
@ -877,256 +946,276 @@ fn type_to_variable<'a>(
|
||||||
) -> Variable {
|
) -> Variable {
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
|
|
||||||
match typ {
|
let mut stack = Vec::with_capacity_in(8, arena);
|
||||||
Variable(var) => *var,
|
|
||||||
RangedNumber(typ, vars) => {
|
|
||||||
let ty_var = type_to_variable(subs, rank, pools, arena, typ);
|
|
||||||
let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied());
|
|
||||||
let content = Content::RangedNumber(ty_var, vars);
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
macro_rules! helper {
|
||||||
}
|
($typ:expr) => {{
|
||||||
Apply(symbol, arguments, _) => {
|
match RegisterVariable::from_type(subs, rank, pools, arena, $typ) {
|
||||||
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
RegisterVariable::Direct(var) => var,
|
||||||
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
RegisterVariable::Deferred => {
|
||||||
let var = type_to_variable(subs, rank, pools, arena, var_index);
|
let var = subs.fresh_unnamed_flex_var();
|
||||||
subs.variables[target_index] = var;
|
stack.push(TypeToVar::Defer($typ, var));
|
||||||
}
|
var
|
||||||
|
|
||||||
let flat_type = FlatType::Apply(*symbol, new_arguments);
|
|
||||||
let content = Content::Structure(flat_type);
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
|
||||||
}
|
|
||||||
EmptyRec => Variable::EMPTY_RECORD,
|
|
||||||
EmptyTagUnion => Variable::EMPTY_TAG_UNION,
|
|
||||||
|
|
||||||
ClosureTag { name, ext } => {
|
|
||||||
let tag_name = TagName::Closure(*name);
|
|
||||||
let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1);
|
|
||||||
|
|
||||||
subs.tag_names.push(tag_name);
|
|
||||||
|
|
||||||
// the first VariableSubsSlice in the array is a zero-length slice
|
|
||||||
let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1));
|
|
||||||
|
|
||||||
let content = Content::Structure(FlatType::TagUnion(union_tags, *ext));
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This case is important for the rank of boolean variables
|
|
||||||
Function(arguments, closure_type, ret_type) => {
|
|
||||||
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
|
||||||
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
|
||||||
let var = type_to_variable(subs, rank, pools, arena, var_index);
|
|
||||||
subs.variables[target_index] = var;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret_var = type_to_variable(subs, rank, pools, arena, ret_type);
|
|
||||||
let closure_var = type_to_variable(subs, rank, pools, arena, closure_type);
|
|
||||||
let content = Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var));
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
|
|
||||||
let mut field_vars = Vec::with_capacity_in(fields.len(), arena);
|
|
||||||
|
|
||||||
for (field, field_type) in fields {
|
|
||||||
let field_var =
|
|
||||||
field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ));
|
|
||||||
|
|
||||||
field_vars.push((field.clone(), field_var));
|
|
||||||
}
|
|
||||||
|
|
||||||
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
|
|
||||||
|
|
||||||
let (it, new_ext_var) =
|
|
||||||
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
|
|
||||||
.expect("Something ended up weird in this record type");
|
|
||||||
|
|
||||||
let it = it
|
|
||||||
.into_iter()
|
|
||||||
.map(|(field, field_type)| (field.clone(), field_type));
|
|
||||||
|
|
||||||
field_vars.extend(it);
|
|
||||||
insertion_sort_by(&mut field_vars, RecordFields::compare);
|
|
||||||
|
|
||||||
let record_fields = RecordFields::insert_into_subs(subs, field_vars);
|
|
||||||
|
|
||||||
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
|
|
||||||
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
|
|
||||||
let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
|
||||||
}
|
|
||||||
FunctionOrTagUnion(tag_name, symbol, ext) => {
|
|
||||||
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
|
|
||||||
|
|
||||||
let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
|
|
||||||
subs,
|
|
||||||
UnionTags::default(),
|
|
||||||
temp_ext_var,
|
|
||||||
);
|
|
||||||
|
|
||||||
for _ in it {
|
|
||||||
unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let slice = SubsIndex::new(subs.tag_names.len() as u32);
|
|
||||||
subs.tag_names.push(tag_name.clone());
|
|
||||||
|
|
||||||
let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext));
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
|
|
||||||
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
|
|
||||||
let content =
|
|
||||||
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext));
|
|
||||||
|
|
||||||
let tag_union_var = register(subs, rank, pools, content);
|
|
||||||
|
|
||||||
register_with_known_var(
|
|
||||||
subs,
|
|
||||||
*rec_var,
|
|
||||||
rank,
|
|
||||||
pools,
|
|
||||||
Content::RecursionVar {
|
|
||||||
opt_name: None,
|
|
||||||
structure: tag_union_var,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tag_union_var
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::Alias {
|
|
||||||
symbol,
|
|
||||||
type_arguments,
|
|
||||||
actual,
|
|
||||||
lambda_set_variables,
|
|
||||||
kind,
|
|
||||||
} => {
|
|
||||||
if let Some(reserved) = Variable::get_reserved(*symbol) {
|
|
||||||
if rank.is_none() {
|
|
||||||
// reserved variables are stored with rank NONE
|
|
||||||
return reserved;
|
|
||||||
} else {
|
|
||||||
// for any other rank, we need to copy; it takes care of adjusting the rank
|
|
||||||
return deep_copy_var_in(subs, rank, pools, reserved, arena);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
let alias_variables = alias_to_var(
|
let result = helper!(typ);
|
||||||
subs,
|
|
||||||
rank,
|
|
||||||
pools,
|
|
||||||
arena,
|
|
||||||
type_arguments,
|
|
||||||
lambda_set_variables,
|
|
||||||
);
|
|
||||||
|
|
||||||
let alias_variable = if let Symbol::RESULT_RESULT = *symbol {
|
while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() {
|
||||||
roc_result_to_var(subs, rank, pools, arena, actual)
|
match typ {
|
||||||
} else {
|
Variable(_) | EmptyRec | EmptyTagUnion => {
|
||||||
type_to_variable(subs, rank, pools, arena, actual)
|
unreachable!("This variant should never be deferred!")
|
||||||
};
|
}
|
||||||
let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind);
|
RangedNumber(typ, vars) => {
|
||||||
|
let ty_var = helper!(typ);
|
||||||
|
let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied());
|
||||||
|
let content = Content::RangedNumber(ty_var, vars);
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
}
|
}
|
||||||
HostExposedAlias {
|
Apply(symbol, arguments, _) => {
|
||||||
name: symbol,
|
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||||
type_arguments,
|
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
||||||
actual: alias_type,
|
let var = helper!(var_index);
|
||||||
actual_var,
|
subs.variables[target_index] = var;
|
||||||
lambda_set_variables,
|
}
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let alias_variables = alias_to_var(
|
|
||||||
subs,
|
|
||||||
rank,
|
|
||||||
pools,
|
|
||||||
arena,
|
|
||||||
type_arguments,
|
|
||||||
lambda_set_variables,
|
|
||||||
);
|
|
||||||
|
|
||||||
let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type);
|
let flat_type = FlatType::Apply(*symbol, new_arguments);
|
||||||
// TODO(opaques): I think host-exposed aliases should always be structural
|
let content = Content::Structure(flat_type);
|
||||||
// (when does it make sense to give a host an opaque type?)
|
|
||||||
let content = Content::Alias(
|
|
||||||
*symbol,
|
|
||||||
alias_variables,
|
|
||||||
alias_variable,
|
|
||||||
AliasKind::Structural,
|
|
||||||
);
|
|
||||||
let result = register(subs, rank, pools, content);
|
|
||||||
|
|
||||||
// We only want to unify the actual_var with the alias once
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
// if it's already redirected (and therefore, redundant)
|
|
||||||
// don't do it again
|
|
||||||
if !subs.redundant(*actual_var) {
|
|
||||||
let descriptor = subs.get(result);
|
|
||||||
subs.union(result, *actual_var, descriptor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
ClosureTag { name, ext } => {
|
||||||
}
|
let tag_name = TagName::Closure(*name);
|
||||||
Erroneous(problem) => {
|
let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1);
|
||||||
let problem_index = SubsIndex::push_new(&mut subs.problems, problem.clone());
|
|
||||||
let content = Content::Structure(FlatType::Erroneous(problem_index));
|
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
subs.tag_names.push(tag_name);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
// the first VariableSubsSlice in the array is a zero-length slice
|
||||||
fn alias_to_var<'a>(
|
let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1));
|
||||||
subs: &mut Subs,
|
|
||||||
rank: Rank,
|
|
||||||
pools: &mut Pools,
|
|
||||||
arena: &'a bumpalo::Bump,
|
|
||||||
type_arguments: &[(roc_module::ident::Lowercase, Type)],
|
|
||||||
lambda_set_variables: &[roc_types::types::LambdaSet],
|
|
||||||
) -> AliasVariables {
|
|
||||||
let length = type_arguments.len() + lambda_set_variables.len();
|
|
||||||
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
|
|
||||||
|
|
||||||
for (target_index, (_, arg_type)) in (new_variables.indices()).zip(type_arguments) {
|
let content = Content::Structure(FlatType::TagUnion(union_tags, *ext));
|
||||||
let copy_var = type_to_variable(subs, rank, pools, arena, arg_type);
|
|
||||||
subs.variables[target_index] = copy_var;
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
// This case is important for the rank of boolean variables
|
||||||
|
Function(arguments, closure_type, ret_type) => {
|
||||||
|
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||||
|
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
||||||
|
let var = helper!(var_index);
|
||||||
|
subs.variables[target_index] = var;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret_var = helper!(ret_type);
|
||||||
|
let closure_var = helper!(closure_type);
|
||||||
|
let content =
|
||||||
|
Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var));
|
||||||
|
|
||||||
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
|
||||||
|
let mut field_vars = Vec::with_capacity_in(fields.len(), arena);
|
||||||
|
|
||||||
|
for (field, field_type) in fields {
|
||||||
|
let field_var = {
|
||||||
|
use roc_types::types::RecordField::*;
|
||||||
|
match &field_type {
|
||||||
|
Optional(t) => Optional(helper!(t)),
|
||||||
|
Required(t) => Required(helper!(t)),
|
||||||
|
Demanded(t) => Demanded(helper!(t)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
field_vars.push((field.clone(), field_var));
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp_ext_var = helper!(ext);
|
||||||
|
|
||||||
|
let (it, new_ext_var) =
|
||||||
|
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
|
||||||
|
.expect("Something ended up weird in this record type");
|
||||||
|
|
||||||
|
let it = it
|
||||||
|
.into_iter()
|
||||||
|
.map(|(field, field_type)| (field.clone(), field_type));
|
||||||
|
|
||||||
|
field_vars.extend(it);
|
||||||
|
insertion_sort_by(&mut field_vars, RecordFields::compare);
|
||||||
|
|
||||||
|
let record_fields = RecordFields::insert_into_subs(subs, field_vars);
|
||||||
|
|
||||||
|
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
|
||||||
|
|
||||||
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
let (union_tags, ext) =
|
||||||
|
type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack);
|
||||||
|
let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
|
||||||
|
|
||||||
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
FunctionOrTagUnion(tag_name, symbol, ext) => {
|
||||||
|
let temp_ext_var = helper!(ext);
|
||||||
|
|
||||||
|
let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
|
||||||
|
subs,
|
||||||
|
UnionTags::default(),
|
||||||
|
temp_ext_var,
|
||||||
|
);
|
||||||
|
|
||||||
|
for _ in it {
|
||||||
|
unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let slice = SubsIndex::new(subs.tag_names.len() as u32);
|
||||||
|
subs.tag_names.push(tag_name.clone());
|
||||||
|
|
||||||
|
let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext));
|
||||||
|
|
||||||
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
|
||||||
|
let (union_tags, ext) =
|
||||||
|
type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack);
|
||||||
|
let content =
|
||||||
|
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext));
|
||||||
|
|
||||||
|
let tag_union_var = destination;
|
||||||
|
register_with_known_var(subs, tag_union_var, rank, pools, content);
|
||||||
|
|
||||||
|
register_with_known_var(
|
||||||
|
subs,
|
||||||
|
*rec_var,
|
||||||
|
rank,
|
||||||
|
pools,
|
||||||
|
Content::RecursionVar {
|
||||||
|
opt_name: None,
|
||||||
|
structure: tag_union_var,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
tag_union_var
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Alias {
|
||||||
|
symbol,
|
||||||
|
type_arguments,
|
||||||
|
actual,
|
||||||
|
lambda_set_variables,
|
||||||
|
kind,
|
||||||
|
} => {
|
||||||
|
debug_assert!(Variable::get_reserved(*symbol).is_none());
|
||||||
|
|
||||||
|
let alias_variables = {
|
||||||
|
let length = type_arguments.len() + lambda_set_variables.len();
|
||||||
|
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
|
||||||
|
|
||||||
|
for (target_index, (_, arg_type)) in
|
||||||
|
(new_variables.indices()).zip(type_arguments)
|
||||||
|
{
|
||||||
|
let copy_var = helper!(arg_type);
|
||||||
|
subs.variables[target_index] = copy_var;
|
||||||
|
}
|
||||||
|
|
||||||
|
let it = (new_variables.indices().skip(type_arguments.len()))
|
||||||
|
.zip(lambda_set_variables);
|
||||||
|
for (target_index, ls) in it {
|
||||||
|
let copy_var = helper!(&ls.0);
|
||||||
|
subs.variables[target_index] = copy_var;
|
||||||
|
}
|
||||||
|
|
||||||
|
AliasVariables {
|
||||||
|
variables_start: new_variables.start,
|
||||||
|
type_variables_len: type_arguments.len() as _,
|
||||||
|
all_variables_len: length as _,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let alias_variable = if let Symbol::RESULT_RESULT = *symbol {
|
||||||
|
roc_result_to_var(subs, rank, pools, arena, actual, &mut stack)
|
||||||
|
} else {
|
||||||
|
helper!(actual)
|
||||||
|
};
|
||||||
|
let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind);
|
||||||
|
|
||||||
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
HostExposedAlias {
|
||||||
|
name: symbol,
|
||||||
|
type_arguments,
|
||||||
|
actual: alias_type,
|
||||||
|
actual_var,
|
||||||
|
lambda_set_variables,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let alias_variables = {
|
||||||
|
let length = type_arguments.len() + lambda_set_variables.len();
|
||||||
|
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
|
||||||
|
|
||||||
|
for (target_index, (_, arg_type)) in
|
||||||
|
(new_variables.indices()).zip(type_arguments)
|
||||||
|
{
|
||||||
|
let copy_var = helper!(arg_type);
|
||||||
|
subs.variables[target_index] = copy_var;
|
||||||
|
}
|
||||||
|
|
||||||
|
AliasVariables {
|
||||||
|
variables_start: new_variables.start,
|
||||||
|
type_variables_len: type_arguments.len() as _,
|
||||||
|
all_variables_len: length as _,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// cannot use helper! here because this variable may be involved in unification below
|
||||||
|
let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type);
|
||||||
|
// TODO(opaques): I think host-exposed aliases should always be structural
|
||||||
|
// (when does it make sense to give a host an opaque type?)
|
||||||
|
let content = Content::Alias(
|
||||||
|
*symbol,
|
||||||
|
alias_variables,
|
||||||
|
alias_variable,
|
||||||
|
AliasKind::Structural,
|
||||||
|
);
|
||||||
|
// let result = register(subs, rank, pools, content);
|
||||||
|
let result = register_with_known_var(subs, destination, rank, pools, content);
|
||||||
|
|
||||||
|
// We only want to unify the actual_var with the alias once
|
||||||
|
// if it's already redirected (and therefore, redundant)
|
||||||
|
// don't do it again
|
||||||
|
if !subs.redundant(*actual_var) {
|
||||||
|
let descriptor = subs.get(result);
|
||||||
|
subs.union(result, *actual_var, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Erroneous(problem) => {
|
||||||
|
let problem_index = SubsIndex::push_new(&mut subs.problems, problem.clone());
|
||||||
|
let content = Content::Structure(FlatType::Erroneous(problem_index));
|
||||||
|
|
||||||
|
register_with_known_var(subs, destination, rank, pools, content)
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let it = (new_variables.indices().skip(type_arguments.len())).zip(lambda_set_variables);
|
result
|
||||||
for (target_index, ls) in it {
|
|
||||||
let copy_var = type_to_variable(subs, rank, pools, arena, &ls.0);
|
|
||||||
subs.variables[target_index] = copy_var;
|
|
||||||
}
|
|
||||||
|
|
||||||
AliasVariables {
|
|
||||||
variables_start: new_variables.start,
|
|
||||||
type_variables_len: type_arguments.len() as _,
|
|
||||||
all_variables_len: length as _,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -1134,8 +1223,9 @@ fn roc_result_to_var<'a>(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
rank: Rank,
|
rank: Rank,
|
||||||
pools: &mut Pools,
|
pools: &mut Pools,
|
||||||
arena: &'a bumpalo::Bump,
|
arena: &'_ bumpalo::Bump,
|
||||||
result_type: &Type,
|
result_type: &'a Type,
|
||||||
|
stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>,
|
||||||
) -> Variable {
|
) -> Variable {
|
||||||
match result_type {
|
match result_type {
|
||||||
Type::TagUnion(tags, ext) => {
|
Type::TagUnion(tags, ext) => {
|
||||||
|
@ -1147,8 +1237,10 @@ fn roc_result_to_var<'a>(
|
||||||
debug_assert_eq!(ok, &subs.tag_names[1]);
|
debug_assert_eq!(ok, &subs.tag_names[1]);
|
||||||
|
|
||||||
if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) {
|
if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) {
|
||||||
let err_var = type_to_variable(subs, rank, pools, arena, err_type);
|
let err_var =
|
||||||
let ok_var = type_to_variable(subs, rank, pools, arena, ok_type);
|
RegisterVariable::with_stack(subs, rank, pools, arena, err_type, stack);
|
||||||
|
let ok_var =
|
||||||
|
RegisterVariable::with_stack(subs, rank, pools, arena, ok_type, stack);
|
||||||
|
|
||||||
let start = subs.variables.len() as u32;
|
let start = subs.variables.len() as u32;
|
||||||
let err_slice = SubsSlice::new(start, 1);
|
let err_slice = SubsSlice::new(start, 1);
|
||||||
|
@ -1239,16 +1331,16 @@ fn sort_and_deduplicate<T>(tag_vars: &mut bumpalo::collections::Vec<(TagName, T)
|
||||||
fn find_tag_name_run<T>(slice: &[(TagName, T)], subs: &mut Subs) -> Option<SubsSlice<TagName>> {
|
fn find_tag_name_run<T>(slice: &[(TagName, T)], subs: &mut Subs) -> Option<SubsSlice<TagName>> {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
let tag_name = slice.get(0)?.0.clone();
|
let tag_name = &slice.get(0)?.0;
|
||||||
|
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
|
|
||||||
// the `SubsSlice<TagName>` that inserting `slice` into subs would give
|
// the `SubsSlice<TagName>` that inserting `slice` into subs would give
|
||||||
let bigger_slice = SubsSlice::new(subs.tag_names.len() as _, slice.len() as _);
|
let bigger_slice = SubsSlice::new(subs.tag_names.len() as _, slice.len() as _);
|
||||||
|
|
||||||
match subs.tag_name_cache.entry(tag_name) {
|
match subs.tag_name_cache.get_mut(tag_name) {
|
||||||
Entry::Occupied(mut occupied) => {
|
Some(occupied) => {
|
||||||
let subs_slice = *occupied.get();
|
let subs_slice = *occupied;
|
||||||
|
|
||||||
let prefix_slice = SubsSlice::new(subs_slice.start, slice.len() as _);
|
let prefix_slice = SubsSlice::new(subs_slice.start, slice.len() as _);
|
||||||
|
|
||||||
|
@ -1282,12 +1374,12 @@ fn find_tag_name_run<T>(slice: &[(TagName, T)], subs: &mut Subs) -> Option<SubsS
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
// switch to the bigger slice that is not inserted yet, but will be soon
|
// switch to the bigger slice that is not inserted yet, but will be soon
|
||||||
occupied.insert(bigger_slice);
|
*occupied = bigger_slice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Entry::Vacant(vacant) => {
|
None => {
|
||||||
vacant.insert(bigger_slice);
|
subs.tag_name_cache.push(tag_name, bigger_slice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1299,8 +1391,9 @@ fn insert_tags_fast_path<'a>(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
rank: Rank,
|
rank: Rank,
|
||||||
pools: &mut Pools,
|
pools: &mut Pools,
|
||||||
arena: &'a bumpalo::Bump,
|
arena: &'_ bumpalo::Bump,
|
||||||
tags: &[(TagName, Vec<Type>)],
|
tags: &'a [(TagName, Vec<Type>)],
|
||||||
|
stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>,
|
||||||
) -> UnionTags {
|
) -> UnionTags {
|
||||||
let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len());
|
let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len());
|
||||||
|
|
||||||
|
@ -1313,7 +1406,8 @@ fn insert_tags_fast_path<'a>(
|
||||||
let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||||
let it = (new_variables.indices()).zip(arguments);
|
let it = (new_variables.indices()).zip(arguments);
|
||||||
for (target_index, argument) in it {
|
for (target_index, argument) in it {
|
||||||
let var = type_to_variable(subs, rank, pools, arena, argument);
|
let var =
|
||||||
|
RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack);
|
||||||
subs.variables[target_index] = var;
|
subs.variables[target_index] = var;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1334,7 +1428,8 @@ fn insert_tags_fast_path<'a>(
|
||||||
let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||||
let it = (new_variables.indices()).zip(arguments);
|
let it = (new_variables.indices()).zip(arguments);
|
||||||
for (target_index, argument) in it {
|
for (target_index, argument) in it {
|
||||||
let var = type_to_variable(subs, rank, pools, arena, argument);
|
let var =
|
||||||
|
RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack);
|
||||||
subs.variables[target_index] = var;
|
subs.variables[target_index] = var;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1351,15 +1446,16 @@ fn insert_tags_slow_path<'a>(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
rank: Rank,
|
rank: Rank,
|
||||||
pools: &mut Pools,
|
pools: &mut Pools,
|
||||||
arena: &'a bumpalo::Bump,
|
arena: &'_ bumpalo::Bump,
|
||||||
tags: &[(TagName, Vec<Type>)],
|
tags: &'a [(TagName, Vec<Type>)],
|
||||||
mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>,
|
mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>,
|
||||||
|
stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>,
|
||||||
) -> UnionTags {
|
) -> UnionTags {
|
||||||
for (tag, tag_argument_types) in tags {
|
for (tag, tag_argument_types) in tags {
|
||||||
let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len());
|
let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len());
|
||||||
|
|
||||||
for (i, arg) in (new_slice.indices()).zip(tag_argument_types) {
|
for (i, arg) in (new_slice.indices()).zip(tag_argument_types) {
|
||||||
let var = type_to_variable(subs, rank, pools, arena, arg);
|
let var = RegisterVariable::with_stack(subs, rank, pools, arena, arg, stack);
|
||||||
subs.variables[i] = var;
|
subs.variables[i] = var;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1375,39 +1471,39 @@ fn type_to_union_tags<'a>(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
rank: Rank,
|
rank: Rank,
|
||||||
pools: &mut Pools,
|
pools: &mut Pools,
|
||||||
arena: &'a bumpalo::Bump,
|
arena: &'_ bumpalo::Bump,
|
||||||
tags: &[(TagName, Vec<Type>)],
|
tags: &'a [(TagName, Vec<Type>)],
|
||||||
ext: &Type,
|
ext: &'a Type,
|
||||||
|
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() {
|
if ext.is_empty_tag_union() {
|
||||||
let ext = type_to_variable(subs, rank, pools, arena, &Type::EmptyTagUnion);
|
let ext = Variable::EMPTY_TAG_UNION;
|
||||||
// let ext = Variable::EMPTY_TAG_UNION;
|
|
||||||
|
|
||||||
let union_tags = if sorted {
|
let union_tags = if sorted {
|
||||||
insert_tags_fast_path(subs, rank, pools, arena, tags)
|
insert_tags_fast_path(subs, rank, pools, arena, tags, stack)
|
||||||
} else {
|
} else {
|
||||||
let tag_vars = Vec::with_capacity_in(tags.len(), arena);
|
let tag_vars = Vec::with_capacity_in(tags.len(), arena);
|
||||||
insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars)
|
insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack)
|
||||||
};
|
};
|
||||||
|
|
||||||
(union_tags, ext)
|
(union_tags, ext)
|
||||||
} else {
|
} else {
|
||||||
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 = type_to_variable(subs, rank, pools, arena, ext);
|
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(subs, UnionTags::default(), temp_ext_var);
|
roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var);
|
||||||
|
|
||||||
tag_vars.extend(it.map(|(n, v)| (n.clone(), v)));
|
tag_vars.extend(it.map(|(n, v)| (n.clone(), v)));
|
||||||
|
|
||||||
let union_tags = if tag_vars.is_empty() && sorted {
|
let union_tags = if tag_vars.is_empty() && sorted {
|
||||||
insert_tags_fast_path(subs, rank, pools, arena, tags)
|
insert_tags_fast_path(subs, rank, pools, arena, tags, stack)
|
||||||
} else {
|
} else {
|
||||||
insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars)
|
insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack)
|
||||||
};
|
};
|
||||||
|
|
||||||
(union_tags, ext)
|
(union_tags, ext)
|
||||||
|
@ -2178,7 +2274,7 @@ fn register_with_known_var(
|
||||||
rank: Rank,
|
rank: Rank,
|
||||||
pools: &mut Pools,
|
pools: &mut Pools,
|
||||||
content: Content,
|
content: Content,
|
||||||
) {
|
) -> Variable {
|
||||||
let descriptor = Descriptor {
|
let descriptor = Descriptor {
|
||||||
content,
|
content,
|
||||||
rank,
|
rank,
|
||||||
|
@ -2189,4 +2285,6 @@ fn register_with_known_var(
|
||||||
subs.set(var, descriptor);
|
subs.set(var, descriptor);
|
||||||
|
|
||||||
pools.get_mut(rank).push(var);
|
pools.get_mut(rank).push(var);
|
||||||
|
|
||||||
|
var
|
||||||
}
|
}
|
||||||
|
|
|
@ -5529,4 +5529,18 @@ mod solve_expr {
|
||||||
r#"a -> Effect a"#,
|
r#"a -> Effect a"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generalized_accessor_function_applied() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
returnFoo = .foo
|
||||||
|
|
||||||
|
returnFoo { foo: "foo" }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Str",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::helpers::wasm::assert_evals_to;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use roc_std::RocList;
|
use roc_std::{RocList, RocStr};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
|
@ -1058,3 +1058,19 @@ fn call_with_bad_record_runtime_error() {
|
||||||
"#
|
"#
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
|
fn generalized_accessor() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
returnFoo = .foo
|
||||||
|
|
||||||
|
returnFoo { foo: "foo" }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RocStr::from("foo"),
|
||||||
|
RocStr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt};
|
use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt};
|
||||||
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
use roc_collections::all::{ImMap, ImSet, MutSet, SendMap};
|
||||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -68,10 +68,52 @@ pub struct Subs {
|
||||||
pub field_names: Vec<Lowercase>,
|
pub field_names: Vec<Lowercase>,
|
||||||
pub record_fields: Vec<RecordField<()>>,
|
pub record_fields: Vec<RecordField<()>>,
|
||||||
pub variable_slices: Vec<VariableSubsSlice>,
|
pub variable_slices: Vec<VariableSubsSlice>,
|
||||||
pub tag_name_cache: MutMap<TagName, SubsSlice<TagName>>,
|
pub tag_name_cache: TagNameCache,
|
||||||
pub problems: Vec<Problem>,
|
pub problems: Vec<Problem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct TagNameCache {
|
||||||
|
globals: Vec<Uppercase>,
|
||||||
|
globals_slices: Vec<SubsSlice<TagName>>,
|
||||||
|
/// Currently private tags and closure tags; in the future just closure tags
|
||||||
|
symbols: Vec<Symbol>,
|
||||||
|
symbols_slices: Vec<SubsSlice<TagName>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TagNameCache {
|
||||||
|
pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice<TagName>> {
|
||||||
|
match tag_name {
|
||||||
|
TagName::Global(uppercase) => {
|
||||||
|
// force into block
|
||||||
|
match self.globals.iter().position(|u| u == uppercase) {
|
||||||
|
Some(index) => Some(&mut self.globals_slices[index]),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TagName::Private(symbol) | TagName::Closure(symbol) => {
|
||||||
|
match self.symbols.iter().position(|s| s == symbol) {
|
||||||
|
Some(index) => Some(&mut self.symbols_slices[index]),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice<TagName>) {
|
||||||
|
match tag_name {
|
||||||
|
TagName::Global(uppercase) => {
|
||||||
|
self.globals.push(uppercase.clone());
|
||||||
|
self.globals_slices.push(slice);
|
||||||
|
}
|
||||||
|
TagName::Private(symbol) | TagName::Closure(symbol) => {
|
||||||
|
self.symbols.push(*symbol);
|
||||||
|
self.symbols_slices.push(slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Subs {
|
impl Default for Subs {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Subs::new()
|
Subs::new()
|
||||||
|
@ -1257,7 +1299,7 @@ impl Subs {
|
||||||
// store an empty slice at the first position
|
// store an empty slice at the first position
|
||||||
// used for "TagOrFunction"
|
// used for "TagOrFunction"
|
||||||
variable_slices: vec![VariableSubsSlice::default()],
|
variable_slices: vec![VariableSubsSlice::default()],
|
||||||
tag_name_cache: MutMap::default(),
|
tag_name_cache: TagNameCache::default(),
|
||||||
problems: Vec::new(),
|
problems: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue