Merge branch 'main' into parser-docs

This commit is contained in:
Trevor Settles 2024-03-15 18:14:56 -06:00 committed by GitHub
commit 6d64bbb65a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 447 additions and 164 deletions

1
Cargo.lock generated
View file

@ -2417,6 +2417,7 @@ dependencies = [
"mimalloc", "mimalloc",
"parking_lot", "parking_lot",
"pretty_assertions", "pretty_assertions",
"regex",
"roc_build", "roc_build",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",

View file

@ -71,6 +71,7 @@ inkwell.workspace = true
libc.workspace = true libc.workspace = true
libloading.workspace = true libloading.workspace = true
mimalloc.workspace = true mimalloc.workspace = true
regex.workspace = true
signal-hook.workspace = true signal-hook.workspace = true
strum.workspace = true strum.workspace = true
target-lexicon.workspace = true target-lexicon.workspace = true

View file

@ -13,13 +13,16 @@ use roc_build::program::{
handle_error_module, handle_loading_problem, standard_load_config, BuildFileError, handle_error_module, handle_loading_problem, standard_load_config, BuildFileError,
BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME, BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME,
}; };
use roc_collections::MutMap;
use roc_error_macros::{internal_error, user_error}; use roc_error_macros::{internal_error, user_error};
use roc_gen_dev::AssemblyBackendMode; use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::LlvmBackendMode; use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{ExpectMetadata, Threading}; use roc_load::{ExpectMetadata, Threading};
use roc_module::symbol::ModuleId;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir; use roc_packaging::cache::RocCacheDir;
use roc_packaging::tarball::Compression; use roc_packaging::tarball::Compression;
use roc_reporting::report::ANSI_STYLE_CODES;
use roc_target::Target; use roc_target::Target;
use std::env; use std::env;
use std::ffi::{CString, OsStr, OsString}; use std::ffi::{CString, OsStr, OsString};
@ -28,7 +31,7 @@ use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::time::Instant; use std::time::{Duration, Instant};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use target_lexicon::{Architecture, Triple}; use target_lexicon::{Architecture, Triple};
#[cfg(not(target_os = "linux"))] #[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_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_VERBOSE: &str = "verbose";
pub const FLAG_LINKER: &str = "linker"; pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PREBUILT: &str = "prebuilt-platform"; pub const FLAG_PREBUILT: &str = "prebuilt-platform";
pub const FLAG_CHECK: &str = "check"; pub const FLAG_CHECK: &str = "check";
@ -112,7 +116,7 @@ pub fn build_app() -> Command {
let flag_profiling = Arg::new(FLAG_PROFILING) let flag_profiling = Arg::new(FLAG_PROFILING)
.long(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) .action(ArgAction::SetTrue)
.required(false); .required(false);
@ -234,6 +238,13 @@ pub fn build_app() -> Command {
.arg(flag_linker.clone()) .arg(flag_linker.clone())
.arg(flag_prebuilt.clone()) .arg(flag_prebuilt.clone())
.arg(flag_fuzz.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(
Arg::new(ROC_FILE) Arg::new(ROC_FILE)
.help("The .roc file for the main module") .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") 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))] #[cfg(not(windows))]
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> { pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
use roc_build::program::report_problems_monomorphized; use roc_build::program::report_problems_monomorphized;
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadMonomorphizedError}; use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadMonomorphizedError};
use roc_packaging::cache; use roc_packaging::cache;
use roc_reporting::report::ANSI_STYLE_CODES;
use roc_target::TargetInfo; use roc_target::TargetInfo;
let start_time = Instant::now(); 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 mut expectations = std::mem::take(&mut loaded.expectations);
let interns = loaded.interns.clone(); 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, arena,
target.clone(), target.clone(),
loaded, loaded,
@ -542,21 +561,45 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let mut writer = std::io::stdout(); 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( let (failed_count, passed_count) = roc_repl_expect::run::run_toplevel_expects(
&mut writer, &mut writer,
roc_reporting::report::RenderTarget::ColorTerminal, roc_reporting::report::RenderTarget::ColorTerminal,
arena, arena,
interns, interns,
&layout_interner.into_global(), &global_layout_interner,
&lib, &dyn_lib,
&mut expectations, &mut expectations,
expects, expects,
) )
.unwrap(); .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! // TODO print this in a more nicely formatted way!
println!("No expectations were found."); println!("No expectations were found.");
@ -567,23 +610,53 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
// running tests altogether! // running tests altogether!
Ok(2) Ok(2)
} else { } 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 { let failed_color = if failed_count == 0 {
ANSI_STYLE_CODES.green ANSI_STYLE_CODES.green
} else { } else {
ANSI_STYLE_CODES.red ANSI_STYLE_CODES.red
}; };
let passed_color = ANSI_STYLE_CODES.green; let passed_color = ANSI_STYLE_CODES.green;
let reset = ANSI_STYLE_CODES.reset; let reset = ANSI_STYLE_CODES.reset;
println!( format!(
"\n{failed_color}{failed_count}{reset} failed and {passed_color}{passed_count}{reset} passed in {} ms.\n", "{failed_color}{failed_count}{reset} failed and {passed_color}{passed_count}{reset} passed in {} ms.",
total_time.as_millis(), tests_duration.as_millis()
); )
Ok((failed_count > 0) as i32)
}
} }
/// Find the element of `options` with the smallest edit distance to /// Find the element of `options` with the smallest edit distance to

