mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge branch 'main' of github.com:roc-lang/roc into simplify_examples
This commit is contained in:
commit
c6ec3d5d30
65 changed files with 3191 additions and 679 deletions
|
@ -5,6 +5,7 @@ use build::BuiltFile;
|
|||
use bumpalo::Bump;
|
||||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||||
use roc_build::link::{LinkType, LinkingStrategy};
|
||||
use roc_build::program::Problems;
|
||||
use roc_collections::VecMap;
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
use roc_load::{Expectations, LoadingProblem, Threading};
|
||||
|
@ -566,89 +567,36 @@ pub fn build(
|
|||
// If possible, report the generated executable name relative to the current dir.
|
||||
let generated_filename = binary_path
|
||||
.strip_prefix(env::current_dir().unwrap())
|
||||
.unwrap_or(&binary_path);
|
||||
.unwrap_or(&binary_path)
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
// No need to waste time freeing this memory,
|
||||
// since the process is about to exit anyway.
|
||||
std::mem::forget(arena);
|
||||
|
||||
println!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while successfully building:\n\n {}",
|
||||
if problems.errors == 0 {
|
||||
32 // green
|
||||
} else {
|
||||
33 // yellow
|
||||
},
|
||||
problems.errors,
|
||||
if problems.errors == 1 {
|
||||
"error"
|
||||
} else {
|
||||
"errors"
|
||||
},
|
||||
if problems.warnings == 0 {
|
||||
32 // green
|
||||
} else {
|
||||
33 // yellow
|
||||
},
|
||||
problems.warnings,
|
||||
if problems.warnings == 1 {
|
||||
"warning"
|
||||
} else {
|
||||
"warnings"
|
||||
},
|
||||
total_time.as_millis(),
|
||||
generated_filename.to_str().unwrap()
|
||||
);
|
||||
print_problems(problems, total_time);
|
||||
println!(" while successfully building:\n\n {generated_filename}");
|
||||
|
||||
// Return a nonzero exit code if there were problems
|
||||
Ok(problems.exit_code())
|
||||
}
|
||||
BuildAndRun => {
|
||||
if problems.errors > 0 || problems.warnings > 0 {
|
||||
print_problems(problems, total_time);
|
||||
println!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
||||
if problems.errors == 0 {
|
||||
32 // green
|
||||
} else {
|
||||
33 // yellow
|
||||
},
|
||||
problems.errors,
|
||||
if problems.errors == 1 {
|
||||
"error"
|
||||
} else {
|
||||
"errors"
|
||||
},
|
||||
if problems.warnings == 0 {
|
||||
32 // green
|
||||
} else {
|
||||
33 // yellow
|
||||
},
|
||||
problems.warnings,
|
||||
if problems.warnings == 1 {
|
||||
"warning"
|
||||
} else {
|
||||
"warnings"
|
||||
},
|
||||
total_time.as_millis(),
|
||||
".\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
);
|
||||
}
|
||||
|
||||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||||
|
||||
let bytes = std::fs::read(&binary_path).unwrap();
|
||||
// don't waste time deallocating; the process ends anyway
|
||||
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
||||
|
||||
let x = roc_run(
|
||||
arena,
|
||||
opt_level,
|
||||
triple,
|
||||
args,
|
||||
&bytes,
|
||||
expectations,
|
||||
interns,
|
||||
);
|
||||
std::mem::forget(bytes);
|
||||
x
|
||||
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
|
||||
}
|
||||
BuildAndRunIfNoErrors => {
|
||||
debug_assert!(
|
||||
|
@ -656,21 +604,16 @@ pub fn build(
|
|||
"if there are errors, they should have been returned as an error variant"
|
||||
);
|
||||
if problems.warnings > 0 {
|
||||
print_problems(problems, total_time);
|
||||
println!(
|
||||
"\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||||
problems.warnings,
|
||||
if problems.warnings == 1 {
|
||||
"warning"
|
||||
} else {
|
||||
"warnings"
|
||||
},
|
||||
total_time.as_millis(),
|
||||
".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
);
|
||||
}
|
||||
|
||||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||||
|
||||
// don't waste time deallocating; the process ends anyway
|
||||
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
||||
|
||||
|
@ -686,40 +629,17 @@ pub fn build(
|
|||
|
||||
let problems = roc_build::program::report_problems_typechecked(&mut module);
|
||||
|
||||
let mut output = format!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with \x1B[32mroc run",
|
||||
if problems.errors == 0 {
|
||||
32 // green
|
||||
} else {
|
||||
33 // yellow
|
||||
},
|
||||
problems.errors,
|
||||
if problems.errors == 1 {
|
||||
"error"
|
||||
} else {
|
||||
"errors"
|
||||
},
|
||||
if problems.warnings == 0 {
|
||||
32 // green
|
||||
} else {
|
||||
33 // yellow
|
||||
},
|
||||
problems.warnings,
|
||||
if problems.warnings == 1 {
|
||||
"warning"
|
||||
} else {
|
||||
"warnings"
|
||||
},
|
||||
total_time.as_millis(),
|
||||
);
|
||||
print_problems(problems, total_time);
|
||||
|
||||
print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
|
||||
|
||||
// If you're running "main.roc" then you can just do `roc run`
|
||||
// to re-run the program.
|
||||
if filename != DEFAULT_ROC_FILENAME {
|
||||
output.push(' ');
|
||||
output.push_str(&filename.to_string_lossy());
|
||||
print!(" {}", &filename.to_string_lossy());
|
||||
}
|
||||
|
||||
println!("{}\x1B[39m", output);
|
||||
println!("\x1B[39m");
|
||||
|
||||
Ok(problems.exit_code())
|
||||
}
|
||||
|
@ -734,6 +654,34 @@ pub fn build(
|
|||
}
|
||||
}
|
||||
|
||||
fn print_problems(problems: Problems, total_time: std::time::Duration) {
|
||||
const GREEN: usize = 32;
|
||||
const YELLOW: usize = 33;
|
||||
|
||||
print!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms",
|
||||
match problems.errors {
|
||||
0 => GREEN,
|
||||
_ => YELLOW,
|
||||
},
|
||||
problems.errors,
|
||||
match problems.errors {
|
||||
1 => "error",
|
||||
_ => "errors",
|
||||
},
|
||||
match problems.warnings {
|
||||
0 => GREEN,
|
||||
_ => YELLOW,
|
||||
},
|
||||
problems.warnings,
|
||||
match problems.warnings {
|
||||
1 => "warning",
|
||||
_ => "warnings",
|
||||
},
|
||||
total_time.as_millis(),
|
||||
);
|
||||
}
|
||||
|
||||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
opt_level: OptLevel,
|
||||
|
@ -814,12 +762,17 @@ fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
|||
// envp is an array of pointers to strings, conventionally of the
|
||||
// form key=value, which are passed as the environment of the new
|
||||
// program. The envp array must be terminated by a NULL pointer.
|
||||
let mut buffer = Vec::with_capacity(100);
|
||||
let envp_cstrings: bumpalo::collections::Vec<CString> = std::env::vars_os()
|
||||
.flat_map(|(k, v)| {
|
||||
[
|
||||
CString::new(k.as_bytes()).unwrap(),
|
||||
CString::new(v.as_bytes()).unwrap(),
|
||||
]
|
||||
.map(|(k, v)| {
|
||||
buffer.clear();
|
||||
|
||||
use std::io::Write;
|
||||
buffer.write_all(k.as_bytes()).unwrap();
|
||||
buffer.write_all(b"=").unwrap();
|
||||
buffer.write_all(v.as_bytes()).unwrap();
|
||||
|
||||
CString::new(buffer.as_slice()).unwrap()
|
||||
})
|
||||
.collect_in(arena);
|
||||
|
||||
|
|
|
@ -70,7 +70,11 @@ mod cli_run {
|
|||
}
|
||||
|
||||
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
|
||||
let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]);
|
||||
let compile_out = run_roc(
|
||||
[CMD_CHECK, file.to_str().unwrap()].iter().chain(flags),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
let err = compile_out.stdout.trim();
|
||||
let err = strip_colors(err);
|
||||
|
||||
|
@ -82,7 +86,7 @@ mod cli_run {
|
|||
}
|
||||
|
||||
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
|
||||
let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]);
|
||||
let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[], &[]);
|
||||
|
||||
assert_eq!(out.status.success(), expects_success_exit_code);
|
||||
}
|
||||
|
@ -92,6 +96,7 @@ mod cli_run {
|
|||
args: I,
|
||||
stdin: &[&str],
|
||||
roc_app_args: &[String],
|
||||
env: &[(&str, &str)],
|
||||
) -> Out {
|
||||
let compile_out = run_roc(
|
||||
// converting these all to String avoids lifetime issues
|
||||
|
@ -100,6 +105,7 @@ mod cli_run {
|
|||
.chain([file.to_str().unwrap().to_string(), "--".to_string()])
|
||||
.chain(roc_app_args.iter().cloned()),
|
||||
stdin,
|
||||
env,
|
||||
);
|
||||
|
||||
let ignorable = "🔨 Rebuilding platform...\n";
|
||||
|
@ -150,7 +156,13 @@ mod cli_run {
|
|||
|
||||
let out = match cli_mode {
|
||||
CliMode::RocBuild => {
|
||||
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], &[]);
|
||||
run_roc_on(
|
||||
file,
|
||||
iter::once(CMD_BUILD).chain(flags.clone()),
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
|
||||
if use_valgrind && ALLOW_VALGRIND {
|
||||
let mut valgrind_args = vec![file
|
||||
|
@ -207,25 +219,18 @@ mod cli_run {
|
|||
}
|
||||
CliMode::Roc => {
|
||||
if !extra_env.is_empty() {
|
||||
// TODO: environment is not currently forwarded by Roc to the target
|
||||
// binary, so this would fail!
|
||||
// TODO: `roc` and `roc dev` are currently buggy for `env.roc`
|
||||
continue;
|
||||
}
|
||||
run_roc_on(file, flags.clone(), stdin, roc_app_args)
|
||||
run_roc_on(file, flags.clone(), stdin, roc_app_args, extra_env)
|
||||
}
|
||||
CliMode::RocRun => {
|
||||
if !extra_env.is_empty() {
|
||||
// TODO: environment is not currently forwarded by Roc to the target
|
||||
// binary, so this would fail!
|
||||
continue;
|
||||
}
|
||||
run_roc_on(
|
||||
CliMode::RocRun => run_roc_on(
|
||||
file,
|
||||
iter::once(CMD_RUN).chain(flags.clone()),
|
||||
stdin,
|
||||
roc_app_args,
|
||||
)
|
||||
}
|
||||
extra_env,
|
||||
),
|
||||
};
|
||||
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
|
@ -305,7 +310,7 @@ mod cli_run {
|
|||
// Since these require things the build system often doesn't have
|
||||
// (e.g. GUIs open a window, Ruby needs ruby installed, WASM needs a browser)
|
||||
// we do `roc build` on them but don't run them.
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[]);
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[], &[]);
|
||||
return;
|
||||
}
|
||||
"swiftui" | "rocLovesSwift" => {
|
||||
|
@ -316,7 +321,7 @@ mod cli_run {
|
|||
);
|
||||
return;
|
||||
} else {
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[]);
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[], &[]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
interface Foo
|
||||
interface ExposedNotDefined
|
||||
exposes [bar]
|
||||
imports []
|
||||
|
|
|
@ -16,6 +16,7 @@ fn exec_bench_w_input<T: Measurement>(
|
|||
let compile_out = run_roc(
|
||||
["build", OPTIMIZE_FLAG, file.to_str().unwrap()],
|
||||
&[stdin_str],
|
||||
&[],
|
||||
);
|
||||
|
||||
if !compile_out.stderr.is_empty() && compile_out.stderr != "🔨 Rebuilding platform...\n" {
|
||||
|
|
|
@ -22,7 +22,7 @@ pub struct Out {
|
|||
pub status: ExitStatus,
|
||||
}
|
||||
|
||||
pub fn run_roc<I, S>(args: I, stdin_vals: &[&str]) -> Out
|
||||
pub fn run_roc<I, S>(args: I, stdin_vals: &[&str], extra_env: &[(&str, &str)]) -> Out
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
|
@ -62,7 +62,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
run_with_stdin(&roc_binary_path, args, stdin_vals)
|
||||
run_with_stdin_and_env(&roc_binary_path, args, stdin_vals, extra_env)
|
||||
}
|
||||
|
||||
pub fn run_glue<I, S>(args: I) -> Out
|
||||
|
@ -118,6 +118,19 @@ pub fn strip_colors(str: &str) -> String {
|
|||
}
|
||||
|
||||
pub fn run_with_stdin<I, S>(path: &Path, args: I, stdin_vals: &[&str]) -> Out
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
run_with_stdin_and_env(path, args, stdin_vals, &[])
|
||||
}
|
||||
|
||||
pub fn run_with_stdin_and_env<I, S>(
|
||||
path: &Path,
|
||||
args: I,
|
||||
stdin_vals: &[&str],
|
||||
extra_env: &[(&str, &str)],
|
||||
) -> Out
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
|
@ -128,6 +141,10 @@ where
|
|||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
for (k, v) in extra_env {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
|
||||
let mut child = cmd
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
|
|
|
@ -517,17 +517,25 @@ pub fn listSublist(
|
|||
len: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) RocList {
|
||||
if (len == 0) {
|
||||
const size = list.len();
|
||||
if (len == 0 or start >= size) {
|
||||
if (list.isUnique()) {
|
||||
// Decrement the reference counts of all elements.
|
||||
if (list.bytes) |source_ptr| {
|
||||
var i: usize = 0;
|
||||
while (i < size) : (i += 1) {
|
||||
const element = source_ptr + i * element_width;
|
||||
dec(element);
|
||||
}
|
||||
var output = list;
|
||||
output.length = 0;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
if (list.bytes) |source_ptr| {
|
||||
const size = list.len();
|
||||
|
||||
if (start >= size) {
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
const keep_len = std.math.min(len, size - start);
|
||||
const drop_start_len = start;
|
||||
const drop_end_len = size - (start + keep_len);
|
||||
|
@ -546,10 +554,17 @@ pub fn listSublist(
|
|||
dec(element);
|
||||
}
|
||||
|
||||
if (start == 0 and list.isUnique()) {
|
||||
if (list.isUnique()) {
|
||||
var output = list;
|
||||
output.length = keep_len;
|
||||
if (start == 0) {
|
||||
return output;
|
||||
} else {
|
||||
// We want memmove due to aliasing. Zig does not expose it directly.
|
||||
// Instead use copy which can write to aliases as long as the dest is before the source.
|
||||
mem.copy(u8, source_ptr[0 .. keep_len * element_width], source_ptr[start * element_width .. (start + keep_len) * element_width]);
|
||||
return output;
|
||||
}
|
||||
} else {
|
||||
const output = RocList.allocate(alignment, keep_len, element_width);
|
||||
const target_ptr = output.bytes orelse unreachable;
|
||||
|
|
|
@ -30,6 +30,23 @@ interface Decode
|
|||
]
|
||||
imports [
|
||||
List,
|
||||
Result.{ Result },
|
||||
Num.{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool },
|
||||
]
|
||||
|
||||
DecodeError : [TooShort]
|
||||
|
|
|
@ -20,6 +20,7 @@ interface Dict
|
|||
Bool.{ Bool },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Num.{ Nat },
|
||||
]
|
||||
|
||||
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
|
||||
|
|
|
@ -27,7 +27,24 @@ interface Encode
|
|||
append,
|
||||
toBytes,
|
||||
]
|
||||
imports []
|
||||
imports [
|
||||
Num.{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool },
|
||||
]
|
||||
|
||||
Encoder fmt := List U8, fmt -> List U8 | fmt has EncoderFormatting
|
||||
|
||||
|
|
84
crates/compiler/builtins/roc/Hash.roc
Normal file
84
crates/compiler/builtins/roc/Hash.roc
Normal file
|
@ -0,0 +1,84 @@
|
|||
interface Hash
|
||||
exposes [
|
||||
Hash,
|
||||
Hasher,
|
||||
hash,
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
addI8,
|
||||
addI16,
|
||||
addI32,
|
||||
addI64,
|
||||
addI128,
|
||||
complete,
|
||||
hashStrBytes,
|
||||
hashList,
|
||||
] imports [
|
||||
List,
|
||||
Str,
|
||||
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128 },
|
||||
]
|
||||
|
||||
## A value that can hashed.
|
||||
Hash has
|
||||
## Hashes a value into a [Hasher].
|
||||
## Note that [hash] does not produce a hash value itself; the hasher must be
|
||||
## [complete]d in order to extract the hash value.
|
||||
hash : hasher, a -> hasher | a has Hash, hasher has Hasher
|
||||
|
||||
## Describes a hashing algorithm that is fed bytes and produces an integer hash.
|
||||
##
|
||||
## The [Hasher] ability describes general-purpose hashers. It only allows
|
||||
## emission of 64-bit unsigned integer hashes. It is not suitable for
|
||||
## cryptographically-secure hashing.
|
||||
Hasher has
|
||||
## Adds a list of bytes to the hasher.
|
||||
addBytes : a, List U8 -> a | a has Hasher
|
||||
|
||||
## Adds a single U8 to the hasher.
|
||||
addU8 : a, U8 -> a | a has Hasher
|
||||
|
||||
## Adds a single U16 to the hasher.
|
||||
addU16 : a, U16 -> a | a has Hasher
|
||||
|
||||
## Adds a single U32 to the hasher.
|
||||
addU32 : a, U32 -> a | a has Hasher
|
||||
|
||||
## Adds a single U64 to the hasher.
|
||||
addU64 : a, U64 -> a | a has Hasher
|
||||
|
||||
## Adds a single U128 to the hasher.
|
||||
addU128 : a, U128 -> a | a has Hasher
|
||||
|
||||
## Adds a single I8 to the hasher.
|
||||
addI8 : a, I8 -> a | a has Hasher
|
||||
|
||||
## Adds a single I16 to the hasher.
|
||||
addI16 : a, I16 -> a | a has Hasher
|
||||
|
||||
## Adds a single I32 to the hasher.
|
||||
addI32 : a, I32 -> a | a has Hasher
|
||||
|
||||
## Adds a single I64 to the hasher.
|
||||
addI64 : a, I64 -> a | a has Hasher
|
||||
|
||||
## Adds a single I128 to the hasher.
|
||||
addI128 : a, I128 -> a | a has Hasher
|
||||
|
||||
## Completes the hasher, extracting a hash value from its
|
||||
## accumulated hash state.
|
||||
complete : a -> U64 | a has Hasher
|
||||
|
||||
## Adds a string into a [Hasher] by hashing its UTF-8 bytes.
|
||||
hashStrBytes = \hasher, s ->
|
||||
Str.walkUtf8WithIndex s hasher \accumHasher, byte, _ ->
|
||||
addU8 accumHasher byte
|
||||
|
||||
## Adds a list of [Hash]able elements to a [Hasher] by hashing each element.
|
||||
hashList = \hasher, lst ->
|
||||
List.walk lst hasher \accumHasher, elem ->
|
||||
hash accumHasher elem
|
|
@ -18,6 +18,23 @@ interface Json
|
|||
DecoderFormatting,
|
||||
DecodeResult,
|
||||
},
|
||||
Num.{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool },
|
||||
Result,
|
||||
]
|
||||
|
||||
Json := {} has [
|
||||
|
|
|
@ -63,6 +63,8 @@ interface List
|
|||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Result.{ Result },
|
||||
Num.{ Nat, Num, Int },
|
||||
]
|
||||
|
||||
## Types
|
||||
|
|
|
@ -145,6 +145,7 @@ interface Num
|
|||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Result.{ Result },
|
||||
]
|
||||
|
||||
## Represents a number that could be either an [Int] or a [Frac].
|
||||
|
@ -868,7 +869,7 @@ bitwiseOr : Int a, Int a -> Int a
|
|||
## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
|
||||
##
|
||||
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
|
||||
shiftLeftBy : Int a, Int a -> Int a
|
||||
shiftLeftBy : Int a, U8 -> Int a
|
||||
|
||||
## Bitwise arithmetic shift of a number by another
|
||||
##
|
||||
|
@ -881,7 +882,7 @@ shiftLeftBy : Int a, Int a -> Int a
|
|||
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
|
||||
##
|
||||
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
|
||||
shiftRightBy : Int a, Int a -> Int a
|
||||
shiftRightBy : Int a, U8 -> Int a
|
||||
|
||||
## Bitwise logical right shift of a number by another
|
||||
##
|
||||
|
@ -895,7 +896,7 @@ shiftRightBy : Int a, Int a -> Int a
|
|||
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
|
||||
##
|
||||
## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
|
||||
shiftRightZfBy : Int a, Int a -> Int a
|
||||
shiftRightZfBy : Int a, U8 -> Int a
|
||||
|
||||
## Round off the given fraction to the nearest integer.
|
||||
round : Frac * -> Int *
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Set
|
|||
intersection,
|
||||
difference,
|
||||
]
|
||||
imports [List, Bool.{ Bool }, Dict.{ Dict }]
|
||||
imports [List, Bool.{ Bool }, Dict.{ Dict }, Num.{ Nat }]
|
||||
|
||||
Set k := Dict.Dict k {}
|
||||
|
||||
|
|
|
@ -44,7 +44,12 @@ interface Str
|
|||
walkScalars,
|
||||
walkScalarsUntil,
|
||||
]
|
||||
imports [Bool.{ Bool }, Result.{ Result }, List]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
|
||||
]
|
||||
|
||||
## # Types
|
||||
##
|
||||
|
|
|
@ -13,6 +13,7 @@ pub fn module_source(module_id: ModuleId) -> &'static str {
|
|||
ModuleId::BOOL => BOOL,
|
||||
ModuleId::ENCODE => ENCODE,
|
||||
ModuleId::DECODE => DECODE,
|
||||
ModuleId::HASH => HASH,
|
||||
ModuleId::JSON => JSON,
|
||||
_ => panic!(
|
||||
"ModuleId {:?} is not part of the standard library",
|
||||
|
@ -31,4 +32,5 @@ const BOX: &str = include_str!("../roc/Box.roc");
|
|||
const BOOL: &str = include_str!("../roc/Bool.roc");
|
||||
const ENCODE: &str = include_str!("../roc/Encode.roc");
|
||||
const DECODE: &str = include_str!("../roc/Decode.roc");
|
||||
const HASH: &str = include_str!("../roc/Hash.roc");
|
||||
const JSON: &str = include_str!("../roc/Json.roc");
|
||||
|
|
|
@ -1516,8 +1516,10 @@ pub(crate) fn sort_can_defs_new(
|
|||
let def = defs.pop().unwrap();
|
||||
let index = group.first_one().unwrap();
|
||||
|
||||
if def_ordering.direct_references.get_row_col(index, index) {
|
||||
// a definition like `x = x + 1`, which is invalid in roc
|
||||
let bad_recursion_body = if def_ordering.direct_references.get_row_col(index, index)
|
||||
{
|
||||
// a definition like `x = x + 1`, which is invalid in roc.
|
||||
// We need to convert the body of the def to a runtime error.
|
||||
let symbol = def_ordering.get_symbol(index).unwrap();
|
||||
|
||||
let entries = vec![make_cycle_entry(symbol, &def)];
|
||||
|
@ -1525,9 +1527,19 @@ pub(crate) fn sort_can_defs_new(
|
|||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone()));
|
||||
env.problem(problem);
|
||||
|
||||
// Declaration::InvalidCycle(entries)
|
||||
todo!("InvalidCycle: {:?}", entries)
|
||||
} else if def_ordering.references.get_row_col(index, index) {
|
||||
Some(Expr::RuntimeError(RuntimeError::CircularDef(entries)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let is_illegally_self_recursive = bad_recursion_body.is_some();
|
||||
let set_opt_invalid_recursion_body = |e: &mut Expr| match bad_recursion_body {
|
||||
Some(err) => *e = err,
|
||||
None => {}
|
||||
};
|
||||
|
||||
if def_ordering.references.get_row_col(index, index) && !is_illegally_self_recursive
|
||||
{
|
||||
// this function calls itself, and must be typechecked as a recursive def
|
||||
match def.loc_pattern.value {
|
||||
Pattern::Identifier(symbol) => match def.loc_expr.value {
|
||||
|
@ -1540,7 +1552,7 @@ pub(crate) fn sort_can_defs_new(
|
|||
None,
|
||||
);
|
||||
}
|
||||
_ => todo!(),
|
||||
e => todo!("{:?}", e),
|
||||
},
|
||||
Pattern::AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
|
@ -1562,7 +1574,9 @@ pub(crate) fn sort_can_defs_new(
|
|||
} else {
|
||||
match def.loc_pattern.value {
|
||||
Pattern::Identifier(symbol) => match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
Closure(mut closure_data) => {
|
||||
set_opt_invalid_recursion_body(&mut closure_data.loc_body.value);
|
||||
|
||||
declarations.push_function_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
Loc::at(def.loc_expr.region, closure_data),
|
||||
|
@ -1572,6 +1586,9 @@ pub(crate) fn sort_can_defs_new(
|
|||
);
|
||||
}
|
||||
_ => {
|
||||
let mut def = def;
|
||||
set_opt_invalid_recursion_body(&mut def.loc_expr.value);
|
||||
|
||||
declarations.push_value_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
def.loc_expr,
|
||||
|
@ -1585,7 +1602,9 @@ pub(crate) fn sort_can_defs_new(
|
|||
ident: symbol,
|
||||
specializes,
|
||||
} => match def.loc_expr.value {
|
||||
Closure(closure_data) => {
|
||||
Closure(mut closure_data) => {
|
||||
set_opt_invalid_recursion_body(&mut closure_data.loc_body.value);
|
||||
|
||||
declarations.push_function_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
Loc::at(def.loc_expr.region, closure_data),
|
||||
|
@ -1595,6 +1614,9 @@ pub(crate) fn sort_can_defs_new(
|
|||
);
|
||||
}
|
||||
_ => {
|
||||
let mut def = def;
|
||||
set_opt_invalid_recursion_body(&mut def.loc_expr.value);
|
||||
|
||||
declarations.push_value_def(
|
||||
Loc::at(def.loc_pattern.region, symbol),
|
||||
def.loc_expr,
|
||||
|
@ -1605,6 +1627,9 @@ pub(crate) fn sort_can_defs_new(
|
|||
}
|
||||
},
|
||||
_ => {
|
||||
let mut def = def;
|
||||
set_opt_invalid_recursion_body(&mut def.loc_expr.value);
|
||||
|
||||
declarations.push_destructure_def(
|
||||
def.loc_pattern,
|
||||
def.loc_expr,
|
||||
|
|
191
crates/compiler/derive/src/hash.rs
Normal file
191
crates/compiler/derive/src/hash.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
//! Derivers for the `Hash` ability.
|
||||
|
||||
use std::iter::once;
|
||||
|
||||
use roc_can::{
|
||||
expr::{AnnotatedMark, ClosureData, Expr, Recursive},
|
||||
pattern::Pattern,
|
||||
};
|
||||
use roc_derive_key::hash::FlatHashKey;
|
||||
use roc_module::{called_via::CalledVia, ident::Lowercase, symbol::Symbol};
|
||||
use roc_region::all::Loc;
|
||||
use roc_types::{
|
||||
subs::{
|
||||
Content, FlatType, LambdaSet, OptVariable, RecordFields, SubsSlice, UnionLambdas, Variable,
|
||||
VariableSubsSlice,
|
||||
},
|
||||
types::RecordField,
|
||||
};
|
||||
|
||||
use crate::{synth_var, util::Env, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
|
||||
let (body, body_type) = match key {
|
||||
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
|
||||
};
|
||||
|
||||
let specialization_lambda_sets =
|
||||
env.get_specialization_lambda_sets(body_type, Symbol::HASH_HASH);
|
||||
|
||||
DerivedBody {
|
||||
body,
|
||||
body_type,
|
||||
specialization_lambda_sets,
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (Expr, Variable) {
|
||||
// Suppose rcd = { f1, ..., fn }.
|
||||
// Build a generalized type t_rcd = { f1: t1, ..., fn: tn }, with fresh t1, ..., tn,
|
||||
// so that we can re-use the derived impl for many records of the same fields.
|
||||
let (record_var, record_fields) = {
|
||||
let flex_fields = fields
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
(
|
||||
name,
|
||||
RecordField::Required(env.subs.fresh_unnamed_flex_var()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(Lowercase, _)>>();
|
||||
let fields = RecordFields::insert_into_subs(env.subs, flex_fields);
|
||||
let record_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)),
|
||||
);
|
||||
|
||||
(record_var, fields)
|
||||
};
|
||||
|
||||
// Now, a hasher for this record is
|
||||
//
|
||||
// hash_rcd : hasher, { f1: t1, ..., fn: tn } -> hasher | hasher has Hasher
|
||||
// hash_rcd = \hasher, rcd ->
|
||||
// Hash.hash (
|
||||
// Hash.hash
|
||||
// ...
|
||||
// (Hash.hash hasher rcd.f1)
|
||||
// ...
|
||||
// rcd.f_n1)
|
||||
// rcd.fn
|
||||
//
|
||||
// So, just a build a fold travelling up the fields.
|
||||
let rcd_sym = env.new_symbol("rcd");
|
||||
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
let (body, body_var) = record_fields.iter_all().fold(
|
||||
(Expr::Var(hasher_sym), hasher_var),
|
||||
|(body, body_var), (field_name, field_var, _)| {
|
||||
let field_name = env.subs[field_name].clone();
|
||||
let field_var = env.subs[field_var];
|
||||
|
||||
let field_access = Expr::Access {
|
||||
record_var,
|
||||
field_var,
|
||||
ext_var: env.subs.fresh_unnamed_flex_var(),
|
||||
loc_expr: Box::new(Loc::at_zero(Expr::Var(rcd_sym))),
|
||||
field: field_name,
|
||||
};
|
||||
|
||||
let (hash_fn_data, returned_hasher_var) = {
|
||||
// build `Hash.hash ...` function type
|
||||
//
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
let exposed_hash_fn_var = env.import_builtin_symbol_var(Symbol::HASH_HASH);
|
||||
|
||||
// (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
let this_arguments_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [body_var, field_var]);
|
||||
let this_hash_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hasher_result_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hash_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_arguments_slice,
|
||||
this_hash_clos_var,
|
||||
this_hasher_result_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
// ~ (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
env.unify(exposed_hash_fn_var, this_hash_fn_var);
|
||||
|
||||
// Hash.hash : hasher, (typeof field) -[clos]-> hasher | hasher has Hasher, (typeof field) has Hash
|
||||
let hash_fn_head = Expr::AbilityMember(Symbol::HASH_HASH, None, this_hash_fn_var);
|
||||
let hash_fn_data = Box::new((
|
||||
this_hash_fn_var,
|
||||
Loc::at_zero(hash_fn_head),
|
||||
this_hash_clos_var,
|
||||
this_hasher_result_var,
|
||||
));
|
||||
|
||||
(hash_fn_data, this_hasher_result_var)
|
||||
};
|
||||
|
||||
let hash_arguments = vec![
|
||||
(body_var, Loc::at_zero(body)),
|
||||
(field_var, Loc::at_zero(field_access)),
|
||||
];
|
||||
let call_hash = Expr::Call(hash_fn_data, hash_arguments, CalledVia::Space);
|
||||
|
||||
(call_hash, returned_hasher_var)
|
||||
},
|
||||
);
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
|
||||
let (fn_var, fn_clos_var) = {
|
||||
// Create fn_var for ambient capture; we fix it up below.
|
||||
let fn_var = synth_var(env.subs, Content::Error);
|
||||
|
||||
// -[fn_name]->
|
||||
let fn_captures = vec![];
|
||||
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, fn_captures)));
|
||||
let fn_clos_var = synth_var(
|
||||
env.subs,
|
||||
Content::LambdaSet(LambdaSet {
|
||||
solved: fn_name_labels,
|
||||
recursion_var: OptVariable::NONE,
|
||||
unspecialized: SubsSlice::default(),
|
||||
ambient_function: fn_var,
|
||||
}),
|
||||
);
|
||||
|
||||
// hasher, rcd_var -[fn_name]-> (hasher = body_var)
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, [hasher_var, record_var]);
|
||||
env.subs.set_content(
|
||||
fn_var,
|
||||
Content::Structure(FlatType::Func(args_slice, fn_clos_var, body_var)),
|
||||
);
|
||||
|
||||
(fn_var, fn_clos_var)
|
||||
};
|
||||
|
||||
let clos_expr = Expr::Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: fn_clos_var,
|
||||
return_type: body_var,
|
||||
name: fn_name,
|
||||
captured_symbols: vec![],
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: vec![
|
||||
(
|
||||
hasher_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(hasher_sym)),
|
||||
),
|
||||
(
|
||||
record_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(rcd_sym)),
|
||||
),
|
||||
],
|
||||
loc_body: Box::new(Loc::at_zero(body)),
|
||||
});
|
||||
|
||||
(clos_expr, fn_var)
|
||||
}
|
|
@ -18,6 +18,7 @@ use util::Env;
|
|||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
mod hash;
|
||||
|
||||
mod util;
|
||||
|
||||
|
@ -77,6 +78,7 @@ fn build_derived_body(
|
|||
DeriveKey::Decoder(decoder_key) => {
|
||||
decoding::derive_decoder(&mut env, decoder_key, derived_symbol)
|
||||
}
|
||||
DeriveKey::Hash(hash_key) => hash::derive_hash(&mut env, hash_key, derived_symbol),
|
||||
};
|
||||
|
||||
let def = Def {
|
||||
|
|
123
crates/compiler/derive_key/src/hash.rs
Normal file
123
crates/compiler/derive_key/src/hash.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use roc_module::{ident::Lowercase, symbol::Symbol};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
use crate::{
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum FlatHash {
|
||||
// `hash` is always of form `hasher, a -> hasher` where `hasher` and `a` are opaque, so all
|
||||
// immediates must have exactly one lambda set!
|
||||
SingleLambdaSetImmediate(Symbol),
|
||||
Key(FlatHashKey),
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub enum FlatHashKey {
|
||||
// Unfortunate that we must allocate here, c'est la vie
|
||||
Record(Vec<Lowercase>),
|
||||
}
|
||||
|
||||
impl FlatHashKey {
|
||||
pub(crate) fn debug_name(&self) -> String {
|
||||
match self {
|
||||
FlatHashKey::Record(fields) => debug_name_record(fields),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlatHash {
|
||||
pub(crate) fn from_var(subs: &Subs, var: Variable) -> Result<FlatHash, DeriveError> {
|
||||
use DeriveError::*;
|
||||
use FlatHash::*;
|
||||
match *subs.get_content_without_compacting(var) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(sym, _) => match sym {
|
||||
Symbol::LIST_LIST => Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_LIST)),
|
||||
Symbol::STR_STR => Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_STR_BYTES)),
|
||||
_ => Err(Underivable),
|
||||
},
|
||||
FlatType::Record(fields, ext) => {
|
||||
let (fields_iter, ext) = fields.unsorted_iterator_and_ext(subs, ext);
|
||||
|
||||
check_derivable_ext_var(subs, ext, |ext| {
|
||||
matches!(ext, Content::Structure(FlatType::EmptyRecord))
|
||||
})?;
|
||||
|
||||
let mut field_names = Vec::with_capacity(fields.len());
|
||||
for (field_name, record_field) in fields_iter {
|
||||
if record_field.is_optional() {
|
||||
// Can't derive a concrete decoder for optional fields, since those are
|
||||
// compile-time-polymorphic
|
||||
return Err(Underivable);
|
||||
}
|
||||
field_names.push(field_name.clone());
|
||||
}
|
||||
|
||||
field_names.sort();
|
||||
|
||||
Ok(Key(FlatHashKey::Record(field_names)))
|
||||
}
|
||||
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))),
|
||||
FlatType::EmptyTagUnion => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
//
|
||||
FlatType::Erroneous(_) => Err(Underivable),
|
||||
FlatType::Func(..) => Err(Underivable),
|
||||
},
|
||||
Content::Alias(sym, _, real_var, _) => match sym {
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U8))
|
||||
}
|
||||
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U16))
|
||||
}
|
||||
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U32))
|
||||
}
|
||||
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U64))
|
||||
}
|
||||
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U128))
|
||||
}
|
||||
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I8))
|
||||
}
|
||||
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I16))
|
||||
}
|
||||
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I32))
|
||||
}
|
||||
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I64))
|
||||
}
|
||||
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
|
||||
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_I128))
|
||||
}
|
||||
// NB: I believe it is okay to unwrap opaques here because derivers are only used
|
||||
// by the backend, and the backend treats opaques like structural aliases.
|
||||
_ => Self::from_var(subs, real_var),
|
||||
},
|
||||
Content::RangedNumber(_) => Err(Underivable),
|
||||
//
|
||||
Content::RecursionVar { .. } => Err(Underivable),
|
||||
Content::Error => Err(Underivable),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
|
||||
Content::LambdaSet(_) => Err(Underivable),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,10 +15,12 @@
|
|||
|
||||
pub mod decoding;
|
||||
pub mod encoding;
|
||||
pub mod hash;
|
||||
mod util;
|
||||
|
||||
use decoding::{FlatDecodable, FlatDecodableKey};
|
||||
use encoding::{FlatEncodable, FlatEncodableKey};
|
||||
use hash::{FlatHash, FlatHashKey};
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::{Subs, Variable};
|
||||
|
@ -37,6 +39,7 @@ pub enum DeriveError {
|
|||
pub enum DeriveKey {
|
||||
ToEncoder(FlatEncodableKey),
|
||||
Decoder(FlatDecodableKey),
|
||||
Hash(FlatHashKey),
|
||||
}
|
||||
|
||||
impl DeriveKey {
|
||||
|
@ -44,6 +47,7 @@ impl DeriveKey {
|
|||
match self {
|
||||
DeriveKey::ToEncoder(key) => format!("toEncoder_{}", key.debug_name()),
|
||||
DeriveKey::Decoder(key) => format!("decoder_{}", key.debug_name()),
|
||||
DeriveKey::Hash(key) => format!("hash_{}", key.debug_name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +56,14 @@ impl DeriveKey {
|
|||
pub enum Derived {
|
||||
/// If a derived implementation name is well-known ahead-of-time, we can inline the symbol
|
||||
/// directly rather than associating a key for an implementation to be made later on.
|
||||
///
|
||||
/// Immediates refer to ability members that are "inlined" at the derivation call site.
|
||||
Immediate(Symbol),
|
||||
/// Like an [Derived::Immediate], but with the additional constraint that the immediate
|
||||
/// symbol is statically known to have exactly one lamdba set.
|
||||
/// This unlocks some optimization opportunities, as regioned lambda sets do not need to be
|
||||
/// chased.
|
||||
SingleLambdaSetImmediate(Symbol),
|
||||
/// Key of the derived implementation to use. This allows association of derived implementation
|
||||
/// names to a key, when the key is known ahead-of-time but the implementation (and it's name)
|
||||
/// is yet-to-be-made.
|
||||
|
@ -64,6 +75,7 @@ pub enum Derived {
|
|||
pub enum DeriveBuiltin {
|
||||
ToEncoder,
|
||||
Decoder,
|
||||
Hash,
|
||||
}
|
||||
|
||||
impl TryFrom<Symbol> for DeriveBuiltin {
|
||||
|
@ -73,6 +85,7 @@ impl TryFrom<Symbol> for DeriveBuiltin {
|
|||
match value {
|
||||
Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder),
|
||||
Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder),
|
||||
Symbol::HASH_HASH => Ok(DeriveBuiltin::Hash),
|
||||
_ => Err(value),
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +106,12 @@ impl Derived {
|
|||
FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))),
|
||||
},
|
||||
DeriveBuiltin::Hash => match hash::FlatHash::from_var(subs, var)? {
|
||||
FlatHash::SingleLambdaSetImmediate(imm) => {
|
||||
Ok(Derived::SingleLambdaSetImmediate(imm))
|
||||
}
|
||||
FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::pattern::fmt_pattern;
|
|||
use crate::spaces::{fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{
|
||||
AbilityMember, Defs, Expr, ExtractSpaces, Pattern, TypeAnnotation, TypeDef, TypeHeader,
|
||||
AbilityMember, Defs, Expr, ExtractSpaces, Pattern, Spaces, TypeAnnotation, TypeDef, TypeHeader,
|
||||
ValueDef,
|
||||
};
|
||||
use roc_region::all::Loc;
|
||||
|
@ -135,12 +135,20 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
if !self.is_multiline() {
|
||||
debug_assert_eq!(members.len(), 1);
|
||||
buf.push_str(" ");
|
||||
members[0].format(buf, indent + INDENT);
|
||||
members[0].format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::No,
|
||||
indent + INDENT,
|
||||
);
|
||||
} else {
|
||||
for demand in members.iter() {
|
||||
buf.newline();
|
||||
buf.indent(indent + INDENT);
|
||||
demand.format(buf, indent + INDENT);
|
||||
for member in members.iter() {
|
||||
member.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::Yes,
|
||||
indent + INDENT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -408,8 +416,18 @@ impl<'a> Formattable for AbilityMember<'a> {
|
|||
self.name.value.is_multiline() || self.typ.is_multiline()
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
buf.push_str(self.name.value.extract_spaces().item);
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
let Spaces { before, item, .. } = self.name.value.extract_spaces();
|
||||
fmt_spaces(buf, before.iter(), indent);
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str(item);
|
||||
buf.spaces(1);
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
|
|
|
@ -1382,9 +1382,9 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
|||
| BinOp::LessThanOrEq
|
||||
| BinOp::GreaterThanOrEq
|
||||
| BinOp::And
|
||||
| BinOp::Or => true,
|
||||
BinOp::Pizza
|
||||
| BinOp::Assignment
|
||||
| BinOp::Or
|
||||
| BinOp::Pizza => true,
|
||||
BinOp::Assignment
|
||||
| BinOp::IsAliasType
|
||||
| BinOp::IsOpaqueType
|
||||
| BinOp::Backpassing => false,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::annotation::{Formattable, Newlines};
|
||||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::expr::fmt_str_literal;
|
||||
use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
|
||||
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Collection, Module, Spaced};
|
||||
use roc_parse::header::{
|
||||
|
@ -31,6 +31,8 @@ pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
|
|||
pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a InterfaceHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent);
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("interface");
|
||||
|
||||
|
@ -56,6 +58,8 @@ pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a Interface
|
|||
pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent);
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("hosted");
|
||||
|
||||
|
@ -94,6 +98,9 @@ pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader
|
|||
|
||||
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent);
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("app");
|
||||
|
||||
|
@ -130,6 +137,8 @@ pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>)
|
|||
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
fmt_comments_only(buf, header.before_header.iter(), NewlineAt::Bottom, indent);
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("platform");
|
||||
|
||||
|
|
|
@ -5582,6 +5582,70 @@ mod test_fmt {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_nested_pipeline() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
(a |> b) |> c
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
a |> b |> c
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_member_doc_comments() {
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
|
||||
A has
|
||||
## This is member ab
|
||||
ab : a -> a | a has A
|
||||
|
||||
## This is member de
|
||||
de : a -> a | a has A
|
||||
|
||||
f = g
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_comments_preserved() {
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
# hello world
|
||||
interface Foo
|
||||
exposes []
|
||||
imports []
|
||||
"#
|
||||
));
|
||||
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
# hello world
|
||||
app "test" packages {} imports [] provides [] to "./platform"
|
||||
"#
|
||||
));
|
||||
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
# hello world
|
||||
platform "hello-world"
|
||||
requires {} { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [mainForHost]
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// this is a parse error atm
|
||||
// #[test]
|
||||
// fn multiline_apply() {
|
||||
|
|
|
@ -714,7 +714,7 @@ pub fn construct_optimization_passes<'a>(
|
|||
OptLevel::Optimize => {
|
||||
pmb.set_optimization_level(OptimizationLevel::Aggressive);
|
||||
// this threshold seems to do what we want
|
||||
pmb.set_inliner_with_threshold(275);
|
||||
pmb.set_inliner_with_threshold(750);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6448,9 +6448,23 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (rhs_arg, rhs_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
debug_assert_eq!(lhs_layout, rhs_layout);
|
||||
let int_width = intwidth_from_layout(*lhs_layout);
|
||||
|
||||
debug_assert_eq!(rhs_layout, &Layout::Builtin(Builtin::Int(IntWidth::U8)));
|
||||
let rhs_arg = if rhs_layout != lhs_layout {
|
||||
// LLVM shift intrinsics expect the left and right sides to have the same type, so
|
||||
// here we cast up `rhs` to the lhs type. Since the rhs was checked to be a U8,
|
||||
// this cast isn't lossy.
|
||||
let rhs_arg = env.builder.build_int_cast(
|
||||
rhs_arg.into_int_value(),
|
||||
lhs_arg.get_type().into_int_type(),
|
||||
"cast_for_shift",
|
||||
);
|
||||
rhs_arg.into()
|
||||
} else {
|
||||
rhs_arg
|
||||
};
|
||||
|
||||
build_int_binop(
|
||||
env,
|
||||
parent,
|
||||
|
@ -7921,11 +7935,10 @@ fn int_abs_with_overflow<'a, 'ctx, 'env>(
|
|||
// (xor arg shifted) - shifted
|
||||
|
||||
let bd = env.builder;
|
||||
let ctx = env.context;
|
||||
let shifted_name = "abs_shift_right";
|
||||
let shifted_alloca = {
|
||||
let bits_to_shift = int_type.get_bit_width() as u64 - 1;
|
||||
let shift_val = ctx.i64_type().const_int(bits_to_shift, false);
|
||||
let shift_val = int_type.const_int(bits_to_shift, false);
|
||||
let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name);
|
||||
let alloca = bd.build_alloca(int_type, "#int_abs_help");
|
||||
|
||||
|
|
|
@ -1686,7 +1686,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
let bit_width = 8 * self
|
||||
.ret_layout
|
||||
.stack_size(backend.env.layout_interner, TARGET_INFO);
|
||||
if bit_width < 32 && symbol_is_signed_int(backend, bits) {
|
||||
if bit_width < 32 && symbol_is_signed_int(backend, num) {
|
||||
let mask = (1 << bit_width) - 1;
|
||||
|
||||
backend
|
||||
|
|
|
@ -22,6 +22,7 @@ const MODULES: &[(ModuleId, &str)] = &[
|
|||
(ModuleId::BOX, "Box.roc"),
|
||||
(ModuleId::ENCODE, "Encode.roc"),
|
||||
(ModuleId::DECODE, "Decode.roc"),
|
||||
(ModuleId::HASH, "Hash.roc"),
|
||||
(ModuleId::JSON, "Json.roc"),
|
||||
];
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader,
|
|||
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{FileError, Parser, SyntaxError};
|
||||
use roc_parse::parser::{FileError, Parser, SourceError, SyntaxError};
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
use roc_reporting::report::{Annotation, RenderTarget};
|
||||
use roc_solve::module::{extract_module_owned_implementations, Solved, SolvedModule};
|
||||
|
@ -61,8 +61,8 @@ use std::str::from_utf8_unchecked;
|
|||
use std::sync::Arc;
|
||||
use std::{env, fs};
|
||||
|
||||
use crate::work::Dependencies;
|
||||
pub use crate::work::Phase;
|
||||
use crate::work::{DepCycle, Dependencies};
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
use crate::wasm_instant::{Duration, Instant};
|
||||
|
@ -177,6 +177,7 @@ impl Default for ModuleCache<'_> {
|
|||
BOX,
|
||||
ENCODE,
|
||||
DECODE,
|
||||
HASH,
|
||||
JSON,
|
||||
}
|
||||
|
||||
|
@ -827,6 +828,8 @@ enum Msg<'a> {
|
|||
filename: PathBuf,
|
||||
error: io::ErrorKind,
|
||||
},
|
||||
|
||||
IncorrectModuleName(FileError<'a, IncorrectModuleName<'a>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1124,6 +1127,13 @@ enum WorkerMsg {
|
|||
TaskAdded,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IncorrectModuleName<'a> {
|
||||
pub module_id: ModuleId,
|
||||
pub found: Loc<PQModuleName<'a>>,
|
||||
pub expected: PQModuleName<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LoadingProblem<'a> {
|
||||
FileProblem {
|
||||
|
@ -1139,6 +1149,9 @@ pub enum LoadingProblem<'a> {
|
|||
|
||||
/// a formatted report
|
||||
FormattedReport(String),
|
||||
|
||||
ImportCycle(PathBuf, Vec<ModuleId>),
|
||||
IncorrectModuleName(FileError<'a, IncorrectModuleName<'a>>),
|
||||
}
|
||||
|
||||
pub enum Phases {
|
||||
|
@ -1233,6 +1246,7 @@ impl<'a> LoadStart<'a> {
|
|||
filename,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
Arc::clone(&arc_modules),
|
||||
Arc::clone(&ident_ids_by_module),
|
||||
root_start_time,
|
||||
|
@ -1288,6 +1302,46 @@ impl<'a> LoadStart<'a> {
|
|||
let buf = to_file_problem_report(&filename, error);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(LoadingProblem::ImportCycle(filename, cycle)) => {
|
||||
let module_ids = Arc::try_unwrap(arc_modules)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("There were still outstanding Arc references to module_ids")
|
||||
})
|
||||
.into_inner()
|
||||
.into_module_ids();
|
||||
|
||||
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
|
||||
let buf = to_import_cycle_report(
|
||||
module_ids,
|
||||
root_exposed_ident_ids,
|
||||
cycle,
|
||||
filename,
|
||||
render,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(LoadingProblem::IncorrectModuleName(FileError {
|
||||
problem: SourceError { problem, bytes },
|
||||
filename,
|
||||
})) => {
|
||||
let module_ids = Arc::try_unwrap(arc_modules)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("There were still outstanding Arc references to module_ids")
|
||||
})
|
||||
.into_inner()
|
||||
.into_module_ids();
|
||||
|
||||
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
|
||||
let buf = to_incorrect_module_name_report(
|
||||
module_ids,
|
||||
root_exposed_ident_ids,
|
||||
problem,
|
||||
filename,
|
||||
bytes,
|
||||
render,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
};
|
||||
|
@ -1605,6 +1659,21 @@ fn state_thread_step<'a>(
|
|||
);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
Msg::IncorrectModuleName(FileError {
|
||||
problem: SourceError { problem, bytes },
|
||||
filename,
|
||||
}) => {
|
||||
let module_ids = (*state.arc_modules).lock().clone().into_module_ids();
|
||||
let buf = to_incorrect_module_name_report(
|
||||
module_ids,
|
||||
state.constrained_ident_ids,
|
||||
problem,
|
||||
filename,
|
||||
bytes,
|
||||
state.render,
|
||||
);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
msg => {
|
||||
// This is where most of the main thread's work gets done.
|
||||
// Everything up to this point has been setting up the threading
|
||||
|
@ -1644,6 +1713,36 @@ fn state_thread_step<'a>(
|
|||
);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
Err(LoadingProblem::ImportCycle(filename, cycle)) => {
|
||||
let module_ids = arc_modules.lock().clone().into_module_ids();
|
||||
|
||||
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
|
||||
let buf = to_import_cycle_report(
|
||||
module_ids,
|
||||
root_exposed_ident_ids,
|
||||
cycle,
|
||||
filename,
|
||||
render,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(LoadingProblem::IncorrectModuleName(FileError {
|
||||
problem: SourceError { problem, bytes },
|
||||
filename,
|
||||
})) => {
|
||||
let module_ids = arc_modules.lock().clone().into_module_ids();
|
||||
|
||||
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
|
||||
let buf = to_incorrect_module_name_report(
|
||||
module_ids,
|
||||
root_exposed_ident_ids,
|
||||
problem,
|
||||
filename,
|
||||
bytes,
|
||||
render,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
@ -1869,6 +1968,9 @@ fn worker_task_step<'a>(
|
|||
.send(Msg::FailedToReadFile { filename, error })
|
||||
.unwrap();
|
||||
}
|
||||
Err(LoadingProblem::IncorrectModuleName(err)) => {
|
||||
msg_tx.send(Msg::IncorrectModuleName(err)).unwrap();
|
||||
}
|
||||
Err(other) => {
|
||||
return Err(other);
|
||||
}
|
||||
|
@ -1933,6 +2035,9 @@ fn worker_task<'a>(
|
|||
.send(Msg::FailedToReadFile { filename, error })
|
||||
.unwrap();
|
||||
}
|
||||
Err(LoadingProblem::IncorrectModuleName(err)) => {
|
||||
msg_tx.send(Msg::IncorrectModuleName(err)).unwrap();
|
||||
}
|
||||
Err(other) => {
|
||||
return Err(other);
|
||||
}
|
||||
|
@ -2127,26 +2232,18 @@ fn update<'a>(
|
|||
// add the prelude
|
||||
let mut header = header;
|
||||
|
||||
if ![ModuleId::RESULT, ModuleId::BOOL].contains(&header.module_id) {
|
||||
extend_header_with_builtin(&mut header, ModuleId::RESULT);
|
||||
}
|
||||
|
||||
if ![ModuleId::NUM, ModuleId::BOOL, ModuleId::RESULT].contains(&header.module_id) {
|
||||
extend_header_with_builtin(&mut header, ModuleId::NUM);
|
||||
}
|
||||
|
||||
if ![ModuleId::BOOL].contains(&header.module_id) {
|
||||
extend_header_with_builtin(&mut header, ModuleId::BOOL);
|
||||
}
|
||||
|
||||
if !header.module_id.is_builtin() {
|
||||
extend_header_with_builtin(&mut header, ModuleId::BOX);
|
||||
extend_header_with_builtin(&mut header, ModuleId::NUM);
|
||||
extend_header_with_builtin(&mut header, ModuleId::BOOL);
|
||||
extend_header_with_builtin(&mut header, ModuleId::STR);
|
||||
extend_header_with_builtin(&mut header, ModuleId::LIST);
|
||||
extend_header_with_builtin(&mut header, ModuleId::RESULT);
|
||||
extend_header_with_builtin(&mut header, ModuleId::DICT);
|
||||
extend_header_with_builtin(&mut header, ModuleId::SET);
|
||||
extend_header_with_builtin(&mut header, ModuleId::LIST);
|
||||
extend_header_with_builtin(&mut header, ModuleId::BOX);
|
||||
extend_header_with_builtin(&mut header, ModuleId::ENCODE);
|
||||
extend_header_with_builtin(&mut header, ModuleId::DECODE);
|
||||
extend_header_with_builtin(&mut header, ModuleId::HASH);
|
||||
}
|
||||
|
||||
state
|
||||
|
@ -2161,11 +2258,23 @@ fn update<'a>(
|
|||
.map(|x| *x.as_inner()),
|
||||
);
|
||||
|
||||
work.extend(state.dependencies.add_module(
|
||||
let added_deps_result = state.dependencies.add_module(
|
||||
header.module_id,
|
||||
&header.package_qualified_imported_modules,
|
||||
state.exec_mode.goal_phase(),
|
||||
);
|
||||
|
||||
let new_work = match added_deps_result {
|
||||
Ok(work) => work,
|
||||
Err(DepCycle { cycle }) => {
|
||||
return Err(LoadingProblem::ImportCycle(
|
||||
header.module_path.clone(),
|
||||
cycle,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
work.extend(new_work);
|
||||
|
||||
state.module_cache.headers.insert(header.module_id, header);
|
||||
|
||||
|
@ -2749,6 +2858,9 @@ fn update<'a>(
|
|||
Msg::FailedToReadFile { .. } => {
|
||||
unreachable!();
|
||||
}
|
||||
Msg::IncorrectModuleName(..) => {
|
||||
internal_error!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3105,13 +3217,14 @@ fn load_builtin_module<'a>(
|
|||
|
||||
let (info, parse_state) = load_builtin_module_help(arena, module_name, src_bytes);
|
||||
|
||||
send_header(
|
||||
let (module_id, _, header) = build_header(
|
||||
info,
|
||||
parse_state,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
module_timing,
|
||||
)
|
||||
);
|
||||
(module_id, Msg::Header(header))
|
||||
}
|
||||
|
||||
/// Load a module by its module name, rather than by its filename
|
||||
|
@ -3164,16 +3277,18 @@ fn load_module<'a>(
|
|||
"Box", ModuleId::BOX
|
||||
"Encode", ModuleId::ENCODE
|
||||
"Decode", ModuleId::DECODE
|
||||
"Hash", ModuleId::HASH
|
||||
"Json", ModuleId::JSON
|
||||
}
|
||||
|
||||
let (filename, opt_shorthand) = module_name_to_path(src_dir, module_name, arc_shorthands);
|
||||
let (filename, opt_shorthand) = module_name_to_path(src_dir, &module_name, arc_shorthands);
|
||||
|
||||
load_filename(
|
||||
arena,
|
||||
filename,
|
||||
false,
|
||||
opt_shorthand,
|
||||
Some(module_name),
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
module_start_time,
|
||||
|
@ -3182,7 +3297,7 @@ fn load_module<'a>(
|
|||
|
||||
fn module_name_to_path<'a>(
|
||||
src_dir: &Path,
|
||||
module_name: PQModuleName<'a>,
|
||||
module_name: &PQModuleName<'a>,
|
||||
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
|
||||
) -> (PathBuf, Option<&'a str>) {
|
||||
let mut filename;
|
||||
|
@ -3199,7 +3314,7 @@ fn module_name_to_path<'a>(
|
|||
}
|
||||
}
|
||||
PQModuleName::Qualified(shorthand, name) => {
|
||||
opt_shorthand = Some(shorthand);
|
||||
opt_shorthand = Some(*shorthand);
|
||||
let shorthands = arc_shorthands.lock();
|
||||
|
||||
match shorthands.get(shorthand) {
|
||||
|
@ -3255,6 +3370,45 @@ fn find_task<T>(local: &Worker<T>, global: &Injector<T>, stealers: &[Stealer<T>]
|
|||
})
|
||||
}
|
||||
|
||||
fn verify_interface_matches_file_path<'a>(
|
||||
interface_name: Loc<roc_parse::header::ModuleName<'a>>,
|
||||
path: &Path,
|
||||
state: &roc_parse::state::State<'a>,
|
||||
) -> Result<(), LoadingProblem<'a>> {
|
||||
let module_parts = interface_name.value.as_str().split(MODULE_SEPARATOR).rev();
|
||||
|
||||
let mut is_mismatched = false;
|
||||
let mut opt_path = Some(path);
|
||||
for part in module_parts {
|
||||
match opt_path.and_then(|path| path.file_stem().map(|fi| (path, fi))) {
|
||||
None => {
|
||||
is_mismatched = true;
|
||||
break;
|
||||
}
|
||||
Some((path, fi)) => {
|
||||
if fi != part {
|
||||
is_mismatched = true;
|
||||
break;
|
||||
}
|
||||
opt_path = path.parent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_mismatched {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
use roc_parse::parser::EHeader;
|
||||
let syntax_problem =
|
||||
SyntaxError::Header(EHeader::InconsistentModuleName(interface_name.region));
|
||||
let problem = LoadingProblem::ParsingFailed(FileError {
|
||||
problem: SourceError::new(syntax_problem, state),
|
||||
filename: path.to_path_buf(),
|
||||
});
|
||||
Err(problem)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_header<'a>(
|
||||
arena: &'a Bump,
|
||||
|
@ -3262,6 +3416,7 @@ fn parse_header<'a>(
|
|||
filename: PathBuf,
|
||||
is_root_module: bool,
|
||||
opt_shorthand: Option<&'a str>,
|
||||
opt_expected_module_name: Option<PackageQualified<'a, ModuleName>>,
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
src_bytes: &'a [u8],
|
||||
|
@ -3280,9 +3435,13 @@ fn parse_header<'a>(
|
|||
|
||||
match parsed {
|
||||
Ok((ast::Module::Interface { header }, parse_state)) => {
|
||||
verify_interface_matches_file_path(header.name, &filename, &parse_state)?;
|
||||
|
||||
let header_name_region = header.name.region;
|
||||
|
||||
let info = HeaderInfo {
|
||||
loc_name: Loc {
|
||||
region: header.name.region,
|
||||
region: header_name_region,
|
||||
value: ModuleNameEnum::Interface(header.name.value),
|
||||
},
|
||||
filename,
|
||||
|
@ -3294,13 +3453,33 @@ fn parse_header<'a>(
|
|||
extra: HeaderFor::Interface,
|
||||
};
|
||||
|
||||
Ok(send_header(
|
||||
let (module_id, module_name, header) = build_header(
|
||||
info,
|
||||
parse_state,
|
||||
parse_state.clone(),
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
module_timing,
|
||||
))
|
||||
);
|
||||
|
||||
if let Some(expected_module_name) = opt_expected_module_name {
|
||||
if expected_module_name != module_name {
|
||||
let problem = SourceError::new(
|
||||
IncorrectModuleName {
|
||||
module_id,
|
||||
found: Loc::at(header_name_region, module_name),
|
||||
expected: expected_module_name,
|
||||
},
|
||||
&parse_state,
|
||||
);
|
||||
let problem = LoadingProblem::IncorrectModuleName(FileError {
|
||||
problem,
|
||||
filename: header.module_path,
|
||||
});
|
||||
return Err(problem);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((module_id, Msg::Header(header)))
|
||||
}
|
||||
Ok((ast::Module::Hosted { header }, parse_state)) => {
|
||||
let info = HeaderInfo {
|
||||
|
@ -3320,13 +3499,15 @@ fn parse_header<'a>(
|
|||
},
|
||||
};
|
||||
|
||||
Ok(send_header(
|
||||
let (module_id, _, header) = build_header(
|
||||
info,
|
||||
parse_state,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
module_timing,
|
||||
))
|
||||
);
|
||||
|
||||
Ok((module_id, Msg::Header(header)))
|
||||
}
|
||||
Ok((ast::Module::App { header }, parse_state)) => {
|
||||
let mut app_file_dir = filename.clone();
|
||||
|
@ -3364,13 +3545,14 @@ fn parse_header<'a>(
|
|||
},
|
||||
};
|
||||
|
||||
let (module_id, app_module_header_msg) = send_header(
|
||||
let (module_id, _, resolved_header) = build_header(
|
||||
info,
|
||||
parse_state,
|
||||
module_ids.clone(),
|
||||
ident_ids_by_module.clone(),
|
||||
module_timing,
|
||||
);
|
||||
let app_module_header_msg = Msg::Header(resolved_header);
|
||||
|
||||
match header.to.value {
|
||||
To::ExistingPackage(existing_package) => {
|
||||
|
@ -3450,6 +3632,7 @@ fn load_filename<'a>(
|
|||
filename: PathBuf,
|
||||
is_root_module: bool,
|
||||
opt_shorthand: Option<&'a str>,
|
||||
opt_expected_module_name: Option<PackageQualified<'a, ModuleName>>,
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
module_start_time: Instant,
|
||||
|
@ -3465,6 +3648,7 @@ fn load_filename<'a>(
|
|||
filename,
|
||||
is_root_module,
|
||||
opt_shorthand,
|
||||
opt_expected_module_name,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
arena.alloc(bytes),
|
||||
|
@ -3497,6 +3681,7 @@ fn load_from_str<'a>(
|
|||
filename,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
src.as_bytes(),
|
||||
|
@ -3517,13 +3702,13 @@ struct HeaderInfo<'a> {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn send_header<'a>(
|
||||
fn build_header<'a>(
|
||||
info: HeaderInfo<'a>,
|
||||
parse_state: roc_parse::state::State<'a>,
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
module_timing: ModuleTiming,
|
||||
) -> (ModuleId, Msg<'a>) {
|
||||
) -> (ModuleId, PQModuleName<'a>, ModuleHeader<'a>) {
|
||||
use ModuleNameEnum::*;
|
||||
|
||||
let HeaderInfo {
|
||||
|
@ -3571,13 +3756,14 @@ fn send_header<'a>(
|
|||
let mut scope: MutMap<Ident, (Symbol, Region)> =
|
||||
HashMap::with_capacity_and_hasher(scope_size, default_hasher());
|
||||
let home: ModuleId;
|
||||
let name: PQModuleName;
|
||||
|
||||
let ident_ids = {
|
||||
// Lock just long enough to perform the minimal operations necessary.
|
||||
let mut module_ids = (*module_ids).lock();
|
||||
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
|
||||
|
||||
let name = match opt_shorthand {
|
||||
name = match opt_shorthand {
|
||||
Some(shorthand) => PQModuleName::Qualified(shorthand, declared_name),
|
||||
None => PQModuleName::Unqualified(declared_name),
|
||||
};
|
||||
|
@ -3706,7 +3892,8 @@ fn send_header<'a>(
|
|||
|
||||
(
|
||||
home,
|
||||
Msg::Header(ModuleHeader {
|
||||
name,
|
||||
ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
is_root_module,
|
||||
|
@ -3722,7 +3909,7 @@ fn send_header<'a>(
|
|||
symbols_from_requires: Vec::new(),
|
||||
header_for: extra,
|
||||
module_timing,
|
||||
}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4575,7 +4762,11 @@ fn canonicalize_and_constrain<'a>(
|
|||
Vacant(vacant) => {
|
||||
let should_include_builtin = matches!(
|
||||
name.module_id(),
|
||||
ModuleId::ENCODE | ModuleId::DECODE | ModuleId::DICT | ModuleId::SET
|
||||
ModuleId::ENCODE
|
||||
| ModuleId::DECODE
|
||||
| ModuleId::DICT
|
||||
| ModuleId::SET
|
||||
| ModuleId::HASH
|
||||
);
|
||||
|
||||
if !name.is_builtin() || should_include_builtin {
|
||||
|
@ -5544,6 +5735,114 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String {
|
|||
buf
|
||||
}
|
||||
|
||||
fn to_import_cycle_report(
|
||||
module_ids: ModuleIds,
|
||||
all_ident_ids: IdentIdsByModule,
|
||||
import_cycle: Vec<ModuleId>,
|
||||
filename: PathBuf,
|
||||
render: RenderTarget,
|
||||
) -> String {
|
||||
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
// import_cycle looks like CycleModule, Import1, ..., ImportN, CycleModule
|
||||
// In a self-referential case, it just looks like CycleModule, CycleModule.
|
||||
debug_assert!(import_cycle.len() >= 2);
|
||||
let source_of_cycle = import_cycle.first().unwrap();
|
||||
|
||||
// We won't be printing any lines for this report, so this is okay.
|
||||
// TODO: it would be nice to show how each module imports another in the cycle.
|
||||
let src_lines = &[];
|
||||
|
||||
let interns = Interns {
|
||||
module_ids,
|
||||
all_ident_ids,
|
||||
};
|
||||
let alloc = RocDocAllocator::new(src_lines, *source_of_cycle, &interns);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("I can't compile "),
|
||||
alloc.module(*source_of_cycle),
|
||||
alloc.reflow(
|
||||
" because it depends on itself through the following chain of module imports:",
|
||||
),
|
||||
]),
|
||||
roc_reporting::report::cycle(
|
||||
&alloc,
|
||||
4,
|
||||
alloc.module(*source_of_cycle),
|
||||
import_cycle
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.map(|module| alloc.module(module))
|
||||
.collect(),
|
||||
),
|
||||
alloc.reflow("Cyclic dependencies are not allowed in Roc! Can you restructure a module in this import chain so that it doesn't have to depend on itself?")
|
||||
]);
|
||||
|
||||
let report = Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "IMPORT CYCLE".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
report.render(render, &mut buf, &alloc, &palette);
|
||||
buf
|
||||
}
|
||||
|
||||
fn to_incorrect_module_name_report<'a>(
|
||||
module_ids: ModuleIds,
|
||||
all_ident_ids: IdentIdsByModule,
|
||||
problem: IncorrectModuleName<'a>,
|
||||
filename: PathBuf,
|
||||
src: &'a [u8],
|
||||
render: RenderTarget,
|
||||
) -> String {
|
||||
use roc_reporting::report::{Report, RocDocAllocator, Severity, DEFAULT_PALETTE};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
let IncorrectModuleName {
|
||||
module_id,
|
||||
found,
|
||||
expected,
|
||||
} = problem;
|
||||
|
||||
// SAFETY: if the module was not UTF-8, that would be reported as a parsing problem, rather
|
||||
// than an incorrect module name problem (the latter can happen only after parsing).
|
||||
let src = unsafe { from_utf8_unchecked(src) };
|
||||
let src_lines = src.lines().collect::<Vec<_>>();
|
||||
let lines = LineInfo::new(src);
|
||||
|
||||
let interns = Interns {
|
||||
module_ids,
|
||||
all_ident_ids,
|
||||
};
|
||||
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow("This module has a different name than I expected:"),
|
||||
alloc.region(lines.convert_region(found.region)),
|
||||
alloc.reflow("Based on the nesting and use of this module, I expect it to have name"),
|
||||
alloc.pq_module_name(expected).indent(4),
|
||||
]);
|
||||
|
||||
let report = Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "INCORRECT MODULE NAME".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
report.render(render, &mut buf, &alloc, &palette);
|
||||
buf
|
||||
}
|
||||
|
||||
fn to_parse_problem_report<'a>(
|
||||
problem: FileError<'a, SyntaxError<'a>>,
|
||||
mut module_ids: ModuleIds,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_collections::{
|
||||
all::{MutMap, MutSet},
|
||||
VecMap,
|
||||
};
|
||||
use roc_module::symbol::{ModuleId, PackageQualified};
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
@ -103,6 +106,10 @@ pub struct Dependencies<'a> {
|
|||
make_specializations_dependents: MakeSpecializationsDependents,
|
||||
}
|
||||
|
||||
pub struct DepCycle {
|
||||
pub cycle: Vec<ModuleId>,
|
||||
}
|
||||
|
||||
impl<'a> Dependencies<'a> {
|
||||
pub fn new(goal_phase: Phase) -> Self {
|
||||
let mut deps = Self {
|
||||
|
@ -127,12 +134,26 @@ impl<'a> Dependencies<'a> {
|
|||
module_id: ModuleId,
|
||||
dependencies: &MutSet<PackageQualified<'a, ModuleId>>,
|
||||
goal_phase: Phase,
|
||||
) -> MutSet<(ModuleId, Phase)> {
|
||||
) -> Result<MutSet<(ModuleId, Phase)>, DepCycle> {
|
||||
use Phase::*;
|
||||
|
||||
let mut output = MutSet::default();
|
||||
|
||||
for dep in dependencies.iter() {
|
||||
// Do a BFS to check if we have an import cycle; if we do, calculate the cycle and
|
||||
// report the error. Although the worst case here is that we do a quadratic amount of
|
||||
// work for all modules added in a batch compilation, in practice, most dependencies
|
||||
// inserted here have not been seen by [Dependencies] yet, so their import chain is
|
||||
// size 0.
|
||||
if self.has_import_dependency(*dep.as_inner(), module_id) {
|
||||
let mut rev_cycle = self.calculate_reverse_import_path(*dep.as_inner(), module_id);
|
||||
rev_cycle.push(module_id);
|
||||
rev_cycle.reverse();
|
||||
let cycle = rev_cycle;
|
||||
|
||||
return Err(DepCycle { cycle });
|
||||
}
|
||||
|
||||
let has_package_dependency = self.add_package_dependency(dep, Phase::LoadHeader);
|
||||
|
||||
let dep = *dep.as_inner();
|
||||
|
@ -183,7 +204,59 @@ impl<'a> Dependencies<'a> {
|
|||
|
||||
self.add_to_status_for_all_phases(module_id, goal_phase);
|
||||
|
||||
output
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn has_import_dependency(&self, module_id: ModuleId, target: ModuleId) -> bool {
|
||||
if module_id.is_builtin() {
|
||||
return false;
|
||||
}
|
||||
let mut stack = vec![module_id];
|
||||
while let Some(module) = stack.pop() {
|
||||
if module.is_builtin() {
|
||||
continue;
|
||||
}
|
||||
if module == target {
|
||||
return true;
|
||||
}
|
||||
if let Some(dependencies) = self.make_specializations_dependents.0.get(&module) {
|
||||
stack.extend(dependencies.succ.iter());
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn calculate_reverse_import_path(
|
||||
&self,
|
||||
module_id: ModuleId,
|
||||
target: ModuleId,
|
||||
) -> Vec<ModuleId> {
|
||||
let mut stack = vec![module_id];
|
||||
let mut backlinks = VecMap::with_capacity(16);
|
||||
let mut found_import = false;
|
||||
while let Some(module) = stack.pop() {
|
||||
if module == target {
|
||||
found_import = true;
|
||||
break;
|
||||
}
|
||||
if let Some(dependencies) = self.make_specializations_dependents.0.get(&module) {
|
||||
for import in dependencies.succ.iter() {
|
||||
backlinks.insert(*import, module);
|
||||
stack.push(*import);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found_import {
|
||||
roc_error_macros::internal_error!("calculate_import_path should only be called when an import path is known to exist!");
|
||||
}
|
||||
|
||||
let mut source = target;
|
||||
let mut rev_path = vec![source];
|
||||
while let Some(&parent) = backlinks.get(&source) {
|
||||
rev_path.push(parent);
|
||||
source = parent;
|
||||
}
|
||||
rev_path
|
||||
}
|
||||
|
||||
/// Adds a status for the given module for exactly one phase.
|
||||
|
|
|
@ -318,7 +318,7 @@ fn import_transitive_alias() {
|
|||
),
|
||||
),
|
||||
(
|
||||
"Main",
|
||||
"Other",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Other exposes [empty] imports [RBTree]
|
||||
|
@ -909,3 +909,164 @@ fn import_builtin_in_platform_and_check_app() {
|
|||
let result = multiple_modules("import_builtin_in_platform_and_check_app", modules);
|
||||
assert!(result.is_ok(), "should check");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_doesnt_match_file_path() {
|
||||
let modules = vec![(
|
||||
"Age",
|
||||
indoc!(
|
||||
r#"
|
||||
interface NotAge exposes [Age] imports []
|
||||
|
||||
Age := U32
|
||||
"#
|
||||
),
|
||||
)];
|
||||
|
||||
let err = multiple_modules("module_doesnt_match_file_path", modules).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
indoc!(
|
||||
r#"
|
||||
── WEIRD MODULE NAME ─────────────────── tmp/module_doesnt_match_file_path/Age ─
|
||||
|
||||
This module name does not correspond with the file path it is defined
|
||||
in:
|
||||
|
||||
1│ interface NotAge exposes [Age] imports []
|
||||
^^^^^^
|
||||
|
||||
Module names must correspond with the file paths they are defined in.
|
||||
For example, I expect to see BigNum defined in BigNum.roc, or Math.Sin
|
||||
defined in Math/Sin.roc."#
|
||||
),
|
||||
"\n{}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_cyclic_import_itself() {
|
||||
let modules = vec![(
|
||||
"Age",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Age exposes [] imports [Age]
|
||||
"#
|
||||
),
|
||||
)];
|
||||
|
||||
let err = multiple_modules("module_cyclic_import_itself", modules).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
indoc!(
|
||||
r#"
|
||||
── IMPORT CYCLE ────────────────────────── tmp/module_cyclic_import_itself/Age ─
|
||||
|
||||
I can't compile Age because it depends on itself through the following
|
||||
chain of module imports:
|
||||
|
||||
┌─────┐
|
||||
│ Age
|
||||
│ ↓
|
||||
│ Age
|
||||
└─────┘
|
||||
|
||||
Cyclic dependencies are not allowed in Roc! Can you restructure a
|
||||
module in this import chain so that it doesn't have to depend on
|
||||
itself?"#
|
||||
),
|
||||
"\n{}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_cyclic_import_transitive() {
|
||||
let modules = vec![
|
||||
(
|
||||
"Age",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Age exposes [] imports [Person]
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"Person",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Person exposes [] imports [Age]
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
let err = multiple_modules("module_cyclic_import_transitive", modules).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
indoc!(
|
||||
r#"
|
||||
── IMPORT CYCLE ────────────────── tmp/module_cyclic_import_transitive/Age.roc ─
|
||||
|
||||
I can't compile Age because it depends on itself through the following
|
||||
chain of module imports:
|
||||
|
||||
┌─────┐
|
||||
│ Age
|
||||
│ ↓
|
||||
│ Person
|
||||
│ ↓
|
||||
│ Age
|
||||
└─────┘
|
||||
|
||||
Cyclic dependencies are not allowed in Roc! Can you restructure a
|
||||
module in this import chain so that it doesn't have to depend on
|
||||
itself?"#
|
||||
),
|
||||
"\n{}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_module_has_incorrect_name() {
|
||||
let modules = vec![
|
||||
(
|
||||
"Dep/Foo.roc",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"I.roc",
|
||||
indoc!(
|
||||
r#"
|
||||
interface I exposes [] imports [Dep.Foo]
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
let err = multiple_modules("nested_module_has_incorrect_name", modules).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
indoc!(
|
||||
r#"
|
||||
── INCORRECT MODULE NAME ──── tmp/nested_module_has_incorrect_name/Dep/Foo.roc ─
|
||||
|
||||
This module has a different name than I expected:
|
||||
|
||||
1│ interface Foo exposes [] imports []
|
||||
^^^
|
||||
|
||||
Based on the nesting and use of this module, I expect it to have name
|
||||
|
||||
Dep.Foo"#
|
||||
),
|
||||
"\n{}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ impl ModuleName {
|
|||
pub const BOX: &'static str = "Box";
|
||||
pub const ENCODE: &'static str = "Encode";
|
||||
pub const DECODE: &'static str = "Decode";
|
||||
pub const HASH: &'static str = "Hash";
|
||||
pub const JSON: &'static str = "Json";
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
|
|
|
@ -50,6 +50,7 @@ const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true;
|
|||
pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] = &[
|
||||
(Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER]),
|
||||
(Symbol::DECODE_DECODING, &[Symbol::DECODE_DECODER]),
|
||||
(Symbol::HASH_HASH_ABILITY, &[Symbol::HASH_HASH]),
|
||||
];
|
||||
|
||||
/// In Debug builds only, Symbol has a name() method that lets
|
||||
|
@ -425,7 +426,7 @@ impl fmt::Debug for ModuleId {
|
|||
/// 4. throw away short names. stash the module id in the can env under the resolved module name
|
||||
/// 5. test:
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PackageQualified<'a, T> {
|
||||
Unqualified(T),
|
||||
Qualified(&'a str, T),
|
||||
|
@ -1194,39 +1195,41 @@ define_builtins! {
|
|||
110 NUM_MAX_U64: "maxU64"
|
||||
111 NUM_MIN_I128: "minI128"
|
||||
112 NUM_MAX_I128: "maxI128"
|
||||
113 NUM_TO_I8: "toI8"
|
||||
114 NUM_TO_I8_CHECKED: "toI8Checked"
|
||||
115 NUM_TO_I16: "toI16"
|
||||
116 NUM_TO_I16_CHECKED: "toI16Checked"
|
||||
117 NUM_TO_I32: "toI32"
|
||||
118 NUM_TO_I32_CHECKED: "toI32Checked"
|
||||
119 NUM_TO_I64: "toI64"
|
||||
120 NUM_TO_I64_CHECKED: "toI64Checked"
|
||||
121 NUM_TO_I128: "toI128"
|
||||
122 NUM_TO_I128_CHECKED: "toI128Checked"
|
||||
123 NUM_TO_U8: "toU8"
|
||||
124 NUM_TO_U8_CHECKED: "toU8Checked"
|
||||
125 NUM_TO_U16: "toU16"
|
||||
126 NUM_TO_U16_CHECKED: "toU16Checked"
|
||||
127 NUM_TO_U32: "toU32"
|
||||
128 NUM_TO_U32_CHECKED: "toU32Checked"
|
||||
129 NUM_TO_U64: "toU64"
|
||||
130 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
131 NUM_TO_U128: "toU128"
|
||||
132 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
133 NUM_TO_NAT: "toNat"
|
||||
134 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
135 NUM_TO_F32: "toF32"
|
||||
136 NUM_TO_F32_CHECKED: "toF32Checked"
|
||||
137 NUM_TO_F64: "toF64"
|
||||
138 NUM_TO_F64_CHECKED: "toF64Checked"
|
||||
139 NUM_MAX_F64: "maxF64"
|
||||
140 NUM_MIN_F64: "minF64"
|
||||
141 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
|
||||
142 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
|
||||
143 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
|
||||
144 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
|
||||
145 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
|
||||
113 NUM_MIN_U128: "minU128"
|
||||
114 NUM_MAX_U128: "maxU128"
|
||||
115 NUM_TO_I8: "toI8"
|
||||
116 NUM_TO_I8_CHECKED: "toI8Checked"
|
||||
117 NUM_TO_I16: "toI16"
|
||||
118 NUM_TO_I16_CHECKED: "toI16Checked"
|
||||
119 NUM_TO_I32: "toI32"
|
||||
120 NUM_TO_I32_CHECKED: "toI32Checked"
|
||||
121 NUM_TO_I64: "toI64"
|
||||
122 NUM_TO_I64_CHECKED: "toI64Checked"
|
||||
123 NUM_TO_I128: "toI128"
|
||||
124 NUM_TO_I128_CHECKED: "toI128Checked"
|
||||
125 NUM_TO_U8: "toU8"
|
||||
126 NUM_TO_U8_CHECKED: "toU8Checked"
|
||||
127 NUM_TO_U16: "toU16"
|
||||
128 NUM_TO_U16_CHECKED: "toU16Checked"
|
||||
129 NUM_TO_U32: "toU32"
|
||||
130 NUM_TO_U32_CHECKED: "toU32Checked"
|
||||
131 NUM_TO_U64: "toU64"
|
||||
132 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
133 NUM_TO_U128: "toU128"
|
||||
134 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
135 NUM_TO_NAT: "toNat"
|
||||
136 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
137 NUM_TO_F32: "toF32"
|
||||
138 NUM_TO_F32_CHECKED: "toF32Checked"
|
||||
139 NUM_TO_F64: "toF64"
|
||||
140 NUM_TO_F64_CHECKED: "toF64Checked"
|
||||
141 NUM_MAX_F64: "maxF64"
|
||||
142 NUM_MIN_F64: "minF64"
|
||||
143 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
|
||||
144 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
|
||||
145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
|
||||
146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
|
||||
147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
|
||||
}
|
||||
4 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
|
||||
|
@ -1485,9 +1488,28 @@ define_builtins! {
|
|||
25 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
|
||||
26 DECODE_FROM_BYTES: "fromBytes"
|
||||
}
|
||||
13 JSON: "Json" => {
|
||||
13 HASH: "Hash" => {
|
||||
0 HASH_HASH_ABILITY: "Hash" exposed_type=true
|
||||
1 HASH_HASH: "hash"
|
||||
2 HASH_HASHER: "Hasher" exposed_type=true
|
||||
3 HASH_ADD_BYTES: "addBytes"
|
||||
4 HASH_ADD_U8: "addU8"
|
||||
5 HASH_ADD_U16: "addU16"
|
||||
6 HASH_ADD_U32: "addU32"
|
||||
7 HASH_ADD_U64: "addU64"
|
||||
8 HASH_ADD_U128: "addU128"
|
||||
9 HASH_ADD_I8: "addI8"
|
||||
10 HASH_ADD_I16: "addI16"
|
||||
11 HASH_ADD_I32: "addI32"
|
||||
12 HASH_ADD_I64: "addI64"
|
||||
13 HASH_ADD_I128: "addI128"
|
||||
14 HASH_COMPLETE: "complete"
|
||||
15 HASH_HASH_STR_BYTES: "hashStrBytes"
|
||||
16 HASH_HASH_LIST: "hashList"
|
||||
}
|
||||
14 JSON: "Json" => {
|
||||
0 JSON_JSON: "Json"
|
||||
}
|
||||
|
||||
num_modules: 14 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
|
||||
num_modules: 15 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
|
||||
}
|
||||
|
|
|
@ -5524,8 +5524,9 @@ fn late_resolve_ability_specialization<'a>(
|
|||
.expect("specialization var not derivable!");
|
||||
|
||||
match derive_key {
|
||||
roc_derive_key::Derived::Immediate(imm) => {
|
||||
// The immediate is an ability member itself, so it must be resolved!
|
||||
roc_derive_key::Derived::Immediate(imm)
|
||||
| roc_derive_key::Derived::SingleLambdaSetImmediate(imm) => {
|
||||
// The immediate may be an ability member itself, so it must be resolved!
|
||||
late_resolve_ability_specialization(env, imm, None, specialization_var)
|
||||
}
|
||||
roc_derive_key::Derived::Key(derive_key) => {
|
||||
|
|
|
@ -1257,8 +1257,17 @@ mod ability {
|
|||
IndentLevel::Exact(wanted) if state.column() > wanted => {
|
||||
// This demand is not indented correctly
|
||||
let indent_difference = state.column() as i32 - wanted as i32;
|
||||
|
||||
// We might be trying to parse at EOF, at which case the indent level
|
||||
// will be off, but there is actually nothing left.
|
||||
let progress = if state.has_reached_end() {
|
||||
NoProgress
|
||||
} else {
|
||||
MadeProgress
|
||||
};
|
||||
|
||||
Err((
|
||||
MadeProgress,
|
||||
progress,
|
||||
EAbility::DemandAlignment(indent_difference, state.pos()),
|
||||
initial,
|
||||
))
|
||||
|
|
|
@ -126,6 +126,8 @@ pub enum EHeader<'a> {
|
|||
AppName(EString<'a>, Position),
|
||||
PlatformName(EPackageName<'a>, Position),
|
||||
IndentStart(Position),
|
||||
|
||||
InconsistentModuleName(Region),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -2,6 +2,8 @@ use roc_region::all::{Position, Region};
|
|||
use std::fmt;
|
||||
|
||||
/// A position in a source file.
|
||||
// NB: [Copy] is explicitly NOT derived to reduce the chance of bugs due to accidentally re-using
|
||||
// parser state.
|
||||
#[derive(Clone)]
|
||||
pub struct State<'a> {
|
||||
/// The raw input bytes from the file.
|
||||
|
|
|
@ -272,6 +272,10 @@ impl ObligationCache {
|
|||
var,
|
||||
)),
|
||||
|
||||
Symbol::HASH_HASH_ABILITY => {
|
||||
Some(DeriveHash::is_derivable(self, abilities_store, subs, var))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -893,6 +897,95 @@ impl DerivableVisitor for DeriveDecoding {
|
|||
}
|
||||
}
|
||||
|
||||
struct DeriveHash;
|
||||
impl DerivableVisitor for DeriveHash {
|
||||
const ABILITY: Symbol = Symbol::HASH_HASH_ABILITY;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
|
||||
is_builtin_number_alias(symbol)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
|
||||
if matches!(
|
||||
symbol,
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
) {
|
||||
Ok(Descend(true))
|
||||
} else {
|
||||
Err(NotDerivable {
|
||||
var,
|
||||
context: NotDerivableContext::NoContext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
fields: RecordFields,
|
||||
) -> Result<Descend, NotDerivable> {
|
||||
for (field_name, _, field) in fields.iter_all() {
|
||||
if subs[field].is_optional() {
|
||||
return Err(NotDerivable {
|
||||
var,
|
||||
context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField(
|
||||
subs[field_name].clone(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
|
||||
if is_builtin_number_alias(symbol) {
|
||||
Ok(Descend(false))
|
||||
} else {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines what type implements an ability member of a specialized signature, given the
|
||||
/// [MustImplementAbility] constraints of the signature.
|
||||
pub fn type_implementing_specialization(
|
||||
|
|
|
@ -601,6 +601,7 @@ enum SpecializationTypeKey {
|
|||
Opaque(Symbol),
|
||||
Derived(DeriveKey),
|
||||
Immediate(Symbol),
|
||||
SingleLambdaSetImmediate(Symbol),
|
||||
}
|
||||
|
||||
enum SpecializeDecision {
|
||||
|
@ -666,6 +667,9 @@ fn make_specialization_decision<P: Phase>(
|
|||
roc_derive_key::Derived::Immediate(imm) => {
|
||||
SpecializeDecision::Specialize(Immediate(imm))
|
||||
}
|
||||
roc_derive_key::Derived::SingleLambdaSetImmediate(imm) => {
|
||||
SpecializeDecision::Specialize(SingleLambdaSetImmediate(imm))
|
||||
}
|
||||
roc_derive_key::Derived::Key(derive_key) => {
|
||||
SpecializeDecision::Specialize(Derived(derive_key))
|
||||
}
|
||||
|
@ -780,10 +784,37 @@ fn get_specialization_lambda_set_ambient_function<P: Phase>(
|
|||
//
|
||||
// THEORY: if something can become an immediate, it will always be available in the
|
||||
// local ability store, because the transformation is local (?)
|
||||
//
|
||||
// TODO: I actually think we can get what we need here by examining `derived_env.exposed_types`,
|
||||
// since immediates can only refer to builtins - and in userspace, all builtin types
|
||||
// are available in `exposed_types`.
|
||||
let immediate_lambda_set_at_region =
|
||||
phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs);
|
||||
|
||||
Ok(immediate_lambda_set_at_region)
|
||||
}
|
||||
|
||||
SpecializationTypeKey::SingleLambdaSetImmediate(imm) => {
|
||||
let module_id = imm.module_id();
|
||||
debug_assert!(module_id.is_builtin());
|
||||
|
||||
let module_types = &derived_env
|
||||
.exposed_types
|
||||
.get(&module_id)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
|
||||
// Since this immediate has only one lambda set, the region must be pointing to 1, and
|
||||
// moreover the imported function type is the ambient function of the single lset.
|
||||
debug_assert_eq!(lset_region, 1);
|
||||
let storage_var = module_types.stored_vars_by_symbol.get(&imm).unwrap();
|
||||
let imported = module_types
|
||||
.storage_subs
|
||||
.export_variable_to(subs, *storage_var);
|
||||
|
||||
roc_types::subs::instantiate_rigids(subs, imported.variable);
|
||||
|
||||
Ok(imported.variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5956,10 +5956,10 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
MHash has hash : a -> U64 | a has MHash
|
||||
"#
|
||||
),
|
||||
"a -> U64 | a has Hash",
|
||||
"a -> U64 | a has MHash",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -5970,14 +5970,14 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
MHash has hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id")],
|
||||
[("MHash:hash", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -5988,17 +5988,17 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hash, hash32] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
hash32 : a -> U32 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
hash32 : a -> U32 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash, hash32}]
|
||||
Id := U64 has [MHash {hash, hash32}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
hash32 = \@Id n -> Num.toU32 n
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id"), ("Hash:hash32", "Id")],
|
||||
[("MHash:hash", "Id"), ("MHash:hash32", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6009,15 +6009,15 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hash, hash32, eq, le] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
hash32 : a -> U32 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
hash32 : a -> U32 | a has MHash
|
||||
|
||||
Ord has
|
||||
eq : a, a -> Bool | a has Ord
|
||||
le : a, a -> Bool | a has Ord
|
||||
|
||||
Id := U64 has [Hash {hash, hash32}, Ord {eq, le}]
|
||||
Id := U64 has [MHash {hash, hash32}, Ord {eq, le}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
hash32 = \@Id n -> Num.toU32 n
|
||||
|
@ -6027,8 +6027,8 @@ mod solve_expr {
|
|||
"#
|
||||
),
|
||||
[
|
||||
("Hash:hash", "Id"),
|
||||
("Hash:hash32", "Id"),
|
||||
("MHash:hash", "Id"),
|
||||
("MHash:hash32", "Id"),
|
||||
("Ord:eq", "Id"),
|
||||
("Ord:le", "Id"),
|
||||
],
|
||||
|
@ -6042,16 +6042,16 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash : Id -> U64
|
||||
hash = \@Id n -> n
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id")],
|
||||
[("MHash:hash", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6062,15 +6062,15 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash : Id -> U64
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id")],
|
||||
[("MHash:hash", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6081,10 +6081,10 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [zero] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
|
||||
|
@ -6102,15 +6102,15 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [thething] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
thething =
|
||||
itis = hash
|
||||
itis
|
||||
"#
|
||||
),
|
||||
"a -> U64 | a has Hash",
|
||||
"a -> U64 | a has MHash",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6138,14 +6138,14 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hashEq] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
hashEq : a, a -> Bool | a has Hash
|
||||
hashEq : a, a -> Bool | a has MHash
|
||||
hashEq = \x, y -> hash x == hash y
|
||||
"#
|
||||
),
|
||||
"a, a -> Bool | a has Hash",
|
||||
"a, a -> Bool | a has MHash",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6156,13 +6156,13 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [hashEq] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
hashEq = \x, y -> hash x == hash y
|
||||
"#
|
||||
),
|
||||
"a, a1 -> Bool | a has Hash, a1 has Hash",
|
||||
"a, a1 -> Bool | a has MHash, a1 has MHash",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6173,12 +6173,12 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
hashEq = \x, y -> hash x == hash y
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
hash = \@Id n -> n
|
||||
|
||||
result = hashEq (@Id 100) (@Id 101)
|
||||
|
@ -6195,18 +6195,18 @@ mod solve_expr {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash { hash: hashId }]
|
||||
Id := U64 has [MHash { hash: hashId }]
|
||||
hashId = \@Id n -> n
|
||||
|
||||
Three := {} has [Hash { hash: hashThree }]
|
||||
Three := {} has [MHash { hash: hashThree }]
|
||||
hashThree = \@Three _ -> 3
|
||||
|
||||
result = mulHashes (@Id 100) (@Three {})
|
||||
result = mulMHashes (@Id 100) (@Three {})
|
||||
"#
|
||||
),
|
||||
"U64",
|
||||
|
@ -7823,4 +7823,22 @@ mod solve_expr {
|
|||
"Result Str [] -> Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_implement_hash() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Noop := {} has [Hash {hash}]
|
||||
|
||||
hash = \hasher, @Noop {} -> hasher
|
||||
|
||||
main = \hasher -> hash hasher (@Noop {})
|
||||
"#
|
||||
),
|
||||
"hasher -> hasher | hasher has Hasher",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
151
crates/compiler/test_derive/src/hash.rs
Normal file
151
crates/compiler/test_derive/src/hash.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
#![cfg(test)]
|
||||
// Even with #[allow(non_snake_case)] on individual idents, rust-analyzer issues diagnostics.
|
||||
// See https://github.com/rust-lang/rust-analyzer/issues/6541.
|
||||
// For the `v!` macro we use uppercase variables when constructing tag unions.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{
|
||||
test_key_eq, test_key_neq,
|
||||
util::{check_derivable, check_single_lset_immediate, check_underivable, derive_test},
|
||||
v,
|
||||
};
|
||||
use insta::assert_snapshot;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use roc_derive_key::{hash::FlatHashKey, DeriveBuiltin::Hash, DeriveError, DeriveKey};
|
||||
|
||||
test_key_eq! {
|
||||
Hash,
|
||||
|
||||
same_record:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(U8), })
|
||||
same_record_fields_diff_types:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(STR), })
|
||||
same_record_fields_any_order:
|
||||
v!({ a: v!(U8), b: v!(U8), c: v!(U8), }),
|
||||
v!({ c: v!(U8), a: v!(U8), b: v!(U8), })
|
||||
explicit_empty_record_and_implicit_empty_record:
|
||||
v!(EMPTY_RECORD), v!({})
|
||||
}
|
||||
|
||||
test_key_neq! {
|
||||
Hash,
|
||||
|
||||
different_record_fields:
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
v!(EMPTY_RECORD), v!({ a: v!(U8), })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
check_single_lset_immediate(Hash, v!(U8), Symbol::HASH_ADD_U8);
|
||||
check_single_lset_immediate(Hash, v!(U16), Symbol::HASH_ADD_U16);
|
||||
check_single_lset_immediate(Hash, v!(U32), Symbol::HASH_ADD_U32);
|
||||
check_single_lset_immediate(Hash, v!(U64), Symbol::HASH_ADD_U64);
|
||||
check_single_lset_immediate(Hash, v!(U128), Symbol::HASH_ADD_U128);
|
||||
check_single_lset_immediate(Hash, v!(I8), Symbol::HASH_ADD_I8);
|
||||
check_single_lset_immediate(Hash, v!(I16), Symbol::HASH_ADD_I16);
|
||||
check_single_lset_immediate(Hash, v!(I32), Symbol::HASH_ADD_I32);
|
||||
check_single_lset_immediate(Hash, v!(I64), Symbol::HASH_ADD_I64);
|
||||
check_single_lset_immediate(Hash, v!(I128), Symbol::HASH_ADD_I128);
|
||||
check_single_lset_immediate(Hash, v!(STR), Symbol::HASH_HASH_STR_BYTES);
|
||||
check_single_lset_immediate(Hash, v!(Symbol::LIST_LIST v!(U8)), Symbol::HASH_HASH_LIST);
|
||||
check_single_lset_immediate(Hash, v!(Symbol::LIST_LIST v!(STR)), Symbol::HASH_HASH_LIST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_record_field_derive_error() {
|
||||
check_underivable(Hash, v!({ ?a: v!(U8), }), DeriveError::Underivable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_record_ext_flex_var() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!({ a: v!(STR), }* ),
|
||||
DeriveKey::Hash(FlatHashKey::Record(vec!["a".into()])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_record_ext_flex_able_var() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!({ a: v!(STR), }a has Symbol::DECODE_DECODER ),
|
||||
DeriveKey::Hash(FlatHashKey::Record(vec!["a".into()])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_record_with_record_ext() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!({ b: v!(STR), }{ a: v!(STR), } ),
|
||||
DeriveKey::Hash(FlatHashKey::Record(vec!["a".into(), "b".into()])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
derive_test(Hash, v!(EMPTY_RECORD), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for {}
|
||||
# hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher
|
||||
# hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_{}(0)]]
|
||||
#Derived.hash_{} = \#Derived.hasher, #Derived.rcd -> #Derived.hasher
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_field_record() {
|
||||
derive_test(Hash, v!({}), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for {}
|
||||
# hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher
|
||||
# hasher, {} -[[hash_{}(0)]]-> hasher | hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_{}(0)]]
|
||||
#Derived.hash_{} = \#Derived.hasher, #Derived.rcd -> #Derived.hasher
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_field_record() {
|
||||
derive_test(Hash, v!({ a: v!(U8), }), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { a : U8 }
|
||||
# hasher, { a : a } -[[hash_{a}(0)]]-> hasher | a has Hash, hasher has Hasher
|
||||
# hasher, { a : a } -[[hash_{a}(0)]]-> hasher | a has Hash, hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_{a}(0)]]
|
||||
#Derived.hash_{a} =
|
||||
\#Derived.hasher, #Derived.rcd -> Hash.hash #Derived.hasher #Derived.rcd.a
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_field_record() {
|
||||
derive_test(Hash, v!({ a: v!(U8), b: v!(STR), }), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { a : U8, b : Str }
|
||||
# hasher, { a : a, b : a1 } -[[hash_{a,b}(0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||
# hasher, { a : a, b : a1 } -[[hash_{a,b}(0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_{a,b}(0)]]
|
||||
#Derived.hash_{a,b} =
|
||||
\#Derived.hasher, #Derived.rcd ->
|
||||
Hash.hash (Hash.hash #Derived.hasher #Derived.rcd.a) #Derived.rcd.b
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
mod hash;
|
||||
|
||||
mod pretty_print;
|
||||
mod util;
|
||||
|
|
|
@ -50,6 +50,11 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa
|
|||
module_source(ModuleId::DECODE),
|
||||
builtins_path.join("Decode.roc"),
|
||||
),
|
||||
DeriveBuiltin::Hash => (
|
||||
ModuleId::HASH,
|
||||
module_source(ModuleId::HASH),
|
||||
builtins_path.join("Hash.roc"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,6 +252,18 @@ where
|
|||
assert_eq!(key, Ok(Derived::Immediate(immediate)));
|
||||
}
|
||||
|
||||
pub(crate) fn check_single_lset_immediate<S>(builtin: DeriveBuiltin, synth: S, immediate: Symbol)
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var = synth(&mut subs);
|
||||
|
||||
let key = Derived::builtin(builtin, &subs, var);
|
||||
|
||||
assert_eq!(key, Ok(Derived::SingleLambdaSetImmediate(immediate)));
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn assemble_derived_golden(
|
||||
subs: &mut Subs,
|
||||
|
|
|
@ -20,10 +20,10 @@ fn hash_specialization() {
|
|||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
|
||||
|
@ -43,14 +43,14 @@ fn hash_specialization_multiple_add() {
|
|||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [ Hash {hash: hashId} ]
|
||||
Id := U64 has [ MHash {hash: hashId} ]
|
||||
|
||||
hashId = \@Id n -> n
|
||||
|
||||
One := {} has [ Hash {hash: hashOne} ]
|
||||
One := {} has [ MHash {hash: hashOne} ]
|
||||
|
||||
hashOne = \@One _ -> 1
|
||||
|
||||
|
@ -70,16 +70,16 @@ fn alias_member_specialization() {
|
|||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
|
||||
main =
|
||||
aliasedHash = hash
|
||||
aliasedHash (@Id 1234)
|
||||
aliasedMHash = hash
|
||||
aliasedMHash (@Id 1234)
|
||||
"#
|
||||
),
|
||||
1234,
|
||||
|
@ -95,16 +95,16 @@ fn ability_constrained_in_non_member_usage() {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes : a, a -> U64 | a has Hash
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes : a, a -> U64 | a has MHash
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
hash = \@Id n -> n
|
||||
|
||||
result = mulHashes (@Id 5) (@Id 7)
|
||||
result = mulMHashes (@Id 5) (@Id 7)
|
||||
"#
|
||||
),
|
||||
35,
|
||||
|
@ -120,15 +120,15 @@ fn ability_constrained_in_non_member_usage_inferred() {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
hash = \@Id n -> n
|
||||
|
||||
result = mulHashes (@Id 5) (@Id 7)
|
||||
result = mulMHashes (@Id 5) (@Id 7)
|
||||
"#
|
||||
),
|
||||
35,
|
||||
|
@ -144,19 +144,19 @@ fn ability_constrained_in_non_member_multiple_specializations() {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes : a, b -> U64 | a has Hash, b has Hash
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes : a, b -> U64 | a has MHash, b has MHash
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash { hash: hashId }]
|
||||
Id := U64 has [MHash { hash: hashId }]
|
||||
hashId = \@Id n -> n
|
||||
|
||||
Three := {} has [Hash { hash: hashThree }]
|
||||
Three := {} has [MHash { hash: hashThree }]
|
||||
hashThree = \@Three _ -> 3
|
||||
|
||||
result = mulHashes (@Id 100) (@Three {})
|
||||
result = mulMHashes (@Id 100) (@Three {})
|
||||
"#
|
||||
),
|
||||
300,
|
||||
|
@ -172,18 +172,18 @@ fn ability_constrained_in_non_member_multiple_specializations_inferred() {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash { hash: hashId }]
|
||||
Id := U64 has [MHash { hash: hashId }]
|
||||
hashId = \@Id n -> n
|
||||
|
||||
Three := {} has [Hash { hash: hashThree }]
|
||||
Three := {} has [MHash { hash: hashThree }]
|
||||
hashThree = \@Three _ -> 3
|
||||
|
||||
result = mulHashes (@Id 100) (@Three {})
|
||||
result = mulMHashes (@Id 100) (@Three {})
|
||||
"#
|
||||
),
|
||||
300,
|
||||
|
@ -199,19 +199,19 @@ fn ability_used_as_type_still_compiles() {
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes : Hash, Hash -> U64
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes : MHash, MHash -> U64
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash { hash: hashId }]
|
||||
Id := U64 has [MHash { hash: hashId }]
|
||||
hashId = \@Id n -> n
|
||||
|
||||
Three := {} has [Hash { hash: hashThree }]
|
||||
Three := {} has [MHash { hash: hashThree }]
|
||||
hashThree = \@Three _ -> 3
|
||||
|
||||
result = mulHashes (@Id 100) (@Three {})
|
||||
result = mulMHashes (@Id 100) (@Three {})
|
||||
"#
|
||||
),
|
||||
300,
|
||||
|
@ -1102,3 +1102,275 @@ fn decode_record_of_record() {
|
|||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
mod hash {
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_evals_to;
|
||||
|
||||
#[cfg(feature = "gen-wasm")]
|
||||
use crate::helpers::wasm::assert_evals_to;
|
||||
|
||||
use indoc::indoc;
|
||||
|
||||
const TEST_HASHER: &str = indoc!(
|
||||
r#"
|
||||
THasher := List U8 has [Hasher {
|
||||
addBytes: tAddBytes,
|
||||
addU8: tAddU8,
|
||||
addU16: tAddU16,
|
||||
addU32: tAddU32,
|
||||
addU64: tAddU64,
|
||||
addU128: tAddU128,
|
||||
addI8: tAddI8,
|
||||
addI16: tAddI16,
|
||||
addI32: tAddI32,
|
||||
addI64: tAddI64,
|
||||
addI128: tAddI128,
|
||||
complete: tComplete,
|
||||
}]
|
||||
|
||||
# ignores endian-ness
|
||||
byteAt = \n, shift ->
|
||||
Num.bitwiseAnd (Num.shiftRightBy n (shift * 8)) 0xFF
|
||||
|> Num.toU8
|
||||
|
||||
do8 = \total, n ->
|
||||
total
|
||||
|> List.append (byteAt n 0)
|
||||
|
||||
do16 = \total, n ->
|
||||
total
|
||||
|> do8 (n |> Num.toU8)
|
||||
|> do8 (Num.shiftRightBy n 8 |> Num.toU8)
|
||||
|
||||
do32 = \total, n ->
|
||||
total
|
||||
|> do16 (n |> Num.toU16)
|
||||
|> do16 (Num.shiftRightBy n 16 |> Num.toU16)
|
||||
|
||||
do64 = \total, n ->
|
||||
total
|
||||
|> do32 (n |> Num.toU32)
|
||||
|> do32 (Num.shiftRightBy n 32 |> Num.toU32)
|
||||
|
||||
do128 = \total, n ->
|
||||
total
|
||||
|> do64 (n |> Num.toU64)
|
||||
|> do64 (Num.shiftRightBy n 64 |> Num.toU64)
|
||||
|
||||
tAddBytes = \@THasher total, bytes -> @THasher (List.concat total bytes)
|
||||
tAddU8 = \@THasher total, n -> @THasher (do8 total n)
|
||||
tAddU16 = \@THasher total, n -> @THasher (do16 total n)
|
||||
tAddU32 = \@THasher total, n -> @THasher (do32 total n)
|
||||
tAddU64 = \@THasher total, n -> @THasher (do64 total n)
|
||||
tAddU128 = \@THasher total, n -> @THasher (do128 total n)
|
||||
tAddI8 = \@THasher total, n -> @THasher (do8 total (Num.toU8 n))
|
||||
tAddI16 = \@THasher total, n -> @THasher (do16 total (Num.toU16 n))
|
||||
tAddI32 = \@THasher total, n -> @THasher (do32 total (Num.toU32 n))
|
||||
tAddI64 = \@THasher total, n -> @THasher (do64 total (Num.toU64 n))
|
||||
tAddI128 = \@THasher total, n -> @THasher (do128 total (Num.toU128 n))
|
||||
tComplete = \@THasher _ -> Num.maxU64
|
||||
|
||||
tRead = \@THasher bytes -> bytes
|
||||
"#
|
||||
);
|
||||
|
||||
fn build_test(input: &str) -> String {
|
||||
format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash ({})
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER, input,
|
||||
)
|
||||
}
|
||||
|
||||
mod immediate {
|
||||
use super::{assert_evals_to, build_test};
|
||||
use roc_std::RocList;
|
||||
|
||||
#[test]
|
||||
fn i8() {
|
||||
assert_evals_to!(
|
||||
&build_test("-2i8"),
|
||||
RocList::from_slice(&[254]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u8() {
|
||||
assert_evals_to!(
|
||||
&build_test("254u8"),
|
||||
RocList::from_slice(&[254]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i16() {
|
||||
assert_evals_to!(
|
||||
&build_test("-2i16"),
|
||||
RocList::from_slice(&[254, 255]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u16() {
|
||||
assert_evals_to!(
|
||||
&build_test("Num.maxU16 - 1"),
|
||||
RocList::from_slice(&[254, 255]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i32() {
|
||||
assert_evals_to!(
|
||||
&build_test("-2i32"),
|
||||
RocList::from_slice(&[254, 255, 255, 255]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u32() {
|
||||
assert_evals_to!(
|
||||
&build_test("Num.maxU32 - 1"),
|
||||
RocList::from_slice(&[254, 255, 255, 255]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i64() {
|
||||
assert_evals_to!(
|
||||
&build_test("-2i64"),
|
||||
RocList::from_slice(&[254, 255, 255, 255, 255, 255, 255, 255]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u64() {
|
||||
assert_evals_to!(
|
||||
&build_test("Num.maxU64 - 1"),
|
||||
RocList::from_slice(&[254, 255, 255, 255, 255, 255, 255, 255]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "gen-wasm"))] // shr not implemented for U128
|
||||
fn i128() {
|
||||
assert_evals_to!(
|
||||
&build_test("-2i128"),
|
||||
RocList::from_slice(&[
|
||||
254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "gen-wasm"))] // shr not implemented for U128
|
||||
fn u128() {
|
||||
assert_evals_to!(
|
||||
&build_test("Num.maxU128 - 1"),
|
||||
RocList::from_slice(&[
|
||||
254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#""ab☃AB""#),
|
||||
RocList::from_slice(&[97, 98, 226, 152, 131, 65, 66]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_u8() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#"[15u8, 23u8, 37u8]"#),
|
||||
RocList::from_slice(&[15, 23, 37]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_string() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#"["ab", "cd", "ef"]"#),
|
||||
RocList::from_slice(&[97, 98, 99, 100, 101, 102]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_list_string() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#"[[ "ab", "cd" ], [ "ef" ]]"#),
|
||||
RocList::from_slice(&[97, 98, 99, 100, 101, 102]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod derived {
|
||||
use super::{assert_evals_to, build_test};
|
||||
use roc_std::RocList;
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#"{}"#),
|
||||
RocList::from_slice(&[] as &[u8]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_of_u8_and_str() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#"{ a: 15u8, b: "bc" }"#),
|
||||
RocList::from_slice(&[15, 98, 99]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_of_records() {
|
||||
assert_evals_to!(
|
||||
&build_test(r#"{ a: { b: 15u8, c: "bc" }, d: { b: 23u8, e: "ef" } }"#),
|
||||
RocList::from_slice(&[15, 98, 99, 23, 101, 102]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_of_list_of_records() {
|
||||
assert_evals_to!(
|
||||
&build_test(
|
||||
r#"{ a: [ { b: 15u8 }, { b: 23u8 } ], b: [ { c: 45u8 }, { c: 73u8 } ] }"#
|
||||
),
|
||||
RocList::from_slice(&[15, 23, 45, 73]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -614,6 +614,25 @@ fn i64_abs() {
|
|||
assert_evals_to!("Num.abs (Num.minI64 + 1)", -(i64::MIN + 1), i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn various_sized_abs() {
|
||||
assert_evals_to!("Num.abs -6i8", 6, i8);
|
||||
assert_evals_to!("Num.abs -6i16", 6, i16);
|
||||
assert_evals_to!("Num.abs -6i32", 6, i32);
|
||||
assert_evals_to!("Num.abs -6i64", 6, i64);
|
||||
if !cfg!(feature = "gen-wasm") {
|
||||
assert_evals_to!("Num.abs -6i128", 6, i128);
|
||||
}
|
||||
assert_evals_to!("Num.abs 6u8", 6, u8);
|
||||
assert_evals_to!("Num.abs 6u16", 6, u16);
|
||||
assert_evals_to!("Num.abs 6u32", 6, u32);
|
||||
assert_evals_to!("Num.abs 6u64", 6, u64);
|
||||
if !cfg!(feature = "gen-wasm") {
|
||||
assert_evals_to!("Num.abs 6u128", 6, u128);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[should_panic(
|
||||
|
@ -2075,6 +2094,7 @@ fn shift_left_by() {
|
|||
assert_evals_to!("Num.shiftLeftBy 0b0000_0001 0", 0b0000_0001, i64);
|
||||
assert_evals_to!("Num.shiftLeftBy 0b0000_0001 1", 0b0000_0010, i64);
|
||||
assert_evals_to!("Num.shiftLeftBy 0b0000_0011 2", 0b0000_1100, i64);
|
||||
assert_evals_to!("Num.shiftLeftBy 2u16 2", 8, u16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2104,7 +2124,6 @@ fn shift_right_by() {
|
|||
assert_evals_to!("Num.shiftRightBy -12 1", -6, i64);
|
||||
assert_evals_to!("Num.shiftRightBy 12 8", 0, i64);
|
||||
assert_evals_to!("Num.shiftRightBy -12 8", -1, i64);
|
||||
assert_evals_to!("Num.shiftRightBy 12 -1", 0, i64);
|
||||
assert_evals_to!("Num.shiftRightBy 0 0", 0, i64);
|
||||
assert_evals_to!("Num.shiftRightBy 0 1", 0, i64);
|
||||
|
||||
|
@ -2120,8 +2139,6 @@ fn shift_right_by() {
|
|||
assert_evals_to!("Num.shiftRightBy 12i8 8", 0, i8);
|
||||
|
||||
if !is_llvm_release_mode {
|
||||
assert_evals_to!("Num.shiftRightBy 0 -1", 0, i64);
|
||||
assert_evals_to!("Num.shiftRightBy -12 -1", -1, i64);
|
||||
assert_evals_to!("Num.shiftRightBy -12i8 8", -1, i8);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1325,10 +1325,10 @@ fn specialize_ability_call() {
|
|||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash : Id -> U64
|
||||
hash = \@Id n -> n
|
||||
|
|
|
@ -3868,11 +3868,11 @@ fn flat_type_to_err_type(
|
|||
ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext)
|
||||
}
|
||||
|
||||
ErrorType::FlexVar(var) => {
|
||||
ErrorType::FlexVar(var) | ErrorType::FlexAbleVar(var, _) => {
|
||||
ErrorType::TagUnion(err_tags, TypeExt::FlexOpen(var))
|
||||
}
|
||||
|
||||
ErrorType::RigidVar(var) => {
|
||||
ErrorType::RigidVar(var) | ErrorType::RigidAbleVar(var, _)=> {
|
||||
ErrorType::TagUnion(err_tags, TypeExt::RigidOpen(var))
|
||||
}
|
||||
|
||||
|
@ -3896,11 +3896,11 @@ fn flat_type_to_err_type(
|
|||
ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext)
|
||||
}
|
||||
|
||||
ErrorType::FlexVar(var) => {
|
||||
ErrorType::FlexVar(var) | ErrorType::FlexAbleVar(var, _) => {
|
||||
ErrorType::TagUnion(err_tags, TypeExt::FlexOpen(var))
|
||||
}
|
||||
|
||||
ErrorType::RigidVar(var) => {
|
||||
ErrorType::RigidVar(var) | ErrorType::RigidAbleVar(var, _)=> {
|
||||
ErrorType::TagUnion(err_tags, TypeExt::RigidOpen(var))
|
||||
}
|
||||
|
||||
|
|
|
@ -2820,7 +2820,7 @@ fn unify_rigid_able<M: MetaCollector>(
|
|||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, rigid wins!
|
||||
merge(env, ctx, RigidVar(*name))
|
||||
merge(env, ctx, RigidAbleVar(*name, ability))
|
||||
}
|
||||
FlexAbleVar(_, other_ability) => {
|
||||
if ability == *other_ability {
|
||||
|
|
|
@ -215,6 +215,7 @@ mod glue_cli_run {
|
|||
.map(|arg| arg.to_string())
|
||||
.chain([app_file.to_str().unwrap().to_string()]),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
|
||||
let ignorable = "🔨 Rebuilding platform...\n";
|
||||
|
|
|
@ -357,7 +357,6 @@ fn gen_and_eval_llvm<'a>(
|
|||
&loaded.interns,
|
||||
DebugPrint::NOTHING,
|
||||
);
|
||||
let content = *loaded.subs.get_content_without_compacting(main_fn_var);
|
||||
|
||||
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
|
@ -381,7 +380,7 @@ fn gen_and_eval_llvm<'a>(
|
|||
&mut app,
|
||||
main_fn_name,
|
||||
main_fn_layout,
|
||||
&content,
|
||||
main_fn_var,
|
||||
&subs,
|
||||
&interns,
|
||||
layout_interner.into_global().fork(),
|
||||
|
|
|
@ -48,7 +48,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
|||
app: &mut A,
|
||||
main_fn_name: &str,
|
||||
layout: ProcLayout<'a>,
|
||||
content: &Content,
|
||||
var: Variable,
|
||||
subs: &Subs,
|
||||
interns: &'a Interns,
|
||||
layout_interner: LayoutInterner<'a>,
|
||||
|
@ -69,7 +69,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
|||
captures_niche: _,
|
||||
} => {
|
||||
// this is a thunk
|
||||
jit_to_ast_help(&mut env, app, main_fn_name, &result, content)
|
||||
jit_to_ast_help(&mut env, app, main_fn_name, &result, var)
|
||||
}
|
||||
_ => Err(ToAstProblem::FunctionLayout),
|
||||
}
|
||||
|
@ -82,6 +82,45 @@ enum NewtypeKind {
|
|||
Opaque(Symbol),
|
||||
}
|
||||
|
||||
fn get_newtype_tag_and_var(
|
||||
env: &mut Env,
|
||||
var: Variable,
|
||||
tags: UnionTags,
|
||||
) -> Option<(TagName, Variable)> {
|
||||
let union_variant = {
|
||||
let mut layout_env = roc_mono::layout::Env::from_components(
|
||||
&mut env.layout_cache,
|
||||
env.subs,
|
||||
env.arena,
|
||||
env.target_info,
|
||||
);
|
||||
roc_mono::layout::union_sorted_tags(&mut layout_env, var).unwrap()
|
||||
};
|
||||
|
||||
let tag_name = match union_variant {
|
||||
UnionVariant::Newtype { tag_name, .. }
|
||||
| UnionVariant::NewtypeByVoid {
|
||||
data_tag_name: tag_name,
|
||||
..
|
||||
} => tag_name.expect_tag(),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let vars = tags
|
||||
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
||||
.find(|(tag, _)| **tag == tag_name)
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
match vars {
|
||||
[var] => Some((tag_name, *var)),
|
||||
_ => {
|
||||
// Multiple variables; we should not display this as a newtype.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unrolls types that are newtypes. These include
|
||||
/// - Singleton tags with one type argument (e.g. `Container Str`)
|
||||
/// - Records with exactly one field (e.g. `{ number: Nat }`)
|
||||
|
@ -97,24 +136,23 @@ enum NewtypeKind {
|
|||
///
|
||||
/// Returns (new type containers, optional alias content, real content).
|
||||
fn unroll_newtypes_and_aliases<'a, 'env>(
|
||||
env: &Env<'a, 'env>,
|
||||
|
||||
mut content: &'env Content,
|
||||
) -> (Vec<'a, NewtypeKind>, Option<&'env Content>, &'env Content) {
|
||||
env: &mut Env<'a, 'env>,
|
||||
var: Variable,
|
||||
) -> (Vec<'a, NewtypeKind>, Option<&'env Content>, Variable) {
|
||||
let mut var = var;
|
||||
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
|
||||
let mut alias_content = None;
|
||||
loop {
|
||||
let content = env.subs.get_content_without_compacting(var);
|
||||
match content {
|
||||
Content::Structure(FlatType::TagUnion(tags, _))
|
||||
if tags.is_newtype_wrapper(env.subs) =>
|
||||
{
|
||||
let (tag_name, vars): (&TagName, &[Variable]) = tags
|
||||
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
|
||||
.next()
|
||||
.unwrap();
|
||||
newtype_containers.push(NewtypeKind::Tag(tag_name.clone()));
|
||||
let var = vars[0];
|
||||
content = env.subs.get_content_without_compacting(var);
|
||||
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||
match get_newtype_tag_and_var(env, var, *tags) {
|
||||
Some((tag_name, inner_var)) => {
|
||||
newtype_containers.push(NewtypeKind::Tag(tag_name));
|
||||
var = inner_var;
|
||||
}
|
||||
None => return (newtype_containers, alias_content, var),
|
||||
}
|
||||
}
|
||||
Content::Structure(FlatType::Record(fields, _)) if fields.len() == 1 => {
|
||||
let (label, field) = fields
|
||||
|
@ -122,11 +160,11 @@ fn unroll_newtypes_and_aliases<'a, 'env>(
|
|||
.next()
|
||||
.unwrap();
|
||||
newtype_containers.push(NewtypeKind::RecordField(label.to_string()));
|
||||
content = env.subs.get_content_without_compacting(field.into_inner());
|
||||
var = field.into_inner();
|
||||
}
|
||||
Content::Alias(name, _, real_var, kind) => {
|
||||
if *name == Symbol::BOOL_BOOL {
|
||||
return (newtype_containers, alias_content, content);
|
||||
return (newtype_containers, alias_content, var);
|
||||
}
|
||||
// We need to pass through aliases too, because their underlying types may have
|
||||
// unrolled newtypes. For example,
|
||||
|
@ -142,9 +180,9 @@ fn unroll_newtypes_and_aliases<'a, 'env>(
|
|||
newtype_containers.push(NewtypeKind::Opaque(*name));
|
||||
}
|
||||
alias_content = Some(content);
|
||||
content = env.subs.get_content_without_compacting(*real_var);
|
||||
var = *real_var;
|
||||
}
|
||||
_ => return (newtype_containers, alias_content, content),
|
||||
_ => return (newtype_containers, alias_content, var),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -292,10 +330,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
app: &mut A,
|
||||
main_fn_name: &str,
|
||||
layout: &Layout<'a>,
|
||||
content: &Content,
|
||||
var: Variable,
|
||||
) -> Result<Expr<'a>, ToAstProblem> {
|
||||
let (newtype_containers, alias_content, raw_content) =
|
||||
unroll_newtypes_and_aliases(env, content);
|
||||
let (newtype_containers, alias_content, raw_var) = unroll_newtypes_and_aliases(env, var);
|
||||
|
||||
macro_rules! num_helper {
|
||||
($ty:ty) => {
|
||||
|
@ -306,10 +343,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
}
|
||||
|
||||
let result = match layout {
|
||||
Layout::Builtin(Builtin::Bool) => Ok(app
|
||||
.call_function(main_fn_name, |mem: &A::Memory, num: bool| {
|
||||
bool_to_ast(env, mem, num, raw_content)
|
||||
})),
|
||||
Layout::Builtin(Builtin::Bool) => Ok(app.call_function(
|
||||
main_fn_name,
|
||||
|mem: &A::Memory, num: bool| {
|
||||
bool_to_ast(
|
||||
env,
|
||||
mem,
|
||||
num,
|
||||
env.subs.get_content_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
)),
|
||||
Layout::Builtin(Builtin::Int(int_width)) => {
|
||||
use Content::*;
|
||||
use IntWidth::*;
|
||||
|
@ -319,7 +363,12 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
(_, U8) => {
|
||||
// This is not a number, it's a tag union or something else
|
||||
app.call_function(main_fn_name, |mem: &A::Memory, num: u8| {
|
||||
byte_to_ast(env, mem, num, raw_content)
|
||||
byte_to_ast(
|
||||
env,
|
||||
mem,
|
||||
num,
|
||||
env.subs.get_content_without_compacting(raw_var),
|
||||
)
|
||||
})
|
||||
}
|
||||
// The rest are numbers... for now
|
||||
|
@ -362,7 +411,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
Ok(app.call_function_returns_roc_list(
|
||||
main_fn_name,
|
||||
|mem: &A::Memory, (addr, len, _cap)| {
|
||||
list_to_ast(env, mem, addr, len, elem_layout, raw_content)
|
||||
list_to_ast(
|
||||
env,
|
||||
mem,
|
||||
addr,
|
||||
len,
|
||||
elem_layout,
|
||||
env.subs.get_content_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -372,7 +428,10 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
|
||||
let result_stack_size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
||||
|
||||
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content {
|
||||
let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match env
|
||||
.subs
|
||||
.get_content_without_compacting(raw_var)
|
||||
{
|
||||
Content::Structure(FlatType::Record(fields, _)) => {
|
||||
Ok(struct_to_ast(env, mem, addr, *fields))
|
||||
}
|
||||
|
@ -433,7 +492,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
addr,
|
||||
layout,
|
||||
WhenRecursive::Unreachable,
|
||||
raw_content,
|
||||
env.subs.get_root_key_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
|
@ -453,7 +512,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
addr,
|
||||
layout,
|
||||
WhenRecursive::Loop(*layout),
|
||||
raw_content,
|
||||
env.subs.get_root_key_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
|
@ -474,7 +533,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
addr,
|
||||
layout,
|
||||
WhenRecursive::Unreachable,
|
||||
raw_content,
|
||||
env.subs.get_root_key_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
|
@ -501,7 +560,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
addr: usize,
|
||||
layout: &Layout<'a>,
|
||||
when_recursive: WhenRecursive<'a>,
|
||||
content: &Content,
|
||||
var: Variable,
|
||||
) -> Expr<'a> {
|
||||
macro_rules! helper {
|
||||
($method: ident, $ty: ty) => {{
|
||||
|
@ -511,8 +570,8 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
}};
|
||||
}
|
||||
|
||||
let (newtype_containers, _alias_content, raw_content) =
|
||||
unroll_newtypes_and_aliases(env, content);
|
||||
let (newtype_containers, _alias_content, raw_var) = unroll_newtypes_and_aliases(env, var);
|
||||
let raw_content = env.subs.get_content_without_compacting(raw_var);
|
||||
|
||||
let expr = match (raw_content, layout) {
|
||||
(Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => {
|
||||
|
@ -594,8 +653,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
},
|
||||
WhenRecursive::Loop(union_layout),
|
||||
) => {
|
||||
let content = env.subs.get_content_without_compacting(*structure);
|
||||
addr_to_ast(env, mem, addr, &union_layout, when_recursive, content)
|
||||
addr_to_ast(env, mem, addr, &union_layout, when_recursive, *structure)
|
||||
}
|
||||
|
||||
(
|
||||
|
@ -607,13 +665,12 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
) => {
|
||||
// It's possible to hit a recursive pointer before the full type layout; just
|
||||
// figure out the actual recursive structure layout at this point.
|
||||
let content = env.subs.get_content_without_compacting(*structure);
|
||||
let union_layout = env.layout_cache
|
||||
.from_var(env.arena, *structure, env.subs)
|
||||
.expect("no layout for structure");
|
||||
debug_assert!(matches!(union_layout, Layout::Union(..)));
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
addr_to_ast(env, mem, addr, &union_layout, when_recursive, content)
|
||||
addr_to_ast(env, mem, addr, &union_layout, when_recursive, *structure)
|
||||
}
|
||||
other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other),
|
||||
},
|
||||
|
@ -813,7 +870,6 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
|
||||
let inner_var_index = args.into_iter().next().unwrap();
|
||||
let inner_var = env.subs[inner_var_index];
|
||||
let inner_content = env.subs.get_content_without_compacting(inner_var);
|
||||
|
||||
let addr_of_inner = mem.deref_usize(addr);
|
||||
let inner_expr = addr_to_ast(
|
||||
|
@ -822,7 +878,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
|||
addr_of_inner,
|
||||
inner_layout,
|
||||
WhenRecursive::Unreachable,
|
||||
inner_content,
|
||||
inner_var,
|
||||
);
|
||||
|
||||
let box_box = env.arena.alloc(Loc::at_zero(Expr::Var {
|
||||
|
@ -855,14 +911,12 @@ fn list_to_ast<'a, M: ReplAppMemory>(
|
|||
elem_layout: &Layout<'a>,
|
||||
content: &Content,
|
||||
) -> Expr<'a> {
|
||||
let elem_content = match content {
|
||||
let elem_var = match content {
|
||||
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => {
|
||||
debug_assert_eq!(vars.len(), 1);
|
||||
|
||||
let elem_var_index = vars.into_iter().next().unwrap();
|
||||
let elem_var = env.subs[elem_var_index];
|
||||
|
||||
env.subs.get_content_without_compacting(elem_var)
|
||||
env.subs[elem_var_index]
|
||||
}
|
||||
other => {
|
||||
unreachable!(
|
||||
|
@ -880,7 +934,7 @@ fn list_to_ast<'a, M: ReplAppMemory>(
|
|||
let offset_bytes = index * elem_size;
|
||||
let elem_addr = addr + offset_bytes;
|
||||
let (newtype_containers, _alias_content, elem_content) =
|
||||
unroll_newtypes_and_aliases(env, elem_content);
|
||||
unroll_newtypes_and_aliases(env, elem_var);
|
||||
let expr = addr_to_ast(
|
||||
env,
|
||||
mem,
|
||||
|
@ -942,15 +996,13 @@ where
|
|||
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
|
||||
{
|
||||
let arena = env.arena;
|
||||
let subs = env.subs;
|
||||
let mut output = Vec::with_capacity_in(sequence.len(), arena);
|
||||
|
||||
// We'll advance this as we iterate through the fields
|
||||
let mut field_addr = addr;
|
||||
|
||||
for (var, layout) in sequence {
|
||||
let content = subs.get_content_without_compacting(var);
|
||||
let expr = addr_to_ast(env, mem, field_addr, layout, when_recursive, content);
|
||||
let expr = addr_to_ast(env, mem, field_addr, layout, when_recursive, var);
|
||||
let loc_expr = Loc::at_zero(expr);
|
||||
|
||||
output.push(&*arena.alloc(loc_expr));
|
||||
|
@ -979,7 +1031,7 @@ fn struct_to_ast<'a, 'env, M: ReplAppMemory>(
|
|||
.next()
|
||||
.unwrap();
|
||||
|
||||
let inner_content = env.subs.get_content_without_compacting(field.into_inner());
|
||||
let inner_var = field.into_inner();
|
||||
let field_layout = env
|
||||
.layout_cache
|
||||
.from_var(arena, field.into_inner(), env.subs)
|
||||
|
@ -993,7 +1045,7 @@ fn struct_to_ast<'a, 'env, M: ReplAppMemory>(
|
|||
addr,
|
||||
&Layout::struct_no_name_order(inner_layouts),
|
||||
WhenRecursive::Unreachable,
|
||||
inner_content,
|
||||
inner_var,
|
||||
),
|
||||
region: Region::zero(),
|
||||
});
|
||||
|
@ -1019,7 +1071,7 @@ fn struct_to_ast<'a, 'env, M: ReplAppMemory>(
|
|||
// always only sorted alphabetically. We want to arrange the rendered record in the order of
|
||||
// the type.
|
||||
for (label, field) in record_fields.sorted_iterator(subs, Variable::EMPTY_RECORD) {
|
||||
let content = subs.get_content_without_compacting(field.into_inner());
|
||||
let field_var = field.into_inner();
|
||||
let field_layout = env
|
||||
.layout_cache
|
||||
.from_var(arena, field.into_inner(), env.subs)
|
||||
|
@ -1032,7 +1084,7 @@ fn struct_to_ast<'a, 'env, M: ReplAppMemory>(
|
|||
field_addr,
|
||||
&field_layout,
|
||||
WhenRecursive::Unreachable,
|
||||
content,
|
||||
field_var,
|
||||
),
|
||||
region: Region::zero(),
|
||||
});
|
||||
|
@ -1153,6 +1205,10 @@ fn bool_to_ast<'a, M: ReplAppMemory>(
|
|||
}
|
||||
}
|
||||
}
|
||||
Alias(Symbol::BOOL_BOOL, _, _, _) => Expr::Var {
|
||||
module_name: "Bool",
|
||||
ident: if value { "true" } else { "false" },
|
||||
},
|
||||
Alias(_, _, var, _) => {
|
||||
let content = env.subs.get_content_without_compacting(*var);
|
||||
|
||||
|
|
|
@ -54,8 +54,6 @@ pub fn get_values<'a>(
|
|||
let expr = {
|
||||
let variable = *variable;
|
||||
|
||||
let content = subs.get_content_without_compacting(variable);
|
||||
|
||||
// TODO: pass layout_cache to jit_to_ast directly
|
||||
let mut layout_cache = LayoutCache::new(layout_interner.fork(), target_info);
|
||||
let layout = layout_cache.from_var(arena, variable, subs).unwrap();
|
||||
|
@ -71,7 +69,7 @@ pub fn get_values<'a>(
|
|||
app,
|
||||
"expect_repl_main_fn",
|
||||
proc_layout,
|
||||
content,
|
||||
variable,
|
||||
subs,
|
||||
interns,
|
||||
layout_interner.fork(),
|
||||
|
@ -853,7 +851,7 @@ mod test {
|
|||
run_expect_test(
|
||||
indoc!(
|
||||
r#"
|
||||
interface A exposes [] imports []
|
||||
interface Test exposes [] imports []
|
||||
|
||||
NonEmpty := [
|
||||
First Str U8,
|
||||
|
@ -899,7 +897,7 @@ mod test {
|
|||
run_expect_test(
|
||||
indoc!(
|
||||
r#"
|
||||
interface A exposes [] imports []
|
||||
interface Test exposes [] imports []
|
||||
|
||||
makeForcer : {} -> (Str -> U8)
|
||||
makeForcer = \{} -> \_ -> 2u8
|
||||
|
|
|
@ -100,22 +100,46 @@ fn num_ceil_checked_division_success() {
|
|||
|
||||
#[test]
|
||||
fn bool_in_record() {
|
||||
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
||||
expect_success("{ x: 1 == 1 }", "{ x: Bool.true } : { x : Bool }");
|
||||
expect_success(
|
||||
"{ z: { y: { x: 1 == 1 } } }",
|
||||
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
|
||||
"{ z: { y: { x: Bool.true } } } : { z : { y : { x : Bool } } }",
|
||||
);
|
||||
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
|
||||
expect_success("{ x: 1 != 1 }", "{ x: Bool.false } : { x : Bool }");
|
||||
expect_success(
|
||||
"{ x: 1 == 1, y: 1 != 1 }",
|
||||
"{ x: True, y: False } : { x : Bool, y : Bool }",
|
||||
"{ x: Bool.true, y: Bool.false } : { x : Bool, y : Bool }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_basic_equality() {
|
||||
expect_success("1 == 1", "True : Bool");
|
||||
expect_success("1 != 1", "False : Bool");
|
||||
expect_success("1 == 1", "Bool.true : Bool");
|
||||
expect_success("1 != 1", "Bool.false : Bool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_true() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Bool.true
|
||||
"#
|
||||
),
|
||||
r#"Bool.true : Bool"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_false() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Bool.false
|
||||
"#
|
||||
),
|
||||
r#"Bool.false : Bool"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -395,9 +419,9 @@ fn list_concat() {
|
|||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn list_contains() {
|
||||
expect_success("List.contains [] 0", "False : Bool");
|
||||
expect_success("List.contains [1, 2, 3] 2", "True : Bool");
|
||||
expect_success("List.contains [1, 2, 3] 4", "False : Bool");
|
||||
expect_success("List.contains [] 0", "Bool.false : Bool");
|
||||
expect_success("List.contains [1, 2, 3] 2", "Bool.true : Bool");
|
||||
expect_success("List.contains [1, 2, 3] 4", "Bool.false : Bool");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
|
@ -1239,3 +1263,24 @@ fn record_of_poly_function_and_string() {
|
|||
r#"{ a: <function>, b: "b" } : { a : * -> Str, b : Str }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_by_void_is_wrapped() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Result.try (Err 42) (\x -> Err (x+1))
|
||||
"#
|
||||
),
|
||||
r#"Err 42 : Result b (Num *)"#,
|
||||
);
|
||||
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Result.try (Ok 42) (\x -> Ok (x+1))
|
||||
"#
|
||||
),
|
||||
r#"Ok 43 : Result (Num *) err"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -203,7 +203,6 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||
&interns,
|
||||
DebugPrint::NOTHING,
|
||||
);
|
||||
let content = subs.get_content_without_compacting(main_fn_var);
|
||||
|
||||
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
|
@ -264,7 +263,7 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||
&mut app,
|
||||
"", // main_fn_name is ignored (only passed to WasmReplApp methods)
|
||||
main_fn_layout,
|
||||
content,
|
||||
main_fn_var,
|
||||
&subs,
|
||||
&interns,
|
||||
layout_interner.into_global().fork(),
|
||||
|
|
|
@ -3,7 +3,8 @@ use roc_module::ident::{Ident, Lowercase, ModuleName};
|
|||
use roc_module::symbol::DERIVABLE_ABILITIES;
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{
|
||||
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, ShadowKind,
|
||||
BadPattern, CycleEntry, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError,
|
||||
ShadowKind,
|
||||
};
|
||||
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
|
||||
use roc_types::types::AliasKind;
|
||||
|
@ -2025,12 +2026,22 @@ pub fn to_circular_def_doc<'b>(
|
|||
// TODO tip?
|
||||
match entries {
|
||||
[] => unreachable!(),
|
||||
[first] => alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first.symbol))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
)),
|
||||
[CycleEntry { symbol, symbol_region, expr_region }] =>
|
||||
alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.symbol_unqualified(*symbol),
|
||||
alloc.reflow(" is defined directly in terms of itself:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(Region::span_across(symbol_region, expr_region))),
|
||||
alloc.concat([
|
||||
alloc.reflow("Since Roc evaluates values strict, running this program would create an infinite number of "),
|
||||
alloc.symbol_unqualified(*symbol),
|
||||
alloc.reflow(" values!"),
|
||||
]),
|
||||
alloc.hint("").append(alloc.concat([
|
||||
alloc.reflow("Did you mean to define "),alloc.symbol_unqualified(*symbol),alloc.reflow(" as a function?"),
|
||||
])),
|
||||
]),
|
||||
[first, others @ ..] => {
|
||||
alloc.stack([
|
||||
alloc
|
||||
|
|
|
@ -3100,6 +3100,33 @@ fn to_header_report<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
EHeader::InconsistentModuleName(region) => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(
|
||||
r"This module name does not correspond with the file path it is defined in:",
|
||||
),
|
||||
alloc.region(lines.convert_region(*region)),
|
||||
alloc.concat([
|
||||
alloc.reflow("Module names must correspond with the file paths they are defined in. For example, I expect to see "),
|
||||
alloc.parser_suggestion("BigNum"),
|
||||
alloc.reflow(" defined in "),
|
||||
alloc.parser_suggestion("BigNum.roc"),
|
||||
alloc.reflow(", or "),
|
||||
alloc.parser_suggestion("Math.Sin"),
|
||||
alloc.reflow(" defined in "),
|
||||
alloc.parser_suggestion("Math/Sin.roc"),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "WEIRD MODULE NAME".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
|
||||
EHeader::AppName(_, pos) => {
|
||||
let surroundings = Region::new(start, *pos);
|
||||
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
|
||||
|
|
|
@ -2036,6 +2036,7 @@ pub enum Problem {
|
|||
BadRigidVar(Lowercase, ErrorType, Option<Symbol>),
|
||||
OptionalRequiredMismatch(Lowercase),
|
||||
OpaqueComparedToNonOpaque,
|
||||
BoolVsBoolTag(TagName),
|
||||
}
|
||||
|
||||
fn problems_to_tip<'b>(
|
||||
|
@ -2579,6 +2580,23 @@ fn to_diff<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
(Alias(Symbol::BOOL_BOOL, _, _, _), TagUnion(tags, _)) | (TagUnion(tags, _), Alias(Symbol::BOOL_BOOL, _, _, _))
|
||||
if tags.len() == 1
|
||||
&& tags.keys().all(|t| t.0.as_str() == "True" || t.0.as_str() == "False") =>
|
||||
{
|
||||
let written_tag = tags.keys().next().unwrap().clone();
|
||||
let (left, left_able) = to_doc(alloc, Parens::InFn, type1);
|
||||
let (right, right_able) = to_doc(alloc, Parens::InFn, type2);
|
||||
|
||||
Diff {
|
||||
left,
|
||||
right,
|
||||
status: Status::Different(vec![Problem::BoolVsBoolTag(written_tag)]),
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
}
|
||||
|
||||
(Alias(sym, _, _, AliasKind::Opaque), _) | (_, Alias(sym, _, _, AliasKind::Opaque))
|
||||
// Skip the hint for numbers; it's not as useful as saying "this type is not a number"
|
||||
if !OPAQUE_NUM_SYMBOLS.contains(&sym)
|
||||
|
@ -3707,6 +3725,18 @@ fn type_problem_to_pretty<'b>(
|
|||
alloc.type_str("@Age 23"),
|
||||
alloc.reflow("."),
|
||||
])),
|
||||
|
||||
(BoolVsBoolTag(tag), _) => alloc.tip().append(alloc.concat([
|
||||
alloc.reflow("Did you mean to use "),
|
||||
alloc.symbol_qualified(if tag.0.as_str() == "True" {
|
||||
Symbol::BOOL_TRUE
|
||||
} else {
|
||||
Symbol::BOOL_FALSE
|
||||
}),
|
||||
alloc.reflow(" rather than "),
|
||||
alloc.tag_name(tag),
|
||||
alloc.reflow("?"),
|
||||
])),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_module::symbol::{Interns, ModuleId, PQModuleName, PackageQualified, Symbol};
|
||||
use roc_region::all::LineColumnRegion;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -458,6 +458,15 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.text(name).annotate(Annotation::Module)
|
||||
}
|
||||
|
||||
pub fn pq_module_name(&'a self, name: PQModuleName<'a>) -> DocBuilder<'a, Self, Annotation> {
|
||||
let name = match name {
|
||||
PackageQualified::Unqualified(n) => n.to_string(),
|
||||
PackageQualified::Qualified(prefix, n) => format!("{prefix}.{n}"),
|
||||
};
|
||||
|
||||
self.text(name).annotate(Annotation::Module)
|
||||
}
|
||||
|
||||
pub fn module_name(&'a self, name: ModuleName) -> DocBuilder<'a, Self, Annotation> {
|
||||
let name = if name.is_empty() {
|
||||
// Render the app module as "app"
|
||||
|
|
|
@ -2158,12 +2158,19 @@ mod test_reporting {
|
|||
f
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
The `f` value is defined directly in terms of itself, causing an
|
||||
infinite loop.
|
||||
"#
|
||||
`f` is defined directly in terms of itself:
|
||||
|
||||
4│ f = f
|
||||
^^^^^
|
||||
|
||||
Since Roc evaluates values strict, running this program would create
|
||||
an infinite number of `f` values!
|
||||
|
||||
Hint: Did you mean to define `f` as a function?
|
||||
"###
|
||||
);
|
||||
|
||||
// invalid mutual recursion
|
||||
|
@ -3616,7 +3623,7 @@ mod test_reporting {
|
|||
Set
|
||||
List
|
||||
Dict
|
||||
Result
|
||||
Hash
|
||||
|
||||
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
|
@ -7559,31 +7566,30 @@ All branches in an `if` must have the same type!
|
|||
);
|
||||
|
||||
test_report!(
|
||||
#[ignore = "Blocked on https://github.com/roc-lang/roc/issues/3385"]
|
||||
unimported_modules_reported,
|
||||
indoc!(
|
||||
r#"
|
||||
main : Task.Task {} []
|
||||
main = "whatever man you don't even know my type"
|
||||
main
|
||||
alt : Task.Task {} []
|
||||
alt = "whatever man you don't even know my type"
|
||||
alt
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
The `Task` module is not imported:
|
||||
|
||||
1│ main : Task.Task {} []
|
||||
4│ alt : Task.Task {} []
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Is there an import missing? Perhaps there is a typo. Did you mean one
|
||||
of these?
|
||||
|
||||
Test
|
||||
Hash
|
||||
List
|
||||
Num
|
||||
Box
|
||||
"#
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8183,16 +8189,16 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [] to "./platform"
|
||||
|
||||
Hash a b c has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash a b c has
|
||||
hash : a -> U64 | a has MHash
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
The definition of the `Hash` ability includes type variables:
|
||||
The definition of the `MHash` ability includes type variables:
|
||||
|
||||
3│ Hash a b c has
|
||||
3│ MHash a b c has
|
||||
^^^^^
|
||||
|
||||
Abilities cannot depend on type variables, but their member values
|
||||
|
@ -8200,14 +8206,14 @@ All branches in an `if` must have the same type!
|
|||
|
||||
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
`Hash` is not used anywhere in your code.
|
||||
`MHash` is not used anywhere in your code.
|
||||
|
||||
3│ Hash a b c has
|
||||
^^^^
|
||||
3│ MHash a b c has
|
||||
^^^^^
|
||||
|
||||
If you didn't intend on using `Hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
If you didn't intend on using `MHash` then remove it so future readers
|
||||
of your code don't wonder why it is there.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8216,17 +8222,17 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
MHash has hash : a, b -> Num.U64 | a has MHash, b has Bool.Bool
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
The type referenced in this "has" clause is not an ability:
|
||||
|
||||
3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
3│ MHash has hash : a, b -> Num.U64 | a has MHash, b has Bool.Bool
|
||||
^^^^^^^^^
|
||||
"#
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8354,36 +8360,36 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [f] to "./platform"
|
||||
|
||||
Hash has hash : (a | a has Hash) -> Num.U64
|
||||
MHash has hash : (a | a has MHash) -> Num.U64
|
||||
|
||||
f : a -> Num.U64 | a has Hash
|
||||
f : a -> Num.U64 | a has MHash
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
A `has` clause is not allowed here:
|
||||
|
||||
3│ Hash has hash : (a | a has Hash) -> Num.U64
|
||||
^^^^^^^^^^
|
||||
3│ MHash has hash : (a | a has MHash) -> Num.U64
|
||||
^^^^^^^^^^^
|
||||
|
||||
`has` clauses can only be specified on the top-level type annotations.
|
||||
|
||||
── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─
|
||||
|
||||
The definition of the ability member `hash` does not include a `has`
|
||||
clause binding a type variable to the ability `Hash`:
|
||||
clause binding a type variable to the ability `MHash`:
|
||||
|
||||
3│ Hash has hash : (a | a has Hash) -> Num.U64
|
||||
3│ MHash has hash : (a | a has MHash) -> Num.U64
|
||||
^^^^
|
||||
|
||||
Ability members must include a `has` clause binding a type variable to
|
||||
an ability, like
|
||||
|
||||
a has Hash
|
||||
a has MHash
|
||||
|
||||
Otherwise, the function does not need to be part of the ability!
|
||||
"#
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8392,9 +8398,9 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
MHash has hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U32 has [Hash {hash}]
|
||||
Id := U32 has [MHash {hash}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
"#
|
||||
|
@ -8452,8 +8458,8 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
hash = \_ -> 0u64
|
||||
"#
|
||||
|
@ -8477,11 +8483,11 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash, One, Two] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
One := {} has [Hash {hash}]
|
||||
Two := {} has [Hash {hash}]
|
||||
One := {} has [MHash {hash}]
|
||||
Two := {} has [MHash {hash}]
|
||||
|
||||
hash = \_ -> 0u64
|
||||
"#
|
||||
|
@ -8493,7 +8499,7 @@ All branches in an `if` must have the same type!
|
|||
This ability member specialization is already claimed to specialize
|
||||
another opaque type:
|
||||
|
||||
7│ Two := {} has [Hash {hash}]
|
||||
7│ Two := {} has [MHash {hash}]
|
||||
^^^^
|
||||
|
||||
Previously, we found it to specialize `hash` for `One`.
|
||||
|
@ -8514,7 +8520,7 @@ All branches in an `if` must have the same type!
|
|||
|
||||
But the type annotation on `hash` says it must match:
|
||||
|
||||
a -> U64 | a has Hash
|
||||
a -> U64 | a has MHash
|
||||
|
||||
Note: The specialized type is too general, and does not provide a
|
||||
concrete type where a type variable is bound to an ability.
|
||||
|
@ -8531,11 +8537,11 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash, One, Two] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
One := {} has [Hash {hash}]
|
||||
Two := {} has [Hash {hash}]
|
||||
One := {} has [MHash {hash}]
|
||||
Two := {} has [MHash {hash}]
|
||||
|
||||
hash = \@One _ -> 0u64
|
||||
"#
|
||||
|
@ -8546,7 +8552,7 @@ All branches in an `if` must have the same type!
|
|||
This ability member specialization is already claimed to specialize
|
||||
another opaque type:
|
||||
|
||||
7│ Two := {} has [Hash {hash}]
|
||||
7│ Two := {} has [MHash {hash}]
|
||||
^^^^
|
||||
|
||||
Previously, we found it to specialize `hash` for `One`.
|
||||
|
@ -8587,10 +8593,7 @@ All branches in an `if` must have the same type!
|
|||
|
||||
You, You -> Bool
|
||||
|
||||
Tip: Type comparisons between an opaque type are only ever equal if
|
||||
both types are the same opaque type. Did you mean to create an opaque
|
||||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||||
an instance of this opaque type by doing @Age 23.
|
||||
Tip: Did you mean to use `Bool.false` rather than `False`?
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -8600,10 +8603,10 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash : Id -> U32
|
||||
hash = \@Id n -> n
|
||||
|
@ -8649,10 +8652,10 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [noGoodVeryBadTerrible] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
|
||||
hash = \@Id n -> n
|
||||
|
||||
|
@ -8673,7 +8676,7 @@ All branches in an `if` must have the same type!
|
|||
15│ notYet: hash (A 1),
|
||||
^^^
|
||||
|
||||
Roc can't generate an implementation of the `#UserApp.Hash` ability for
|
||||
Roc can't generate an implementation of the `#UserApp.MHash` ability for
|
||||
|
||||
[A (Num a)]b
|
||||
|
||||
|
@ -8686,7 +8689,7 @@ All branches in an `if` must have the same type!
|
|||
14│ nope: hash (@User {}),
|
||||
^^^^^^^^
|
||||
|
||||
The type `User` does not fully implement the ability `Hash`.
|
||||
The type `User` does not fully implement the ability `MHash`.
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -8697,8 +8700,8 @@ All branches in an `if` must have the same type!
|
|||
app "test" provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
123
|
||||
"#
|
||||
|
@ -8708,8 +8711,8 @@ All branches in an `if` must have the same type!
|
|||
|
||||
This ability definition is not on the top-level of a module:
|
||||
|
||||
4│> Hash has
|
||||
5│> hash : a -> U64 | a has Hash
|
||||
4│> MHash has
|
||||
5│> hash : a -> U64 | a has MHash
|
||||
|
||||
Abilities can only be defined on the top-level of a Roc module.
|
||||
"#
|
||||
|
@ -8721,22 +8724,22 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash, hashable] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
Id := U64 has [Hash {hash}]
|
||||
Id := U64 has [MHash {hash}]
|
||||
hash = \@Id n -> n
|
||||
|
||||
hashable : a | a has Hash
|
||||
hashable : a | a has MHash
|
||||
hashable = @Id 15
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
Something is off with the body of the `hashable` definition:
|
||||
|
||||
9│ hashable : a | a has Hash
|
||||
9│ hashable : a | a has MHash
|
||||
10│ hashable = @Id 15
|
||||
^^^^^^
|
||||
|
||||
|
@ -8746,14 +8749,14 @@ All branches in an `if` must have the same type!
|
|||
|
||||
But the type annotation on `hashable` says it should be:
|
||||
|
||||
a | a has Hash
|
||||
a | a has MHash
|
||||
|
||||
Tip: The type annotation uses the type variable `a` to say that this
|
||||
definition can produce any value implementing the `Hash` ability. But in
|
||||
the body I see that it will only produce a `Id` value of a single
|
||||
definition can produce any value implementing the `MHash` ability. But
|
||||
in the body I see that it will only produce a `Id` value of a single
|
||||
specific type. Maybe change the type annotation to be more specific?
|
||||
Maybe change the code to be more general?
|
||||
"#
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8762,50 +8765,50 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
MHash has
|
||||
hash : a -> U64 | a has MHash
|
||||
|
||||
mulHashes : Hash, Hash -> U64
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
mulMHashes : MHash, MHash -> U64
|
||||
mulMHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64 has [Hash {hash: hashId}]
|
||||
Id := U64 has [MHash {hash: hashId}]
|
||||
hashId = \@Id n -> n
|
||||
|
||||
Three := {} has [Hash {hash: hashThree}]
|
||||
Three := {} has [MHash {hash: hashThree}]
|
||||
hashThree = \@Three _ -> 3
|
||||
|
||||
result = mulHashes (@Id 100) (@Three {})
|
||||
result = mulMHashes (@Id 100) (@Three {})
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
You are attempting to use the ability `Hash` as a type directly:
|
||||
You are attempting to use the ability `MHash` as a type directly:
|
||||
|
||||
6│ mulHashes : Hash, Hash -> U64
|
||||
^^^^
|
||||
6│ mulMHashes : MHash, MHash -> U64
|
||||
^^^^^
|
||||
|
||||
Abilities can only be used in type annotations to constrain type
|
||||
variables.
|
||||
|
||||
Hint: Perhaps you meant to include a `has` annotation, like
|
||||
|
||||
a has Hash
|
||||
a has MHash
|
||||
|
||||
── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
You are attempting to use the ability `Hash` as a type directly:
|
||||
You are attempting to use the ability `MHash` as a type directly:
|
||||
|
||||
6│ mulHashes : Hash, Hash -> U64
|
||||
^^^^
|
||||
6│ mulMHashes : MHash, MHash -> U64
|
||||
^^^^^
|
||||
|
||||
Abilities can only be used in type annotations to constrain type
|
||||
variables.
|
||||
|
||||
Hint: Perhaps you meant to include a `has` annotation, like
|
||||
|
||||
b has Hash
|
||||
"#
|
||||
b has MHash
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -9732,9 +9735,9 @@ All branches in an `if` must have the same type!
|
|||
|
||||
F a b := b | a has Foo
|
||||
|
||||
Hash := {}
|
||||
MHash := {}
|
||||
|
||||
x : F Hash {}
|
||||
x : F MHash {}
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
|
@ -10090,7 +10093,7 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash, Id] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
MHash has hash : a -> U64 | a has MHash
|
||||
|
||||
Id := {}
|
||||
|
||||
|
@ -10116,9 +10119,9 @@ All branches in an `if` must have the same type!
|
|||
r#"
|
||||
app "test" provides [hash, Id, Id2] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
MHash has hash : a -> U64 | a has MHash
|
||||
|
||||
Id := {} has [Hash {hash}]
|
||||
Id := {} has [MHash {hash}]
|
||||
Id2 := {}
|
||||
|
||||
hash = \@Id2 _ -> 0
|
||||
|
@ -10768,4 +10771,305 @@ All branches in an `if` must have the same type!
|
|||
@r###"
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
invalid_toplevel_cycle,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
if Bool.true then \{} -> {} else main
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
`main` is defined directly in terms of itself:
|
||||
|
||||
3│> main =
|
||||
4│> if Bool.true then \{} -> {} else main
|
||||
|
||||
Since Roc evaluates values strict, running this program would create
|
||||
an infinite number of `main` values!
|
||||
|
||||
Hint: Did you mean to define `main` as a function?
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
bool_vs_true_tag,
|
||||
indoc!(
|
||||
r#"
|
||||
if True then "" else ""
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This `if` condition needs to be a Bool:
|
||||
|
||||
4│ if True then "" else ""
|
||||
^^^^
|
||||
|
||||
This `True` tag has the type:
|
||||
|
||||
[True]a
|
||||
|
||||
But I need every `if` condition to evaluate to a Bool—either `Bool.true`
|
||||
or `Bool.false`.
|
||||
|
||||
Tip: Did you mean to use `Bool.true` rather than `True`?
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
bool_vs_false_tag,
|
||||
indoc!(
|
||||
r#"
|
||||
if False then "" else ""
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This `if` condition needs to be a Bool:
|
||||
|
||||
4│ if False then "" else ""
|
||||
^^^^^
|
||||
|
||||
This `False` tag has the type:
|
||||
|
||||
[False]a
|
||||
|
||||
But I need every `if` condition to evaluate to a Bool—either `Bool.true`
|
||||
or `Bool.false`.
|
||||
|
||||
Tip: Did you mean to use `Bool.false` rather than `False`?
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
derive_hash_for_function,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [A] to "./platform"
|
||||
|
||||
A a := a -> a has [Hash]
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
|
||||
|
||||
Roc can't derive an implementation of the `Hash.Hash` for `A`:
|
||||
|
||||
3│ A a := a -> a has [Hash]
|
||||
^^^^
|
||||
|
||||
Note: `Hash` cannot be generated for functions.
|
||||
|
||||
Tip: You can define a custom implementation of `Hash.Hash` for `A`.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
derive_hash_for_non_hash_opaque,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [A] to "./platform"
|
||||
|
||||
A := B has [Hash]
|
||||
|
||||
B := {}
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
|
||||
|
||||
Roc can't derive an implementation of the `Hash.Hash` for `A`:
|
||||
|
||||
3│ A := B has [Hash]
|
||||
^^^^
|
||||
|
||||
Tip: `B` does not implement `Hash`. Consider adding a custom
|
||||
implementation or `has Hash.Hash` to the definition of `B`.
|
||||
|
||||
Tip: You can define a custom implementation of `Hash.Hash` for `A`.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
derive_hash_for_other_has_hash,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [A] to "./platform"
|
||||
|
||||
A := B has [Hash]
|
||||
|
||||
B := {} has [Hash]
|
||||
"#
|
||||
),
|
||||
@"" // no error
|
||||
);
|
||||
|
||||
test_report!(
|
||||
derive_hash_for_recursive_deriving,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [MyNat] to "./platform"
|
||||
|
||||
MyNat := [S MyNat, Z] has [Hash]
|
||||
"#
|
||||
),
|
||||
@"" // no error
|
||||
);
|
||||
|
||||
test_report!(
|
||||
derive_hash_for_record,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
foo : a -> {} | a has Hash
|
||||
|
||||
main = foo {a: "", b: 1}
|
||||
"#
|
||||
),
|
||||
@"" // no error
|
||||
);
|
||||
|
||||
test_report!(
|
||||
derive_hash_for_tag,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
foo : a -> {} | a has Hash
|
||||
|
||||
t : [A {}, B U8 U64, C Str]
|
||||
|
||||
main = foo t
|
||||
"#
|
||||
),
|
||||
@"" // no error
|
||||
);
|
||||
|
||||
test_report!(
|
||||
cannot_derive_hash_for_function,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
foo : a -> {} | a has Hash
|
||||
|
||||
main = foo (\x -> x)
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
5│ main = foo (\x -> x)
|
||||
^^^^^^^
|
||||
|
||||
Roc can't generate an implementation of the `Hash.Hash` ability for
|
||||
|
||||
a -> a
|
||||
|
||||
Note: `Hash` cannot be generated for functions.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
cannot_derive_hash_for_structure_containing_function,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
foo : a -> {} | a has Hash
|
||||
|
||||
main = foo (A (\x -> x) B)
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
5│ main = foo (A (\x -> x) B)
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Roc can't generate an implementation of the `Hash.Hash` ability for
|
||||
|
||||
[A (a -> a) [B]a]b
|
||||
|
||||
In particular, an implementation for
|
||||
|
||||
a -> a
|
||||
|
||||
cannot be generated.
|
||||
|
||||
Note: `Hash` cannot be generated for functions.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
shift_by_negative,
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
a: Num.shiftLeftBy 1 -1,
|
||||
b: Num.shiftRightBy 1 -1,
|
||||
c: Num.shiftRightZfBy 1 -1,
|
||||
}
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This 2nd argument to `shiftRightZfBy` has an unexpected type:
|
||||
|
||||
7│ c: Num.shiftRightZfBy 1 -1,
|
||||
^^
|
||||
|
||||
The argument is a number of type:
|
||||
|
||||
I8, I16, F32, I32, F64, I64, I128, or Dec
|
||||
|
||||
But `shiftRightZfBy` needs its 2nd argument to be:
|
||||
|
||||
U8
|
||||
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This 2nd argument to `shiftRightBy` has an unexpected type:
|
||||
|
||||
6│ b: Num.shiftRightBy 1 -1,
|
||||
^^
|
||||
|
||||
The argument is a number of type:
|
||||
|
||||
I8, I16, F32, I32, F64, I64, I128, or Dec
|
||||
|
||||
But `shiftRightBy` needs its 2nd argument to be:
|
||||
|
||||
U8
|
||||
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This 2nd argument to `shiftLeftBy` has an unexpected type:
|
||||
|
||||
5│ a: Num.shiftLeftBy 1 -1,
|
||||
^^
|
||||
|
||||
The argument is a number of type:
|
||||
|
||||
I8, I16, F32, I32, F64, I64, I128, or Dec
|
||||
|
||||
But `shiftLeftBy` needs its 2nd argument to be:
|
||||
|
||||
U8
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ interface Arg
|
|||
bool,
|
||||
str,
|
||||
i64,
|
||||
positional,
|
||||
subCommand,
|
||||
choice,
|
||||
withParser,
|
||||
|
@ -31,7 +32,8 @@ NamedParser a := {
|
|||
## needs, consider transforming it into a [NamedParser].
|
||||
Parser a := [
|
||||
Succeed a,
|
||||
Arg Config (List Str -> Result a [NotFound Str, WrongType { arg : Str, expected : Type }]),
|
||||
Arg ArgConfig (MarkedArgs -> Result { newlyTaken : Taken, val : a } (ParseError [])),
|
||||
Positional PositionalConfig (MarkedArgs -> Result { newlyTaken : Taken, val : a } (ParseError [])),
|
||||
# TODO: hiding the record behind an alias currently causes a panic
|
||||
SubCommand
|
||||
(List {
|
||||
|
@ -44,12 +46,23 @@ Parser a := [
|
|||
Lazy ({} -> a),
|
||||
]
|
||||
|
||||
## Indices in an arguments list that have already been parsed.
|
||||
Taken : Set Nat
|
||||
|
||||
## A representation of parsed and unparsed arguments in a constant list of
|
||||
## command-line arguments.
|
||||
## Used only internally, for efficient representation of parsed and unparsed
|
||||
## arguments.
|
||||
MarkedArgs : { args : List Str, taken : Taken }
|
||||
|
||||
## Enumerates errors that can occur during parsing a list of command line arguments.
|
||||
ParseError a : [
|
||||
## The program name was not found as the first argument to be parsed.
|
||||
ProgramNameNotProvided Str,
|
||||
## An argument is required, but it was not found.
|
||||
MissingRequiredArg Str,
|
||||
## A positional argument is required, but it was not found.
|
||||
MissingPositionalArg Str,
|
||||
## An argument was found, but it didn't have the expected [Type].
|
||||
WrongType
|
||||
{
|
||||
|
@ -83,13 +96,20 @@ Help : [
|
|||
Config (List Config),
|
||||
]
|
||||
|
||||
Config : {
|
||||
ArgConfig : {
|
||||
long : Str,
|
||||
short : Str,
|
||||
help : Str,
|
||||
type : Type,
|
||||
}
|
||||
|
||||
PositionalConfig : {
|
||||
name : Str,
|
||||
help : Str,
|
||||
}
|
||||
|
||||
Config : [Arg ArgConfig, Positional PositionalConfig]
|
||||
|
||||
## Generates help metadata from a [Parser].
|
||||
##
|
||||
## This is useful if you would like to use this metadata to generate your own
|
||||
|
@ -114,7 +134,7 @@ toHelpHelper = \@Parser parser, configs ->
|
|||
toHelpHelper innerParser (List.append configs config)
|
||||
|
||||
Arg config _ ->
|
||||
List.append configs config
|
||||
List.append configs (Arg config)
|
||||
|> Config
|
||||
|
||||
SubCommand commands ->
|
||||
|
@ -123,24 +143,39 @@ toHelpHelper = \@Parser parser, configs ->
|
|||
(\{ name, parser: innerParser } -> { name, help: toHelpHelper innerParser [] })
|
||||
|> SubCommands
|
||||
|
||||
findOneArg : Str, Str, List Str -> Result Str [NotFound]*
|
||||
findOneArg = \long, short, args ->
|
||||
argMatches = \arg ->
|
||||
if arg == "--\(long)" then
|
||||
Bool.true
|
||||
Positional config _ ->
|
||||
List.append configs (Positional config)
|
||||
|> Config
|
||||
|
||||
findOneArg : Str, Str, MarkedArgs -> Result { val : Str, newlyTaken : Taken } [NotFound]*
|
||||
findOneArg = \long, short, { args, taken } ->
|
||||
argMatches = \{ index, found: _ }, arg ->
|
||||
if Set.contains taken index || Set.contains taken (index + 1) then
|
||||
Continue { index: index + 1, found: Bool.false }
|
||||
else if arg == "--\(long)" then
|
||||
Break { index, found: Bool.true }
|
||||
else if Bool.not (Str.isEmpty short) && arg == "-\(short)" then
|
||||
Break { index, found: Bool.true }
|
||||
else
|
||||
Bool.not (Str.isEmpty short) && arg == "-\(short)"
|
||||
Continue { index: index + 1, found: Bool.false }
|
||||
|
||||
# TODO allow = as well, etc.
|
||||
result = List.findFirstIndex args argMatches
|
||||
{ index: argIndex, found } = List.walkUntil args { index: 0, found: Bool.false } argMatches
|
||||
|
||||
when result is
|
||||
Ok index ->
|
||||
if !found then
|
||||
Err NotFound
|
||||
else
|
||||
# Return the next arg after the given one
|
||||
List.get args (index + 1)
|
||||
|> Result.mapErr \_ -> NotFound
|
||||
List.get args (argIndex + 1)
|
||||
|> Result.mapErr (\_ -> NotFound)
|
||||
|> Result.map
|
||||
(\val ->
|
||||
newUsed = Set.fromList [argIndex, argIndex + 1]
|
||||
|
||||
Err NotFound -> Err NotFound
|
||||
{ val, newlyTaken: newUsed })
|
||||
|
||||
updateTaken : MarkedArgs, Taken -> MarkedArgs
|
||||
updateTaken = \{ args, taken }, taken2 -> { args, taken: Set.union taken taken2 }
|
||||
|
||||
# andMap : Parser a, Parser (a -> b) -> Parser b
|
||||
andMap = \@Parser parser, @Parser mapper ->
|
||||
|
@ -162,7 +197,12 @@ andMap = \@Parser parser, @Parser mapper ->
|
|||
Arg config run ->
|
||||
Arg config \args ->
|
||||
run args
|
||||
|> Result.map fn
|
||||
|> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken })
|
||||
|
||||
Positional config run ->
|
||||
Positional config \args ->
|
||||
run args
|
||||
|> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken })
|
||||
|
||||
SubCommand cmds ->
|
||||
mapSubParser = \{ name, parser: parser2 } ->
|
||||
|
@ -176,13 +216,13 @@ andMap = \@Parser parser, @Parser mapper ->
|
|||
Succeed a ->
|
||||
Arg config \args ->
|
||||
when run args is
|
||||
Ok fn -> Ok (fn a)
|
||||
Ok { val: fn, newlyTaken } -> Ok { val: fn a, newlyTaken }
|
||||
Err err -> Err err
|
||||
|
||||
Lazy thunk ->
|
||||
Arg config \args ->
|
||||
when run args is
|
||||
Ok fn -> Ok (fn (thunk {}))
|
||||
Ok { val: fn, newlyTaken } -> Ok { val: fn (thunk {}), newlyTaken }
|
||||
Err err -> Err err
|
||||
|
||||
WithConfig parser2 config2 ->
|
||||
|
@ -194,12 +234,83 @@ andMap = \@Parser parser, @Parser mapper ->
|
|||
# Parse first the one and then the other.
|
||||
combinedParser = Arg config2 \args ->
|
||||
when run args is
|
||||
Ok fn -> run2 args |> Result.map fn
|
||||
Ok { val: fn, newlyTaken } ->
|
||||
run2 (updateTaken args newlyTaken)
|
||||
|> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 })
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
# Store the extra config.
|
||||
@Parser combinedParser
|
||||
|> WithConfig config
|
||||
|> WithConfig (Arg config)
|
||||
|
||||
Positional config2 run2 ->
|
||||
combinedParser = Positional config2 \args ->
|
||||
when run args is
|
||||
Ok { val: fn, newlyTaken } ->
|
||||
run2 (updateTaken args newlyTaken)
|
||||
|> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 })
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
# Store the extra config.
|
||||
@Parser combinedParser
|
||||
|> WithConfig (Arg config)
|
||||
|
||||
SubCommand cmds ->
|
||||
# For each subcommand, first run the subcommand, then
|
||||
# push the result through the arg parser.
|
||||
mapSubParser = \{ name, parser: parser2 } ->
|
||||
{ name, parser: andMap parser2 (@Parser mapper) }
|
||||
|
||||
List.map cmds mapSubParser
|
||||
|> SubCommand
|
||||
|
||||
Positional config run ->
|
||||
when parser is
|
||||
Succeed a ->
|
||||
Positional config \args ->
|
||||
when run args is
|
||||
Ok { val: fn, newlyTaken } -> Ok { val: fn a, newlyTaken }
|
||||
Err err -> Err err
|
||||
|
||||
Lazy thunk ->
|
||||
Positional config \args ->
|
||||
when run args is
|
||||
Ok { val: fn, newlyTaken } -> Ok { val: fn (thunk {}), newlyTaken }
|
||||
Err err -> Err err
|
||||
|
||||
WithConfig parser2 config2 ->
|
||||
parser2
|
||||
|> andMap (@Parser mapper)
|
||||
|> WithConfig config2
|
||||
|
||||
Arg config2 run2 ->
|
||||
# Parse first the one and then the other.
|
||||
combinedParser = Arg config2 \args ->
|
||||
when run args is
|
||||
Ok { val: fn, newlyTaken } ->
|
||||
run2 (updateTaken args newlyTaken)
|
||||
|> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 })
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
# Store the extra config.
|
||||
@Parser combinedParser
|
||||
|> WithConfig (Positional config)
|
||||
|
||||
Positional config2 run2 ->
|
||||
combinedParser = Positional config2 \args ->
|
||||
when run args is
|
||||
Ok { val: fn, newlyTaken } ->
|
||||
run2 (updateTaken args newlyTaken)
|
||||
|> Result.map (\{ val, newlyTaken: newlyTaken2 } -> { val: fn val, newlyTaken: Set.union newlyTaken newlyTaken2 })
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
# Store the extra config.
|
||||
@Parser combinedParser
|
||||
|> WithConfig (Positional config)
|
||||
|
||||
SubCommand cmds ->
|
||||
# For each subcommand, first run the subcommand, then
|
||||
|
@ -228,7 +339,12 @@ andMap = \@Parser parser, @Parser mapper ->
|
|||
Arg config run ->
|
||||
Arg config \args ->
|
||||
run args
|
||||
|> Result.map fn
|
||||
|> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken })
|
||||
|
||||
Positional config run ->
|
||||
Positional config \args ->
|
||||
run args
|
||||
|> Result.map (\{ val, newlyTaken } -> { val: fn val, newlyTaken })
|
||||
|
||||
SubCommand cmds ->
|
||||
mapSubParser = \{ name, parser: parser2 } ->
|
||||
|
@ -275,22 +391,26 @@ parse = \@NamedParser parser, args ->
|
|||
then
|
||||
Err (ProgramNameNotProvided parser.name)
|
||||
else
|
||||
parseHelp parser.parser (List.split args 1).others
|
||||
markedArgs = { args, taken: Set.single 0 }
|
||||
|
||||
parseHelp : Parser a, List Str -> Result a (ParseError *)
|
||||
parseHelp parser.parser markedArgs
|
||||
|
||||
parseHelp : Parser a, MarkedArgs -> Result a (ParseError [])
|
||||
parseHelp = \@Parser parser, args ->
|
||||
when parser is
|
||||
Succeed val -> Ok val
|
||||
Arg _ run ->
|
||||
when run args is
|
||||
Ok val -> Ok val
|
||||
Err (NotFound long) -> Err (MissingRequiredArg long)
|
||||
Err (WrongType { arg, expected }) -> Err (WrongType { arg, expected })
|
||||
run args
|
||||
|> Result.map .val
|
||||
|
||||
Positional _ run ->
|
||||
run args
|
||||
|> Result.map .val
|
||||
|
||||
SubCommand cmds ->
|
||||
when List.get args 0 is
|
||||
Ok cmd ->
|
||||
argsRest = (List.split args 1).others
|
||||
when nextUnmarked args is
|
||||
Ok { index, val: cmd } ->
|
||||
argsRest = { args & taken: Set.insert args.taken index }
|
||||
state =
|
||||
List.walkUntil
|
||||
cmds
|
||||
|
@ -313,6 +433,17 @@ parseHelp = \@Parser parser, args ->
|
|||
WithConfig parser2 _config ->
|
||||
parseHelp parser2 args
|
||||
|
||||
nextUnmarked : MarkedArgs -> Result { index : Nat, val : Str } [OutOfBounds]
|
||||
nextUnmarked = \marked ->
|
||||
help = \index ->
|
||||
if Set.contains marked.taken index then
|
||||
help (index + 1)
|
||||
else
|
||||
List.get marked.args index
|
||||
|> Result.map \val -> { index, val }
|
||||
|
||||
help 0
|
||||
|
||||
## Creates a parser for a boolean flag argument.
|
||||
## Flags of value "true" and "false" will be parsed as [Bool.true] and [Bool.false], respectively.
|
||||
## All other values will result in a `WrongType` error.
|
||||
|
@ -320,10 +451,12 @@ bool : _ -> Parser Bool # TODO: panics if parameter annotation given
|
|||
bool = \{ long, short ? "", help ? "" } ->
|
||||
fn = \args ->
|
||||
when findOneArg long short args is
|
||||
Err NotFound -> Err (NotFound long)
|
||||
Ok "true" -> Ok Bool.true
|
||||
Ok "false" -> Ok Bool.false
|
||||
Ok _ -> Err (WrongType { arg: long, expected: Bool })
|
||||
Err NotFound -> Err (MissingRequiredArg long)
|
||||
Ok { val, newlyTaken } ->
|
||||
when val is
|
||||
"true" -> Ok { val: Bool.true, newlyTaken }
|
||||
"false" -> Ok { val: Bool.false, newlyTaken }
|
||||
_ -> Err (WrongType { arg: long, expected: Bool })
|
||||
|
||||
@Parser (Arg { long, short, help, type: Bool } fn)
|
||||
|
||||
|
@ -332,8 +465,8 @@ str : _ -> Parser Str # TODO: panics if parameter annotation given
|
|||
str = \{ long, short ? "", help ? "" } ->
|
||||
fn = \args ->
|
||||
when findOneArg long short args is
|
||||
Err NotFound -> Err (NotFound long)
|
||||
Ok foundArg -> Ok foundArg
|
||||
Err NotFound -> Err (MissingRequiredArg long)
|
||||
Ok { val, newlyTaken } -> Ok { val, newlyTaken }
|
||||
|
||||
@Parser (Arg { long, short, help, type: Str } fn)
|
||||
|
||||
|
@ -342,13 +475,24 @@ i64 : _ -> Parser I64 # TODO: panics if parameter annotation given
|
|||
i64 = \{ long, short ? "", help ? "" } ->
|
||||
fn = \args ->
|
||||
when findOneArg long short args is
|
||||
Err NotFound -> Err (NotFound long)
|
||||
Ok foundArg ->
|
||||
Str.toI64 foundArg
|
||||
|> Result.mapErr \_ -> WrongType { arg: long, expected: I64 }
|
||||
Err NotFound -> Err (MissingRequiredArg long)
|
||||
Ok { val, newlyTaken } ->
|
||||
Str.toI64 val
|
||||
|> Result.mapErr (\_ -> WrongType { arg: long, expected: I64 })
|
||||
|> Result.map (\v -> { val: v, newlyTaken })
|
||||
|
||||
@Parser (Arg { long, short, help, type: I64 } fn)
|
||||
|
||||
## Parses a single positional argument as a string.
|
||||
positional : _ -> Parser Str
|
||||
positional = \{ name, help ? "" } ->
|
||||
fn = \args ->
|
||||
nextUnmarked args
|
||||
|> Result.mapErr (\OutOfBounds -> MissingPositionalArg name)
|
||||
|> Result.map (\{ val, index } -> { val, newlyTaken: Set.insert args.taken index })
|
||||
|
||||
@Parser (Positional { name, help } fn)
|
||||
|
||||
## Wraps a given parser as a subcommand parser.
|
||||
##
|
||||
## When parsing arguments, the subcommand name will be expected to be parsed
|
||||
|
@ -390,6 +534,13 @@ indentLevel = 4
|
|||
|
||||
mapNonEmptyStr = \s, f -> if Str.isEmpty s then s else f s
|
||||
|
||||
filterMap : List a, (a -> [Some b, None]) -> List b
|
||||
filterMap = \lst, transform ->
|
||||
List.walk lst [] \all, elem ->
|
||||
when transform elem is
|
||||
Some v -> List.append all v
|
||||
None -> all
|
||||
|
||||
# formatHelp : NamedParser a -> Str
|
||||
formatHelp = \@NamedParser { name, help, parser } ->
|
||||
fmtHelp =
|
||||
|
@ -397,40 +548,88 @@ formatHelp = \@NamedParser { name, help, parser } ->
|
|||
|
||||
cmdHelp = toHelp parser
|
||||
|
||||
fmtCmdHeading =
|
||||
when cmdHelp is
|
||||
SubCommands _ -> "COMMANDS:"
|
||||
Config _ -> "OPTIONS:"
|
||||
|
||||
fmtCmdHelp = formatCmdHelp indentLevel cmdHelp
|
||||
fmtCmdHelp = formatHelpHelp 0 cmdHelp
|
||||
|
||||
"""
|
||||
\(name)\(fmtHelp)
|
||||
|
||||
\(fmtCmdHeading)
|
||||
\(fmtCmdHelp)
|
||||
"""
|
||||
|
||||
# formatCmdHelp : Nat, Help -> Str <- TODO: layout-gen panics when the following annotation is applied!
|
||||
formatCmdHelp = \n, help ->
|
||||
when help is
|
||||
# formatHelpHelp : Nat, Help -> Str
|
||||
formatHelpHelp = \n, cmdHelp ->
|
||||
indented = indent n
|
||||
|
||||
when cmdHelp is
|
||||
SubCommands cmds ->
|
||||
fmtCmdHelp =
|
||||
Str.joinWith
|
||||
(List.map cmds \subCmd -> formatSubCommand n subCmd)
|
||||
(List.map cmds \subCmd -> formatSubCommand (n + indentLevel) subCmd)
|
||||
"\n\n"
|
||||
|
||||
"""
|
||||
|
||||
\(indented)COMMANDS:
|
||||
\(fmtCmdHelp)
|
||||
"""
|
||||
|
||||
Config configs ->
|
||||
Str.joinWith (List.map configs \c -> formatConfig n c) "\n"
|
||||
argConfigs =
|
||||
filterMap
|
||||
configs
|
||||
(\config ->
|
||||
when config is
|
||||
Arg c -> Some c
|
||||
_ -> None)
|
||||
|
||||
positionalConfigs =
|
||||
filterMap
|
||||
configs
|
||||
(\config ->
|
||||
when config is
|
||||
Positional c -> Some c
|
||||
_ -> None)
|
||||
|
||||
fmtArgsHelp =
|
||||
if List.isEmpty argConfigs then
|
||||
""
|
||||
else
|
||||
helpStr =
|
||||
argConfigs
|
||||
|> List.map (\c -> formatArgConfig (n + indentLevel) c)
|
||||
|> Str.joinWith "\n"
|
||||
|
||||
"""
|
||||
|
||||
\(indented)OPTIONS:
|
||||
\(helpStr)
|
||||
"""
|
||||
|
||||
fmtPositionalsHelp =
|
||||
if List.isEmpty positionalConfigs then
|
||||
""
|
||||
else
|
||||
helpStr =
|
||||
positionalConfigs
|
||||
|> List.map (\c -> formatPositionalConfig (n + indentLevel) c)
|
||||
|> Str.joinWith "\n"
|
||||
|
||||
"""
|
||||
|
||||
\(indented)POSITIONAL ARGUMENTS:
|
||||
\(helpStr)
|
||||
"""
|
||||
|
||||
Str.concat fmtArgsHelp fmtPositionalsHelp
|
||||
|
||||
formatSubCommand = \n, { name, help } ->
|
||||
indented = indent n
|
||||
|
||||
fmtHelp = formatCmdHelp (n + indentLevel) help
|
||||
fmtHelp = formatHelpHelp (n + indentLevel) help
|
||||
|
||||
"\(indented)\(name)\n\(fmtHelp)"
|
||||
"\(indented)\(name)\(fmtHelp)"
|
||||
|
||||
formatConfig : Nat, Config -> Str
|
||||
formatConfig = \n, { long, short, help, type } ->
|
||||
formatArgConfig : Nat, ArgConfig -> Str
|
||||
formatArgConfig = \n, { long, short, help, type } ->
|
||||
indented = indent n
|
||||
|
||||
formattedShort =
|
||||
|
@ -443,6 +642,15 @@ formatConfig = \n, { long, short, help, type } ->
|
|||
|
||||
"\(indented)--\(long)\(formattedShort)\(formattedHelp) (\(formattedType))"
|
||||
|
||||
formatPositionalConfig : Nat, PositionalConfig -> Str
|
||||
formatPositionalConfig = \n, { name, help } ->
|
||||
indented = indent n
|
||||
|
||||
formattedHelp =
|
||||
mapNonEmptyStr help \h -> " \(h)"
|
||||
|
||||
"\(indented)\(name)\(formattedHelp)"
|
||||
|
||||
formatType : Type -> Str
|
||||
formatType = \type ->
|
||||
when type is
|
||||
|
@ -461,6 +669,9 @@ formatError = \err ->
|
|||
MissingRequiredArg arg ->
|
||||
"Argument `--\(arg)` is required but was not provided!"
|
||||
|
||||
MissingPositionalArg arg ->
|
||||
"A positional argument for `\(arg)` is required but was not provided!"
|
||||
|
||||
WrongType { arg, expected } ->
|
||||
formattedType = formatType expected
|
||||
|
||||
|
@ -507,107 +718,109 @@ formatError = \err ->
|
|||
## ```
|
||||
withParser = \arg1, arg2 -> andMap arg2 arg1
|
||||
|
||||
mark = \args -> { args, taken: Set.empty }
|
||||
|
||||
# bool undashed long argument is missing
|
||||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
parseHelp parser ["foo"] == Err (MissingRequiredArg "foo")
|
||||
parseHelp parser (mark ["foo"]) == Err (MissingRequiredArg "foo")
|
||||
|
||||
# bool dashed long argument without value is missing
|
||||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo"] == Err (MissingRequiredArg "foo")
|
||||
parseHelp parser (mark ["--foo"]) == Err (MissingRequiredArg "foo")
|
||||
|
||||
# bool dashed long argument with value is determined true
|
||||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo", "true"] == Ok Bool.true
|
||||
parseHelp parser (mark ["--foo", "true"]) == Ok Bool.true
|
||||
|
||||
# bool dashed long argument with value is determined false
|
||||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo", "false"] == Ok Bool.false
|
||||
parseHelp parser (mark ["--foo", "false"]) == Ok Bool.false
|
||||
|
||||
# bool dashed long argument with value is determined wrong type
|
||||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo", "not-a-bool"] == Err (WrongType { arg: "foo", expected: Bool })
|
||||
parseHelp parser (mark ["--foo", "not-a-bool"]) == Err (WrongType { arg: "foo", expected: Bool })
|
||||
|
||||
# bool dashed short argument with value is determined true
|
||||
expect
|
||||
parser = bool { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F", "true"] == Ok Bool.true
|
||||
parseHelp parser (mark ["-F", "true"]) == Ok Bool.true
|
||||
|
||||
# bool dashed short argument with value is determined false
|
||||
expect
|
||||
parser = bool { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F", "false"] == Ok Bool.false
|
||||
parseHelp parser (mark ["-F", "false"]) == Ok Bool.false
|
||||
|
||||
# bool dashed short argument with value is determined wrong type
|
||||
expect
|
||||
parser = bool { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F", "not-a-bool"] == Err (WrongType { arg: "foo", expected: Bool })
|
||||
parseHelp parser (mark ["-F", "not-a-bool"]) == Err (WrongType { arg: "foo", expected: Bool })
|
||||
|
||||
# string dashed long argument without value is missing
|
||||
expect
|
||||
parser = str { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo"] == Err (MissingRequiredArg "foo")
|
||||
parseHelp parser (mark ["--foo"]) == Err (MissingRequiredArg "foo")
|
||||
|
||||
# string dashed long argument with value is determined
|
||||
expect
|
||||
parser = str { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo", "itsme"] == Ok "itsme"
|
||||
parseHelp parser (mark ["--foo", "itsme"]) == Ok "itsme"
|
||||
|
||||
# string dashed short argument without value is missing
|
||||
expect
|
||||
parser = str { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F"] == Err (MissingRequiredArg "foo")
|
||||
parseHelp parser (mark ["-F"]) == Err (MissingRequiredArg "foo")
|
||||
|
||||
# string dashed short argument with value is determined
|
||||
expect
|
||||
parser = str { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F", "itsme"] == Ok "itsme"
|
||||
parseHelp parser (mark ["-F", "itsme"]) == Ok "itsme"
|
||||
|
||||
# i64 dashed long argument without value is missing
|
||||
expect
|
||||
parser = i64 { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo"] == Err (MissingRequiredArg "foo")
|
||||
parseHelp parser (mark ["--foo"]) == Err (MissingRequiredArg "foo")
|
||||
|
||||
# i64 dashed long argument with value is determined positive
|
||||
expect
|
||||
parser = i64 { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo", "1234"] == Ok 1234
|
||||
parseHelp parser (mark ["--foo", "1234"]) == Ok 1234
|
||||
|
||||
# i64 dashed long argument with value is determined negative
|
||||
expect
|
||||
parser = i64 { long: "foo" }
|
||||
|
||||
parseHelp parser ["--foo", "-1234"] == Ok -1234
|
||||
parseHelp parser (mark ["--foo", "-1234"]) == Ok -1234
|
||||
|
||||
# i64 dashed short argument without value is missing
|
||||
expect
|
||||
parser = i64 { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F"] == Err (MissingRequiredArg "foo")
|
||||
parseHelp parser (mark ["-F"]) == Err (MissingRequiredArg "foo")
|
||||
|
||||
# i64 dashed short argument with value is determined
|
||||
expect
|
||||
parser = i64 { long: "foo", short: "F" }
|
||||
|
||||
parseHelp parser ["-F", "1234"] == Ok 1234
|
||||
parseHelp parser (mark ["-F", "1234"]) == Ok 1234
|
||||
|
||||
# two string parsers complete cases
|
||||
expect
|
||||
|
@ -622,7 +835,7 @@ expect
|
|||
["--foo", "true", "--bar", "baz", "--other", "something"],
|
||||
]
|
||||
|
||||
List.all cases \args -> parseHelp parser args == Ok "foo: true bar: baz"
|
||||
List.all cases \args -> parseHelp parser (mark args) == Ok "foo: true bar: baz"
|
||||
|
||||
# one argument is missing out of multiple
|
||||
expect
|
||||
|
@ -633,8 +846,8 @@ expect
|
|||
|
||||
List.all
|
||||
[
|
||||
parseHelp parser ["--foo", "zaz"] == Err (MissingRequiredArg "bar"),
|
||||
parseHelp parser ["--bar", "zaz"] == Err (MissingRequiredArg "foo"),
|
||||
parseHelp parser (mark ["--foo", "zaz"]) == Err (MissingRequiredArg "bar"),
|
||||
parseHelp parser (mark ["--bar", "zaz"]) == Err (MissingRequiredArg "foo"),
|
||||
]
|
||||
(\b -> b)
|
||||
|
||||
|
@ -648,16 +861,16 @@ expect
|
|||
|
||||
toHelp parser
|
||||
== Config [
|
||||
{ long: "foo", short: "", help: "the foo flag", type: Str },
|
||||
{ long: "bar", short: "B", help: "", type: Str },
|
||||
{ long: "bool", short: "", help: "", type: Bool },
|
||||
Arg { long: "foo", short: "", help: "the foo flag", type: Str },
|
||||
Arg { long: "bar", short: "B", help: "", type: Str },
|
||||
Arg { long: "bool", short: "", help: "", type: Bool },
|
||||
]
|
||||
|
||||
# format argument is missing
|
||||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
when parseHelp parser ["foo"] is
|
||||
when parseHelp parser (mark ["foo"]) is
|
||||
Ok _ -> Bool.false
|
||||
Err e ->
|
||||
err = formatError e
|
||||
|
@ -668,7 +881,7 @@ expect
|
|||
expect
|
||||
parser = bool { long: "foo" }
|
||||
|
||||
when parseHelp parser ["--foo", "12"] is
|
||||
when parseHelp parser (mark ["--foo", "12"]) is
|
||||
Ok _ -> Bool.false
|
||||
Err e ->
|
||||
err = formatError e
|
||||
|
@ -719,10 +932,12 @@ expect
|
|||
|
||||
COMMANDS:
|
||||
login
|
||||
OPTIONS:
|
||||
--user (string)
|
||||
--pw (string)
|
||||
|
||||
publish
|
||||
OPTIONS:
|
||||
--file (string)
|
||||
--url (string)
|
||||
"""
|
||||
|
@ -741,7 +956,6 @@ expect
|
|||
|
||||
COMMANDS:
|
||||
login
|
||||
|
||||
"""
|
||||
|
||||
# subcommand parser
|
||||
|
@ -786,7 +1000,7 @@ expect
|
|||
parser =
|
||||
choice [subCommand (succeed "") "auth", subCommand (succeed "") "publish"]
|
||||
|
||||
when parseHelp parser [] is
|
||||
when parseHelp parser (mark []) is
|
||||
Ok _ -> Bool.true
|
||||
Err e ->
|
||||
err = formatError e
|
||||
|
@ -804,7 +1018,7 @@ expect
|
|||
parser =
|
||||
choice [subCommand (succeed "") "auth", subCommand (succeed "") "publish"]
|
||||
|
||||
when parseHelp parser ["logs"] is
|
||||
when parseHelp parser (mark ["logs"]) is
|
||||
Ok _ -> Bool.true
|
||||
Err e ->
|
||||
err = formatError e
|
||||
|
@ -816,3 +1030,38 @@ expect
|
|||
The available subcommands are:
|
||||
\t"auth", "publish"
|
||||
"""
|
||||
|
||||
# parse positional argument
|
||||
expect
|
||||
parser = positional { name: "foo" }
|
||||
|
||||
parseHelp parser (mark ["myArg"]) == Ok "myArg"
|
||||
|
||||
# parse positional argument with argument flag
|
||||
expect
|
||||
parser =
|
||||
succeed (\foo -> \bar -> "foo: \(foo), bar: \(bar)")
|
||||
|> withParser (str { long: "foo" })
|
||||
|> withParser (positional { name: "bar" })
|
||||
|
||||
cases = [
|
||||
["--foo", "true", "baz"],
|
||||
["baz", "--foo", "true"],
|
||||
]
|
||||
|
||||
List.all cases \args -> parseHelp parser (mark args) == Ok "foo: true, bar: baz"
|
||||
|
||||
# parse positional argument with subcommand
|
||||
expect
|
||||
parser = choice [
|
||||
positional { name: "bar" }
|
||||
|> subCommand "hello",
|
||||
]
|
||||
|
||||
parseHelp parser (mark ["hello", "foo"]) == Ok "foo"
|
||||
|
||||
# missing positional argument
|
||||
expect
|
||||
parser = positional { name: "bar" }
|
||||
|
||||
parseHelp parser (mark []) == Err (MissingPositionalArg "bar")
|
||||
|
|
|
@ -12,6 +12,6 @@ update : Model, Event -> Model
|
|||
update = \model, _ -> model
|
||||
|
||||
render : Model -> List Elem
|
||||
render = \model -> [Text model.text]
|
||||
render = \model -> [Text { text: model.text, top: 0, left: 0, size: 40, color: { r: 1, g: 1, b: 1, a: 1 } }]
|
||||
|
||||
program = { init, update, render }
|
||||
|
|
|
@ -6,8 +6,8 @@ Rgba : { r : F32, g : F32, b : F32, a : F32 }
|
|||
|
||||
Bounds : { height : F32, width : F32 }
|
||||
|
||||
Elem : [Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text Str]
|
||||
Elem : [Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text { text : Str, color : Rgba, left : F32, top : F32, size : F32 }]
|
||||
|
||||
KeyCode : [Left, Right, Other]
|
||||
KeyCode : [Left, Right, Other, Up, Down]
|
||||
|
||||
Event : [Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128]
|
||||
|
|
|
@ -455,7 +455,7 @@ fn to_drawable(
|
|||
wgpu_glyph::HorizontalAlign::Left
|
||||
});
|
||||
|
||||
let section = owned_section_from_str(text.as_str(), bounds, layout);
|
||||
let section = owned_section_from_str(text.text.as_str(),text.color, text.size, bounds, layout);
|
||||
|
||||
// Calculate the bounds and offset by measuring glyphs
|
||||
let text_bounds;
|
||||
|
@ -481,7 +481,7 @@ fn to_drawable(
|
|||
}
|
||||
|
||||
let drawable = Drawable {
|
||||
pos: (0.0, 0.0).into(), // TODO store the pos in Text and read it here
|
||||
pos: (text.left, text.top).into(),
|
||||
bounds: text_bounds,
|
||||
content: DrawableContent::Text(section, offset),
|
||||
};
|
||||
|
@ -493,13 +493,11 @@ fn to_drawable(
|
|||
|
||||
fn owned_section_from_str(
|
||||
string: &str,
|
||||
color: Rgba,
|
||||
size: f32,
|
||||
bounds: Bounds,
|
||||
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
) -> OwnedSection {
|
||||
// TODO don't hardcode any of this!
|
||||
let color = Rgba::WHITE;
|
||||
let size: f32 = 40.0;
|
||||
|
||||
OwnedSection {
|
||||
bounds: (bounds.width, bounds.height),
|
||||
layout,
|
||||
|
|
|
@ -142,9 +142,11 @@ impl RocEvent {
|
|||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocKeyCode {
|
||||
Left = 0,
|
||||
Down = 0,
|
||||
Left,
|
||||
Other,
|
||||
Right,
|
||||
Up,
|
||||
}
|
||||
|
||||
impl From<VirtualKeyCode> for RocKeyCode {
|
||||
|
@ -154,6 +156,8 @@ impl From<VirtualKeyCode> for RocKeyCode {
|
|||
match keycode {
|
||||
Left => RocKeyCode::Left,
|
||||
Right => RocKeyCode::Right,
|
||||
Up => RocKeyCode::Up,
|
||||
Down => RocKeyCode::Down,
|
||||
_ => RocKeyCode::Other,
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +214,7 @@ pub struct ElemId(*const RocElemEntry);
|
|||
#[repr(C)]
|
||||
pub union RocElemEntry {
|
||||
pub rect: ManuallyDrop<RocRect>,
|
||||
pub text: ManuallyDrop<RocStr>,
|
||||
pub text: ManuallyDrop<RocText>,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
|
@ -284,6 +288,16 @@ pub struct RocRect {
|
|||
pub width: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RocText {
|
||||
pub text: RocStr,
|
||||
pub color: Rgba,
|
||||
pub left: f32,
|
||||
pub size: f32,
|
||||
pub top: f32,
|
||||
}
|
||||
|
||||
impl Clone for RocElem {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
|
|
|
@ -212,7 +212,7 @@ function updateHistoryEntry(index, ok, outputText) {
|
|||
outputElem.classList.add("output");
|
||||
outputElem.classList.add(ok ? "output-ok" : "output-error");
|
||||
|
||||
const historyItem = repl.elemHistory.childNodes[index];
|
||||
const historyItem = repl.elemHistory.children[index];
|
||||
historyItem.appendChild(outputElem);
|
||||
|
||||
repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue