Merge remote-tracking branch 'origin/trunk' into content-flattype-copy

This commit is contained in:
Folkert 2022-03-06 20:52:04 +01:00
commit ba2f9ba779
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
8 changed files with 564 additions and 331 deletions

View file

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

View file

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

View file

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

View file

@ -3882,44 +3882,24 @@ pub fn with_hole<'a>(
stmt stmt
} }
Accessor { Accessor(accessor_data) => {
let field_var = accessor_data.field_var;
let fresh_record_symbol = env.unique_symbol();
let ClosureData {
name, name,
function_var, function_type,
record_var, arguments,
closure_ext_var: _, loc_body,
ext_var, ..
field_var, } = accessor_data.to_closure_data(fresh_record_symbol);
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 arguments = vec![(
record_var,
Loc::at_zero(roc_can::pattern::Pattern::Identifier(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.
// //

View file

@ -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 {
if let Type::Variable(var) = typ {
*var
} else {
let mut arena = take_scratchpad(); 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,29 +946,47 @@ fn type_to_variable<'a>(
) -> Variable { ) -> Variable {
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
let mut stack = Vec::with_capacity_in(8, arena);
macro_rules! helper {
($typ:expr) => {{
match RegisterVariable::from_type(subs, rank, pools, arena, $typ) {
RegisterVariable::Direct(var) => var,
RegisterVariable::Deferred => {
let var = subs.fresh_unnamed_flex_var();
stack.push(TypeToVar::Defer($typ, var));
var
}
}
}};
}
let result = helper!(typ);
while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() {
match typ { match typ {
Variable(var) => *var, Variable(_) | EmptyRec | EmptyTagUnion => {
unreachable!("This variant should never be deferred!")
}
RangedNumber(typ, vars) => { RangedNumber(typ, vars) => {
let ty_var = type_to_variable(subs, rank, pools, arena, typ); let ty_var = helper!(typ);
let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied());
let content = Content::RangedNumber(ty_var, vars); let content = Content::RangedNumber(ty_var, vars);
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
Apply(symbol, arguments, _) => { Apply(symbol, arguments, _) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
let var = type_to_variable(subs, rank, pools, arena, var_index); let var = helper!(var_index);
subs.variables[target_index] = var; subs.variables[target_index] = var;
} }
let flat_type = FlatType::Apply(*symbol, new_arguments); let flat_type = FlatType::Apply(*symbol, new_arguments);
let content = Content::Structure(flat_type); let content = Content::Structure(flat_type);
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
EmptyRec => Variable::EMPTY_RECORD,
EmptyTagUnion => Variable::EMPTY_TAG_UNION,
ClosureTag { name, ext } => { ClosureTag { name, ext } => {
let tag_name = TagName::Closure(*name); let tag_name = TagName::Closure(*name);
@ -912,22 +999,22 @@ fn type_to_variable<'a>(
let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); let content = Content::Structure(FlatType::TagUnion(union_tags, *ext));
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
// This case is important for the rank of boolean variables // This case is important for the rank of boolean variables
Function(arguments, closure_type, ret_type) => { Function(arguments, closure_type, ret_type) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
let var = type_to_variable(subs, rank, pools, arena, var_index); let var = helper!(var_index);
subs.variables[target_index] = var; subs.variables[target_index] = var;
} }
let ret_var = type_to_variable(subs, rank, pools, arena, ret_type); let ret_var = helper!(ret_type);
let closure_var = type_to_variable(subs, rank, pools, arena, closure_type); let closure_var = helper!(closure_type);
let content = Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); let content =
Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var));
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
Record(fields, ext) => { Record(fields, ext) => {
// An empty fields is inefficient (but would be correct) // An empty fields is inefficient (but would be correct)
@ -937,13 +1024,19 @@ fn type_to_variable<'a>(
let mut field_vars = Vec::with_capacity_in(fields.len(), arena); let mut field_vars = Vec::with_capacity_in(fields.len(), arena);
for (field, field_type) in fields { for (field, field_type) in fields {
let field_var = let field_var = {
field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ)); 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)); field_vars.push((field.clone(), field_var));
} }
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); let temp_ext_var = helper!(ext);
let (it, new_ext_var) = let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
@ -960,20 +1053,22 @@ fn type_to_variable<'a>(
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
TagUnion(tags, ext) => { TagUnion(tags, ext) => {
// An empty tags is inefficient (but would be correct) // An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization // If hit, try to turn the value into an EmptyTagUnion in canonicalization
debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union());
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); 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)); let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
FunctionOrTagUnion(tag_name, symbol, ext) => { FunctionOrTagUnion(tag_name, symbol, ext) => {
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); let temp_ext_var = helper!(ext);
let (it, ext) = roc_types::types::gather_tags_unsorted_iter( let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
subs, subs,
@ -990,18 +1085,20 @@ fn type_to_variable<'a>(
let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext));
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
RecursiveTagUnion(rec_var, tags, ext) => { RecursiveTagUnion(rec_var, tags, ext) => {
// An empty tags is inefficient (but would be correct) // An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization // If hit, try to turn the value into an EmptyTagUnion in canonicalization
debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union());
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); let (union_tags, ext) =
type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack);
let content = let content =
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext));
let tag_union_var = register(subs, rank, pools, content); let tag_union_var = destination;
register_with_known_var(subs, tag_union_var, rank, pools, content);
register_with_known_var( register_with_known_var(
subs, subs,
@ -1024,33 +1121,41 @@ fn type_to_variable<'a>(
lambda_set_variables, lambda_set_variables,
kind, kind,
} => { } => {
if let Some(reserved) = Variable::get_reserved(*symbol) { debug_assert!(Variable::get_reserved(*symbol).is_none());
if rank.is_none() {
// reserved variables are stored with rank NONE let alias_variables = {
return reserved; let length = type_arguments.len() + lambda_set_variables.len();
} else { let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
// 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); 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 alias_variables = alias_to_var( let it = (new_variables.indices().skip(type_arguments.len()))
subs, .zip(lambda_set_variables);
rank, for (target_index, ls) in it {
pools, let copy_var = helper!(&ls.0);
arena, subs.variables[target_index] = copy_var;
type_arguments, }
lambda_set_variables,
); 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 { let alias_variable = if let Symbol::RESULT_RESULT = *symbol {
roc_result_to_var(subs, rank, pools, arena, actual) roc_result_to_var(subs, rank, pools, arena, actual, &mut stack)
} else { } else {
type_to_variable(subs, rank, pools, arena, actual) helper!(actual)
}; };
let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind);
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
HostExposedAlias { HostExposedAlias {
name: symbol, name: symbol,
@ -1060,15 +1165,25 @@ fn type_to_variable<'a>(
lambda_set_variables, lambda_set_variables,
.. ..
} => { } => {
let alias_variables = alias_to_var( let alias_variables = {
subs, let length = type_arguments.len() + lambda_set_variables.len();
rank, let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
pools,
arena,
type_arguments,
lambda_set_variables,
);
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); let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type);
// TODO(opaques): I think host-exposed aliases should always be structural // TODO(opaques): I think host-exposed aliases should always be structural
// (when does it make sense to give a host an opaque type?) // (when does it make sense to give a host an opaque type?)
@ -1078,7 +1193,8 @@ fn type_to_variable<'a>(
alias_variable, alias_variable,
AliasKind::Structural, AliasKind::Structural,
); );
let result = register(subs, rank, pools, content); // 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 // We only want to unify the actual_var with the alias once
// if it's already redirected (and therefore, redundant) // if it's already redirected (and therefore, redundant)
@ -1094,39 +1210,12 @@ fn type_to_variable<'a>(
let problem_index = SubsIndex::push_new(&mut subs.problems, problem.clone()); let problem_index = SubsIndex::push_new(&mut subs.problems, problem.clone());
let content = Content::Structure(FlatType::Erroneous(problem_index)); let content = Content::Structure(FlatType::Erroneous(problem_index));
register(subs, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
} };
}
#[inline(always)]
fn alias_to_var<'a>(
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 copy_var = type_to_variable(subs, rank, pools, arena, arg_type);
subs.variables[target_index] = copy_var;
} }
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
} }

View file

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

View file

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

View file

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