View file

@ -16,6 +16,7 @@ mod cli_run {
}; };
use const_format::concatcp; use const_format::concatcp;
use indoc::indoc; use indoc::indoc;
use regex::Regex;
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_FORMAT, CMD_RUN, CMD_TEST}; use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_FORMAT, CMD_RUN, CMD_TEST};
use roc_reporting::report::strip_colors; use roc_reporting::report::strip_colors;
use roc_test_utils::assert_multiline_str_eq; use roc_test_utils::assert_multiline_str_eq;
@ -199,7 +200,7 @@ mod cli_run {
vec.into_iter() vec.into_iter()
}; };
let out = match cli_mode { let cmd_output = match cli_mode {
CliMode::RocBuild => { CliMode::RocBuild => {
run_roc_on_failure_is_panic( run_roc_on_failure_is_panic(
file, 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(); 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!( panic!(
"> expected output to end with:\n{}\n> but instead got:\n{}\n> stderr was:\n{}", "> 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. // We don't need stdout, Cargo prints it for us.
panic!( panic!(
"Example program exited with status {:?}\nstderr was:\n{:#?}", "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. // 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, flags: &[&str], expected_ending: &str) {
// fn test_roc_expect(dir_name: &str, roc_filename: &str) { let path = file_path_from_root(dir_name, roc_filename);
// let path = file_path_from_root(dir_name, roc_filename); check_output_with_stdin(
// let out = run_roc([CMD_TEST, path.to_str().unwrap()], &[], &[]); &path,
// assert!(out.status.success()); &[],
// } flags,
&[],
&[],
expected_ending,
UseValgrind::Yes,
TestCliCommands::Test,
);
}
// when you don't need args, stdin or extra_env // when you don't need args, stdin or extra_env
fn test_roc_app_slim( fn test_roc_app_slim(
@ -630,14 +638,50 @@ mod cli_run {
b = 2 b = 2
1 failed and 0 passed in <ignored for test> ms.
1 failed and 0 passed in <ignored for test> ms."# "#
), ),
UseValgrind::Yes, UseValgrind::Yes,
TestCliCommands::Test, 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] #[test]
#[cfg_attr( #[cfg_attr(
windows, windows,

View 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"

View file

@ -0,0 +1,9 @@
interface Transitive
exposes [
add,
]
imports []
add = \num1, num2 -> (num1 + num2)
expect add 1 2 == 3

View file

@ -0,0 +1,5 @@
package "transitive-tests"
exposes [
Direct,
]
packages {}

View file

@ -1441,6 +1441,7 @@ pub fn canonicalize_expr<'a>(
bad_expr bad_expr
); );
} }
ast::Expr::Suffixed(_) => todo!(),
}; };
// At the end, diff used_idents and defined_idents to see which were unused. // 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::SpaceBefore(_, _)
| ast::RecordBuilderField::SpaceAfter(_, _) => false, | ast::RecordBuilderField::SpaceAfter(_, _) => false,
}), }),
ast::Expr::Suffixed(_) => todo!(),
} }
} }

