mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Merge pull request #5284 from roc-lang/i5131
Handle record updates of imported module thunks
This commit is contained in:
commit
526cd25611
9 changed files with 359 additions and 83 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -5084,8 +5084,12 @@ dependencies = [
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"regex",
|
"regex",
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
|
"roc_collections",
|
||||||
"roc_derive",
|
"roc_derive",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
|
"roc_module",
|
||||||
|
"roc_mono",
|
||||||
|
"roc_packaging",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_problem",
|
"roc_problem",
|
||||||
"roc_reporting",
|
"roc_reporting",
|
||||||
|
|
|
@ -5049,9 +5049,12 @@ pub fn with_hole<'a>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CopyExisting(index) => {
|
CopyExisting(index) => {
|
||||||
let record_needs_specialization =
|
let structure_needs_specialization =
|
||||||
procs.ability_member_aliases.get(structure).is_some();
|
procs.ability_member_aliases.get(structure).is_some()
|
||||||
let specialized_structure_sym = if record_needs_specialization {
|
|| 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.
|
// We need to specialize the record now; create a new one for it.
|
||||||
// TODO: reuse this symbol for all updates
|
// TODO: reuse this symbol for all updates
|
||||||
env.unique_symbol()
|
env.unique_symbol()
|
||||||
|
@ -5068,10 +5071,7 @@ pub fn with_hole<'a>(
|
||||||
stmt =
|
stmt =
|
||||||
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
|
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
|
||||||
|
|
||||||
// If the records needs specialization or it's a thunk, we need to
|
if structure_needs_specialization {
|
||||||
// create the specialized definition or force the thunk, respectively.
|
|
||||||
// Both cases are handled below.
|
|
||||||
if record_needs_specialization || procs.is_module_thunk(structure) {
|
|
||||||
stmt = specialize_symbol(
|
stmt = specialize_symbol(
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
|
|
|
@ -31,7 +31,7 @@ mod solve_expr {
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
src,
|
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 mut can_problems = can_problems.remove(&home).unwrap_or_default();
|
||||||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||||||
|
@ -103,7 +103,7 @@ mod solve_expr {
|
||||||
interns,
|
interns,
|
||||||
abilities_store,
|
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 can_problems = can_problems.remove(&home).unwrap_or_default();
|
||||||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||||||
|
|
|
@ -44,8 +44,9 @@ fn promote_expr_to_module(src: &str) -> String {
|
||||||
buffer
|
buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_load_and_infer(
|
pub fn run_load_and_infer<'a>(
|
||||||
src: &str,
|
src: &str,
|
||||||
|
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||||
no_promote: bool,
|
no_promote: bool,
|
||||||
) -> Result<(LoadedModule, String), std::io::Error> {
|
) -> Result<(LoadedModule, String), std::io::Error> {
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
@ -65,6 +66,11 @@ pub fn run_load_and_infer(
|
||||||
|
|
||||||
let loaded = {
|
let loaded = {
|
||||||
let dir = tempdir()?;
|
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 filename = PathBuf::from("Test.roc");
|
||||||
let file_path = dir.path().join(filename);
|
let file_path = dir.path().join(filename);
|
||||||
let result = roc_load::load_and_typecheck_str(
|
let result = roc_load::load_and_typecheck_str(
|
||||||
|
@ -338,7 +344,11 @@ impl InferredProgram {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram, Box<dyn Error>> {
|
pub fn infer_queries<'a>(
|
||||||
|
src: &str,
|
||||||
|
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||||
|
options: InferOptions,
|
||||||
|
) -> Result<InferredProgram, Box<dyn Error>> {
|
||||||
let (
|
let (
|
||||||
LoadedModule {
|
LoadedModule {
|
||||||
module_id: home,
|
module_id: home,
|
||||||
|
@ -351,7 +361,7 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
src,
|
src,
|
||||||
) = run_load_and_infer(src, options.no_promote)?;
|
) = run_load_and_infer(src, dependencies, options.no_promote)?;
|
||||||
|
|
||||||
let declarations = declarations_by_id.remove(&home).unwrap();
|
let declarations = declarations_by_id.remove(&home).unwrap();
|
||||||
let subs = solved.inner_mut();
|
let subs = solved.inner_mut();
|
||||||
|
@ -373,9 +383,6 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram
|
||||||
|
|
||||||
let line_info = LineInfo::new(&src);
|
let line_info = LineInfo::new(&src);
|
||||||
let queries = parse_queries(&src, &line_info);
|
let queries = parse_queries(&src, &line_info);
|
||||||
if queries.is_empty() {
|
|
||||||
return Err("No queries provided!".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut inferred_queries = Vec::with_capacity(queries.len());
|
let mut inferred_queries = Vec::with_capacity(queries.len());
|
||||||
let exposed_by_module = ExposedByModule::default();
|
let exposed_by_module = ExposedByModule::default();
|
||||||
|
@ -561,40 +568,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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,8 +14,12 @@ harness = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_builtins = { path = "../builtins" }
|
roc_builtins = { path = "../builtins" }
|
||||||
|
roc_collections = { path = "../collections" }
|
||||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
|
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
|
||||||
roc_load = { path = "../load" }
|
roc_load = { path = "../load" }
|
||||||
|
roc_packaging = { path = "../../packaging" }
|
||||||
|
roc_module = { path = "../module", features = ["debug-symbols"] }
|
||||||
|
roc_mono = { path = "../mono" }
|
||||||
roc_parse = { path = "../parse" }
|
roc_parse = { path = "../parse" }
|
||||||
roc_problem = { path = "../problem" }
|
roc_problem = { path = "../problem" }
|
||||||
roc_reporting = { path = "../../reporting" }
|
roc_reporting = { path = "../../reporting" }
|
||||||
|
|
135
crates/compiler/uitest/src/mono.rs
Normal file
135
crates/compiler/uitest/src/mono.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_collections::MutMap;
|
||||||
|
use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError, Threading};
|
||||||
|
use roc_module::symbol::{Interns, Symbol};
|
||||||
|
use roc_mono::{
|
||||||
|
ir::{Proc, ProcLayout},
|
||||||
|
layout::STLayoutInterner,
|
||||||
|
};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[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<Item = (&'a str, &'a str)>,
|
||||||
|
options: MonoOptions,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
use roc_packaging::cache::RocCacheDir;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
let exec_mode = ExecutionMode::Executable;
|
||||||
|
|
||||||
|
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 file_path = dir.path().join(filename);
|
||||||
|
|
||||||
|
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,
|
||||||
|
file_path,
|
||||||
|
test_module,
|
||||||
|
dir.path().to_path_buf(),
|
||||||
|
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();
|
||||||
|
|
||||||
|
if !options.no_check {
|
||||||
|
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<Symbol>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let mut procs_strings = procedures
|
||||||
|
.values()
|
||||||
|
.map(|proc| proc.to_pretty(&interner, 200, false))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
|
@ -8,11 +8,15 @@ use std::{
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use libtest_mimic::{run, Arguments, Failed, Trial};
|
use libtest_mimic::{run, Arguments, Failed, Trial};
|
||||||
|
use mono::MonoOptions;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use roc_collections::VecMap;
|
||||||
use test_solve_helpers::{
|
use test_solve_helpers::{
|
||||||
infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER,
|
infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod mono;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let args = Arguments::from_args();
|
let args = Arguments::from_args();
|
||||||
|
|
||||||
|
@ -36,9 +40,17 @@ lazy_static! {
|
||||||
static ref RE_OPT_INFER: Regex =
|
static ref RE_OPT_INFER: Regex =
|
||||||
Regex::new(r#"# \+opt infer:(?P<opt>.*)"#).unwrap();
|
Regex::new(r#"# \+opt infer:(?P<opt>.*)"#).unwrap();
|
||||||
|
|
||||||
/// # +opt print:<opt>
|
/// # +opt mono:<opt>
|
||||||
static ref RE_OPT_PRINT: Regex =
|
static ref RE_OPT_MONO: Regex =
|
||||||
Regex::new(r#"# \+opt print:(?P<opt>.*)"#).unwrap();
|
Regex::new(r#"# \+opt mono:(?P<opt>.*)"#).unwrap();
|
||||||
|
|
||||||
|
/// # +emit:<opt>
|
||||||
|
static ref RE_EMIT: Regex =
|
||||||
|
Regex::new(r#"# \+emit:(?P<opt>.*)"#).unwrap();
|
||||||
|
|
||||||
|
/// ## module <name>
|
||||||
|
static ref RE_MODULE: Regex =
|
||||||
|
Regex::new(r#"## module (?P<name>.*)"#).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_uitest_files() -> io::Result<Vec<PathBuf>> {
|
fn collect_uitest_files() -> io::Result<Vec<PathBuf>> {
|
||||||
|
@ -79,11 +91,19 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
|
||||||
let data = std::fs::read_to_string(&path)?;
|
let data = std::fs::read_to_string(&path)?;
|
||||||
let TestCase {
|
let TestCase {
|
||||||
infer_options,
|
infer_options,
|
||||||
print_options,
|
emit_options,
|
||||||
source,
|
mono_options,
|
||||||
} = TestCase::parse(data)?;
|
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()
|
let mut fd = fs::OpenOptions::new()
|
||||||
|
@ -91,7 +111,13 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(&path)?;
|
.open(&path)?;
|
||||||
|
|
||||||
assemble_query_output(&mut fd, &source, inferred_program, print_options)?;
|
assemble_query_output(
|
||||||
|
&mut fd,
|
||||||
|
program,
|
||||||
|
inferred_program,
|
||||||
|
mono_options,
|
||||||
|
emit_options,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
check_for_changes(&path)?;
|
check_for_changes(&path)?;
|
||||||
|
@ -101,32 +127,93 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
|
||||||
|
|
||||||
const EMIT_HEADER: &str = "# -emit:";
|
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,
|
infer_options: InferOptions,
|
||||||
print_options: PrintOptions,
|
mono_options: MonoOptions,
|
||||||
source: String,
|
emit_options: EmitOptions,
|
||||||
|
program: Modules<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct PrintOptions {
|
struct EmitOptions {
|
||||||
can_decls: bool,
|
can_decls: bool,
|
||||||
|
mono: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCase {
|
impl<'a> TestCase<'a> {
|
||||||
fn parse(mut data: String) -> Result<Self, Failed> {
|
fn parse(mut data: &'a str) -> Result<Self, Failed> {
|
||||||
// Drop anything following `# -emit:` header lines; that's the output.
|
// Drop anything following `# -emit:` header lines; that's the output.
|
||||||
if let Some(drop_at) = data.find(EMIT_HEADER) {
|
if let Some(drop_at) = data.find(EMIT_HEADER) {
|
||||||
data.truncate(drop_at);
|
data = data[..drop_at].trim_end();
|
||||||
data.truncate(data.trim_end().len());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
Ok(TestCase {
|
||||||
infer_options: Self::parse_infer_options(&data)?,
|
infer_options,
|
||||||
print_options: Self::parse_print_options(&data)?,
|
mono_options,
|
||||||
source: data,
|
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<InferOptions, Failed> {
|
fn parse_infer_options(data: &str) -> Result<InferOptions, Failed> {
|
||||||
let mut infer_opts = InferOptions {
|
let mut infer_opts = InferOptions {
|
||||||
no_promote: true,
|
no_promote: true,
|
||||||
|
@ -146,19 +233,35 @@ impl TestCase {
|
||||||
Ok(infer_opts)
|
Ok(infer_opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_print_options(data: &str) -> Result<PrintOptions, Failed> {
|
fn parse_mono_options(data: &str) -> Result<MonoOptions, Failed> {
|
||||||
let mut print_opts = PrintOptions::default();
|
let mut mono_opts = MonoOptions::default();
|
||||||
|
|
||||||
let found_infer_opts = RE_OPT_PRINT.captures_iter(data);
|
let found_infer_opts = RE_OPT_MONO.captures_iter(data);
|
||||||
for infer_opt in found_infer_opts {
|
for infer_opt in found_infer_opts {
|
||||||
let opt = infer_opt.name("opt").unwrap().as_str();
|
let opt = infer_opt.name("opt").unwrap().as_str();
|
||||||
match opt.trim() {
|
match opt.trim() {
|
||||||
"can_decls" => print_opts.can_decls = true,
|
"no_check" => mono_opts.no_check = true,
|
||||||
other => return Err(format!("unknown print option: {other:?}").into()),
|
other => return Err(format!("unknown mono option: {other:?}").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(print_opts)
|
Ok(mono_opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_emit_options(data: &str) -> Result<EmitOptions, Failed> {
|
||||||
|
let mut emit_opts = EmitOptions::default();
|
||||||
|
|
||||||
|
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" => emit_opts.can_decls = true,
|
||||||
|
"mono" => emit_opts.mono = true,
|
||||||
|
other => return Err(format!("unknown emit option: {other:?}").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(emit_opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,25 +287,53 @@ fn check_for_changes(path: &Path) -> Result<(), Failed> {
|
||||||
/// Assemble the output for a test, with queries elaborated in-line.
|
/// Assemble the output for a test, with queries elaborated in-line.
|
||||||
fn assemble_query_output(
|
fn assemble_query_output(
|
||||||
writer: &mut impl io::Write,
|
writer: &mut impl io::Write,
|
||||||
source: &str,
|
program: Modules<'_>,
|
||||||
inferred_program: InferredProgram,
|
inferred_program: InferredProgram,
|
||||||
print_options: PrintOptions,
|
mono_options: MonoOptions,
|
||||||
|
emit_options: EmitOptions,
|
||||||
) -> io::Result<()> {
|
) -> 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.
|
// 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 (queries, program) = inferred_program.decompose();
|
||||||
let mut sorted_queries = queries.into_sorted();
|
let mut sorted_queries = queries.into_sorted();
|
||||||
sorted_queries.reverse();
|
sorted_queries.reverse();
|
||||||
|
|
||||||
let mut reflow = Reflow::new_unindented(writer);
|
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 print options we were asked to provide.
|
// Finish up with any remaining emit options we were asked to provide.
|
||||||
let PrintOptions { can_decls } = print_options;
|
let EmitOptions { can_decls, mono } = emit_options;
|
||||||
if can_decls {
|
if can_decls {
|
||||||
writeln!(writer, "\n{EMIT_HEADER}can_decls")?;
|
writeln!(writer, "\n{EMIT_HEADER}can_decls")?;
|
||||||
program.write_can_decls(writer)?;
|
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, test_module, other_modules, mono_options)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# +opt print:can_decls
|
|
||||||
# +opt infer:print_only_under_alias
|
# +opt infer:print_only_under_alias
|
||||||
|
# +emit:can_decls
|
||||||
app "test" provides [main] to "./platform"
|
app "test" provides [main] to "./platform"
|
||||||
|
|
||||||
Parser a : {} -> a
|
Parser a : {} -> a
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# +emit:mono
|
||||||
|
# +opt mono:no_check
|
||||||
|
|
||||||
|
## 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 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.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;
|
Loading…
Add table
Add a link
Reference in a new issue