diff --git a/.cargo/config b/.cargo/config index 4e1ae989ec..357a124bad 100644 --- a/.cargo/config +++ b/.cargo/config @@ -23,6 +23,7 @@ ROC_WORKSPACE_DIR = { value = "", relative = true } # Set = "1" to turn a debug flag on. ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0" ROC_PRINT_UNIFICATIONS = "0" +ROC_TRACE_COMPACTION = "1" ROC_PRINT_UNIFICATIONS_DERIVED = "0" ROC_PRINT_MISMATCHES = "0" ROC_VERIFY_RIGID_LET_GENERALIZED = "0" diff --git a/crates/compiler/debug_flags/src/lib.rs b/crates/compiler/debug_flags/src/lib.rs index 4980098cf6..35767c07c1 100644 --- a/crates/compiler/debug_flags/src/lib.rs +++ b/crates/compiler/debug_flags/src/lib.rs @@ -65,6 +65,9 @@ flags! { /// Only use this in single-threaded mode! ROC_PRINT_UNIFICATIONS + /// Prints traces of unspecialized lambda set compaction + ROC_TRACE_COMPACTION + /// Like ROC_PRINT_UNIFICATIONS, in the context of typechecking derived implementations. /// Only use this in single-threaded mode! ROC_PRINT_UNIFICATIONS_DERIVED diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index a24c71b920..81e0dc5ea5 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -11,7 +11,7 @@ use roc_can::expr::PendingDerives; use roc_collections::all::MutMap; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] -use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; +use roc_debug_flags::{ROC_TRACE_COMPACTION, ROC_VERIFY_RIGID_LET_GENERALIZED}; use roc_derive_key::{DeriveError, Derived, GlobalDerivedSymbols}; use roc_error_macros::internal_error; use roc_module::ident::TagName; @@ -1487,34 +1487,38 @@ fn solve( state } + // TODO: turning off opportunistic specialization for now because it doesn't mesh well with + // the polymorphic lambda resolution algorithm. After + // https://github.com/rtfeldman/roc/issues/3207 is resolved, this may be redundant + // anyway. &Resolve(OpportunisticResolve { - specialization_variable, - specialization_expectation, - member, - specialization_id, + specialization_variable: _, + specialization_expectation: _, + member: _, + specialization_id: _, }) => { - if let Some(Resolved::Specialization(specialization)) = - resolve_ability_specialization( - subs, - abilities_store, - member, - specialization_variable, - ) - { - abilities_store.insert_resolved(specialization_id, specialization); + // if let Some(Resolved::Specialization(specialization)) = + // resolve_ability_specialization( + // subs, + // abilities_store, + // member, + // specialization_variable, + // ) + // { + // abilities_store.insert_resolved(specialization_id, specialization); - // We must now refine the current type state to account for this specialization. - let lookup_constr = arena.alloc(Constraint::Lookup( - specialization, - specialization_expectation, - Region::zero(), - )); - stack.push(Work::Constraint { - env, - rank, - constraint: lookup_constr, - }); - } + // // We must now refine the current type state to account for this specialization. + // let lookup_constr = arena.alloc(Constraint::Lookup( + // specialization, + // specialization_expectation, + // Region::zero(), + // )); + // stack.push(Work::Constraint { + // env, + // rank, + // constraint: lookup_constr, + // }); + // } state } @@ -1758,12 +1762,121 @@ fn check_ability_specialization( } } +#[cfg(debug_assertions)] +fn trace_compaction_step_1(subs: &Subs, c_a: Variable, uls_a: &[Variable]) { + let c_a = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(c_a), subs); + let uls_a = uls_a + .iter() + .map(|v| { + format!( + "{:?}", + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(*v), subs) + ) + }) + .collect::>() + .join(","); + eprintln!("===lambda set compaction==="); + eprintln!(" concrete type: {:?}", c_a); + eprintln!(" step 1:"); + eprintln!(" uls_a = {{ {} }}", uls_a); +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_2(subs: &Subs, uls_a: &[Variable]) { + let uls_a = uls_a + .iter() + .map(|v| { + format!( + "{:?}", + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(*v), subs) + ) + }) + .collect::>() + .join(","); + eprintln!(" step 2:"); + eprintln!(" uls_a' = {{ {} }}", uls_a); +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_3start() { + eprintln!(" step 3:"); +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_3iter_start( + subs: &Subs, + iteration_lambda_set: Variable, + t_f1: Variable, + t_f2: Variable, +) { + let iteration_lambda_set = roc_types::subs::SubsFmtContent( + subs.get_content_without_compacting(iteration_lambda_set), + subs, + ); + let t_f1 = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f1), subs); + let t_f2 = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f2), subs); + eprintln!(" - iteration: {:?}", iteration_lambda_set); + eprintln!(" {:?}", t_f1); + eprintln!(" ~ {:?}", t_f2); +} + +#[cfg(debug_assertions)] +#[rustfmt::skip] +fn trace_compaction_step_3iter_end(subs: &Subs, t_f_result: Variable, skipped: bool) { + let t_f_result = + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f_result), subs); + if skipped { + eprintln!(" SKIP"); + } + eprintln!(" = {:?}\n", t_f_result); +} + +macro_rules! trace_compact { + (1. $subs:expr, $c_a:expr, $uls_a:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_1($subs, $c_a, $uls_a) + }) + }}; + (2. $subs:expr, $uls_a:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_2($subs, $uls_a) + }) + }}; + (3start.) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { trace_compaction_step_3start() }) + }}; + (3iter_start. $subs:expr, $iteration_lset:expr, $t_f1:expr, $t_f2:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_3iter_start($subs, $iteration_lset, $t_f1, $t_f2) + }) + }}; + (3iter_end. $subs:expr, $t_f_result:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_3iter_end($subs, $t_f_result, false) + }) + }}; + (3iter_end_skipped. $subs:expr, $t_f_result:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_3iter_end($subs, $t_f_result, true) + }) + }}; +} + +#[inline(always)] +fn iter_concrete_of_unspecialized<'a>( + subs: &'a Subs, + c_a: Variable, + uls: &'a [Uls], +) -> impl Iterator { + uls.iter() + .filter(move |Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)) +} + /// Gets the unique unspecialized lambda resolving to concrete type `c_a` in a list of /// unspecialized lambda sets. +#[inline(always)] fn unique_unspecialized_lambda(subs: &Subs, c_a: Variable, uls: &[Uls]) -> Option { - let mut iter_concrete = uls - .iter() - .filter(|Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)); + let mut iter_concrete = iter_concrete_of_unspecialized(subs, c_a, uls); let uls = iter_concrete.next()?; debug_assert!(iter_concrete.next().is_none(), "multiple concrete"); Some(*uls) @@ -1791,15 +1904,79 @@ pub fn compact_lambda_sets_of_vars( // NB: There may be multiple unspecialized lambdas of form `C:f:r, C:f1:r1, ..., C:fn:rn` in `l`. // In this case, let `t1, ... tm` be the other unspecialized lambdas not of form `C:_:_`, // that is, none of which are now specialized to the type `C`. Then, deconstruct - // `l` such that `l' = [concrete_lambdas + t1 + ... + tm + C:f:r` and `l1 = [[] + C:f1:r1], ..., ln = [[] + C:fn:rn]`. + // `l` such that `l' = [concrete_lambdas + t1 + ... + tm + C:f:r]` and `l1 = [[] + C:f1:r1], ..., ln = [[] + C:fn:rn]`. // Replace `l` with `l', l1, ..., ln` in `uls_a`, flattened. // TODO: the flattening step described above - let mut uls_a = uls_a.into_vec(); - // We can remove all the lambda sets that don't have any unspecialized lambdas. - uls_a.retain(|lambda_set| { - let unspec = subs.get_subs_slice(subs.get_lambda_set(*lambda_set).unspecialized); - unique_unspecialized_lambda(subs, c_a, unspec).is_some() - }); + let uls_a = uls_a.into_vec(); + trace_compact!(1. subs, c_a, &uls_a); + + // The flattening step - remove lambda sets that don't reference the concrete var, and for + // flatten lambda sets that reference it more than once. + let mut uls_a: Vec<_> = uls_a + .into_iter() + .flat_map(|lambda_set| { + let LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function, + } = subs.get_lambda_set(lambda_set); + let lambda_set_rank = subs.get_rank(lambda_set); + let unspecialized = subs.get_subs_slice(unspecialized); + // TODO: is it faster to traverse once, see if we only have one concrete lambda, and + // bail in that happy-path, rather than always splitting? + let (concrete, mut not_concrete): (Vec<_>, Vec<_>) = unspecialized + .iter() + .copied() + .partition(|Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)); + if concrete.len() == 1 { + // No flattening needs to be done, just return the lambda set as-is + return vec![lambda_set]; + } + // Must flatten + concrete + .into_iter() + .enumerate() + .map(|(i, concrete_lambda)| { + let (var, unspecialized) = if i == 0 { + // `l' = [concrete_lambdas + t1 + ... + tm + C:f:r` + let unspecialized = SubsSlice::extend_new( + &mut subs.unspecialized_lambda_sets, + not_concrete + .drain(..) + .chain(std::iter::once(concrete_lambda)), + ); + (lambda_set, unspecialized) + } else { + // ln = [[] + C:fn:rn] + let unspecialized = SubsSlice::extend_new( + &mut subs.unspecialized_lambda_sets, + [concrete_lambda], + ); + let var = subs.fresh(Descriptor { + content: Content::Error, + rank: lambda_set_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }); + (var, unspecialized) + }; + + subs.set_content( + var, + Content::LambdaSet(LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function, + }), + ); + var + }) + .collect() + }) + .collect(); + // 2. Now, each `l` in `uls_a` has a unique unspecialized lambda of form `C:f:r`. // Sort `uls_a` primarily by `f` (arbitrary order), and secondarily by `r` in descending order. uls_a.sort_by(|v1, v2| { @@ -1817,12 +1994,15 @@ pub fn compact_lambda_sets_of_vars( ord => ord, } }); + trace_compact!(2. subs, &uls_a); + // 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`: // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set. // - For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`. // 2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`. // - For example, `(b -[[] + b:g:1]-> {})` if `C:f:r=Fo:f:2`, running on example from above. // 3. Unify `t_f1 ~ t_f2`. + trace_compact!(3start.); for l in uls_a { // let root_lset = subs.get_root_key_without_compacting(l); // if seen.contains(&root_lset) { @@ -1870,7 +2050,7 @@ fn compact_lambda_set( let unspecialized = subs.get_subs_slice(unspecialized); - // 1. jLet `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. + // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. let Uls(c, f, r) = unique_unspecialized_lambda(subs, resolved_concrete, unspecialized).unwrap(); debug_assert!(subs.equivalent_without_compacting(c, resolved_concrete)); @@ -2034,8 +2214,10 @@ fn compact_lambda_set( let t_f2 = deep_copy_var_in(subs, target_rank, pools, t_f2, arena); // 3. Unify `t_f1 ~ t_f2`. + trace_compact!(3iter_start. subs, this_lambda_set, t_f1, t_f2); let (vars, new_must_implement_ability, new_lambda_sets_to_specialize, _meta) = unify(subs, t_f1, t_f2, Mode::EQ).expect_success("ambient functions don't unify"); + trace_compact!(3iter_end. subs, t_f1); introduce(subs, target_rank, pools, &vars); @@ -2044,6 +2226,7 @@ fn compact_lambda_set( Spec::Drop => { // Do nothing other than to remove the concrete lambda to drop from the lambda set, // which we already did in 1b above. + trace_compact!(3iter_end_skipped. subs, t_f1); (Default::default(), Default::default()) } } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index d76c2d757e..55706f3a1f 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -7200,7 +7200,19 @@ mod solve_expr { &[ "Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G", "Go#g(11) : Go -[[g(11)]]-> {}", - // TODO this is wrong: should be let-generalized? + // TODO SERIOUS: Let generalization is broken here, and this is NOT correct!! + // Two problems: + // - 1. `{}` always has its rank adjusted to the toplevel, which forces the rest + // of the type to the toplevel, but that is NOT correct here! + // - 2. During solving lambda set compaction cannot happen until an entire module + // is solved, which forces resolved-but-not-yet-compacted lambdas in + // unspecialized lambda sets to pull the rank into a lower, non-generalized + // rank. Special-casing for that is a TERRIBLE HACK that interferes very + // poorly with (1) + // + // We are BLOCKED on https://github.com/rtfeldman/roc/issues/3207 to make this work + // correctly! + // See also https://github.com/rtfeldman/roc/pull/3175, a separate, but similar problem. "h : Go -[[g(11)]]-> {}", "Fo#f(10) : Fo -[[f(10)]]-> (Go -[[g(11)]]-> {})", "h : Go -[[g(11)]]-> {}", @@ -7208,6 +7220,41 @@ mod solve_expr { ); } + #[test] + fn polymorphic_lambda_set_specialization_with_let_generalization_unapplied() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + F has f : a -> (b -> {}) | a has F, b has G + G has g : b -> {} | b has G + + Fo := {} + f = \@Fo {} -> g + #^{-1} + + Go := {} + g = \@Go {} -> {} + #^{-1} + + main = + #^^^^{-1} + h = f (@Fo {}) + # ^ ^ + h + "# + ), + &[ + "Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G", + "Go#g(11) : Go -[[g(11)]]-> {}", + "main : b -[[] + b:g(8):1]-> {} | b has G", + "h : b -[[] + b:g(8):1]-> {} | b has G", + "Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G", + ], + ); + } + #[test] fn polymorphic_lambda_set_specialization_with_deep_specialization_and_capture() { infer_queries!( @@ -7235,7 +7282,7 @@ mod solve_expr { "Fo#f(10) : Fo, b -[[f(10)]]-> ({} -[[13(13) b]]-> ({} -[[] + b:g(8):2]-> {})) | b has G", "Go#g(11) : Go -[[g(11)]]-> ({} -[[14(14)]]-> {})", // TODO this is wrong: why is there a unspecialized lambda set left over? - "Fo#f(10) : Fo, Go -[[f(10)]]-> ({} -[[13(13) Go]]-> ({} -[[14(14)] + b:g(8):2]-> {})) | b has G", + "Fo#f(10) : Fo, Go -[[f(10)]]-> ({} -[[13(13) Go]]-> ({} -[[14(14)]]-> {})) | b has G", ], ); } diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index 7b661a054f..6a819b56dd 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -778,8 +778,20 @@ impl<'a> fmt::Debug for SubsFmtContent<'a> { fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result { match this { - Content::FlexVar(name) => write!(f, "Flex({:?})", name), - Content::FlexAbleVar(name, symbol) => write!(f, "FlexAble({:?}, {:?})", name, symbol), + Content::FlexVar(name) => { + let name = match name { + Some(index) => subs[*index].as_str(), + None => "_", + }; + write!(f, "Flex({})", name) + } + Content::FlexAbleVar(name, symbol) => { + let name = match name { + Some(index) => subs[*index].as_str(), + None => "_", + }; + write!(f, "FlexAble({}, {:?})", name, symbol) + } Content::RigidVar(name) => write!(f, "Rigid({:?})", name), Content::RigidAbleVar(name, symbol) => write!(f, "RigidAble({:?}, {:?})", name, symbol), Content::RecursionVar {