View file

@ -619,6 +619,7 @@ pub fn desugar_expr<'a>(
}) })
} }
LowLevelDbg(_, _, _) => unreachable!("Only exists after desugaring"), LowLevelDbg(_, _, _) => unreachable!("Only exists after desugaring"),
Suffixed(_) => todo!(),
} }
} }

View file

@ -8984,9 +8984,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {

View file

@ -107,6 +107,7 @@ impl<'a> Formattable for Expr<'a> {
Tuple(fields) => is_collection_multiline(fields), Tuple(fields) => is_collection_multiline(fields),
RecordUpdate { fields, .. } => is_collection_multiline(fields), RecordUpdate { fields, .. } => is_collection_multiline(fields),
RecordBuilder(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 { .. } => {} MultipleRecordBuilders { .. } => {}
UnappliedRecordBuilder { .. } => {} UnappliedRecordBuilder { .. } => {}
IngestedFile(_, _) => {} IngestedFile(_, _) => {}
Suffixed(sub_expr) => {
sub_expr.format_with_options(buf, parens, newlines, indent);
buf.push('!');
}
} }
} }
} }

View file

@ -761,6 +761,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
Expr::SpaceBefore(a, _) => a.remove_spaces(arena), Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena), Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a), Expr::SingleQuote(a) => Expr::Num(a),
Expr::Suffixed(a) => a.remove_spaces(arena),
} }
} }
} }

View file

