From 53af6ed4ebed712825956e8bcae3b1e472c3bd74 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Apr 2023 10:50:30 -0500 Subject: [PATCH 1/6] Offer a way to print mono IR in uitest --- Cargo.lock | 4 + crates/compiler/test_solve_helpers/src/lib.rs | 3 - crates/compiler/uitest/Cargo.toml | 4 + crates/compiler/uitest/src/mono.rs | 116 ++++++++++++++++++ crates/compiler/uitest/src/uitest.rs | 14 ++- .../record_update_between_modules.txt | 9 ++ 6 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 crates/compiler/uitest/src/mono.rs create mode 100644 crates/compiler/uitest/tests/specialize/record_update_between_modules.txt diff --git a/Cargo.lock b/Cargo.lock index 09595750c4..a41b95749d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5084,8 +5084,12 @@ dependencies = [ "pretty_assertions", "regex", "roc_builtins", + "roc_collections", "roc_derive", "roc_load", + "roc_module", + "roc_mono", + "roc_packaging", "roc_parse", "roc_problem", "roc_reporting", diff --git a/crates/compiler/test_solve_helpers/src/lib.rs b/crates/compiler/test_solve_helpers/src/lib.rs index 61e0c0477d..063f43ef05 100644 --- a/crates/compiler/test_solve_helpers/src/lib.rs +++ b/crates/compiler/test_solve_helpers/src/lib.rs @@ -373,9 +373,6 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result io::Result<()> { + use roc_packaging::cache::RocCacheDir; + use std::path::PathBuf; + + let exec_mode = ExecutionMode::Executable; + + let arena = &Bump::new(); + + let filename = PathBuf::from("Test.roc"); + let src_dir = PathBuf::from("fake/test/path"); + + let load_config = LoadConfig { + target_info: roc_target::TargetInfo::default_x86_64(), + threading: Threading::Single, + render: roc_reporting::report::RenderTarget::Generic, + palette: roc_reporting::report::DEFAULT_PALETTE, + exec_mode, + }; + let loaded = roc_load::load_and_monomorphize_from_str( + arena, + filename, + module_source, + src_dir, + RocCacheDir::Disallowed, + load_config, + ); + + let loaded = match loaded { + Ok(x) => x, + Err(LoadMonomorphizedError::LoadingProblem(roc_load::LoadingProblem::FormattedReport( + report, + ))) => { + println!("{}", report); + panic!(); + } + Err(e) => panic!("{:?}", e), + }; + + use roc_load::MonomorphizedModule; + let MonomorphizedModule { + procedures, + exposed_to_host, + mut layout_interner, + interns, + .. + } = loaded; + + let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next(); + + check_procedures(arena, &interns, &mut layout_interner, &procedures); + + write_procedures(writer, layout_interner, procedures, main_fn_symbol) +} + +fn check_procedures<'a>( + arena: &'a Bump, + interns: &Interns, + interner: &mut STLayoutInterner<'a>, + procedures: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, +) { + use roc_mono::debug::{check_procs, format_problems}; + let problems = check_procs(arena, interner, procedures); + if problems.is_empty() { + return; + } + let formatted = format_problems(interns, interner, problems); + panic!("IR problems found:\n{formatted}"); +} + +fn write_procedures<'a>( + writer: &mut impl io::Write, + interner: STLayoutInterner<'a>, + procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, + opt_main_fn_symbol: Option, +) -> io::Result<()> { + let mut procs_strings = procedures + .values() + .map(|proc| proc.to_pretty(&interner, 200, false)) + .collect::>(); + + let opt_main_fn = opt_main_fn_symbol.map(|main_fn_symbol| { + let index = procedures + .keys() + .position(|(s, _)| *s == main_fn_symbol) + .unwrap(); + procs_strings.swap_remove(index) + }); + + procs_strings.sort(); + + if let Some(main_fn) = opt_main_fn { + procs_strings.push(main_fn); + } + + let mut procs = procs_strings.iter().peekable(); + while let Some(proc) = procs.next() { + if procs.peek().is_some() { + writeln!(writer, "{}", proc)?; + } else { + write!(writer, "{}", proc)?; + } + } + + Ok(()) +} diff --git a/crates/compiler/uitest/src/uitest.rs b/crates/compiler/uitest/src/uitest.rs index 7e75e435f1..f540793215 100644 --- a/crates/compiler/uitest/src/uitest.rs +++ b/crates/compiler/uitest/src/uitest.rs @@ -13,6 +13,8 @@ use test_solve_helpers::{ infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER, }; +mod mono; + fn main() -> Result<(), Box> { let args = Arguments::from_args(); @@ -110,6 +112,7 @@ struct TestCase { #[derive(Default)] struct PrintOptions { can_decls: bool, + mono: bool, } impl TestCase { @@ -154,6 +157,7 @@ impl TestCase { let opt = infer_opt.name("opt").unwrap().as_str(); match opt.trim() { "can_decls" => print_opts.can_decls = true, + "mono" => print_opts.mono = true, other => return Err(format!("unknown print option: {other:?}").into()), } } @@ -197,12 +201,20 @@ fn assemble_query_output( write_source_with_answers(&mut reflow, source, sorted_queries, 0)?; // Finish up with any remaining print options we were asked to provide. - let PrintOptions { can_decls } = print_options; + let PrintOptions { can_decls, mono } = print_options; if can_decls { writeln!(writer, "\n{EMIT_HEADER}can_decls")?; program.write_can_decls(writer)?; } + if mono { + writeln!(writer, "\n{EMIT_HEADER}mono")?; + // Unfortunately, with the current setup we must now recompile into the IR. + // TODO: extend the data returned by a monomorphized module to include + // that of a solved module. + mono::write_compiled_ir(writer, source)?; + } + Ok(()) } diff --git a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt new file mode 100644 index 0000000000..d661c2d824 --- /dev/null +++ b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt @@ -0,0 +1,9 @@ +# +opt print:mono +app "test" provides [main] to "./platform" + +main = "" + +# -emit:mono +procedure Test.0 (): + let Test.1 : Str = ""; + ret Test.1; From 1afae9affdeb6072f57926e1eb18d6cf0930d396 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Apr 2023 10:55:56 -0500 Subject: [PATCH 2/6] Update emit headers --- crates/compiler/uitest/src/uitest.rs | 36 +++++++++---------- ..._result_in_disjoint_parents_issue_4712.txt | 2 +- .../record_update_between_modules.txt | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/compiler/uitest/src/uitest.rs b/crates/compiler/uitest/src/uitest.rs index f540793215..937f556fe1 100644 --- a/crates/compiler/uitest/src/uitest.rs +++ b/crates/compiler/uitest/src/uitest.rs @@ -38,9 +38,9 @@ lazy_static! { static ref RE_OPT_INFER: Regex = Regex::new(r#"# \+opt infer:(?P.*)"#).unwrap(); - /// # +opt print: - static ref RE_OPT_PRINT: Regex = - Regex::new(r#"# \+opt print:(?P.*)"#).unwrap(); + /// # +emit: + static ref RE_EMIT: Regex = + Regex::new(r#"# \+emit:(?P.*)"#).unwrap(); } fn collect_uitest_files() -> io::Result> { @@ -81,7 +81,7 @@ fn run_test(path: PathBuf) -> Result<(), Failed> { let data = std::fs::read_to_string(&path)?; let TestCase { infer_options, - print_options, + emit_options, source, } = TestCase::parse(data)?; @@ -93,7 +93,7 @@ fn run_test(path: PathBuf) -> Result<(), Failed> { .truncate(true) .open(&path)?; - assemble_query_output(&mut fd, &source, inferred_program, print_options)?; + assemble_query_output(&mut fd, &source, inferred_program, emit_options)?; } check_for_changes(&path)?; @@ -105,12 +105,12 @@ const EMIT_HEADER: &str = "# -emit:"; struct TestCase { infer_options: InferOptions, - print_options: PrintOptions, + emit_options: EmitOptions, source: String, } #[derive(Default)] -struct PrintOptions { +struct EmitOptions { can_decls: bool, mono: bool, } @@ -125,7 +125,7 @@ impl TestCase { Ok(TestCase { infer_options: Self::parse_infer_options(&data)?, - print_options: Self::parse_print_options(&data)?, + emit_options: Self::parse_emit_options(&data)?, source: data, }) } @@ -149,20 +149,20 @@ impl TestCase { Ok(infer_opts) } - fn parse_print_options(data: &str) -> Result { - let mut print_opts = PrintOptions::default(); + fn parse_emit_options(data: &str) -> Result { + let mut emit_opts = EmitOptions::default(); - let found_infer_opts = RE_OPT_PRINT.captures_iter(data); + let found_infer_opts = RE_EMIT.captures_iter(data); for infer_opt in found_infer_opts { let opt = infer_opt.name("opt").unwrap().as_str(); match opt.trim() { - "can_decls" => print_opts.can_decls = true, - "mono" => print_opts.mono = true, - other => return Err(format!("unknown print option: {other:?}").into()), + "can_decls" => emit_opts.can_decls = true, + "mono" => emit_opts.mono = true, + other => return Err(format!("unknown emit option: {other:?}").into()), } } - Ok(print_opts) + Ok(emit_opts) } } @@ -190,7 +190,7 @@ fn assemble_query_output( writer: &mut impl io::Write, source: &str, inferred_program: InferredProgram, - print_options: PrintOptions, + emit_options: EmitOptions, ) -> io::Result<()> { // Reverse the queries so that we can pop them off the end as we pass through the lines. let (queries, program) = inferred_program.decompose(); @@ -200,8 +200,8 @@ fn assemble_query_output( let mut reflow = Reflow::new_unindented(writer); write_source_with_answers(&mut reflow, source, sorted_queries, 0)?; - // Finish up with any remaining print options we were asked to provide. - let PrintOptions { can_decls, mono } = print_options; + // Finish up with any remaining emit options we were asked to provide. + let EmitOptions { can_decls, mono } = emit_options; if can_decls { writeln!(writer, "\n{EMIT_HEADER}can_decls")?; program.write_can_decls(writer)?; diff --git a/crates/compiler/uitest/tests/lambda_set/disjoint_nested_lambdas_result_in_disjoint_parents_issue_4712.txt b/crates/compiler/uitest/tests/lambda_set/disjoint_nested_lambdas_result_in_disjoint_parents_issue_4712.txt index 1165451017..55a9402db2 100644 --- a/crates/compiler/uitest/tests/lambda_set/disjoint_nested_lambdas_result_in_disjoint_parents_issue_4712.txt +++ b/crates/compiler/uitest/tests/lambda_set/disjoint_nested_lambdas_result_in_disjoint_parents_issue_4712.txt @@ -1,5 +1,5 @@ -# +opt print:can_decls # +opt infer:print_only_under_alias +# +emit:can_decls app "test" provides [main] to "./platform" Parser a : {} -> a diff --git a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt index d661c2d824..c2bf54c779 100644 --- a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt +++ b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt @@ -1,4 +1,4 @@ -# +opt print:mono +# +emit:mono app "test" provides [main] to "./platform" main = "" From 0ec0568ef9bf848703f10f5e983d27e9d16547df Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Apr 2023 11:16:20 -0500 Subject: [PATCH 3/6] Drop unused function --- crates/compiler/test_solve_helpers/src/lib.rs | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/crates/compiler/test_solve_helpers/src/lib.rs b/crates/compiler/test_solve_helpers/src/lib.rs index 063f43ef05..8622659413 100644 --- a/crates/compiler/test_solve_helpers/src/lib.rs +++ b/crates/compiler/test_solve_helpers/src/lib.rs @@ -558,40 +558,3 @@ impl<'a> QueryCtx<'a> { }) } } - -pub fn infer_queries_help(src: &str, expected: impl FnOnce(&str), options: InferOptions) { - let InferredProgram { - program, - inferred_queries, - } = infer_queries(src, options).unwrap(); - - let mut output_parts = Vec::with_capacity(inferred_queries.len() + 2); - - if options.print_can_decls { - use roc_can::debug::{pretty_print_declarations, PPCtx}; - let ctx = PPCtx { - home: program.home, - interns: &program.interns, - print_lambda_names: true, - }; - let pretty_decls = pretty_print_declarations(&ctx, &program.declarations); - output_parts.push(pretty_decls); - output_parts.push("\n".to_owned()); - } - - for InferredQuery { elaboration, .. } in inferred_queries { - let output_part = match elaboration { - Elaboration::Specialization { - specialized_name, - typ, - } => format!("{specialized_name} : {typ}"), - Elaboration::Source { source, typ } => format!("{source} : {typ}"), - Elaboration::Instantiation { .. } => panic!("Use uitest instead"), - }; - output_parts.push(output_part); - } - - let pretty_output = output_parts.join("\n"); - - expected(&pretty_output); -} From 014ec8c0922488b6d9d111d6541a479194f75c5c Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Apr 2023 11:35:33 -0500 Subject: [PATCH 4/6] Support multiple modules in uitest --- crates/compiler/solve/tests/solve_expr.rs | 4 +- crates/compiler/test_solve_helpers/src/lib.rs | 16 +- crates/compiler/uitest/src/mono.rs | 31 +++- crates/compiler/uitest/src/uitest.rs | 151 ++++++++++++++++-- .../record_update_between_modules.txt | 20 ++- 5 files changed, 192 insertions(+), 30 deletions(-) diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 1aeb12b22d..c86499166a 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -31,7 +31,7 @@ mod solve_expr { .. }, src, - ) = run_load_and_infer(src, false)?; + ) = run_load_and_infer(src, [], false)?; let mut can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); @@ -103,7 +103,7 @@ mod solve_expr { interns, abilities_store, .. - } = run_load_and_infer(src, false).unwrap().0; + } = run_load_and_infer(src, [], false).unwrap().0; let can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); diff --git a/crates/compiler/test_solve_helpers/src/lib.rs b/crates/compiler/test_solve_helpers/src/lib.rs index 8622659413..74a818d9c1 100644 --- a/crates/compiler/test_solve_helpers/src/lib.rs +++ b/crates/compiler/test_solve_helpers/src/lib.rs @@ -44,8 +44,9 @@ fn promote_expr_to_module(src: &str) -> String { buffer } -pub fn run_load_and_infer( +pub fn run_load_and_infer<'a>( src: &str, + dependencies: impl IntoIterator, no_promote: bool, ) -> Result<(LoadedModule, String), std::io::Error> { use tempfile::tempdir; @@ -65,6 +66,11 @@ pub fn run_load_and_infer( let loaded = { let dir = tempdir()?; + + for (file, source) in dependencies { + std::fs::write(dir.path().join(format!("{file}.roc")), source)?; + } + let filename = PathBuf::from("Test.roc"); let file_path = dir.path().join(filename); let result = roc_load::load_and_typecheck_str( @@ -338,7 +344,11 @@ impl InferredProgram { } } -pub fn infer_queries(src: &str, options: InferOptions) -> Result> { +pub fn infer_queries<'a>( + src: &str, + dependencies: impl IntoIterator, + options: InferOptions, +) -> Result> { let ( LoadedModule { module_id: home, @@ -351,7 +361,7 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result io::Result<()> { +#[derive(Default)] +pub struct MonoOptions { + pub no_check: bool, +} + +pub fn write_compiled_ir<'a>( + writer: &mut impl io::Write, + test_module: &str, + dependencies: impl IntoIterator, + options: MonoOptions, +) -> io::Result<()> { use roc_packaging::cache::RocCacheDir; use std::path::PathBuf; @@ -17,8 +28,14 @@ pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io let arena = &Bump::new(); + let dir = tempdir()?; + + for (file, source) in dependencies { + std::fs::write(dir.path().join(format!("{file}.roc")), source)?; + } + let filename = PathBuf::from("Test.roc"); - let src_dir = PathBuf::from("fake/test/path"); + let file_path = dir.path().join(filename); let load_config = LoadConfig { target_info: roc_target::TargetInfo::default_x86_64(), @@ -29,9 +46,9 @@ pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io }; let loaded = roc_load::load_and_monomorphize_from_str( arena, - filename, - module_source, - src_dir, + file_path, + test_module, + dir.path().to_path_buf(), RocCacheDir::Disallowed, load_config, ); @@ -58,7 +75,9 @@ pub fn write_compiled_ir(writer: &mut impl io::Write, module_source: &str) -> io let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next(); - check_procedures(arena, &interns, &mut layout_interner, &procedures); + if !options.no_check { + check_procedures(arena, &interns, &mut layout_interner, &procedures); + } write_procedures(writer, layout_interner, procedures, main_fn_symbol) } diff --git a/crates/compiler/uitest/src/uitest.rs b/crates/compiler/uitest/src/uitest.rs index 937f556fe1..c072cd7bbf 100644 --- a/crates/compiler/uitest/src/uitest.rs +++ b/crates/compiler/uitest/src/uitest.rs @@ -8,7 +8,9 @@ use std::{ use lazy_static::lazy_static; use libtest_mimic::{run, Arguments, Failed, Trial}; +use mono::MonoOptions; use regex::Regex; +use roc_collections::VecMap; use test_solve_helpers::{ infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER, }; @@ -38,9 +40,17 @@ lazy_static! { static ref RE_OPT_INFER: Regex = Regex::new(r#"# \+opt infer:(?P.*)"#).unwrap(); + /// # +opt mono: + static ref RE_OPT_MONO: Regex = + Regex::new(r#"# \+opt mono:(?P.*)"#).unwrap(); + /// # +emit: static ref RE_EMIT: Regex = Regex::new(r#"# \+emit:(?P.*)"#).unwrap(); + + /// ## module + static ref RE_MODULE: Regex = + Regex::new(r#"## module (?P.*)"#).unwrap(); } fn collect_uitest_files() -> io::Result> { @@ -82,10 +92,18 @@ fn run_test(path: PathBuf) -> Result<(), Failed> { let TestCase { infer_options, emit_options, - source, - } = TestCase::parse(data)?; + mono_options, + program, + } = TestCase::parse(&data)?; - let inferred_program = infer_queries(&source, infer_options)?; + let inferred_program = infer_queries( + program.test_module, + program + .other_modules + .iter() + .map(|(md, src)| (&**md, &**src)), + infer_options, + )?; { let mut fd = fs::OpenOptions::new() @@ -93,7 +111,13 @@ fn run_test(path: PathBuf) -> Result<(), Failed> { .truncate(true) .open(&path)?; - assemble_query_output(&mut fd, &source, inferred_program, emit_options)?; + assemble_query_output( + &mut fd, + program, + inferred_program, + mono_options, + emit_options, + )?; } check_for_changes(&path)?; @@ -103,10 +127,17 @@ fn run_test(path: PathBuf) -> Result<(), Failed> { const EMIT_HEADER: &str = "# -emit:"; -struct TestCase { +struct Modules<'a> { + before_any: &'a str, + other_modules: VecMap<&'a str, &'a str>, + test_module: &'a str, +} + +struct TestCase<'a> { infer_options: InferOptions, + mono_options: MonoOptions, emit_options: EmitOptions, - source: String, + program: Modules<'a>, } #[derive(Default)] @@ -115,21 +146,74 @@ struct EmitOptions { mono: bool, } -impl TestCase { - fn parse(mut data: String) -> Result { +impl<'a> TestCase<'a> { + fn parse(mut data: &'a str) -> Result { // Drop anything following `# -emit:` header lines; that's the output. if let Some(drop_at) = data.find(EMIT_HEADER) { - data.truncate(drop_at); - data.truncate(data.trim_end().len()); + data = data[..drop_at].trim_end(); } + let infer_options = Self::parse_infer_options(&data)?; + let mono_options = Self::parse_mono_options(&data)?; + let emit_options = Self::parse_emit_options(&data)?; + + let program = Self::parse_modules(data); + Ok(TestCase { - infer_options: Self::parse_infer_options(&data)?, - emit_options: Self::parse_emit_options(&data)?, - source: data, + infer_options, + mono_options, + emit_options, + program, }) } + fn parse_modules(data: &'a str) -> Modules<'a> { + let mut module_starts = RE_MODULE.captures_iter(&data).peekable(); + + let first_module_start = match module_starts.peek() { + None => { + // This is just a single module with no name; it is the test module. + return Modules { + before_any: Default::default(), + other_modules: Default::default(), + test_module: data, + }; + } + Some(p) => p.get(0).unwrap().start(), + }; + + let before_any = data[..first_module_start].trim(); + + let mut test_module = None; + let mut other_modules = VecMap::new(); + + while let Some(module_start) = module_starts.next() { + let module_name = module_start.name("name").unwrap().as_str(); + let module_start = module_start.get(0).unwrap().end(); + let module = match module_starts.peek() { + None => &data[module_start..], + Some(next_module_start) => { + let module_end = next_module_start.get(0).unwrap().start(); + &data[module_start..module_end] + } + } + .trim(); + + if module_name == "Test" { + test_module = Some(module); + } else { + other_modules.insert(module_name, module); + } + } + + let test_module = test_module.expect("no Test module found"); + Modules { + before_any, + other_modules, + test_module, + } + } + fn parse_infer_options(data: &str) -> Result { let mut infer_opts = InferOptions { no_promote: true, @@ -149,6 +233,21 @@ impl TestCase { Ok(infer_opts) } + fn parse_mono_options(data: &str) -> Result { + let mut mono_opts = MonoOptions::default(); + + let found_infer_opts = RE_OPT_MONO.captures_iter(data); + for infer_opt in found_infer_opts { + let opt = infer_opt.name("opt").unwrap().as_str(); + match opt.trim() { + "no_check" => mono_opts.no_check = true, + other => return Err(format!("unknown mono option: {other:?}").into()), + } + } + + Ok(mono_opts) + } + fn parse_emit_options(data: &str) -> Result { let mut emit_opts = EmitOptions::default(); @@ -188,17 +287,37 @@ fn check_for_changes(path: &Path) -> Result<(), Failed> { /// Assemble the output for a test, with queries elaborated in-line. fn assemble_query_output( writer: &mut impl io::Write, - source: &str, + program: Modules<'_>, inferred_program: InferredProgram, + mono_options: MonoOptions, emit_options: EmitOptions, ) -> io::Result<()> { + let Modules { + before_any, + other_modules, + test_module, + } = program; + + if !before_any.is_empty() { + writeln!(writer, "{before_any}\n")?; + } + + for (module, source) in other_modules.iter() { + writeln!(writer, "## module {module}")?; + writeln!(writer, "{}\n", source)?; + } + + if !other_modules.is_empty() { + writeln!(writer, "## module Test")?; + } + // Reverse the queries so that we can pop them off the end as we pass through the lines. let (queries, program) = inferred_program.decompose(); let mut sorted_queries = queries.into_sorted(); sorted_queries.reverse(); let mut reflow = Reflow::new_unindented(writer); - write_source_with_answers(&mut reflow, source, sorted_queries, 0)?; + write_source_with_answers(&mut reflow, test_module, sorted_queries, 0)?; // Finish up with any remaining emit options we were asked to provide. let EmitOptions { can_decls, mono } = emit_options; @@ -212,7 +331,7 @@ fn assemble_query_output( // Unfortunately, with the current setup we must now recompile into the IR. // TODO: extend the data returned by a monomorphized module to include // that of a solved module. - mono::write_compiled_ir(writer, source)?; + mono::write_compiled_ir(writer, test_module, other_modules, mono_options)?; } Ok(()) diff --git a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt index c2bf54c779..4aa46d647f 100644 --- a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt +++ b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt @@ -1,9 +1,23 @@ # +emit:mono -app "test" provides [main] to "./platform" +# +opt mono:no_check -main = "" +## module Dep +interface Dep exposes [defaultRequest] imports [] + +defaultRequest = { + url: "", + body: "", +} + +## module Test +app "test" imports [Dep.{ defaultRequest }] provides [main] to "./platform" + +main = + { defaultRequest & url: "http://www.example.com" } # -emit:mono procedure Test.0 (): - let Test.1 : Str = ""; + let Test.3 : Str = "http://www.example.com"; + let Test.2 : Str = StructAtIndex 0 Dep.0; + let Test.1 : {Str, Str} = Struct {Test.2, Test.3}; ret Test.1; From bbd09fed3c2a305da01fb7046034c6bee25dec2a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Apr 2023 11:36:06 -0500 Subject: [PATCH 5/6] Remove redundant references --- crates/compiler/uitest/src/uitest.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/compiler/uitest/src/uitest.rs b/crates/compiler/uitest/src/uitest.rs index c072cd7bbf..01c75487b3 100644 --- a/crates/compiler/uitest/src/uitest.rs +++ b/crates/compiler/uitest/src/uitest.rs @@ -153,9 +153,9 @@ impl<'a> TestCase<'a> { data = data[..drop_at].trim_end(); } - let infer_options = Self::parse_infer_options(&data)?; - let mono_options = Self::parse_mono_options(&data)?; - let emit_options = Self::parse_emit_options(&data)?; + let infer_options = Self::parse_infer_options(data)?; + let mono_options = Self::parse_mono_options(data)?; + let emit_options = Self::parse_emit_options(data)?; let program = Self::parse_modules(data); @@ -168,7 +168,7 @@ impl<'a> TestCase<'a> { } fn parse_modules(data: &'a str) -> Modules<'a> { - let mut module_starts = RE_MODULE.captures_iter(&data).peekable(); + let mut module_starts = RE_MODULE.captures_iter(data).peekable(); let first_module_start = match module_starts.peek() { None => { From 65911f88b12cf7fe278386cc8b4fa621b038c9e1 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Apr 2023 11:39:14 -0500 Subject: [PATCH 6/6] Handle record updates of imported module thunks Closes #5131 --- crates/compiler/mono/src/ir.rs | 14 +++++++------- .../specialize/record_update_between_modules.txt | 11 ++++++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index dc807292db..e453f75319 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -5049,9 +5049,12 @@ pub fn with_hole<'a>( ); } CopyExisting(index) => { - let record_needs_specialization = - procs.ability_member_aliases.get(structure).is_some(); - let specialized_structure_sym = if record_needs_specialization { + let structure_needs_specialization = + procs.ability_member_aliases.get(structure).is_some() + || procs.is_module_thunk(structure) + || procs.is_imported_module_thunk(structure); + + let specialized_structure_sym = if structure_needs_specialization { // We need to specialize the record now; create a new one for it. // TODO: reuse this symbol for all updates env.unique_symbol() @@ -5068,10 +5071,7 @@ pub fn with_hole<'a>( stmt = Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); - // If the records needs specialization or it's a thunk, we need to - // create the specialized definition or force the thunk, respectively. - // Both cases are handled below. - if record_needs_specialization || procs.is_module_thunk(structure) { + if structure_needs_specialization { stmt = specialize_symbol( env, procs, diff --git a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt index 4aa46d647f..f12c711389 100644 --- a/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt +++ b/crates/compiler/uitest/tests/specialize/record_update_between_modules.txt @@ -16,8 +16,17 @@ main = { defaultRequest & url: "http://www.example.com" } # -emit:mono +procedure Dep.0 (): + let Dep.2 : Str = ""; + let Dep.3 : Str = ""; + let Dep.1 : {Str, Str} = Struct {Dep.2, Dep.3}; + ret Dep.1; + procedure Test.0 (): let Test.3 : Str = "http://www.example.com"; - let Test.2 : Str = StructAtIndex 0 Dep.0; + let Test.4 : {Str, Str} = CallByName Dep.0; + let Test.2 : Str = StructAtIndex 0 Test.4; + inc Test.2; + dec Test.4; let Test.1 : {Str, Str} = Struct {Test.2, Test.3}; ret Test.1;