mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
Merge branch 'main' into parser-docs
This commit is contained in:
commit
6d64bbb65a
26 changed files with 447 additions and 164 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2417,6 +2417,7 @@ dependencies = [
|
|||
"mimalloc",
|
||||
"parking_lot",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
|
|
|
@ -71,6 +71,7 @@ inkwell.workspace = true
|
|||
libc.workspace = true
|
||||
libloading.workspace = true
|
||||
mimalloc.workspace = true
|
||||
regex.workspace = true
|
||||
signal-hook.workspace = true
|
||||
strum.workspace = true
|
||||
target-lexicon.workspace = true
|
||||
|
|
|
@ -13,13 +13,16 @@ use roc_build::program::{
|
|||
handle_error_module, handle_loading_problem, standard_load_config, BuildFileError,
|
||||
BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME,
|
||||
};
|
||||
use roc_collections::MutMap;
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
use roc_gen_dev::AssemblyBackendMode;
|
||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_load::{ExpectMetadata, Threading};
|
||||
use roc_module::symbol::ModuleId;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_packaging::cache::RocCacheDir;
|
||||
use roc_packaging::tarball::Compression;
|
||||
use roc_reporting::report::ANSI_STYLE_CODES;
|
||||
use roc_target::Target;
|
||||
use std::env;
|
||||
use std::ffi::{CString, OsStr, OsString};
|
||||
|
@ -28,7 +31,7 @@ use std::mem::ManuallyDrop;
|
|||
use std::os::raw::{c_char, c_int};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::time::Instant;
|
||||
use std::time::{Duration, Instant};
|
||||
use strum::IntoEnumIterator;
|
||||
use target_lexicon::{Architecture, Triple};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
|
@ -61,6 +64,7 @@ pub const FLAG_LIB: &str = "lib";
|
|||
pub const FLAG_NO_LINK: &str = "no-link";
|
||||
pub const FLAG_TARGET: &str = "target";
|
||||
pub const FLAG_TIME: &str = "time";
|
||||
pub const FLAG_VERBOSE: &str = "verbose";
|
||||
pub const FLAG_LINKER: &str = "linker";
|
||||
pub const FLAG_PREBUILT: &str = "prebuilt-platform";
|
||||
pub const FLAG_CHECK: &str = "check";
|
||||
|
@ -112,7 +116,7 @@ pub fn build_app() -> Command {
|
|||
|
||||
let flag_profiling = Arg::new(FLAG_PROFILING)
|
||||
.long(FLAG_PROFILING)
|
||||
.help("Keep debug info in the final generated program even in optmized builds")
|
||||
.help("Keep debug info in the final generated program even in optimized builds")
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false);
|
||||
|
||||
|
@ -234,6 +238,13 @@ pub fn build_app() -> Command {
|
|||
.arg(flag_linker.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(flag_fuzz.clone())
|
||||
.arg(
|
||||
Arg::new(FLAG_VERBOSE)
|
||||
.long(FLAG_VERBOSE)
|
||||
.help("Print detailed test statistics by module")
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false)
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file for the main module")
|
||||
|
@ -435,12 +446,18 @@ pub fn test(_matches: &ArgMatches, _triple: Triple) -> io::Result<i32> {
|
|||
todo!("running tests does not work on windows right now")
|
||||
}
|
||||
|
||||
struct ModuleTestResults {
|
||||
module_id: ModuleId,
|
||||
failed_count: usize,
|
||||
passed_count: usize,
|
||||
tests_duration: Duration,
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||||
use roc_build::program::report_problems_monomorphized;
|
||||
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadMonomorphizedError};
|
||||
use roc_packaging::cache;
|
||||
use roc_reporting::report::ANSI_STYLE_CODES;
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
@ -514,8 +531,10 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
let mut expectations = std::mem::take(&mut loaded.expectations);
|
||||
|
||||
let interns = loaded.interns.clone();
|
||||
let sources = loaded.sources.clone();
|
||||
|
||||
let (lib, expects, layout_interner) = roc_repl_expect::run::expect_mono_module_to_dylib(
|
||||
let (dyn_lib, expects_by_module, layout_interner) =
|
||||
roc_repl_expect::run::expect_mono_module_to_dylib(
|
||||
arena,
|
||||
target.clone(),
|
||||
loaded,
|
||||
|
@ -542,21 +561,45 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
|
||||
let mut writer = std::io::stdout();
|
||||
|
||||
let mut total_failed_count = 0;
|
||||
let mut total_passed_count = 0;
|
||||
|
||||
let mut results_by_module = Vec::new();
|
||||
let global_layout_interner = layout_interner.into_global();
|
||||
|
||||
let compilation_duration = start_time.elapsed();
|
||||
|
||||
for (module_id, expects) in expects_by_module.into_iter() {
|
||||
let test_start_time = Instant::now();
|
||||
|
||||
let (failed_count, passed_count) = roc_repl_expect::run::run_toplevel_expects(
|
||||
&mut writer,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
interns,
|
||||
&layout_interner.into_global(),
|
||||
&lib,
|
||||
&global_layout_interner,
|
||||
&dyn_lib,
|
||||
&mut expectations,
|
||||
expects,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total_time = start_time.elapsed();
|
||||
let tests_duration = test_start_time.elapsed();
|
||||
|
||||
if failed_count == 0 && passed_count == 0 {
|
||||
results_by_module.push(ModuleTestResults {
|
||||
module_id,
|
||||
failed_count,
|
||||
passed_count,
|
||||
tests_duration,
|
||||
});
|
||||
|
||||
total_failed_count += failed_count;
|
||||
total_passed_count += passed_count;
|
||||
}
|
||||
|
||||
let total_duration = start_time.elapsed();
|
||||
|
||||
if total_failed_count == 0 && total_passed_count == 0 {
|
||||
// TODO print this in a more nicely formatted way!
|
||||
println!("No expectations were found.");
|
||||
|
||||
|
@ -567,23 +610,53 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
// running tests altogether!
|
||||
Ok(2)
|
||||
} else {
|
||||
if matches.get_flag(FLAG_VERBOSE) {
|
||||
println!("Compiled in {} ms.", compilation_duration.as_millis());
|
||||
for module_test_results in results_by_module {
|
||||
print_test_results(module_test_results, &sources);
|
||||
}
|
||||
} else {
|
||||
let test_summary_str =
|
||||
test_summary(total_failed_count, total_passed_count, total_duration);
|
||||
println!("{test_summary_str}");
|
||||
}
|
||||
|
||||
Ok((total_failed_count > 0) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_test_results(
|
||||
module_test_results: ModuleTestResults,
|
||||
sources: &MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
) {
|
||||
let ModuleTestResults {
|
||||
module_id,
|
||||
failed_count,
|
||||
passed_count,
|
||||
tests_duration,
|
||||
} = module_test_results;
|
||||
|
||||
let test_summary_str = test_summary(failed_count, passed_count, tests_duration);
|
||||
|
||||
let (module_path, _) = sources.get(&module_id).unwrap();
|
||||
let module_name = module_path.file_name().unwrap().to_str().unwrap();
|
||||
|
||||
println!("\n{module_name}:\n {test_summary_str}",);
|
||||
}
|
||||
|
||||
fn test_summary(failed_count: usize, passed_count: usize, tests_duration: Duration) -> String {
|
||||
let failed_color = if failed_count == 0 {
|
||||
ANSI_STYLE_CODES.green
|
||||
} else {
|
||||
ANSI_STYLE_CODES.red
|
||||
};
|
||||
|
||||
let passed_color = ANSI_STYLE_CODES.green;
|
||||
|
||||
let reset = ANSI_STYLE_CODES.reset;
|
||||
|
||||
println!(
|
||||
"\n{failed_color}{failed_count}{reset} failed and {passed_color}{passed_count}{reset} passed in {} ms.\n",
|
||||
total_time.as_millis(),
|
||||
);
|
||||
|
||||
Ok((failed_count > 0) as i32)
|
||||
}
|
||||
format!(
|
||||
"{failed_color}{failed_count}{reset} failed and {passed_color}{passed_count}{reset} passed in {} ms.",
|
||||
tests_duration.as_millis()
|
||||
)
|
||||
}
|
||||
|
||||
/// Find the element of `options` with the smallest edit distance to
|
||||
|
|
|
@ -16,6 +16,7 @@ mod cli_run {
|
|||
};
|
||||
use const_format::concatcp;
|
||||
use indoc::indoc;
|
||||
use regex::Regex;
|
||||
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_FORMAT, CMD_RUN, CMD_TEST};
|
||||
use roc_reporting::report::strip_colors;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
@ -199,7 +200,7 @@ mod cli_run {
|
|||
vec.into_iter()
|
||||
};
|
||||
|
||||
let out = match cli_mode {
|
||||
let cmd_output = match cli_mode {
|
||||
CliMode::RocBuild => {
|
||||
run_roc_on_failure_is_panic(
|
||||
file,
|
||||
|
@ -295,41 +296,48 @@ mod cli_run {
|
|||
}
|
||||
};
|
||||
|
||||
let mut actual = strip_colors(&out.stdout);
|
||||
|
||||
// e.g. "1 failed and 0 passed in 123 ms."
|
||||
if let Some(split) = actual.rfind("passed in ") {
|
||||
let (before_first_digit, _) = actual.split_at(split);
|
||||
actual = format!("{before_first_digit}passed in <ignored for test> ms.");
|
||||
}
|
||||
|
||||
let self_path = file.display().to_string();
|
||||
actual = actual.replace(&self_path, "<ignored for tests>");
|
||||
|
||||
if !actual.ends_with(expected_ending) {
|
||||
let actual_cmd_stdout = ignore_test_timings(&strip_colors(&cmd_output.stdout))
|
||||
.replace(&self_path, "<ignored for tests>");
|
||||
|
||||
if !actual_cmd_stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
"> expected output to end with:\n{}\n> but instead got:\n{}\n> stderr was:\n{}",
|
||||
expected_ending, actual, out.stderr
|
||||
expected_ending, actual_cmd_stdout, cmd_output.stderr
|
||||
);
|
||||
}
|
||||
|
||||
if !out.status.success() && !matches!(cli_mode, CliMode::RocTest) {
|
||||
if !cmd_output.status.success() && !matches!(cli_mode, CliMode::RocTest) {
|
||||
// We don't need stdout, Cargo prints it for us.
|
||||
panic!(
|
||||
"Example program exited with status {:?}\nstderr was:\n{:#?}",
|
||||
out.status, out.stderr
|
||||
cmd_output.status, cmd_output.stderr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ignore_test_timings(cmd_output: &str) -> String {
|
||||
let regex = Regex::new(r" in (\d+) ms\.").expect("Invalid regex pattern");
|
||||
let replacement = " in <ignored for test> ms.";
|
||||
regex.replace_all(cmd_output, replacement).to_string()
|
||||
}
|
||||
|
||||
// when you want to run `roc test` to execute `expect`s, perhaps on a library rather than an application.
|
||||
// not currently used
|
||||
// fn test_roc_expect(dir_name: &str, roc_filename: &str) {
|
||||
// let path = file_path_from_root(dir_name, roc_filename);
|
||||
// let out = run_roc([CMD_TEST, path.to_str().unwrap()], &[], &[]);
|
||||
// assert!(out.status.success());
|
||||
// }
|
||||
fn test_roc_expect(dir_name: &str, roc_filename: &str, flags: &[&str], expected_ending: &str) {
|
||||
let path = file_path_from_root(dir_name, roc_filename);
|
||||
check_output_with_stdin(
|
||||
&path,
|
||||
&[],
|
||||
flags,
|
||||
&[],
|
||||
&[],
|
||||
expected_ending,
|
||||
UseValgrind::Yes,
|
||||
TestCliCommands::Test,
|
||||
);
|
||||
}
|
||||
|
||||
// when you don't need args, stdin or extra_env
|
||||
fn test_roc_app_slim(
|
||||
|
@ -630,14 +638,50 @@ mod cli_run {
|
|||
b = 2
|
||||
|
||||
|
||||
|
||||
1 failed and 0 passed in <ignored for test> ms."#
|
||||
1 failed and 0 passed in <ignored for test> ms.
|
||||
"#
|
||||
),
|
||||
UseValgrind::Yes,
|
||||
TestCliCommands::Test,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn transitive_expects() {
|
||||
test_roc_expect(
|
||||
"crates/cli/tests/expects_transitive",
|
||||
"main.roc",
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
0 failed and 3 passed in <ignored for test> ms.
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn transitive_expects_verbose() {
|
||||
test_roc_expect(
|
||||
"crates/cli/tests/expects_transitive",
|
||||
"main.roc",
|
||||
&["--verbose"],
|
||||
indoc!(
|
||||
r#"
|
||||
Compiled in <ignored for test> ms.
|
||||
|
||||
Direct.roc:
|
||||
0 failed and 2 passed in <ignored for test> ms.
|
||||
|
||||
Transitive.roc:
|
||||
0 failed and 1 passed in <ignored for test> ms.
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
windows,
|
||||
|
|
14
crates/cli/tests/expects_transitive/Direct.roc
Normal file
14
crates/cli/tests/expects_transitive/Direct.roc
Normal file
|
@ -0,0 +1,14 @@
|
|||
interface Direct
|
||||
exposes [
|
||||
addAndStringify,
|
||||
]
|
||||
imports [
|
||||
Transitive,
|
||||
]
|
||||
|
||||
addAndStringify = \num1, num2 ->
|
||||
Num.toStr (Transitive.add num1 num2)
|
||||
|
||||
expect addAndStringify 1 2 == "3"
|
||||
|
||||
expect addAndStringify 3 4 == "7"
|
9
crates/cli/tests/expects_transitive/Transitive.roc
Normal file
9
crates/cli/tests/expects_transitive/Transitive.roc
Normal file
|
@ -0,0 +1,9 @@
|
|||
interface Transitive
|
||||
exposes [
|
||||
add,
|
||||
]
|
||||
imports []
|
||||
|
||||
add = \num1, num2 -> (num1 + num2)
|
||||
|
||||
expect add 1 2 == 3
|
5
crates/cli/tests/expects_transitive/main.roc
Normal file
5
crates/cli/tests/expects_transitive/main.roc
Normal file
|
@ -0,0 +1,5 @@
|
|||
package "transitive-tests"
|
||||
exposes [
|
||||
Direct,
|
||||
]
|
||||
packages {}
|
|
@ -1441,6 +1441,7 @@ pub fn canonicalize_expr<'a>(
|
|||
bad_expr
|
||||
);
|
||||
}
|
||||
ast::Expr::Suffixed(_) => todo!(),
|
||||
};
|
||||
|
||||
// At the end, diff used_idents and defined_idents to see which were unused.
|
||||
|
@ -2506,6 +2507,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
ast::RecordBuilderField::SpaceBefore(_, _)
|
||||
| ast::RecordBuilderField::SpaceAfter(_, _) => false,
|
||||
}),
|
||||
ast::Expr::Suffixed(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -619,6 +619,7 @@ pub fn desugar_expr<'a>(
|
|||
})
|
||||
}
|
||||
LowLevelDbg(_, _, _) => unreachable!("Only exists after desugaring"),
|
||||
Suffixed(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
crates/compiler/checkmate/www/package-lock.json
generated
6
crates/compiler/checkmate/www/package-lock.json
generated
|
@ -8984,9 +8984,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
|
@ -107,6 +107,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
Tuple(fields) => is_collection_multiline(fields),
|
||||
RecordUpdate { fields, .. } => is_collection_multiline(fields),
|
||||
RecordBuilder(fields) => is_collection_multiline(fields),
|
||||
Suffixed(subexpr) => subexpr.is_multiline(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,6 +513,10 @@ impl<'a> Formattable for Expr<'a> {
|
|||
MultipleRecordBuilders { .. } => {}
|
||||
UnappliedRecordBuilder { .. } => {}
|
||||
IngestedFile(_, _) => {}
|
||||
Suffixed(sub_expr) => {
|
||||
sub_expr.format_with_options(buf, parens, newlines, indent);
|
||||
buf.push('!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -761,6 +761,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
|||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
Expr::Suffixed(a) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ use roc_debug_flags::dbg_do;
|
|||
#[cfg(debug_assertions)]
|
||||
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
|
||||
use roc_error_macros::{internal_error, todo_lambda_erasure};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::ir::{
|
||||
BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet,
|
||||
HostExposedLambdaSets, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
|
||||
|
@ -5667,10 +5667,16 @@ pub fn build_procedures_expose_expects<'a>(
|
|||
env: &Env<'a, '_, '_>,
|
||||
layout_interner: &STLayoutInterner<'a>,
|
||||
opt_level: OptLevel,
|
||||
expects: &'a [Symbol],
|
||||
expects_by_module: MutMap<ModuleId, Vec<'a, Symbol>>,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
) -> Vec<'a, &'a str> {
|
||||
let entry_point = EntryPoint::Expects { symbols: expects };
|
||||
) -> MutMap<ModuleId, Vec<'a, &'a str>> {
|
||||
// converts Vec<Vec<Symbol>> into Vec<Symbol>
|
||||
let flattened_symbols: Vec<Symbol> =
|
||||
Vec::from_iter_in(expects_by_module.values().flatten().copied(), env.arena);
|
||||
|
||||
let entry_point = EntryPoint::Expects {
|
||||
symbols: &flattened_symbols,
|
||||
};
|
||||
|
||||
let mod_solutions = build_procedures_help(
|
||||
env,
|
||||
|
@ -5690,24 +5696,34 @@ pub fn build_procedures_expose_expects<'a>(
|
|||
niche: captures_niche,
|
||||
};
|
||||
|
||||
let mut expect_names_by_module = MutMap::default();
|
||||
|
||||
for (module_id, expects) in expects_by_module {
|
||||
let mut expect_names = Vec::with_capacity_in(expects.len(), env.arena);
|
||||
|
||||
for symbol in expects.iter().copied() {
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes =
|
||||
roc_alias_analysis::func_name_bytes_help(symbol, it, captures_niche, top_level.result);
|
||||
let func_name = FuncName(&bytes);
|
||||
let args_iter = top_level.arguments.iter().copied();
|
||||
|
||||
let func_name_bytes = roc_alias_analysis::func_name_bytes_help(
|
||||
symbol,
|
||||
args_iter,
|
||||
captures_niche,
|
||||
top_level.result,
|
||||
);
|
||||
|
||||
let func_name = FuncName(&func_name_bytes);
|
||||
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
|
||||
|
||||
let mut it = func_solutions.specs();
|
||||
let func_spec = match it.next() {
|
||||
let mut func_spec_iter = func_solutions.specs();
|
||||
|
||||
let func_spec = match func_spec_iter.next() {
|
||||
Some(spec) => spec,
|
||||
None => panic!("no specialization for expect {symbol}"),
|
||||
None => panic!("No specialization for expect {symbol}."),
|
||||
};
|
||||
|
||||
debug_assert!(
|
||||
it.next().is_none(),
|
||||
"we expect only one specialization of this symbol"
|
||||
func_spec_iter.next().is_none(),
|
||||
"We expect only one specialization of this symbol."
|
||||
);
|
||||
|
||||
// NOTE fake layout; it is only used for debug prints
|
||||
|
@ -5717,8 +5733,8 @@ pub fn build_procedures_expose_expects<'a>(
|
|||
let name = roc_main_fn.get_name().to_str().unwrap();
|
||||
|
||||
let expect_name = &format!("Expect_{name}");
|
||||
let expect_name = env.arena.alloc_str(expect_name);
|
||||
expect_names.push(&*expect_name);
|
||||
let expect_name_str = env.arena.alloc_str(expect_name);
|
||||
expect_names.push(&*expect_name_str);
|
||||
|
||||
// Add main to the module.
|
||||
let _ = expose_function_to_host_help_c_abi(
|
||||
|
@ -5731,8 +5747,10 @@ pub fn build_procedures_expose_expects<'a>(
|
|||
&format!("Expect_{name}"),
|
||||
);
|
||||
}
|
||||
expect_names_by_module.insert(module_id, expect_names);
|
||||
}
|
||||
|
||||
expect_names
|
||||
expect_names_by_module
|
||||
}
|
||||
|
||||
fn build_procedures_help<'a>(
|
||||
|
|
|
@ -702,7 +702,7 @@ struct State<'a> {
|
|||
pub dependencies: Dependencies<'a>,
|
||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
pub host_exposed_lambda_sets: HostExposedLambdaSets<'a>,
|
||||
pub toplevel_expects: ToplevelExpects,
|
||||
pub toplevel_expects: MutMap<ModuleId, ToplevelExpects>,
|
||||
pub exposed_to_host: ExposedToHost,
|
||||
|
||||
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
|
||||
|
@ -784,7 +784,7 @@ impl<'a> State<'a> {
|
|||
dependencies,
|
||||
procedures: MutMap::default(),
|
||||
host_exposed_lambda_sets: std::vec::Vec::new(),
|
||||
toplevel_expects: ToplevelExpects::default(),
|
||||
toplevel_expects: MutMap::default(),
|
||||
exposed_to_host: ExposedToHost::default(),
|
||||
exposed_modules: &[],
|
||||
exposed_types,
|
||||
|
@ -2787,8 +2787,9 @@ fn update<'a>(
|
|||
|
||||
let subs = solved_subs.into_inner();
|
||||
|
||||
state.toplevel_expects.pure.extend(toplevel_expects.pure);
|
||||
state.toplevel_expects.fx.extend(toplevel_expects.fx);
|
||||
if !toplevel_expects.pure.is_empty() || !toplevel_expects.fx.is_empty() {
|
||||
state.toplevel_expects.insert(module_id, toplevel_expects);
|
||||
}
|
||||
|
||||
state
|
||||
.module_cache
|
||||
|
|
|
@ -177,7 +177,7 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
pub host_exposed_lambda_sets: HostExposedLambdaSets<'a>,
|
||||
pub toplevel_expects: ToplevelExpects,
|
||||
pub toplevel_expects: MutMap<ModuleId, ToplevelExpects>,
|
||||
pub entry_point: EntryPoint<'a>,
|
||||
pub exposed_to_host: ExposedToHost,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
|
|
|
@ -267,6 +267,9 @@ pub enum Expr<'a> {
|
|||
// Collection Literals
|
||||
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||
|
||||
/// An expression followed by `!``
|
||||
Suffixed(&'a Expr<'a>),
|
||||
|
||||
RecordUpdate {
|
||||
update: &'a Loc<Expr<'a>>,
|
||||
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
|
@ -1568,6 +1571,7 @@ impl<'a> Malformed for Expr<'a> {
|
|||
PrecedenceConflict(_) |
|
||||
MultipleRecordBuilders(_) |
|
||||
UnappliedRecordBuilder(_) => true,
|
||||
Suffixed(expr) => expr.is_malformed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -311,8 +311,20 @@ fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, E
|
|||
|
||||
fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
||||
line_min_indent(move |arena, state: State<'a>, min_indent: u32| {
|
||||
let (_, expr, state) =
|
||||
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
|
||||
let (_, expr, state) = loc_possibly_negative_or_negated_term(options)
|
||||
.parse(arena, state, min_indent)
|
||||
.map(|(progress, expr, state)| {
|
||||
// If the next thing after the expression is a `!`, then it's Suffixed
|
||||
if state.bytes().starts_with(b"!") {
|
||||
(
|
||||
progress,
|
||||
Loc::at(expr.region, Expr::Suffixed(arena.alloc(expr.value))),
|
||||
state.advance(1),
|
||||
)
|
||||
} else {
|
||||
(progress, expr, state)
|
||||
}
|
||||
})?;
|
||||
|
||||
let initial_state = state.clone();
|
||||
let end = state.pos();
|
||||
|
@ -1640,7 +1652,7 @@ fn parse_expr_end<'a>(
|
|||
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
||||
Ok((
|
||||
_,
|
||||
has @ Loc {
|
||||
implements @ Loc {
|
||||
value:
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
|
@ -1672,17 +1684,17 @@ fn parse_expr_end<'a>(
|
|||
}
|
||||
|
||||
// Attach any spaces to the `implements` keyword
|
||||
let has = if !expr_state.spaces_after.is_empty() {
|
||||
let implements = if !expr_state.spaces_after.is_empty() {
|
||||
arena
|
||||
.alloc(Implements::Implements)
|
||||
.with_spaces_before(expr_state.spaces_after, has.region)
|
||||
.with_spaces_before(expr_state.spaces_after, implements.region)
|
||||
} else {
|
||||
Loc::at(has.region, Implements::Implements)
|
||||
Loc::at(implements.region, Implements::Implements)
|
||||
};
|
||||
|
||||
let args = arguments.into_bump_slice();
|
||||
let (_, (type_def, def_region), state) =
|
||||
finish_parsing_ability_def_help(min_indent, name, args, has, arena, state)?;
|
||||
finish_parsing_ability_def_help(min_indent, name, args, implements, arena, state)?;
|
||||
|
||||
let mut defs = Defs::default();
|
||||
|
||||
|
@ -1817,6 +1829,18 @@ fn parse_expr_end<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
.map(|(progress, expr, state)| {
|
||||
// If the next thing after the expression is a `!`, then it's Suffixed
|
||||
if state.bytes().starts_with(b"!") {
|
||||
(
|
||||
progress,
|
||||
Expr::Suffixed(arena.alloc(expr)),
|
||||
state.advance(1),
|
||||
)
|
||||
} else {
|
||||
(progress, expr, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loc_expr<'a>(accept_multi_backpassing: bool) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
||||
|
@ -1945,6 +1969,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
Expr::Str(string) => Pattern::StrLiteral(string),
|
||||
Expr::SingleQuote(string) => Pattern::SingleQuote(string),
|
||||
Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem),
|
||||
Expr::Suffixed(_) => todo!(),
|
||||
};
|
||||
|
||||
// Now we re-add the spaces
|
||||
|
@ -2997,6 +3022,7 @@ where
|
|||
Err((NoProgress, to_error("->", state.pos())))
|
||||
}
|
||||
"<-" => good!(BinOp::Backpassing, 2),
|
||||
"!" => Err((NoProgress, to_error("!", state.pos()))),
|
||||
_ => bad_made_progress!(chomped),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
Suffixed(
|
||||
Var {
|
||||
module_name: "Stdout",
|
||||
ident: "line",
|
||||
},
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
Stdout.line!
|
|
@ -0,0 +1 @@
|
|||
foo! (bar! baz) (blah stuff)
|
|
@ -0,0 +1,43 @@
|
|||
Apply(
|
||||
@0-3 Suffixed(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
),
|
||||
[
|
||||
@9-17 ParensAround(
|
||||
Apply(
|
||||
@9-12 Suffixed(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "bar",
|
||||
},
|
||||
),
|
||||
[
|
||||
@14-17 Var {
|
||||
module_name: "",
|
||||
ident: "baz",
|
||||
},
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
@22-32 ParensAround(
|
||||
Apply(
|
||||
@22-26 Var {
|
||||
module_name: "",
|
||||
ident: "blah",
|
||||
},
|
||||
[
|
||||
@27-32 Var {
|
||||
module_name: "",
|
||||
ident: "stuff",
|
||||
},
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
],
|
||||
Space,
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
foo! ( bar! baz) ( blah stuff)
|
|
@ -441,6 +441,8 @@ mod test_snapshots {
|
|||
pass/string_without_escape.expr,
|
||||
pass/sub_var_with_spaces.expr,
|
||||
pass/sub_with_spaces.expr,
|
||||
pass/suffixed.expr,
|
||||
pass/suffixed_nested.expr,
|
||||
pass/tag_pattern.expr,
|
||||
pass/ten_times_eleven.expr,
|
||||
pass/three_arg_closure.expr,
|
||||
|
|
|
@ -700,6 +700,7 @@ impl IterTokens for Loc<Expr<'_>> {
|
|||
Expr::MalformedIdent(_, _) | Expr::MalformedClosure | Expr::PrecedenceConflict(_) => {
|
||||
bumpvec![in arena;]
|
||||
}
|
||||
Expr::Suffixed(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ mod test {
|
|||
|
||||
let interns = loaded.interns.clone();
|
||||
|
||||
let (lib, expects, layout_interner) = expect_mono_module_to_dylib(
|
||||
let (dy_lib, expects_by_module, layout_interner) = expect_mono_module_to_dylib(
|
||||
arena,
|
||||
target.clone(),
|
||||
loaded,
|
||||
|
@ -168,23 +168,27 @@ mod test {
|
|||
let mut memory = crate::run::ExpectMemory::from_slice(&mut shared_buffer);
|
||||
|
||||
// communicate the mmapped name to zig/roc
|
||||
let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ());
|
||||
let set_shared_buffer = run_roc_dylib!(dy_lib, "set_shared_buffer", (*mut u8, usize), ());
|
||||
let mut result = RocCallResult::default();
|
||||
unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) };
|
||||
|
||||
let mut writer = Vec::with_capacity(1024);
|
||||
|
||||
let global_layout_interner = layout_interner.into_global();
|
||||
for (_, expect_funcs) in expects_by_module {
|
||||
let (_failed, _passed) = crate::run::run_expects_with_memory(
|
||||
&mut writer,
|
||||
RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
interns,
|
||||
&layout_interner.into_global(),
|
||||
&lib,
|
||||
&global_layout_interner,
|
||||
&dy_lib,
|
||||
&mut expectations,
|
||||
expects,
|
||||
expect_funcs,
|
||||
&mut memory,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Remove ANSI escape codes from the answer - for example:
|
||||
//
|
||||
|
|
|
@ -11,7 +11,7 @@ use bumpalo::Bump;
|
|||
use inkwell::context::Context;
|
||||
use roc_build::link::llvm_module_to_dylib;
|
||||
use roc_can::expr::ExpectLookup;
|
||||
use roc_collections::{MutSet, VecMap};
|
||||
use roc_collections::{MutMap, MutSet, VecMap};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_gen_llvm::{
|
||||
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
|
||||
|
@ -620,7 +620,7 @@ pub fn expect_mono_module_to_dylib<'a>(
|
|||
) -> Result<
|
||||
(
|
||||
libloading::Library,
|
||||
ExpectFunctions<'a>,
|
||||
MutMap<ModuleId, ExpectFunctions<'a>>,
|
||||
STLayoutInterner<'a>,
|
||||
),
|
||||
libloading::Error,
|
||||
|
@ -666,25 +666,41 @@ pub fn expect_mono_module_to_dylib<'a>(
|
|||
// platform to provide them.
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let capacity = toplevel_expects.pure.len() + toplevel_expects.fx.len();
|
||||
let mut expect_symbols = BumpVec::with_capacity_in(capacity, env.arena);
|
||||
|
||||
expect_symbols.extend(toplevel_expects.pure.keys().copied());
|
||||
expect_symbols.extend(toplevel_expects.fx.keys().copied());
|
||||
let expects_symbols = toplevel_expects
|
||||
.iter()
|
||||
.map(|(module_id, expects)| {
|
||||
(
|
||||
*module_id,
|
||||
bumpalo::collections::Vec::from_iter_in(
|
||||
expects
|
||||
.pure
|
||||
.keys()
|
||||
.copied()
|
||||
.chain(expects.fx.keys().copied()),
|
||||
env.arena,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expect_names = roc_gen_llvm::llvm::build::build_procedures_expose_expects(
|
||||
&env,
|
||||
&layout_interner,
|
||||
opt_level,
|
||||
expect_symbols.into_bump_slice(),
|
||||
expects_symbols,
|
||||
procedures,
|
||||
);
|
||||
|
||||
let mut modules_expects: MutMap<ModuleId, ExpectFunctions> = MutMap::default();
|
||||
|
||||
for (module_id, expects) in toplevel_expects.into_iter() {
|
||||
let expect_names = expect_names.get(&module_id).unwrap();
|
||||
|
||||
let expects_fx = bumpalo::collections::Vec::from_iter_in(
|
||||
toplevel_expects
|
||||
expects
|
||||
.fx
|
||||
.into_iter()
|
||||
.zip(expect_names.iter().skip(toplevel_expects.pure.len()))
|
||||
.zip(expect_names.iter().skip(expects.pure.len()))
|
||||
.map(|((symbol, region), name)| ToplevelExpect {
|
||||
symbol,
|
||||
region,
|
||||
|
@ -693,24 +709,26 @@ pub fn expect_mono_module_to_dylib<'a>(
|
|||
env.arena,
|
||||
);
|
||||
|
||||
let expects_pure = bumpalo::collections::Vec::from_iter_in(
|
||||
toplevel_expects
|
||||
.pure
|
||||
.into_iter()
|
||||
.zip(expect_names.iter())
|
||||
.map(|((symbol, region), name)| ToplevelExpect {
|
||||
let expects_pure =
|
||||
bumpalo::collections::Vec::from_iter_in(
|
||||
expects.pure.into_iter().zip(expect_names.iter()).map(
|
||||
|((symbol, region), name)| ToplevelExpect {
|
||||
symbol,
|
||||
region,
|
||||
name,
|
||||
}),
|
||||
},
|
||||
),
|
||||
env.arena,
|
||||
);
|
||||
|
||||
let expects = ExpectFunctions {
|
||||
let expect_funs = ExpectFunctions {
|
||||
pure: expects_pure,
|
||||
fx: expects_fx,
|
||||
};
|
||||
|
||||
modules_expects.insert(module_id, expect_funs);
|
||||
}
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
|
@ -735,5 +753,6 @@ pub fn expect_mono_module_to_dylib<'a>(
|
|||
env.module.print_to_file(path).unwrap();
|
||||
}
|
||||
|
||||
llvm_module_to_dylib(env.module, &target, opt_level).map(|lib| (lib, expects, layout_interner))
|
||||
llvm_module_to_dylib(env.module, &target, opt_level)
|
||||
.map(|dy_lib| (dy_lib, modules_expects, layout_interner))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue