Link ambient functions of alias lambda set variables

This commit is contained in:
Ayaz Hafiz 2022-07-05 11:05:39 -04:00
parent 5d74a376af
commit 918e808aff
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
3 changed files with 161 additions and 51 deletions

View file

@ -952,11 +952,8 @@ fn resolve_abilities<'a>(
let signature = {
let mut signature = member_annot.typ;
signature.instantiate_lambda_sets_as_unspecialized(
var_store,
var_bound_to_ability,
member_sym,
);
signature
.instantiate_lambda_sets_as_unspecialized(var_bound_to_ability, member_sym);
signature
};

View file

@ -21,9 +21,9 @@ use roc_problem::can::CycleEntry;
use roc_region::all::{Loc, Region};
use roc_types::solved_types::Solved;
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, LambdaSet, Mark, OptVariable, Rank,
RecordFields, Subs, SubsIndex, SubsSlice, UlsOfVar, UnionLabels, UnionLambdas, UnionTags,
Variable, VariableSubsSlice,
self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, LambdaSet, Mark,
OptVariable, Rank, RecordFields, Subs, SubsIndex, SubsSlice, UlsOfVar, UnionLabels,
UnionLambdas, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::Type::{self, *};
use roc_types::types::{
@ -375,7 +375,16 @@ impl Aliases {
// assumption: an alias does not (transitively) syntactically contain itself
// (if it did it would have to be a recursive tag union, which we should have fixed up
// during canonicalization)
let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t);
let alias_variable = type_to_variable(
subs,
rank,
pools,
arena,
self,
&t,
false,
alias_variables.lambda_set_variables(),
);
{
match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) {
@ -2059,7 +2068,16 @@ pub(crate) fn type_to_var(
} else {
let mut arena = take_scratchpad();
let var = type_to_variable(subs, rank, pools, &arena, aliases, typ);
let var = type_to_variable(
subs,
rank,
pools,
&arena,
aliases,
typ,
false,
SubsSlice::default(),
);
arena.reset();
put_scratchpad(arena);
@ -2135,16 +2153,67 @@ impl RegisterVariable {
Self::Direct(var) => var,
Self::Deferred => {
let var = subs.fresh_unnamed_flex_var();
stack.push(TypeToVar::Defer(typ, var));
stack.push(TypeToVar::Defer {
typ,
destination: var,
ambient_function: AmbientFunctionPolicy::NoFunction,
});
var
}
}
}
}
/// Instantiation of ambient functions in unspecialized lambda sets is somewhat tricky due to other
/// optimizations we have in place. This struct tells us how they should be instantiated.
#[derive(Debug)]
enum AmbientFunctionPolicy {
/// We're not in a function. This variant may never hold for unspecialized lambda sets.
NoFunction,
/// We're in a known function.
Function(Variable),
}
impl AmbientFunctionPolicy {
fn link_to_alias_lambda_set_var(&self, subs: &mut Subs, var: Variable) {
let ambient_function = match self {
AmbientFunctionPolicy::Function(var) => *var,
_ => internal_error!("Not a function, can't link the var"),
};
let content = subs.get_content_without_compacting(var);
let new_content = match content {
Content::LambdaSet(LambdaSet {
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => Content::LambdaSet(LambdaSet {
solved: *solved,
recursion_var: *recursion_var,
unspecialized: *unspecialized,
ambient_function,
}),
Content::FlexVar(_) => {
// Something like
// Encoder fmt <a> : List U8, fmt -a-> List U8 | fmt has EncoderFormatting
// THEORY: Just allow this, it's fine, because the lambda set is unbound,
// but it's not part of an ability signature, so it doesn't have an unspecialized
// lambda set, and hence doesn't need a link to the ambient function.
*content
}
content => internal_error!("{:?}({:?}) not a lambda set", content, var),
};
subs.set_content_unchecked(var, new_content);
}
}
#[derive(Debug)]
enum TypeToVar<'a> {
Defer(&'a Type, Variable),
Defer {
typ: &'a Type,
destination: Variable,
ambient_function: AmbientFunctionPolicy,
},
}
fn type_to_variable<'a>(
@ -2154,27 +2223,53 @@ fn type_to_variable<'a>(
arena: &'a bumpalo::Bump,
aliases: &mut Aliases,
typ: &Type,
// Helpers for instantiating ambient functions of lambda set variables from type aliases.
is_alias_lambda_set_arg: bool,
// If we're instantiating a delayed alias in this call, what lambda sets do we need to link to
// their ambient function types?
delayed_alias_lambda_set_vars: VariableSubsSlice,
) -> Variable {
use bumpalo::collections::Vec;
let mut stack = Vec::with_capacity_in(8, arena);
macro_rules! helper {
($typ:expr) => {{
($typ:expr, $ambient_function_policy:expr) => {{
match RegisterVariable::from_type(subs, rank, pools, arena, $typ) {
RegisterVariable::Direct(var) => var,
RegisterVariable::Direct(var) => {
if delayed_alias_lambda_set_vars.len() > 0 {
let slice = subs.get_subs_slice(delayed_alias_lambda_set_vars);
if slice.contains(&var) {
$ambient_function_policy.link_to_alias_lambda_set_var(subs, var);
}
}
var
}
RegisterVariable::Deferred => {
let var = subs.fresh_unnamed_flex_var();
stack.push(TypeToVar::Defer($typ, var));
stack.push(TypeToVar::Defer {
typ: $typ,
destination: var,
ambient_function: $ambient_function_policy,
});
var
}
}
}};
($typ:expr) => {{
helper!($typ, AmbientFunctionPolicy::NoFunction)
}};
}
let result = helper!(typ);
while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() {
while let Some(TypeToVar::Defer {
typ,
destination,
ambient_function,
}) = stack.pop()
{
match typ {
Variable(_) | EmptyRec | EmptyTagUnion => {
unreachable!("This variant should never be deferred!")
@ -2216,20 +2311,31 @@ fn type_to_variable<'a>(
register_with_known_var(subs, destination, rank, pools, content)
}
UnspecializedLambdaSet {
unspecialized,
ambient_function,
} => {
let unspecialized = SubsSlice::extend_new(
UnspecializedLambdaSet { unspecialized } => {
let unspecialized_slice = SubsSlice::extend_new(
&mut subs.unspecialized_lambda_sets,
std::iter::once(*unspecialized),
);
// `ClosureTag` ambient functions are resolved during constraint generation.
// But `UnspecializedLambdaSet`s can only ever live in a type signature, and don't
// correspond to a expression, so they are never constrained.
// Instead, we resolve their ambient functions during type translation, observing
// the invariant that a lambda set can only ever appear under a function type.
let ambient_function = match ambient_function {
AmbientFunctionPolicy::NoFunction => {
debug_assert!(is_alias_lambda_set_arg);
// To be filled in during delayed type alias instantiation
Variable::NULL
}
AmbientFunctionPolicy::Function(var) => var,
};
let content = Content::LambdaSet(subs::LambdaSet {
unspecialized,
unspecialized: unspecialized_slice,
solved: UnionLabels::default(),
recursion_var: OptVariable::NONE,
ambient_function: *ambient_function,
ambient_function,
});
register_with_known_var(subs, destination, rank, pools, content)
@ -2243,7 +2349,8 @@ fn type_to_variable<'a>(
}
let ret_var = helper!(ret_type);
let closure_var = helper!(closure_type);
let closure_var =
helper!(closure_type, AmbientFunctionPolicy::Function(destination));
let content =
Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var));
@ -2370,7 +2477,18 @@ fn type_to_variable<'a>(
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);
// We MUST do this now, otherwise when linking the ambient function during
// instantiation of the real var, there will be nothing to link against.
let copy_var = type_to_variable(
subs,
rank,
pools,
arena,
aliases,
&ls.0,
true,
SubsSlice::default(),
);
subs.variables[target_index] = copy_var;
}
@ -2435,7 +2553,11 @@ fn type_to_variable<'a>(
// TODO associate the type to the bound ability, and check
// that it correctly implements the ability.
let var = subs.fresh_unnamed_flex_var();
stack.push(TypeToVar::Defer(typ, var));
stack.push(TypeToVar::Defer {
typ,
destination: var,
ambient_function: AmbientFunctionPolicy::NoFunction,
});
var
}
}
@ -2491,9 +2613,19 @@ fn type_to_variable<'a>(
}
};
let lambda_set_variables_slice = alias_variables.lambda_set_variables();
// cannot use helper! here because this variable may be involved in unification below
let alias_variable =
type_to_variable(subs, rank, pools, arena, aliases, alias_type);
let alias_variable = type_to_variable(
subs,
rank,
pools,
arena,
aliases,
alias_type,
false,
lambda_set_variables_slice,
);
// 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(

View file

@ -244,7 +244,6 @@ pub enum Type {
},
UnspecializedLambdaSet {
unspecialized: Uls,
ambient_function: Variable,
},
DelayedAlias(AliasCommon),
Alias {
@ -325,12 +324,8 @@ impl Clone for Type {
captures: captures.clone(),
ambient_function: *ambient_function,
},
Self::UnspecializedLambdaSet {
unspecialized,
ambient_function,
} => Self::UnspecializedLambdaSet {
Self::UnspecializedLambdaSet { unspecialized } => Self::UnspecializedLambdaSet {
unspecialized: *unspecialized,
ambient_function: *ambient_function,
},
Self::DelayedAlias(arg0) => Self::DelayedAlias(arg0.clone()),
Self::Alias {
@ -674,10 +669,7 @@ impl fmt::Debug for Type {
Type::RangedNumber(range_vars) => {
write!(f, "Ranged({:?})", range_vars)
}
Type::UnspecializedLambdaSet {
unspecialized,
ambient_function: _,
} => {
Type::UnspecializedLambdaSet { unspecialized } => {
write!(f, "{:?}", unspecialized)
}
}
@ -823,7 +815,6 @@ impl Type {
RangedNumber(_) => {}
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
ambient_function: _,
} => {
debug_assert!(
substitutions.get(v).is_none(),
@ -945,7 +936,6 @@ impl Type {
RangedNumber(_) => {}
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
ambient_function: _,
} => {
debug_assert!(
substitutions.get(v).is_none(),
@ -1108,7 +1098,6 @@ impl Type {
RangedNumber(_) => false,
UnspecializedLambdaSet {
unspecialized: Uls(_, sym, _),
ambient_function: _,
} => *sym == rep_symbol,
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false,
}
@ -1139,7 +1128,6 @@ impl Type {
} => captures.iter().any(|t| t.contains_variable(rep_variable)),
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
ambient_function: _,
} => *v == rep_variable,
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
Self::contains_variable_ext(ext, rep_variable)
@ -1435,11 +1423,10 @@ impl Type {
pub fn instantiate_lambda_sets_as_unspecialized(
&mut self,
var_store: &mut VarStore,
able_var: Variable,
ability_member: Symbol,
) {
instantiate_lambda_sets_as_unspecialized(var_store, self, able_var, ability_member)
instantiate_lambda_sets_as_unspecialized(self, able_var, ability_member)
}
pub fn is_tag_union_like(&self) -> bool {
@ -1567,7 +1554,6 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
RangedNumber(_) => {}
UnspecializedLambdaSet {
unspecialized: Uls(_, _sym, _),
ambient_function: _,
} => {
// ignore the member symbol because unspecialized lambda sets are internal-only
}
@ -1624,7 +1610,6 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
}
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
ambient_function: _,
} => {
accum.insert(*v);
}
@ -1782,7 +1767,6 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
}
UnspecializedLambdaSet {
unspecialized: Uls(var, _, _),
ambient_function: _,
} => {
accum.type_variables.insert(*var);
}
@ -2779,7 +2763,6 @@ pub fn gather_tags(subs: &Subs, other_fields: UnionTags, var: Variable) -> TagUn
}
fn instantiate_lambda_sets_as_unspecialized(
var_store: &mut VarStore,
typ: &mut Type,
able_var: Variable,
ability_member: Symbol,
@ -2793,7 +2776,6 @@ fn instantiate_lambda_sets_as_unspecialized(
region += 1;
Type::UnspecializedLambdaSet {
unspecialized: Uls(able_var, ability_member, region),
ambient_function: var_store.fresh(),
}
};
@ -2911,14 +2893,13 @@ mod test {
let able_var = var_store.fresh();
let member = Symbol::UNDERSCORE;
typ.instantiate_lambda_sets_as_unspecialized(&mut var_store, able_var, member);
typ.instantiate_lambda_sets_as_unspecialized(able_var, member);
macro_rules! check_uls {
($typ:expr, $region:literal) => {{
match $typ {
Type::UnspecializedLambdaSet {
unspecialized: Uls(var1, member1, $region),
ambient_function: _,
} => {
assert!(var1 == able_var && member1 == member)
}