@ -42,7 +42,7 @@ use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION; use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_error_macros::{internal_error, todo_lambda_erasure}; 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::{ use roc_mono::ir::{
BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet, BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet,
HostExposedLambdaSets, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint, HostExposedLambdaSets, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
@ -5667,10 +5667,16 @@ pub fn build_procedures_expose_expects<'a>(
env: &Env<'a, '_, '_>, env: &Env<'a, '_, '_>,
layout_interner: &STLayoutInterner<'a>, layout_interner: &STLayoutInterner<'a>,
opt_level: OptLevel, opt_level: OptLevel,
expects: &'a [Symbol], expects_by_module: MutMap<ModuleId, Vec<'a, Symbol>>,
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
) -> Vec<'a, &'a str> { ) -> MutMap<ModuleId, Vec<'a, &'a str>> {
let entry_point = EntryPoint::Expects { symbols: expects }; // 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( let mod_solutions = build_procedures_help(
env, env,
@ -5690,24 +5696,34 @@ pub fn build_procedures_expose_expects<'a>(
niche: captures_niche, 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); let mut expect_names = Vec::with_capacity_in(expects.len(), env.arena);
for symbol in expects.iter().copied() { for symbol in expects.iter().copied() {
let it = top_level.arguments.iter().copied(); let args_iter = top_level.arguments.iter().copied();
let bytes =
roc_alias_analysis::func_name_bytes_help(symbol, it, captures_niche, top_level.result); let func_name_bytes = roc_alias_analysis::func_name_bytes_help(
let func_name = FuncName(&bytes); 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 func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs(); let mut func_spec_iter = func_solutions.specs();
let func_spec = match it.next() {
let func_spec = match func_spec_iter.next() {
Some(spec) => spec, Some(spec) => spec,
None => panic!("no specialization for expect {symbol}"), None => panic!("No specialization for expect {symbol}."),
}; };
debug_assert!( debug_assert!(
it.next().is_none(), func_spec_iter.next().is_none(),
"we expect only one specialization of this symbol" "We expect only one specialization of this symbol."
); );
// NOTE fake layout; it is only used for debug prints // 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 name = roc_main_fn.get_name().to_str().unwrap();
let expect_name = &format!("Expect_{name}"); let expect_name = &format!("Expect_{name}");
let expect_name = env.arena.alloc_str(expect_name); let expect_name_str = env.arena.alloc_str(expect_name);
expect_names.push(&*expect_name); expect_names.push(&*expect_name_str);
// Add main to the module. // Add main to the module.
let _ = expose_function_to_host_help_c_abi( let _ = expose_function_to_host_help_c_abi(
@ -5731,8 +5747,10 @@ pub fn build_procedures_expose_expects<'a>(
&format!("Expect_{name}"), &format!("Expect_{name}"),
); );
} }
expect_names_by_module.insert(module_id, expect_names);
}
expect_names expect_names_by_module
} }
fn build_procedures_help<'a>( fn build_procedures_help<'a>(

View file

@ -702,7 +702,7 @@ struct State<'a> {
pub dependencies: Dependencies<'a>, pub dependencies: Dependencies<'a>,
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
pub host_exposed_lambda_sets: HostExposedLambdaSets<'a>, pub host_exposed_lambda_sets: HostExposedLambdaSets<'a>,
pub toplevel_expects: ToplevelExpects, pub toplevel_expects: MutMap<ModuleId, ToplevelExpects>,
pub exposed_to_host: ExposedToHost, pub exposed_to_host: ExposedToHost,
/// This is the "final" list of IdentIds, after canonicalization and constraint gen /// This is the "final" list of IdentIds, after canonicalization and constraint gen
@ -784,7 +784,7 @@ impl<'a> State<'a> {
dependencies, dependencies,
procedures: MutMap::default(), procedures: MutMap::default(),
host_exposed_lambda_sets: std::vec::Vec::new(), host_exposed_lambda_sets: std::vec::Vec::new(),
toplevel_expects: ToplevelExpects::default(), toplevel_expects: MutMap::default(),
exposed_to_host: ExposedToHost::default(), exposed_to_host: ExposedToHost::default(),
exposed_modules: &[], exposed_modules: &[],
exposed_types, exposed_types,
@ -2787,8 +2787,9 @@ fn update<'a>(
let subs = solved_subs.into_inner(); let subs = solved_subs.into_inner();
state.toplevel_expects.pure.extend(toplevel_expects.pure); if !toplevel_expects.pure.is_empty() || !toplevel_expects.fx.is_empty() {
state.toplevel_expects.fx.extend(toplevel_expects.fx); state.toplevel_expects.insert(module_id, toplevel_expects);
}
state state
.module_cache .module_cache

View file

@ -177,7 +177,7 @@ pub struct MonomorphizedModule<'a> {
pub type_problems: MutMap<ModuleId, Vec<TypeError>>, pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
pub host_exposed_lambda_sets: HostExposedLambdaSets<'a>, pub host_exposed_lambda_sets: HostExposedLambdaSets<'a>,
pub toplevel_expects: ToplevelExpects, pub toplevel_expects: MutMap<ModuleId, ToplevelExpects>,
pub entry_point: EntryPoint<'a>, pub entry_point: EntryPoint<'a>,
pub exposed_to_host: ExposedToHost, pub exposed_to_host: ExposedToHost,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,

View file

@ -267,6 +267,9 @@ pub enum Expr<'a> {
// Collection Literals // Collection Literals
List(Collection<'a, &'a Loc<Expr<'a>>>), List(Collection<'a, &'a Loc<Expr<'a>>>),
/// An expression followed by `!``
Suffixed(&'a Expr<'a>),
RecordUpdate { RecordUpdate {
update: &'a Loc<Expr<'a>>, update: &'a Loc<Expr<'a>>,
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>, fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
@ -1568,6 +1571,7 @@ impl<'a> Malformed for Expr<'a> {
PrecedenceConflict(_) | PrecedenceConflict(_) |
MultipleRecordBuilders(_) | MultipleRecordBuilders(_) |
UnappliedRecordBuilder(_) => true, UnappliedRecordBuilder(_) => true,
Suffixed(expr) => expr.is_malformed(),
} }
} }
} }

