Unify variables directly when possible

instead of going through a solved type
This commit is contained in:
Folkert 2021-08-22 16:29:57 +02:00
parent 1348ec433b
commit ecba687243
3 changed files with 141 additions and 49 deletions

View file

@ -694,10 +694,8 @@ impl<'a> Procs<'a> {
layout: ProcLayout<'a>, layout: ProcLayout<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) { ) {
let tuple = (name, layout);
// If we've already specialized this one, no further work is needed. // If we've already specialized this one, no further work is needed.
if self.specialized.contains_key(&tuple) { if self.specialized.contains_key(&(name, layout)) {
return; return;
} }
@ -707,15 +705,12 @@ impl<'a> Procs<'a> {
return; return;
} }
// We're done with that tuple, so move layout back out to avoid cloning it.
let (name, layout) = tuple;
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
// This should only be called when pending_specializations is Some. // This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass! // Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations { match &mut self.pending_specializations {
Some(pending_specializations) => { Some(pending_specializations) => {
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
// register the pending specialization, so this gets code genned later // register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&name) { if self.module_thunks.contains(&name) {
debug_assert!(layout.arguments.is_empty()); debug_assert!(layout.arguments.is_empty());
@ -736,7 +731,26 @@ impl<'a> Procs<'a> {
// (We had a bug around this before this system existed!) // (We had a bug around this before this system existed!)
self.specialized.insert((symbol, layout), InProgress); self.specialized.insert((symbol, layout), InProgress);
match specialize(env, self, symbol, layout_cache, pending, partial_proc) { // See https://github.com/rtfeldman/roc/issues/1600
//
// The annotation variable is the generic/lifted/top-level annotation.
// It is connected to the variables of the function's body
//
// fn_var is the variable representing the type that we actually need for the
// function right here.
//
// For some reason, it matters that we unify with the original variable. Extracting
// that variable into a SolvedType and then introducing it again severs some
// connection that turns out to be important
match specialize_variable(
env,
self,
symbol,
layout_cache,
fn_var,
Default::default(),
partial_proc,
) {
Ok((proc, _ignore_layout)) => { Ok((proc, _ignore_layout)) => {
// the `layout` is a function pointer, while `_ignore_layout` can be a // the `layout` is a function pointer, while `_ignore_layout` can be a
// closure. We only specialize functions, storing this value with a closure // closure. We only specialize functions, storing this value with a closure
@ -2448,13 +2462,57 @@ fn specialize_solved_type<'a>(
host_exposed_aliases: BumpMap<Symbol, SolvedType>, host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> { ) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
specialize_variable_help(
env,
procs,
proc_name,
layout_cache,
|env| introduce_solved_type_to_subs(env, &solved_type),
host_exposed_aliases,
partial_proc,
)
}
fn specialize_variable<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
specialize_variable_help(
env,
procs,
proc_name,
layout_cache,
|_| fn_var,
host_exposed_aliases,
partial_proc,
)
}
fn specialize_variable_help<'a, F>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>,
fn_var_thunk: F,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>>
where
F: FnOnce(&mut Env<'a, '_>) -> Variable,
{
// add the specializations that other modules require of us // add the specializations that other modules require of us
use roc_solve::solve::instantiate_rigids; use roc_solve::solve::instantiate_rigids;
let snapshot = env.subs.snapshot(); let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot(); let cache_snapshot = layout_cache.snapshot();
let fn_var = introduce_solved_type_to_subs(env, &solved_type); // important: evaluate after the snapshot has been created!
let fn_var = fn_var_thunk(env);
// for debugging only // for debugging only
let raw = layout_cache let raw = layout_cache
@ -2723,7 +2781,10 @@ pub fn with_hole<'a>(
hole, hole,
), ),
Num(var, num) => match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) { Num(var, num) => {
// first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) {
IntOrFloat::SignedIntType(precision) => Stmt::Let( IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(num.into())), Expr::Literal(Literal::Int(num.into())),
@ -2748,7 +2809,8 @@ pub fn with_hole<'a>(
Layout::Builtin(Builtin::Decimal), Layout::Builtin(Builtin::Decimal),
hole, hole,
), ),
}, }
}
LetNonRec(def, cont, _) => { LetNonRec(def, cont, _) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure { if let Closure {
@ -7865,6 +7927,12 @@ fn union_lambda_set_to_switch<'a>(
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
// NOTE this can happen if there is a type error somewhere. Since the lambda set is empty,
// there is really nothing we can do here, so we just proceed with the hole itself and
// hope that the type error is communicated in a clear way elsewhere.
if lambda_set.is_empty() {
return hole.clone();
}
debug_assert!(!lambda_set.is_empty()); debug_assert!(!lambda_set.is_empty());
let join_point_id = JoinPointId(env.unique_symbol()); let join_point_id = JoinPointId(env.unique_symbol());

View file

@ -554,10 +554,10 @@ impl<'a> LambdaSet<'a> {
} }
Ok(()) | Err((_, Content::FlexVar(_))) => { Ok(()) | Err((_, Content::FlexVar(_))) => {
// TODO hack for builting functions. // this can happen when there is a type error somewhere
Ok(LambdaSet { Ok(LambdaSet {
set: &[], set: &[],
representation: arena.alloc(Layout::Struct(&[])), representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))),
}) })
} }
_ => panic!("called LambdaSet.from_var on invalid input"), _ => panic!("called LambdaSet.from_var on invalid input"),

View file

@ -2683,26 +2683,50 @@ fn list_walk_until() {
} }
#[test] #[test]
#[ignore] fn int_literal_not_specialized_with_annotation() {
fn int_literal_not_specialized() {
// see https://github.com/rtfeldman/roc/issues/1600 // see https://github.com/rtfeldman/roc/issues/1600
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app "test" provides [ main ] to "./platform" app "test" provides [ main ] to "./platform"
main =
satisfy : (U8 -> Bool) -> Str satisfy : (U8 -> Str) -> Str
satisfy = \_ -> "foo" satisfy = \_ -> "foo"
myEq : a, a -> Str
myEq = \_, _ -> "bar"
p1 : Num * -> Str
p1 = (\u -> myEq u 64)
when satisfy p1 is
_ -> 32
"#
),
32,
i64
);
}
#[test]
fn int_literal_not_specialized_no_annotation() {
// see https://github.com/rtfeldman/roc/issues/1600
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : I64
main = main =
p1 = (\u -> u == 97) satisfy : (U8 -> Str) -> Str
satisfy = \_ -> "foo"
satisfyA = satisfy p1 myEq : a, a -> Str
myEq = \_, _ -> "bar"
when satisfyA is p1 = (\u -> myEq u 64)
when satisfy p1 is
_ -> 32 _ -> 32
"# "#
), ),