diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 0724c67553..da5d6e543a 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1938,7 +1938,7 @@ pub mod test_constrain { use roc_parse::parser::{SourceError, SyntaxError}; use roc_region::all::Region; use roc_types::{ - pretty_print::{name_and_print_var, PrintLambdaSets}, + pretty_print::{name_and_print_var, DebugPrint}, solved_types::Solved, subs::{Subs, VarStore, Variable}, }; @@ -2060,7 +2060,7 @@ pub mod test_constrain { }; let actual_str = - name_and_print_var(var, subs, mod_id, &interns, PrintLambdaSets::No); + name_and_print_var(var, subs, mod_id, &interns, DebugPrint::NOTHING); assert_eq!(actual_str, expected_str); } diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index c5ed1bdd89..af351b7490 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -30,7 +30,7 @@ mod test_load { use roc_reporting::report::RocDocAllocator; use roc_target::TargetInfo; use roc_types::pretty_print::name_and_print_var; - use roc_types::pretty_print::PrintLambdaSets; + use roc_types::pretty_print::DebugPrint; use roc_types::subs::Subs; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -239,7 +239,7 @@ mod test_load { ) { for (symbol, expr_var) in &def.pattern_vars { let actual_str = - name_and_print_var(*expr_var, subs, home, interns, PrintLambdaSets::No); + name_and_print_var(*expr_var, subs, home, interns, DebugPrint::NOTHING); let fully_qualified = symbol.fully_qualified(interns, home).to_string(); let expected_type = expected_types .remove(fully_qualified.as_str()) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 1946f79234..9fa3aa27ed 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1791,14 +1791,14 @@ fn compact_lambda_set( arena: &Bump, pools: &mut Pools, abilities_store: &AbilitiesStore, - lambda_set: Variable, + this_lambda_set: Variable, ) { let LambdaSet { solved, recursion_var, unspecialized, - } = subs.get_lambda_set(lambda_set); - let target_rank = subs.get_rank(lambda_set); + } = subs.get_lambda_set(this_lambda_set); + let target_rank = subs.get_rank(this_lambda_set); if unspecialized.is_empty() { return; @@ -1854,7 +1854,9 @@ fn compact_lambda_set( }; // Ensure the specialization lambda set is already compacted. - compact_lambda_set(subs, arena, pools, abilities_store, specialized_lambda_set); + if subs.get_root_key(specialized_lambda_set) != subs.get_root_key(this_lambda_set) { + compact_lambda_set(subs, arena, pools, abilities_store, specialized_lambda_set); + } // Ensure the specialization lambda set we'll unify with is not a generalized one, but one // at the rank of the lambda set being compacted. @@ -1871,14 +1873,14 @@ fn compact_lambda_set( recursion_var, unspecialized: new_unspecialized_slice, }); - subs.set_content(lambda_set, partial_compacted_lambda_set); + subs.set_content(this_lambda_set, partial_compacted_lambda_set); for other_specialized in specialized_to_unify_with.into_iter() { let (vars, must_implement_ability, lambda_sets_to_specialize) = - unify(subs, lambda_set, other_specialized, Mode::EQ) + unify(subs, this_lambda_set, other_specialized, Mode::EQ) .expect_success("lambda sets don't unify"); - introduce(subs, subs.get_rank(lambda_set), pools, &vars); + introduce(subs, subs.get_rank(this_lambda_set), pools, &vars); debug_assert!( must_implement_ability.is_empty(), diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 311ee7d8b3..e5630ebcc1 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -19,7 +19,7 @@ mod solve_expr { use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region}; use roc_reporting::report::{can_problem, type_problem, RocDocAllocator}; use roc_solve::solve::TypeError; - use roc_types::pretty_print::{name_and_print_var, PrintLambdaSets}; + use roc_types::pretty_print::{name_and_print_var, DebugPrint}; use std::path::PathBuf; // HELPERS @@ -177,7 +177,7 @@ mod solve_expr { debug_assert!(exposed_to_host.len() == 1); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); - let actual_str = name_and_print_var(variable, subs, home, &interns, PrintLambdaSets::No); + let actual_str = name_and_print_var(variable, subs, home, &interns, DebugPrint::NOTHING); Ok((type_problems, can_problems, actual_str)) } @@ -235,7 +235,7 @@ mod solve_expr { assert_eq!(actual, expected.to_string()); } - fn infer_queries(src: &str, expected: &[&'static str]) { + fn infer_queries_help(src: &str, expected: &[&'static str], print_only_under_alias: bool) { let ( LoadedModule { module_id: home, @@ -277,7 +277,16 @@ mod solve_expr { let var = find_type_at(region, &decls) .unwrap_or_else(|| panic!("No type for {:?} ({:?})!", &text, region)); - let actual_str = name_and_print_var(var, subs, home, &interns, PrintLambdaSets::Yes); + let actual_str = name_and_print_var( + var, + subs, + home, + &interns, + DebugPrint { + print_lambda_sets: true, + print_only_under_alias, + }, + ); let elaborated = match find_ability_member_and_owning_type_at(region, &decls, &abilities_store) { @@ -301,6 +310,15 @@ mod solve_expr { assert_eq!(solved_queries, expected) } + macro_rules! infer_queries { + ($program:expr, $queries:expr $(,)?) => { + infer_queries_help($program, $queries, false) + }; + ($program:expr, $queries:expr, print_only_under_alias=true $(,)?) => { + infer_queries_help($program, $queries, true) + }; + } + fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I) where I: IntoIterator, @@ -6150,7 +6168,7 @@ mod solve_expr { #[test] fn intermediate_branch_types() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [foo] to "./platform" @@ -6278,7 +6296,7 @@ mod solve_expr { #[test] fn encoder() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [myU8Bytes] to "./platform" @@ -6324,7 +6342,7 @@ mod solve_expr { #[test] fn decoder() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [myU8] to "./platform" @@ -6406,7 +6424,7 @@ mod solve_expr { #[test] fn static_specialization() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [main] to "./platform" @@ -6455,7 +6473,7 @@ mod solve_expr { #[test] fn encode_record() { - infer_queries( + infer_queries!( indoc!( r#" app "test" @@ -6474,7 +6492,7 @@ mod solve_expr { #[test] fn encode_record_with_nested_custom_impl() { - infer_queries( + infer_queries!( indoc!( r#" app "test" @@ -6494,7 +6512,7 @@ mod solve_expr { #[test] fn resolve_lambda_set_generalized_ability_alias() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [main] to "./platform" @@ -6529,7 +6547,7 @@ mod solve_expr { #[test] fn resolve_lambda_set_ability_chain() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [main] to "./platform" @@ -6565,7 +6583,7 @@ mod solve_expr { #[test] fn resolve_lambda_set_branches_ability_vs_non_ability() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [main] to "./platform" @@ -6603,7 +6621,7 @@ mod solve_expr { #[test] fn resolve_lambda_set_branches_same_ability() { - infer_queries( + infer_queries!( indoc!( r#" app "test" provides [main] to "./platform" @@ -6634,4 +6652,169 @@ mod solve_expr { ], ) } + + #[test] + fn resolve_unspecialized_lambda_set_behind_alias() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Thunk a : {} -> a + + Id has id : a -> Thunk a | a has Id + + A := {} + id = \@A {} -> \{} -> @A {} + #^^{-1} + + main = + alias = id + # ^^ + + a : A + a = (alias (@A {})) {} + # ^^^^^ + + a + "# + ), + &[ + "A#id(7) : {} -[[id(7)]]-> ({} -[[8(8)]]-> {})", + "Id#id(6) : {} -[[id(7)]]-> ({} -[[8(8)]]-> {})", + "alias : {} -[[id(7)]]-> ({} -[[8(8)]]-> {})", + ], + print_only_under_alias = true, + ) + } + + #[test] + fn resolve_unspecialized_lambda_set_behind_opaque() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Thunk a := {} -> a + + Id has id : a -> Thunk a | a has Id + + A := {} + id = \@A {} -> @Thunk (\{} -> @A {}) + #^^{-1} + + main = + thunk = id (@A {}) + @Thunk it = thunk + it {} + #^^{-1} + "# + ), + &[ + "A#id(7) : {} -[[id(7)]]-> ({} -[[8(8)]]-> {})", + "it : {} -[[8(8)]]-> {}", + ], + print_only_under_alias = true, + ) + } + + #[test] + fn resolve_two_unspecialized_lambda_sets_in_one_lambda_set() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Thunk a : {} -> a + + Id has id : a -> Thunk a | a has Id + + A := {} + id = \@A {} -> \{} -> @A {} + #^^{-1} + + main = + a : A + a = (id (@A {})) {} + # ^^ + + a + "# + ), + &[ + "A#id(7) : {} -[[id(7)]]-> ({} -[[8(8)]]-> {})", + "A#id(7) : {} -[[id(7)]]-> ({} -[[8(8)]]-> {})", + ], + print_only_under_alias = true, + ) + } + + #[test] + fn resolve_recursive_ability_lambda_set() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Diverge has diverge : a -> a | a has Diverge + + A := {} + diverge = \@A {} -> diverge (@A {}) + #^^^^^^^{-1} ^^^^^^^ + + main = + a : A + a = diverge (@A {}) + # ^^^^^^^ + + a + "# + ), + &[ + "A#diverge(5) : A -[[diverge(5)]]-> A", + "Diverge#diverge(4) : A -[[diverge(5)]]-> A", + // + "A#diverge(5) : A -[[diverge(5)]]-> A", + ], + ) + } + + #[test] + fn resolve_mutually_recursive_ability_lambda_sets() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Bounce has + ping : a -> a | a has Bounce + pong : a -> a | a has Bounce + + A := {} + + ping = \@A {} -> pong (@A {}) + #^^^^{-1} ^^^^ + + pong = \@A {} -> ping (@A {}) + #^^^^{-1} ^^^^ + + main = + a : A + a = ping (@A {}) + # ^^^^ + + a + "# + ), + &[ + "A#ping(7) : A -[[ping(7)]]-> A", + "Bounce#pong(6) : A -[[pong(8)]]-> A", + // + "A#pong(8) : A -[[pong(8)]]-> A", + "A#ping(7) : A -[[ping(7)]]-> A", + // + "A#ping(7) : A -[[ping(7)]]-> A", + ], + ) + } } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 0f2e38709e..172069340d 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -48,15 +48,22 @@ macro_rules! write_parens { }; } -pub enum PrintLambdaSets { - Yes, - No, +pub struct DebugPrint { + pub print_lambda_sets: bool, + pub print_only_under_alias: bool, +} + +impl DebugPrint { + pub const NOTHING: DebugPrint = DebugPrint { + print_lambda_sets: false, + print_only_under_alias: false, + }; } struct Env<'a> { home: ModuleId, interns: &'a Interns, - print_lambda_sets: PrintLambdaSets, + debug: DebugPrint, } /// How many times a root variable appeared in Subs. @@ -376,13 +383,13 @@ fn content_to_string( home: ModuleId, interns: &Interns, named_result: NamedResult, - print_lambda_sets: PrintLambdaSets, + debug_print: DebugPrint, ) -> String { let mut buf = String::new(); let env = Env { home, interns, - print_lambda_sets, + debug: debug_print, }; let mut ctx = Context { able_variables: vec![], @@ -408,18 +415,11 @@ pub fn name_and_print_var( subs: &mut Subs, home: ModuleId, interns: &Interns, - print_lambda_sets: PrintLambdaSets, + debug_print: DebugPrint, ) -> String { let named_result = name_all_type_vars(var, subs); let content = subs.get_content_without_compacting(var); - content_to_string( - content, - subs, - home, - interns, - named_result, - print_lambda_sets, - ) + content_to_string(content, subs, home, interns, named_result, debug_print) } pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content { @@ -495,7 +495,7 @@ fn write_content<'a>( } }, Structure(flat_type) => write_flat_type(env, ctx, flat_type, subs, buf, parens), - Alias(symbol, args, _actual, _kind) => { + Alias(symbol, args, actual, _kind) => { let write_parens = parens == Parens::InTypeParam && !args.is_empty(); match *symbol { @@ -553,6 +553,11 @@ fn write_content<'a>( write_parens, ), + _ if env.debug.print_only_under_alias => write_parens!(write_parens, buf, { + let content = subs.get_content_without_compacting(*actual); + write_content(env, ctx, content, subs, buf, parens) + }), + _ => write_parens!(write_parens, buf, { write_symbol(env, *symbol, buf); @@ -571,7 +576,7 @@ fn write_content<'a>( roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRETTY_PRINT_ALIAS_CONTENTS, { buf.push_str("[[ but really "); - let content = subs.get_content_without_compacting(*_actual); + let content = subs.get_content_without_compacting(*actual); write_content(env, ctx, content, subs, buf, parens); buf.push_str("]]"); }); @@ -583,7 +588,7 @@ fn write_content<'a>( recursion_var, unspecialized, }) => { - debug_assert!(matches!(env.print_lambda_sets, PrintLambdaSets::Yes)); + debug_assert!(env.debug.print_lambda_sets); buf.push_str("[["); @@ -1192,22 +1197,19 @@ fn write_fn<'a>( ); } - match env.print_lambda_sets { - PrintLambdaSets::No => { - buf.push_str(" -> "); - } - PrintLambdaSets::Yes => { - buf.push_str(" -"); - write_content( - env, - ctx, - subs.get_content_without_compacting(closure), - subs, - buf, - parens, - ); - buf.push_str("-> "); - } + if !env.debug.print_lambda_sets { + buf.push_str(" -> "); + } else { + buf.push_str(" -"); + write_content( + env, + ctx, + subs.get_content_without_compacting(closure), + subs, + buf, + parens, + ); + buf.push_str("-> "); } write_content( diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index d6013ece65..eac1f69870 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -2786,7 +2786,10 @@ fn instantiate_lambda_sets_as_unspecialized( type_arguments, lambda_set_variables, }) => { - stack.extend(lambda_set_variables.iter_mut().rev().map(|ls| &mut ls.0)); + for lambda_set in lambda_set_variables.iter_mut() { + debug_assert!(matches!(lambda_set.0, Type::Variable(_))); + lambda_set.0 = new_uls(); + } stack.extend(type_arguments.iter_mut().rev()); } Type::Alias { @@ -2796,8 +2799,11 @@ fn instantiate_lambda_sets_as_unspecialized( actual, kind: _, } => { + for lambda_set in lambda_set_variables.iter_mut() { + debug_assert!(matches!(lambda_set.0, Type::Variable(_))); + lambda_set.0 = new_uls(); + } stack.push(actual); - stack.extend(lambda_set_variables.iter_mut().rev().map(|ls| &mut ls.0)); stack.extend(type_arguments.iter_mut().rev().map(|t| &mut t.typ)); } Type::HostExposedAlias { @@ -2807,8 +2813,11 @@ fn instantiate_lambda_sets_as_unspecialized( actual_var: _, actual, } => { + for lambda_set in lambda_set_variables.iter_mut() { + debug_assert!(matches!(lambda_set.0, Type::Variable(_))); + lambda_set.0 = new_uls(); + } stack.push(actual); - stack.extend(lambda_set_variables.iter_mut().rev().map(|ls| &mut ls.0)); stack.extend(type_arguments.iter_mut().rev()); } Type::Apply(_sym, args, _region) => { diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 77d1fc7f45..a7db882873 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -60,7 +60,7 @@ use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::pretty_print::name_and_print_var; -use roc_types::pretty_print::PrintLambdaSets; +use roc_types::pretty_print::DebugPrint; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; use snafu::OptionExt; @@ -468,7 +468,7 @@ impl<'a> EdModel<'a> { subs, self.module.env.home, &self.loaded_module.interns, - PrintLambdaSets::No, + DebugPrint::NOTHING, ); PoolStr::new(&pretty_var, self.module.env.pool) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index dca339ad84..cadc017eab 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -23,7 +23,7 @@ use roc_repl_eval::{ReplApp, ReplAppMemory}; use roc_reporting::report::DEFAULT_PALETTE; use roc_std::RocStr; use roc_target::TargetInfo; -use roc_types::pretty_print::{name_and_print_var, PrintLambdaSets}; +use roc_types::pretty_print::{name_and_print_var, DebugPrint}; const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; @@ -228,7 +228,7 @@ fn gen_and_eval_llvm<'a>( // pretty-print the expr type string for later. let expr_type_str = - name_and_print_var(main_fn_var, &mut subs, home, &interns, PrintLambdaSets::No); + name_and_print_var(main_fn_var, &mut subs, home, &interns, DebugPrint::NOTHING); let content = subs.get_content_without_compacting(main_fn_var); let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs index d5099754d4..c8d2fbd362 100644 --- a/repl_wasm/src/repl.rs +++ b/repl_wasm/src/repl.rs @@ -12,7 +12,7 @@ use roc_repl_eval::{ }; use roc_reporting::report::DEFAULT_PALETTE_HTML; use roc_target::TargetInfo; -use roc_types::pretty_print::{name_and_print_var, PrintLambdaSets}; +use roc_types::pretty_print::{name_and_print_var, DebugPrint}; use crate::{js_create_app, js_get_result_and_memory, js_run_app}; @@ -189,7 +189,7 @@ pub async fn entrypoint_from_js(src: String) -> Result { &mut subs, module_id, &interns, - PrintLambdaSets::No, + DebugPrint::NOTHING, ); let content = subs.get_content_without_compacting(main_fn_var);