View file

@ -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>> { 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| { line_min_indent(move |arena, state: State<'a>, min_indent: u32| {
let (_, expr, state) = let (_, expr, state) = loc_possibly_negative_or_negated_term(options)
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?; .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 initial_state = state.clone();
let end = state.pos(); let end = state.pos();
@ -1640,7 +1652,7 @@ fn parse_expr_end<'a>(
Err((MadeProgress, f)) => Err((MadeProgress, f)), Err((MadeProgress, f)) => Err((MadeProgress, f)),
Ok(( Ok((
_, _,
has @ Loc { implements @ Loc {
value: value:
Expr::Var { Expr::Var {
module_name: "", module_name: "",
@ -1672,17 +1684,17 @@ fn parse_expr_end<'a>(
} }
// Attach any spaces to the `implements` keyword // 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 arena
.alloc(Implements::Implements) .alloc(Implements::Implements)
.with_spaces_before(expr_state.spaces_after, has.region) .with_spaces_before(expr_state.spaces_after, implements.region)
} else { } else {
Loc::at(has.region, Implements::Implements) Loc::at(implements.region, Implements::Implements)
}; };
let args = arguments.into_bump_slice(); let args = arguments.into_bump_slice();
let (_, (type_def, def_region), state) = 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(); 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>> { 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::Str(string) => Pattern::StrLiteral(string),
Expr::SingleQuote(string) => Pattern::SingleQuote(string), Expr::SingleQuote(string) => Pattern::SingleQuote(string),
Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem), Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem),
Expr::Suffixed(_) => todo!(),
}; };
// Now we re-add the spaces // Now we re-add the spaces
@ -2997,6 +3022,7 @@ where
Err((NoProgress, to_error("->", state.pos()))) Err((NoProgress, to_error("->", state.pos())))
} }
"<-" => good!(BinOp::Backpassing, 2), "<-" => good!(BinOp::Backpassing, 2),
"!" => Err((NoProgress, to_error("!", state.pos()))),
_ => bad_made_progress!(chomped), _ => bad_made_progress!(chomped),
} }
} }

View file

@ -0,0 +1,6 @@
Suffixed(
Var {
module_name: "Stdout",
ident: "line",
},
)

View file

@ -0,0 +1 @@
Stdout.line!

View file

@ -0,0 +1 @@
foo! (bar! baz) (blah stuff)

View file

@ -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,
)

View file

@ -0,0 +1 @@
foo! ( bar! baz) ( blah stuff)

View file

@ -441,6 +441,8 @@ mod test_snapshots {
pass/string_without_escape.expr, pass/string_without_escape.expr,
pass/sub_var_with_spaces.expr, pass/sub_var_with_spaces.expr,
pass/sub_with_spaces.expr, pass/sub_with_spaces.expr,
pass/suffixed.expr,
pass/suffixed_nested.expr,
pass/tag_pattern.expr, pass/tag_pattern.expr,
pass/ten_times_eleven.expr, pass/ten_times_eleven.expr,
pass/three_arg_closure.expr, pass/three_arg_closure.expr,

View file

@ -700,6 +700,7 @@ impl IterTokens for Loc<Expr<'_>> {
Expr::MalformedIdent(_, _) | Expr::MalformedClosure | Expr::PrecedenceConflict(_) => { Expr::MalformedIdent(_, _) | Expr::MalformedClosure | Expr::PrecedenceConflict(_) => {
bumpvec![in arena;] bumpvec![in arena;]
} }
Expr::Suffixed(_) => todo!(),
} }
} }
} }

View file

@ -150,7 +150,7 @@ mod test {
let interns = loaded.interns.clone(); 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, arena,
target.clone(), target.clone(),
loaded, loaded,
@ -168,23 +168,27 @@ mod test {
let mut memory = crate::run::ExpectMemory::from_slice(&mut shared_buffer); let mut memory = crate::run::ExpectMemory::from_slice(&mut shared_buffer);
// communicate the mmapped name to zig/roc // 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(); let mut result = RocCallResult::default();
unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) }; unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) };
let mut writer = Vec::with_capacity(1024); 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( let (_failed, _passed) = crate::run::run_expects_with_memory(
&mut writer, &mut writer,
RenderTarget::ColorTerminal, RenderTarget::ColorTerminal,
arena, arena,
interns, interns,
&layout_interner.into_global(), &global_layout_interner,
&lib, &dy_lib,
&mut expectations, &mut expectations,
expects, expect_funcs,
&mut memory, &mut memory,
) )
.unwrap(); .unwrap();
}
// Remove ANSI escape codes from the answer - for example: // Remove ANSI escape codes from the answer - for example:
// //

View file

@ -11,7 +11,7 @@ use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use roc_build::link::llvm_module_to_dylib; use roc_build::link::llvm_module_to_dylib;
use roc_can::expr::ExpectLookup; use roc_can::expr::ExpectLookup;
use roc_collections::{MutSet, VecMap}; use roc_collections::{MutMap, MutSet, VecMap};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_gen_llvm::{ use roc_gen_llvm::{
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs}, llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
@ -620,7 +620,7 @@ pub fn expect_mono_module_to_dylib<'a>(
) -> Result< ) -> Result<
( (
libloading::Library, libloading::Library,
ExpectFunctions<'a>, MutMap<ModuleId, ExpectFunctions<'a>>,
STLayoutInterner<'a>, STLayoutInterner<'a>,
), ),
libloading::Error, libloading::Error,
@ -666,25 +666,41 @@ pub fn expect_mono_module_to_dylib<'a>(
// platform to provide them. // platform to provide them.
add_default_roc_externs(&env); add_default_roc_externs(&env);
let capacity = toplevel_expects.pure.len() + toplevel_expects.fx.len(); let expects_symbols = toplevel_expects
let mut expect_symbols = BumpVec::with_capacity_in(capacity, env.arena); .iter()
.map(|(module_id, expects)| {
expect_symbols.extend(toplevel_expects.pure.keys().copied()); (
expect_symbols.extend(toplevel_expects.fx.keys().copied()); *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( let expect_names = roc_gen_llvm::llvm::build::build_procedures_expose_expects(
&env, &env,
&layout_interner, &layout_interner,
opt_level, opt_level,
expect_symbols.into_bump_slice(), expects_symbols,
procedures, 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( let expects_fx = bumpalo::collections::Vec::from_iter_in(
toplevel_expects expects
.fx .fx
.into_iter() .into_iter()
.zip(expect_names.iter().skip(toplevel_expects.pure.len())) .zip(expect_names.iter().skip(expects.pure.len()))
.map(|((symbol, region), name)| ToplevelExpect { .map(|((symbol, region), name)| ToplevelExpect {
symbol, symbol,
region, region,
@ -693,24 +709,26 @@ pub fn expect_mono_module_to_dylib<'a>(
env.arena, env.arena,
); );
let expects_pure = bumpalo::collections::Vec::from_iter_in( let expects_pure =
toplevel_expects bumpalo::collections::Vec::from_iter_in(
.pure expects.pure.into_iter().zip(expect_names.iter()).map(
.into_iter() |((symbol, region), name)| ToplevelExpect {
.zip(expect_names.iter())
.map(|((symbol, region), name)| ToplevelExpect {
symbol, symbol,
region, region,
name, name,
}), },
),
env.arena, env.arena,
); );
let expects = ExpectFunctions { let expect_funs = ExpectFunctions {
pure: expects_pure, pure: expects_pure,
fx: expects_fx, fx: expects_fx,
}; };
modules_expects.insert(module_id, expect_funs);
}
env.dibuilder.finalize(); env.dibuilder.finalize();
// Uncomment this to see the module's un-optimized LLVM instruction output: // 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(); 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))
} }