Merge branch 'main' of github.com:roc-lang/roc into wasm_interp_test_gen

This commit is contained in:
Brian Carroll 2022-12-16 14:50:09 +00:00
commit d389601035
No known key found for this signature in database
GPG Key ID: 5C7B2EC4101703C0
61 changed files with 1506 additions and 537 deletions

View File

@ -14,16 +14,15 @@ jobs:
- name: write version to file
run: ./ci/write_version.sh
# build has to be done before tests #2572
- name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: execute rust tests
run: cargo test --release --locked -- --skip opaque_wrap_function --skip bool_list_literal --skip platform_switching_swift --skip swift_ui
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV

1
Cargo.lock generated
View File

@ -3700,6 +3700,7 @@ dependencies = [
"roc_serialize",
"roc_types",
"static_assertions",
"ven_pretty",
]
[[package]]

View File

@ -39,6 +39,8 @@ target-all = [
"target-wasm32"
]
sanitizers = ["roc_build/sanitizers"]
[dependencies]
roc_collections = { path = "../compiler/collections" }

View File

@ -186,7 +186,9 @@ pub fn build_file<'a>(
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if is_prebuilt {
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_prebuilt {
if !preprocessed_host_path.exists() {
if prebuilt_requested {
eprintln!(
@ -378,10 +380,11 @@ pub fn build_file<'a>(
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
app_o_file.to_str().unwrap(),
];
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
inputs.push(host_input_path.as_path().to_str().unwrap());
}
let builtins_host_tempfile = {
#[cfg(unix)]

View File

@ -568,7 +568,7 @@ mod cli_run {
r#"
This expectation failed:
14 expect x != x
18 expect x != x
^^^^^^
When it failed, these variables had these values:
@ -576,8 +576,11 @@ mod cli_run {
x : Num *
x = 42
[<ignored for tests> 15:9] 42
[<ignored for tests> 16:9] "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests> 19:9] 42
[<ignored for tests> 20:9] "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests> 13:9] "abc"
[<ignored for tests> 13:9] 10
[<ignored for tests> 13:9] A (B C)
Program finished!
"#
),

View File

@ -4,3 +4,4 @@ libapp.so
dynhost
preprocessedhost
metadata
expects

View File

@ -9,9 +9,17 @@ expect
a == b
polyDbg = \x ->
dbg x
x
main =
x = 42
expect x != x
dbg x
dbg "Fjoer en ferdjer frieten oan dyn geve lea"
"Program finished!\n"
r = {x : polyDbg "abc", y: polyDbg 10u8, z : polyDbg (A (B C))}
when r is
_ -> "Program finished!\n"

View File

@ -1287,6 +1287,14 @@ fn lowlevel_spec<'a>(
builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code])
}
Dbg => {
let arguments = [env.symbols[&arguments[0]]];
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
_other => {
// println!("missing {:?}", _other);
// TODO overly pessimstic

View File

@ -47,3 +47,7 @@ target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"]
target-wasm32 = []
# This is used to enable fuzzing and sanitizers.
# Example use is describe here: https://github.com/bhansconnect/roc-fuzz
sanitizers = []

View File

@ -1248,14 +1248,17 @@ fn link_macos(
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let (link_type_arg, output_path) = match link_type {
LinkType::Executable => ("-execute", output_path),
let (link_type_args, output_path) = match link_type {
LinkType::Executable => (vec!["-execute"], output_path),
LinkType::Dylib => {
let mut output_path = output_path;
output_path.set_extension("dylib");
("-dylib", output_path)
(
vec!["-dylib", "-undefined", "dynamic_lookup", "-no_fixup_chains"],
output_path,
)
}
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
};
@ -1272,13 +1275,13 @@ fn link_macos(
// The `-l` flags should go after the `.o` arguments
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&link_type_args)
.args([
// NOTE: we don't do --gc-sections on macOS because the default
// macOS linker doesn't support it, but it's a performance
// optimization, so if we ever switch to a different linker,
// we'd like to re-enable it on macOS!
// "--gc-sections",
link_type_arg,
"-arch",
&arch,
"-macos_version_min",

View File

@ -237,15 +237,94 @@ fn gen_from_mono_module_llvm<'a>(
// annotate the LLVM IR output with debug info
// so errors are reported with the line number of the LLVM source
let memory_buffer = if emit_debug_info {
let memory_buffer = if cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok() {
let dir = tempfile::tempdir().unwrap();
let dir = dir.into_path();
let app_ll_file = dir.join("app.ll");
let app_bc_file = dir.join("app.bc");
let app_o_file = dir.join("app.o");
// write the ll code to a file, so we can modify it
module.print_to_file(&app_ll_file).unwrap();
// Apply coverage passes.
// Note, this is specifically tailored for `cargo afl` and afl++.
// It most likely will not work with other fuzzer setups without modification.
let mut passes = vec![];
let mut extra_args = vec![];
let mut unrecognized = vec![];
for sanitizer in std::env::var("ROC_SANITIZERS")
.unwrap()
.split(',')
.map(|x| x.trim())
{
match sanitizer {
"address" => passes.push("asan-module"),
"memory" => passes.push("msan-module"),
"thread" => passes.push("tsan-module"),
"fuzzer" => {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=3",
"-sanitizer-coverage-prune-blocks=0",
"-sanitizer-coverage-trace-pc-guard",
// This can be used instead of the line above to enable working with `cargo fuzz` and libFuzzer.
// "-sanitizer-coverage-inline-8bit-counters",
]);
}
x => unrecognized.push(x.to_owned()),
}
}
if !unrecognized.is_empty() {
let out = unrecognized
.iter()
.map(|x| format!("{:?}", x))
.collect::<Vec<String>>()
.join(", ");
eprintln!("Unrecognized sanitizer: {}\nSupported options are \"address\", \"memory\", \"thread\", and \"fuzzer\"", out);
}
use std::process::Command;
let mut opt = Command::new("opt");
opt.args([
app_ll_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.args(extra_args);
if !passes.is_empty() {
opt.arg(format!("-passes={}", passes.join(",")));
}
let opt = opt.output().unwrap();
assert!(opt.stderr.is_empty(), "{:#?}", opt);
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let bc_to_object = Command::new("llc")
.args(&[
"-relocation-model=pic",
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(bc_to_object.status.success(), "{:#?}", bc_to_object);
MemoryBuffer::create_from_file(&app_o_file).expect("memory buffer creation works")
} else if emit_debug_info {
module.strip_debug_info();
let mut app_ll_dbg_file = PathBuf::from(roc_file_path);
app_ll_dbg_file.set_extension("dbg.ll");
let mut app_bc_file = PathBuf::from(roc_file_path);
app_bc_file.set_extension("bc");
let mut app_o_file = PathBuf::from(roc_file_path);
app_o_file.set_extension("o");
@ -277,33 +356,23 @@ fn gen_from_mono_module_llvm<'a>(
| Architecture::X86_32(_)
| Architecture::Aarch64(_)
| Architecture::Wasm32 => {
let ll_to_bc = Command::new("llvm-as")
.args([
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(ll_to_bc.stderr.is_empty(), "{:#?}", ll_to_bc);
let llc_args = &[
"-relocation-model=pic",
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
];
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let bc_to_object = Command::new("llc").args(llc_args).output().unwrap();
let ll_to_object = Command::new("llc")
.args(&[
"-relocation-model=pic",
"-filetype=obj",
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(bc_to_object.stderr.is_empty(), "{:#?}", bc_to_object);
assert!(ll_to_object.stderr.is_empty(), "{:#?}", ll_to_object);
}
_ => unreachable!(),
}

View File

@ -73,7 +73,6 @@ pub enum DecWidth {
pub enum FloatWidth {
F32,
F64,
F128,
}
impl FloatWidth {
@ -86,7 +85,6 @@ impl FloatWidth {
match self {
F32 => 4,
F64 => 8,
F128 => 16,
}
}
@ -99,7 +97,7 @@ impl FloatWidth {
// the compiler is targeting (e.g. what the Roc code will be compiled to).
match self {
F32 => 4,
F64 | F128 => match target_info.architecture {
F64 => match target_info.architecture {
X86_64 | Aarch64 | Wasm32 => 8,
X86_32 | Aarch32 => 4,
},
@ -225,7 +223,6 @@ impl Index<FloatWidth> for IntrinsicName {
match index {
FloatWidth::F32 => self.options[1],
FloatWidth::F64 => self.options[2],
FloatWidth::F128 => self.options[3],
}
}
}
@ -256,7 +253,6 @@ macro_rules! float_intrinsic {
output.options[1] = concat!($name, ".f32");
output.options[2] = concat!($name, ".f64");
output.options[3] = concat!($name, ".f128");
output
}};

View File

@ -21,6 +21,8 @@ bumpalo.workspace = true
static_assertions.workspace = true
bitvec.workspace = true
ven_pretty = { path = "../../vendor/pretty" }
[dev-dependencies]
pretty_assertions.workspace = true
indoc.workspace = true

View File

@ -0,0 +1,5 @@
mod pretty_print;
pub use pretty_print::pretty_print_declarations;
pub use pretty_print::pretty_print_def;
pub use pretty_print::Ctx as PPCtx;

View File

@ -1,16 +1,50 @@
//! Pretty-prints the canonical AST back to check our work - do things look reasonable?
use roc_can::def::Def;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch};
use roc_can::pattern::{Pattern, RecordDestruct};
use crate::def::Def;
use crate::expr::Expr::{self, *};
use crate::expr::{
ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData, WhenBranch,
};
use crate::pattern::{Pattern, RecordDestruct};
use roc_module::symbol::Interns;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use ven_pretty::{Arena, DocAllocator, DocBuilder};
pub struct Ctx<'a> {
pub home: ModuleId,
pub interns: &'a Interns,
pub print_lambda_names: bool,
}
pub fn pretty_print_declarations(c: &Ctx, declarations: &Declarations) -> String {
let f = Arena::new();
let mut defs = Vec::with_capacity(declarations.len());
for (index, tag) in declarations.iter_bottom_up() {
let symbol = declarations.symbols[index].value;
let body = &declarations.expressions[index];
let def = match tag {
DeclarationTag::Value => def_symbol_help(c, &f, symbol, &body.value),
DeclarationTag::Function(f_index)
| DeclarationTag::Recursive(f_index)
| DeclarationTag::TailRecursive(f_index) => {
let function_def = &declarations.function_bodies[f_index.index()].value;
toplevel_function(c, &f, symbol, function_def, &body.value)
}
DeclarationTag::Expectation => todo!(),
DeclarationTag::ExpectationFx => todo!(),
DeclarationTag::Destructure(_) => todo!(),
DeclarationTag::MutualRecursion { .. } => todo!(),
};
defs.push(def);
}
f.intersperse(defs, f.hardline().append(f.hardline()))
.1
.pretty(80)
.to_string()
}
pub fn pretty_print_def(c: &Ctx, d: &Def) -> String {
@ -40,10 +74,58 @@ fn def<'a>(c: &Ctx, f: &'a Arena<'a>, d: &'a Def) -> DocBuilder<'a, Arena<'a>> {
annotation: _,
} = d;
pattern(c, PPrec::Free, f, &loc_pattern.value)
def_help(c, f, &loc_pattern.value, &loc_expr.value)
}
fn def_symbol_help<'a>(
c: &Ctx,
f: &'a Arena<'a>,
sym: Symbol,
body: &'a Expr,
) -> DocBuilder<'a, Arena<'a>> {
pp_sym(c, f, sym)
.append(f.text(" ="))
.append(f.line())
.append(expr(c, EPrec::Free, f, &loc_expr.value))
.append(expr(c, EPrec::Free, f, body))
.nest(2)
.group()
}
fn def_help<'a>(
c: &Ctx,
f: &'a Arena<'a>,
pat: &'a Pattern,
body: &'a Expr,
) -> DocBuilder<'a, Arena<'a>> {
pattern(c, PPrec::Free, f, pat)
.append(f.text(" ="))
.append(f.line())
.append(expr(c, EPrec::Free, f, body))
.nest(2)
.group()
}
fn toplevel_function<'a>(
c: &Ctx,
f: &'a Arena<'a>,
sym: Symbol,
function_def: &'a FunctionDef,
body: &'a Expr,
) -> DocBuilder<'a, Arena<'a>> {
let FunctionDef { arguments, .. } = function_def;
let args = arguments
.iter()
.map(|arg| pattern(c, PPrec::Free, f, &arg.2.value));
pp_sym(c, f, sym)
.append(f.text(" ="))
.append(f.line())
.append(f.text("\\"))
.append(f.intersperse(args, f.text(", ")))
.append(f.text("->"))
.append(f.line())
.append(expr(c, EPrec::Free, f, body))
.nest(2)
.group()
}
@ -87,11 +169,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
.append("]")
.group(),
),
Var(sym, _) | AbilityMember(sym, _, _) => f.text(format!(
"{}.{}",
sym.module_string(c.interns),
sym.as_str(c.interns),
)),
Var(sym, _) | AbilityMember(sym, _, _) => pp_sym(c, f, *sym),
When {
loc_cond, branches, ..
} => maybe_paren!(
@ -184,6 +262,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
Closure(ClosureData {
arguments,
loc_body,
name,
..
}) => f
.text("\\")
@ -195,7 +274,13 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
f.text(", "),
),
)
.append(f.text(" ->"))
.append(if c.print_lambda_names {
f.text(" -[")
.append(pp_sym(c, f, *name))
.append(f.text("]->"))
} else {
f.text(" ->")
})
.append(f.line())
.append(expr(c, Free, f, &loc_body.value))
.nest(2)
@ -290,6 +375,18 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
}
}
fn pp_sym<'a>(c: &Ctx, f: &'a Arena<'a>, sym: Symbol) -> DocBuilder<'a, Arena<'a>> {
if sym.module_id() == c.home {
f.text(sym.as_str(c.interns).to_owned())
} else {
f.text(format!(
"{}.{}",
sym.module_string(c.interns),
sym.as_str(c.interns),
))
}
}
fn branch<'a>(c: &Ctx, f: &'a Arena<'a>, b: &'a WhenBranch) -> DocBuilder<'a, Arena<'a>> {
let WhenBranch {
patterns,
@ -333,11 +430,7 @@ fn pattern<'a>(
Identifier(sym)
| AbilityMemberSpecialization {
specializes: sym, ..
} => f.text(format!(
"{}.{}",
sym.module_string(c.interns),
sym.as_str(c.interns),
)),
} => pp_sym(c, f, *sym),
AppliedTag {
tag_name,
arguments,
@ -373,12 +466,12 @@ fn pattern<'a>(
f.intersperse(
destructs.iter().map(|l| &l.value).map(
|RecordDestruct { label, typ, .. }| match typ {
roc_can::pattern::DestructType::Required => f.text(label.as_str()),
roc_can::pattern::DestructType::Optional(_, e) => f
crate::pattern::DestructType::Required => f.text(label.as_str()),
crate::pattern::DestructType::Optional(_, e) => f
.text(label.as_str())
.append(f.text(" ? "))
.append(expr(c, EPrec::Free, f, &e.value)),
roc_can::pattern::DestructType::Guard(_, p) => f
crate::pattern::DestructType::Guard(_, p) => f
.text(label.as_str())
.append(f.text(": "))
.append(pattern(c, Free, f, &p.value)),

View File

@ -28,3 +28,5 @@ pub mod string;
pub mod traverse;
pub use derive::DERIVED_REGION;
pub mod debug;

View File

@ -705,7 +705,7 @@ pub fn constrain_expr(
expected,
);
constraints.exists_many([], [cond_con, continuation_con])
constraints.exists_many([*variable], [cond_con, continuation_con])
}
If {

View File

@ -709,7 +709,6 @@ fn float_with_precision<'a, 'ctx, 'env>(
match float_width {
FloatWidth::F64 => env.context.f64_type().const_float(value).into(),
FloatWidth::F32 => env.context.f32_type().const_float(value).into(),
FloatWidth::F128 => todo!("F128 is not implemented"),
}
}
@ -2586,7 +2585,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
condition: cond_symbol,
region,
lookups,
layouts: _,
variables,
remainder,
} => {
let bd = env.builder;
@ -2621,6 +2620,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
*cond_symbol,
*region,
lookups,
variables,
);
if let LlvmBackendMode::BinaryDev = env.mode {
@ -2655,7 +2655,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
condition: cond_symbol,
region,
lookups,
layouts: _,
variables,
remainder,
} => {
let bd = env.builder;
@ -2690,6 +2690,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
*cond_symbol,
*region,
lookups,
variables,
);
bd.build_unconditional_branch(then_block);
@ -3036,7 +3037,6 @@ fn build_switch_ir<'a, 'ctx, 'env>(
let int_type = match float_width {
FloatWidth::F32 => env.context.i32_type(),
FloatWidth::F64 => env.context.i64_type(),
FloatWidth::F128 => env.context.i128_type(),
};
builder

View File

@ -111,7 +111,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
use FloatWidth::*;
let name = match float_width {
F128 => "eq_f128",
F64 => "eq_f64",
F32 => "eq_f32",
};
@ -276,7 +275,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
use FloatWidth::*;
let name = match float_width {
F128 => "neq_f128",
F64 => "neq_f64",
F32 => "neq_f32",
};

View File

@ -202,7 +202,6 @@ pub fn float_type_from_float_width<'a, 'ctx, 'env>(
use FloatWidth::*;
match float_width {
F128 => todo!("F128 is not implemented"),
F64 => env.context.f64_type(),
F32 => env.context.f32_type(),
}

View File

@ -10,6 +10,7 @@ use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::ir::LookupType;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use roc_region::all::Region;
@ -143,6 +144,21 @@ pub(crate) fn notify_parent_dbg(env: &Env, shared_memory: &SharedMemoryPointer)
);
}
// Shape of expect frame:
//
// ===
// Fixed-size header
// ===
// /-- ptr_lookup_1 (ptr_size)
// | var_lookup_1 (u32)
// | ..
// | ptr_lookup_n (ptr_size)
// | var_lookup_n (u32)
// \-> lookup_val_1 (varsize)
// ..
// lookup_val_n (varsize)
//
#[allow(clippy::too_many_arguments)]
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
@ -151,6 +167,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
condition: Symbol,
region: Region,
lookups: &[Symbol],
lookup_variables: &[LookupType],
) {
let original_ptr = shared_memory.0;
@ -160,9 +177,11 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
let after_header = offset;
let space_for_offsets = env
.ptr_int()
.const_int((lookups.len() * env.target_info.ptr_size()) as _, false);
let space_for_offsets = env.ptr_int().const_int(
(lookups.len() * env.target_info.ptr_size() + lookups.len() * std::mem::size_of::<u32>())
as _,
false,
);
let mut lookup_starts = bumpalo::collections::Vec::with_capacity_in(lookups.len(), env.arena);
@ -203,14 +222,43 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
{
let mut offset = after_header;
for lookup_start in lookup_starts {
build_copy(env, original_ptr, offset, lookup_start.into());
for (lookup_start, lookup_var) in lookup_starts.into_iter().zip(lookup_variables) {
// Store the pointer to the value
{
build_copy(env, original_ptr, offset, lookup_start.into());
let ptr_width = env
.ptr_int()
.const_int(env.target_info.ptr_size() as _, false);
let ptr_width = env
.ptr_int()
.const_int(env.target_info.ptr_size() as _, false);
offset = env.builder.build_int_add(offset, ptr_width, "offset")
offset = env.builder.build_int_add(offset, ptr_width, "offset");
}
// Store the specialized variable of the value
{
let ptr = unsafe {
env.builder
.build_in_bounds_gep(original_ptr, &[offset], "at_current_offset")
};
let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::Generic);
let ptr = env
.builder
.build_pointer_cast(ptr, u32_ptr, "cast_ptr_type");
let var_value = env
.context
.i32_type()
.const_int(lookup_var.index() as _, false);
env.builder.build_store(ptr, var_value);
let var_size = env
.ptr_int()
.const_int(std::mem::size_of::<u32>() as _, false);
offset = env.builder.build_int_add(offset, var_size, "offset");
}
}
}

View File

@ -34,7 +34,6 @@ fn add_float_intrinsic<'ctx, F>(
check!(FloatWidth::F32, ctx.f32_type());
check!(FloatWidth::F64, ctx.f64_type());
// check!(IntWidth::F128, ctx.i128_type());
}
fn add_int_intrinsic<'ctx, F>(

View File

@ -11,7 +11,7 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_module::{low_level::LowLevel, symbol::Symbol};
use roc_mono::{
ir::HigherOrderLowLevel,
ir::{HigherOrderLowLevel, LookupType},
layout::{Builtin, LambdaSet, Layout, LayoutIds},
};
use roc_target::PtrWidth;
@ -1120,14 +1120,19 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
}
},
Dbg => {
// now what
arguments!(condition);
assert_eq!(args.len(), 2);
let condition = load_symbol(scope, &args[0]);
let dbg_spec_var_symbol = args[1];
if env.mode.runs_expects() {
let region = unsafe { std::mem::transmute::<_, roc_region::all::Region>(args[0]) };
let shared_memory = crate::llvm::expect::SharedMemoryPointer::get(env);
// HACK(dbg-spec-var): the specialized type variable is passed along as a fake symbol
let specialized_var =
unsafe { LookupType::from_index(dbg_spec_var_symbol.ident_id().index() as _) };
crate::llvm::expect::clone_to_shared_memory(
env,
scope,
@ -1136,6 +1141,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
args[0],
region,
&[args[0]],
&[specialized_var],
);
crate::llvm::expect::notify_parent_dbg(env, &shared_memory);
@ -2124,13 +2130,6 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
"f64_to_f32",
),
(FloatWidth::F64, FloatWidth::F64) => arg.into(),
(FloatWidth::F128, FloatWidth::F128) => arg.into(),
(FloatWidth::F128, _) => {
unimplemented!("I cannot handle F128 with Num.toFrac yet")
}
(_, FloatWidth::F128) => {
unimplemented!("I cannot handle F128 with Num.toFrac yet")
}
}
}
NumCeiling => {

View File

@ -21,7 +21,6 @@ pub enum StackMemoryFormat {
/// Record, Str, List, etc.
DataStructure,
Int128,
Float128,
Decimal,
}
@ -71,11 +70,6 @@ impl WasmLayout {
match float_width {
F32 => Self::Primitive(ValueType::F32, size),
F64 => Self::Primitive(ValueType::F64, size),
F128 => Self::StackMemory {
size,
alignment_bytes,
format: StackMemoryFormat::Float128,
},
}
}
@ -156,7 +150,7 @@ impl CallConv {
use ValueType::*;
match format {
Int128 | Float128 | Decimal => &[I64, I64],
Int128 | Decimal => &[I64, I64],
DataStructure => {
if size == 0 {
@ -191,7 +185,7 @@ impl CallConv {
use StackMemoryFormat::*;
match format {
Int128 | Float128 | Decimal => WriteToPointerArg,
Int128 | Decimal => WriteToPointerArg,
DataStructure => {
if size == 0 {

View File

@ -30,7 +30,6 @@ enum CodeGenNumType {
F32, // Supported in Wasm instruction set
F64, // Supported in Wasm instruction set
I128, // Bytes in memory, needs Zig builtins
F128, // Bytes in memory, needs Zig builtins
Decimal, // Bytes in memory, needs Zig builtins
}
@ -66,7 +65,6 @@ impl From<Layout<'_>> for CodeGenNumType {
Builtin::Float(float_width) => match float_width {
FloatWidth::F32 => F32,
FloatWidth::F64 => F64,
FloatWidth::F128 => F128,
},
Builtin::Decimal => Decimal,
_ => not_num_error(),
@ -91,7 +89,6 @@ impl From<StackMemoryFormat> for CodeGenNumType {
fn from(format: StackMemoryFormat) -> CodeGenNumType {
match format {
StackMemoryFormat::Int128 => CodeGenNumType::I128,
StackMemoryFormat::Float128 => CodeGenNumType::F128,
StackMemoryFormat::Decimal => CodeGenNumType::Decimal,
StackMemoryFormat::DataStructure => {
internal_error!("Tried to perform a Num low-level operation on a data structure")
@ -804,7 +801,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_add()
}
FloatWidth::F128 => todo!("Num.add for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_ADD_OR_PANIC)
@ -841,7 +837,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_add()
}
FloatWidth::F128 => todo!("Num.add for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
// TODO: don't panic
@ -897,7 +892,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_sub()
}
FloatWidth::F128 => todo!("Num.sub for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_SUB_OR_PANIC)
@ -934,7 +928,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_sub()
}
FloatWidth::F128 => todo!("Num.sub for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
// TODO: don't panic
@ -988,7 +981,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_mul()
}
FloatWidth::F128 => todo!("Num.mul for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_MUL_OR_PANIC)
@ -1024,7 +1016,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_mul()
}
FloatWidth::F128 => todo!("Num.mul for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
// TODO: don't panic
@ -1466,9 +1457,6 @@ impl<'a> LowLevelCall<'a> {
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
backend.code_builder.f64_sqrt()
}
Layout::Builtin(Builtin::Float(FloatWidth::F128)) => {
todo!("sqrt for f128")
}
_ => panic_ret_type(),
}
}
@ -2009,8 +1997,6 @@ impl<'a> LowLevelCall<'a> {
StackMemoryFormat::Int128 => Self::eq_num128_bytes(backend, locations),
StackMemoryFormat::Float128 => todo!("equality for f128"),
StackMemoryFormat::DataStructure => {
internal_error!("Data structure equality is handled elsewhere")
}
@ -2057,7 +2043,6 @@ impl<'a> LowLevelCall<'a> {
FloatWidth::F64 => {
self.load_args_and_call_zig(backend, &bitcode::STR_FROM_FLOAT[width]);
}
FloatWidth::F128 => todo!("F128 to Str"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_TO_STR)
@ -2095,27 +2080,13 @@ fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) {
}
}
}
StackMemory {
format, location, ..
} => {
let (local_id, offset) = location.local_and_offset(backend.storage.stack_frame_pointer);
StackMemory { format, .. } => {
match format {
// Integers and fixed-point numbers are always finite. Just return True.
StackMemoryFormat::Int128 | StackMemoryFormat::Decimal => {
backend.code_builder.i32_const(1)
}
// f128 is not supported anywhere else but it's easy to support it here, so why not...
StackMemoryFormat::Float128 => {
backend.code_builder.get_local(local_id);
backend.code_builder.i64_load(Align::Bytes4, offset + 8);
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
backend.code_builder.i64_and();
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
backend.code_builder.i64_ne();
}
StackMemoryFormat::DataStructure => {
internal_error!("Tried to perform NumIsFinite on a data structure")
}

View File

@ -252,7 +252,7 @@ impl<'a> Storage<'a> {
.extend_from_slice(CallConv::C.stack_memory_arg_types(size, format));
let location = match format {
Int128 | Float128 | Decimal => {
Int128 | Decimal => {
// passed as two i64's but stored in the stack frame
wide_number_args.push(local_index);
let loc =

View File

@ -138,7 +138,6 @@ struct ModuleCache<'a> {
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
late_specializations: MutMap<ModuleId, LateSpecializationsModule<'a>>,
external_specializations_requested: MutMap<ModuleId, Vec<ExternalSpecializations<'a>>>,
expectations: VecMap<ModuleId, Expectations>,
/// Various information
imports: MutMap<ModuleId, MutSet<ModuleId>>,
@ -215,7 +214,6 @@ impl Default for ModuleCache<'_> {
can_problems: Default::default(),
type_problems: Default::default(),
sources: Default::default(),
expectations: Default::default(),
}
}
}
@ -435,6 +433,7 @@ fn start_phase<'a>(
decls,
ident_ids,
abilities_store,
expectations,
} = typechecked;
let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena);
@ -451,8 +450,8 @@ fn start_phase<'a>(
let derived_module = SharedDerivedModule::clone(&state.derived_module);
let build_expects = matches!(state.exec_mode, ExecutionMode::Test)
&& state.module_cache.expectations.contains_key(&module_id);
let build_expects =
matches!(state.exec_mode, ExecutionMode::Test) && expectations.is_some();
BuildTask::BuildPendingSpecializations {
layout_cache,
@ -467,6 +466,7 @@ fn start_phase<'a>(
// TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(),
derived_module,
expectations,
build_expects,
}
}
@ -488,67 +488,90 @@ fn start_phase<'a>(
specializations_we_must_make.extend(derived_synth_specializations)
}
let (mut ident_ids, mut subs, mut procs_base, layout_cache, mut module_timing) =
if state.make_specializations_pass.current_pass() == 1
&& module_id == ModuleId::DERIVED_GEN
{
// This is the first time the derived module is introduced into the load
// graph. It has no abilities of its own or anything, just generate fresh
// information for it.
(
IdentIds::default(),
Subs::default(),
ProcsBase::default(),
LayoutCache::new(state.layout_interner.fork(), state.target_info),
ModuleTiming::new(Instant::now()),
)
} else if state.make_specializations_pass.current_pass() == 1 {
let found_specializations = state
.module_cache
.found_specializations
.remove(&module_id)
.unwrap();
let (
mut ident_ids,
mut subs,
expectations,
mut procs_base,
layout_cache,
mut module_timing,
) = if state.make_specializations_pass.current_pass() == 1
&& module_id == ModuleId::DERIVED_GEN
{
// This is the first time the derived module is introduced into the load
// graph. It has no abilities of its own or anything, just generate fresh
// information for it.
(
IdentIds::default(),
Subs::default(),
None, // no expectations for derived module
ProcsBase::default(),
LayoutCache::new(state.layout_interner.fork(), state.target_info),
ModuleTiming::new(Instant::now()),
)
} else if state.make_specializations_pass.current_pass() == 1 {
let found_specializations = state
.module_cache
.found_specializations
.remove(&module_id)
.unwrap();
let FoundSpecializationsModule {
ident_ids,
subs,
procs_base,
layout_cache,
module_timing,
abilities_store,
} = found_specializations;
let FoundSpecializationsModule {
ident_ids,
subs,
procs_base,
layout_cache,
module_timing,
abilities_store,
expectations,
} = found_specializations;
let our_exposed_types = state
.exposed_types
.get(&module_id)
.unwrap_or_else(|| {
internal_error!("Exposed types for {:?} missing", module_id)
})
.clone();
let our_exposed_types = state
.exposed_types
.get(&module_id)
.unwrap_or_else(|| {
internal_error!("Exposed types for {:?} missing", module_id)
})
.clone();
// Add our abilities to the world.
state.world_abilities.insert(
module_id,
abilities_store,
our_exposed_types.exposed_types_storage_subs,
);
// Add our abilities to the world.
state.world_abilities.insert(
module_id,
abilities_store,
our_exposed_types.exposed_types_storage_subs,
);
(ident_ids, subs, procs_base, layout_cache, module_timing)
} else {
let LateSpecializationsModule {
ident_ids,
subs,
module_timing,
layout_cache,
procs_base,
} = state
.module_cache
.late_specializations
.remove(&module_id)
.unwrap();
(
ident_ids,
subs,
expectations,
procs_base,
layout_cache,
module_timing,
)
} else {
let LateSpecializationsModule {
ident_ids,
subs,
expectations,
module_timing,
layout_cache,
procs_base,
} = state
.module_cache
.late_specializations
.remove(&module_id)
.unwrap();
(ident_ids, subs, procs_base, layout_cache, module_timing)
};
(
ident_ids,
subs,
expectations,
procs_base,
layout_cache,
module_timing,
)
};
if module_id == ModuleId::DERIVED_GEN {
load_derived_partial_procs(
@ -579,6 +602,7 @@ fn start_phase<'a>(
// TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(),
derived_module,
expectations,
}
}
}
@ -679,6 +703,7 @@ pub struct TypeCheckedModule<'a> {
pub decls: Declarations,
pub ident_ids: IdentIds,
pub abilities_store: AbilitiesStore,
pub expectations: Option<Expectations>,
}
#[derive(Debug)]
@ -689,6 +714,7 @@ struct FoundSpecializationsModule<'a> {
subs: Subs,
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
expectations: Option<Expectations>,
}
#[derive(Debug)]
@ -698,6 +724,7 @@ struct LateSpecializationsModule<'a> {
module_timing: ModuleTiming,
layout_cache: LayoutCache<'a>,
procs_base: ProcsBase<'a>,
expectations: Option<Expectations>,
}
#[derive(Debug, Default)]
@ -831,6 +858,7 @@ enum Msg<'a> {
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
toplevel_expects: ToplevelExpects,
expectations: Option<Expectations>,
},
MadeSpecializations {
module_id: ModuleId,
@ -842,6 +870,7 @@ enum Msg<'a> {
update_mode_ids: UpdateModeIds,
module_timing: ModuleTiming,
subs: Subs,
expectations: Option<Expectations>,
},
/// The task is to only typecheck AND monomorphize modules
@ -852,6 +881,7 @@ enum Msg<'a> {
/// DO NOT use the one on state; that is left in an empty state after specialization is complete!
layout_interner: STLayoutInterner<'a>,
exposed_to_host: ExposedToHost,
module_expectations: VecMap<ModuleId, Expectations>,
},
FailedToParse(FileError<'a, SyntaxError<'a>>),
@ -1147,6 +1177,7 @@ enum BuildTask<'a> {
exposed_by_module: ExposedByModule,
abilities_store: AbilitiesStore,
derived_module: SharedDerivedModule,
expectations: Option<Expectations>,
build_expects: bool,
},
MakeSpecializations {
@ -1160,6 +1191,7 @@ enum BuildTask<'a> {
exposed_by_module: ExposedByModule,
world_abilities: WorldAbilities,
derived_module: SharedDerivedModule,
expectations: Option<Expectations>,
},
}
@ -1717,6 +1749,7 @@ fn state_thread_step<'a>(
subs,
layout_interner,
exposed_to_host,
module_expectations,
} => {
// We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty());
@ -1727,6 +1760,7 @@ fn state_thread_step<'a>(
subs,
layout_interner,
exposed_to_host,
module_expectations,
)?;
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
@ -2608,22 +2642,19 @@ fn update<'a>(
.expect("root or this module is not yet known - that's a bug!")
};
if should_include_expects {
let opt_expectations = if should_include_expects {
let (path, _) = state.module_cache.sources.get(&module_id).unwrap();
let expectations = Expectations {
Some(Expectations {
expectations: loc_expects,
dbgs: loc_dbgs,
subs: solved_subs.clone().into_inner(),
path: path.to_owned(),
ident_ids: ident_ids.clone(),
};
state
.module_cache
.expectations
.insert(module_id, expectations);
}
})
} else {
None
};
let work = state.dependencies.notify(module_id, Phase::SolveTypes);
@ -2736,6 +2767,7 @@ fn update<'a>(
decls,
ident_ids,
abilities_store,
expectations: opt_expectations,
};
state
@ -2775,6 +2807,7 @@ fn update<'a>(
module_timing,
abilities_store,
toplevel_expects,
expectations,
} => {
log!("found specializations for {:?}", module_id);
@ -2797,6 +2830,7 @@ fn update<'a>(
subs,
module_timing,
abilities_store,
expectations,
};
state
@ -2822,6 +2856,7 @@ fn update<'a>(
external_specializations_requested,
module_timing,
layout_cache,
expectations,
..
} => {
debug_assert!(
@ -2843,6 +2878,7 @@ fn update<'a>(
subs,
layout_cache,
procs_base,
expectations,
},
);
@ -2900,6 +2936,9 @@ fn update<'a>(
);
}
let mut module_expectations =
VecMap::with_capacity(state.module_cache.module_names.len());
// Flush late-specialization module information to the top-level of the state
// where it will be visible to others, since we don't need late specialization
// anymore.
@ -2911,6 +2950,7 @@ fn update<'a>(
module_timing,
layout_cache: _layout_cache,
procs_base: _,
expectations,
},
) in state.module_cache.late_specializations.drain()
{
@ -2919,6 +2959,9 @@ fn update<'a>(
state.root_subs = Some(subs);
}
state.timings.insert(module_id, module_timing);
if let Some(expectations) = expectations {
module_expectations.insert(module_id, expectations);
}
#[cfg(debug_assertions)]
{
@ -2981,6 +3024,7 @@ fn update<'a>(
subs,
layout_interner,
exposed_to_host: state.exposed_to_host.clone(),
module_expectations,
})
.map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -3114,6 +3158,7 @@ fn finish_specialization<'a>(
subs: Subs,
layout_interner: STLayoutInterner<'a>,
exposed_to_host: ExposedToHost,
module_expectations: VecMap<ModuleId, Expectations>,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
if false {
println!(
@ -3154,7 +3199,6 @@ fn finish_specialization<'a>(
} = state;
let ModuleCache {
expectations,
type_problems,
can_problems,
sources,
@ -3245,7 +3289,7 @@ fn finish_specialization<'a>(
can_problems,
type_problems,
output_path,
expectations,
expectations: module_expectations,
exposed_to_host,
module_id: state.root_id,
subs,
@ -5230,6 +5274,7 @@ fn make_specializations<'a>(
world_abilities: WorldAbilities,
exposed_by_module: &ExposedByModule,
derived_module: SharedDerivedModule,
mut expectations: Option<Expectations>,
) -> Msg<'a> {
let make_specializations_start = Instant::now();
let mut update_mode_ids = UpdateModeIds::new();
@ -5237,6 +5282,7 @@ fn make_specializations<'a>(
let mut mono_env = roc_mono::ir::Env {
arena,
subs: &mut subs,
expectation_subs: expectations.as_mut().map(|e| &mut e.subs),
home,
ident_ids: &mut ident_ids,
target_info,
@ -5288,6 +5334,7 @@ fn make_specializations<'a>(
procedures,
update_mode_ids,
subs,
expectations,
external_specializations_requested,
module_timing,
}
@ -5307,6 +5354,7 @@ fn build_pending_specializations<'a>(
exposed_by_module: &ExposedByModule,
abilities_store: AbilitiesStore,
derived_module: SharedDerivedModule,
mut expectations: Option<Expectations>,
build_expects: bool,
) -> Msg<'a> {
let find_specializations_start = Instant::now();
@ -5327,6 +5375,7 @@ fn build_pending_specializations<'a>(
let mut mono_env = roc_mono::ir::Env {
arena,
subs: &mut subs,
expectation_subs: expectations.as_mut().map(|e| &mut e.subs),
home,
ident_ids: &mut ident_ids,
target_info,
@ -5716,6 +5765,7 @@ fn build_pending_specializations<'a>(
module_timing,
abilities_store,
toplevel_expects,
expectations,
}
}
@ -5757,6 +5807,8 @@ fn load_derived_partial_procs<'a>(
let mut mono_env = roc_mono::ir::Env {
arena,
subs,
// There are no derived expectations.
expectation_subs: None,
home,
ident_ids,
target_info,
@ -5916,6 +5968,7 @@ fn run_task<'a>(
abilities_store,
exposed_by_module,
derived_module,
expectations,
build_expects,
} => Ok(build_pending_specializations(
arena,
@ -5931,6 +5984,7 @@ fn run_task<'a>(
&exposed_by_module,
abilities_store,
derived_module,
expectations,
build_expects,
)),
MakeSpecializations {
@ -5944,6 +5998,7 @@ fn run_task<'a>(
world_abilities,
exposed_by_module,
derived_module,
expectations,
} => Ok(make_specializations(
arena,
module_id,
@ -5957,6 +6012,7 @@ fn run_task<'a>(
world_abilities,
&exposed_by_module,
derived_module,
expectations,
)),
}?;

View File

@ -594,6 +594,13 @@ impl IdentId {
pub const fn index(self) -> usize {
self.0 as usize
}
/// # Safety
///
/// The index is not guaranteed to know to exist.
pub unsafe fn from_index(index: u32) -> Self {
Self(index)
}
}
/// Stores a mapping between Ident and IdentId.

View File

@ -162,9 +162,15 @@ impl<'a> DeclarationToIndex<'a> {
}
}
}
let similar = self
.elements
.iter()
.filter_map(|((s, lay), _)| if *s == needle_symbol { Some(lay) } else { None })
.collect::<std::vec::Vec<_>>();
unreachable!(
"symbol/layout {:?} {:#?} combo must be in DeclarationToIndex",
needle_symbol, needle_layout
"symbol/layout {:?} {:#?} combo must be in DeclarationToIndex\nHowever {} similar layouts were found:\n{:#?}",
needle_symbol, needle_layout, similar.len(), similar
)
}
}
@ -942,7 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed, /* dbg-spec-var */ irrelevant]),
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")

View File

@ -309,14 +309,14 @@ impl<'a, 'r> Ctx<'a, 'r> {
condition,
region: _,
lookups,
layouts,
variables: _,
remainder,
}
| &Stmt::ExpectFx {
condition,
region: _,
lookups,
layouts,
variables: _,
remainder,
} => {
self.check_sym_layout(
@ -324,8 +324,8 @@ impl<'a, 'r> Ctx<'a, 'r> {
Layout::Builtin(Builtin::Bool),
UseKind::ExpectCond,
);
for (sym, lay) in lookups.iter().zip(layouts) {
self.check_sym_layout(*sym, *lay, UseKind::ExpectLookup);
for sym in lookups.iter() {
self.check_sym_exists(*sym);
}
self.check_stmt(remainder);
}

View File

@ -555,7 +555,13 @@ impl<'a, 'i> Context<'a, 'i> {
match &call_type {
LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let b = match op {
roc_module::low_level::LowLevel::Dbg => {
// NB(dbg-spec-var) second var is the Variable
self.add_dec_after_lowlevel(&arguments[..1], ps, b, b_live_vars)
}
_ => self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars),
};
let v = Expr::Call(crate::ir::Call {
call_type,
@ -1199,7 +1205,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition,
region,
lookups,
layouts,
variables,
} => {
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
@ -1207,7 +1213,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
});
@ -1223,7 +1229,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition,
region,
lookups,
layouts,
variables,
} => {
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
@ -1231,7 +1237,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
});

View File

@ -27,14 +27,14 @@ use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapsh
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_problem::can::{RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
use roc_types::subs::{
instantiate_rigids, Content, ExhaustiveMark, FlatType, RedundantMark, StorageSubs, Subs,
Variable, VariableSubsSlice,
instantiate_rigids, storage_copy_var_to, Content, ExhaustiveMark, FlatType, RedundantMark,
StorageSubs, Subs, Variable, VariableSubsSlice,
};
use std::collections::HashMap;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
@ -1465,6 +1465,9 @@ impl<'a> Specializations<'a> {
pub struct Env<'a, 'i> {
pub arena: &'a Bump,
pub subs: &'i mut Subs,
/// [Subs] to write specialized variables of lookups in expects.
/// [None] if this module doesn't produce any expects.
pub expectation_subs: Option<&'i mut Subs>,
pub home: ModuleId,
pub ident_ids: &'i mut IdentIds,
pub target_info: TargetInfo,
@ -1601,6 +1604,9 @@ pub fn cond<'a>(
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
/// The specialized type of a lookup. Represented as a type-variable.
pub type LookupType = Variable;
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
@ -1622,7 +1628,7 @@ pub enum Stmt<'a> {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
layouts: &'a [Layout<'a>],
variables: &'a [LookupType],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
@ -1630,7 +1636,7 @@ pub enum Stmt<'a> {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
layouts: &'a [Layout<'a>],
variables: &'a [LookupType],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
@ -6570,13 +6576,14 @@ pub fn from_can<'a>(
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut layouts = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut lookup_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut specialized_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
} in lookups_in_cond.iter().copied()
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
@ -6587,20 +6594,28 @@ pub fn from_can<'a>(
),
None => symbol,
};
let res_layout = layout_cache.from_var(env.arena, var, env.subs);
let layout = return_on_layout_error!(env, res_layout, "Expect");
if !matches!(layout, Layout::LambdaSet(..)) {
let expectation_subs = env
.expectation_subs
.as_deref_mut()
.expect("if expects are compiled, their subs should be available");
let spec_var = expectation_subs.fresh_unnamed_flex_var();
if !env.subs.is_function(var) {
// Exclude functions from lookups
lookups.push(symbol);
layouts.push(layout);
lookup_variables.push(var);
specialized_variables.push(spec_var);
}
}
let specialized_variables = specialized_variables.into_bump_slice();
let mut stmt = Stmt::Expect {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
layouts: layouts.into_bump_slice(),
variables: specialized_variables,
remainder: env.arena.alloc(rest),
};
@ -6614,6 +6629,10 @@ pub fn from_can<'a>(
env.arena.alloc(stmt),
);
// Now that the condition has been specialized, export the specialized types of our
// lookups into the expectation subs.
store_specialized_expectation_lookups(env, lookup_variables, specialized_variables);
stmt
}
@ -6626,13 +6645,14 @@ pub fn from_can<'a>(
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut layouts = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut lookup_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut specialized_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
} in lookups_in_cond.iter().copied()
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
@ -6643,20 +6663,28 @@ pub fn from_can<'a>(
),
None => symbol,
};
let res_layout = layout_cache.from_var(env.arena, var, env.subs);
let layout = return_on_layout_error!(env, res_layout, "Expect");
if !matches!(layout, Layout::LambdaSet(..)) {
let expectation_subs = env
.expectation_subs
.as_deref_mut()
.expect("if expects are compiled, their subs should be available");
let spec_var = expectation_subs.fresh_unnamed_flex_var();
if !env.subs.is_function(var) {
// Exclude functions from lookups
lookups.push(symbol);
layouts.push(layout);
lookup_variables.push(var);
specialized_variables.push(spec_var);
}
}
let specialized_variables = specialized_variables.into_bump_slice();
let mut stmt = Stmt::ExpectFx {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
layouts: layouts.into_bump_slice(),
variables: specialized_variables,
remainder: env.arena.alloc(rest),
};
@ -6670,6 +6698,8 @@ pub fn from_can<'a>(
env.arena.alloc(stmt),
);
store_specialized_expectation_lookups(env, lookup_variables, specialized_variables);
stmt
}
@ -6681,12 +6711,23 @@ pub fn from_can<'a>(
} => {
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
let spec_var = env
.expectation_subs
.as_mut()
.unwrap()
.fresh_unnamed_flex_var();
// HACK(dbg-spec-var): pass the specialized type variable along injected into a fake symbol
let dbg_spec_var_symbol = Symbol::new(ModuleId::ATTR, unsafe {
IdentId::from_index(spec_var.index())
});
// TODO: need to store the specialized variable of this dbg in the expectation_subs
let call = crate::ir::Call {
call_type: CallType::LowLevel {
op: LowLevel::Dbg,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([dbg_symbol]),
arguments: env.arena.alloc([dbg_symbol, dbg_spec_var_symbol]),
};
let dbg_layout = layout_cache
@ -6714,6 +6755,10 @@ pub fn from_can<'a>(
);
}
// Now that the dbg value has been specialized, export its specialized type into the
// expectations subs.
store_specialized_expectation_lookups(env, [variable], &[spec_var]);
stmt
}
@ -6752,6 +6797,21 @@ pub fn from_can<'a>(
}
}
fn store_specialized_expectation_lookups(
env: &mut Env,
lookup_variables: impl IntoIterator<Item = Variable>,
specialized_variables: &[Variable],
) {
let subs = &env.subs;
let expectation_subs = env.expectation_subs.as_deref_mut().unwrap();
for (lookup_var, stored_var) in lookup_variables.into_iter().zip(specialized_variables) {
let stored_specialized_var =
storage_copy_var_to(&mut Default::default(), subs, expectation_subs, lookup_var);
let stored_specialized_desc = expectation_subs.get(stored_specialized_var);
expectation_subs.union(*stored_var, stored_specialized_var, stored_specialized_desc);
}
}
fn to_opt_branches<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
@ -7082,7 +7142,7 @@ fn substitute_in_stmt_help<'a>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let new_remainder =
@ -7097,7 +7157,7 @@ fn substitute_in_stmt_help<'a>(
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
layouts,
variables,
remainder: new_remainder,
};
@ -7108,7 +7168,7 @@ fn substitute_in_stmt_help<'a>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let new_remainder =
@ -7123,7 +7183,7 @@ fn substitute_in_stmt_help<'a>(
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
layouts,
variables,
remainder: new_remainder,
};
@ -8234,49 +8294,71 @@ fn specialize_symbol<'a>(
Err(e) => return_on_layout_error_help!(env, e, "specialize_symbol"),
};
if procs.is_imported_module_thunk(original) {
let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => {
Layout::LambdaSet(lambda_set)
}
};
match raw {
RawFunctionLayout::Function(_, lambda_set, _)
if !procs.is_imported_module_thunk(original) =>
{
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, original, &[]);
let raw = RawFunctionLayout::ZeroArgumentThunk(layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
debug_assert!(
lambda_name.no_captures(),
"imported functions are top-level and should never capture"
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
let function_ptr_layout = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
lambda_name.captures_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
lambda_name,
function_ptr_layout,
layout_cache,
);
force_thunk(env, original, layout, assign_to, env.arena.alloc(result))
} else {
// Imported symbol, so it must have no captures niche (since
// top-levels can't capture)
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
lambda_name,
&[],
assign_to,
env.arena.alloc(result),
)
}
_ => {
// This is an imported ZAT that returns either a value, or the closure
// data for a lambda set.
let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => {
Layout::LambdaSet(lambda_set)
}
};
let_empty_struct(assign_to, env.arena.alloc(result))
let raw = RawFunctionLayout::ZeroArgumentThunk(layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
force_thunk(env, original, layout, assign_to, env.arena.alloc(result))
}
}
}

View File

@ -2890,7 +2890,6 @@ impl<'a> Builtin<'a> {
use FloatWidth::*;
match float_width {
F128 => alloc.text("Float128"),
F64 => alloc.text("Float64"),
F32 => alloc.text("Float32"),
}

View File

@ -195,7 +195,7 @@ fn function_s<'a, 'i>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let continuation: &Stmt = remainder;
@ -208,7 +208,7 @@ fn function_s<'a, 'i>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: new_continuation,
};
@ -220,7 +220,7 @@ fn function_s<'a, 'i>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let continuation: &Stmt = remainder;
@ -233,7 +233,7 @@ fn function_s<'a, 'i>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: new_continuation,
};
@ -442,7 +442,7 @@ fn function_d_main<'a, 'i>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let (b, found) = function_d_main(env, x, c, remainder);
@ -452,7 +452,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
};
@ -464,7 +464,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
};
@ -475,7 +475,7 @@ fn function_d_main<'a, 'i>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let (b, found) = function_d_main(env, x, c, remainder);
@ -485,7 +485,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
};
@ -497,7 +497,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
};
@ -660,7 +660,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let b = function_r(env, remainder);
@ -669,7 +669,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
};
@ -680,7 +680,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition,
region,
lookups,
layouts,
variables,
remainder,
} => {
let b = function_r(env, remainder);
@ -689,7 +689,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: b,
};

View File

@ -253,7 +253,7 @@ fn insert_jumps<'a>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => match insert_jumps(
arena,
@ -267,7 +267,7 @@ fn insert_jumps<'a>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: cont,
})),
None => None,
@ -277,7 +277,7 @@ fn insert_jumps<'a>(
condition,
region,
lookups,
layouts,
variables,
remainder,
} => match insert_jumps(
arena,
@ -291,7 +291,7 @@ fn insert_jumps<'a>(
condition: *condition,
region: *region,
lookups,
layouts,
variables,
remainder: cont,
})),
None => None,

View File

@ -262,6 +262,7 @@ mod solve_expr {
#[derive(Default)]
struct InferOptions {
print_can_decls: bool,
print_only_under_alias: bool,
allow_errors: bool,
}
@ -302,7 +303,20 @@ mod solve_expr {
let queries = parse_queries(&src);
assert!(!queries.is_empty(), "No queries provided!");
let mut solved_queries = Vec::with_capacity(queries.len());
let mut output_parts = Vec::with_capacity(queries.len() + 2);
if options.print_can_decls {
use roc_can::debug::{pretty_print_declarations, PPCtx};
let ctx = PPCtx {
home,
interns: &interns,
print_lambda_names: true,
};
let pretty_decls = pretty_print_declarations(&ctx, &decls);
output_parts.push(pretty_decls);
output_parts.push("\n".to_owned());
}
for TypeQuery(region) in queries.into_iter() {
let start = region.start().offset;
let end = region.end().offset;
@ -340,12 +354,12 @@ mod solve_expr {
}
};
solved_queries.push(elaborated);
output_parts.push(elaborated);
}
let pretty_solved_queries = solved_queries.join("\n");
let pretty_output = output_parts.join("\n");
expected(&pretty_solved_queries);
expected(&pretty_output);
}
macro_rules! infer_queries {
@ -6720,9 +6734,9 @@ mod solve_expr {
"#
),
@r#"
A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {})
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
Id#id(3) : a -[[] + a:id(3):1]-> ({} -[[] + a:id(3):2]-> a) | a has Id
alias : {} -[[id(5)]]-> ({} -[[8(8)]]-> {})
alias : {} -[[id(5)]]-> ({} -[[8]]-> {})
"#
print_only_under_alias: true
)
@ -6751,8 +6765,8 @@ mod solve_expr {
"#
),
@r#"
A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {})
it : {} -[[8(8)]]-> {}
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
it : {} -[[8]]-> {}
"#
print_only_under_alias: true
)
@ -6782,8 +6796,8 @@ mod solve_expr {
"#
),
@r#"
A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {})
A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {})
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
"#
print_only_under_alias: true
)
@ -6903,7 +6917,7 @@ mod solve_expr {
#^^^^^^^^^^^^^^^^^^^^^^{-1}
"#
),
@r#"[\{} -> {}, \{} -> {}] : List ({}* -[[1(1), 2(2)]]-> {})"#
@r###"[\{} -> {}, \{} -> {}] : List ({}* -[[1, 2]]-> {})"###
)
}
@ -7078,7 +7092,7 @@ mod solve_expr {
#^^^{-1}
"#
),
@r#"fun : {} -[[thunk(9) (({} -[[15(15)]]-> { s1 : Str })) ({ s1 : Str } -[[g(4)]]-> ({} -[[13(13) Str]]-> Str)), thunk(9) (({} -[[14(14)]]-> Str)) (Str -[[f(3)]]-> ({} -[[11(11)]]-> Str))]]-> Str"#
@r#"fun : {} -[[thunk(9) (({} -[[15]]-> { s1 : Str })) ({ s1 : Str } -[[g(4)]]-> ({} -[[13 Str]]-> Str)), thunk(9) (({} -[[14]]-> Str)) (Str -[[f(3)]]-> ({} -[[11]]-> Str))]]-> Str"#
print_only_under_alias: true
);
}
@ -7323,9 +7337,9 @@ mod solve_expr {
"#
),
@r###"
Fo#f(7) : Fo, b -[[f(7)]]-> ({} -[[13(13) b]]-> ({} -[[] + b:g(4):2]-> {})) | b has G
Go#g(8) : Go -[[g(8)]]-> ({} -[[14(14)]]-> {})
Fo#f(7) : Fo, Go -[[f(7)]]-> ({} -[[13(13) Go]]-> ({} -[[14(14)]]-> {}))
Fo#f(7) : Fo, b -[[f(7)]]-> ({} -[[13 b]]-> ({} -[[] + b:g(4):2]-> {})) | b has G
Go#g(8) : Go -[[g(8)]]-> ({} -[[14]]-> {})
Fo#f(7) : Fo, Go -[[f(7)]]-> ({} -[[13 Go]]-> ({} -[[14]]-> {}))
"###
);
}
@ -7692,7 +7706,7 @@ mod solve_expr {
@r###"
const : Str -[[const(2)]]-> (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str)
compose : (Str -a-> Str), (Str -[[]]-> Str) -[[compose(1)]]-> (Str -a-> Str)
\c1, c2 -> compose c1 c2 : (Str -a-> Str), (Str -[[]]-> Str) -[[11(11)]]-> (Str -a-> Str)
\c1, c2 -> compose c1 c2 : (Str -a-> Str), (Str -[[]]-> Str) -[[11]]-> (Str -a-> Str)
res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str
res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str
"###
@ -8077,7 +8091,7 @@ mod solve_expr {
# ^^^^^^^^^^^^^^
"#
),
@"N#Decode.decoder(3) : List U8, fmt -[[7(7)]]-> { rest : List U8, result : [Err [TooShort], Ok U8] } | fmt has DecoderFormatting"
@"N#Decode.decoder(3) : List U8, fmt -[[7]]-> { rest : List U8, result : [Err [TooShort], Ok U8] } | fmt has DecoderFormatting"
print_only_under_alias: true
);
}
@ -8360,7 +8374,7 @@ mod solve_expr {
),
@r###"
isEqQ : ({} -[[]]-> Str), ({} -[[]]-> Str) -[[isEqQ(2)]]-> [False, True]
isEqQ : ({} -[[6(6), 7(7)]]-> Str), ({} -[[6(6), 7(7)]]-> Str) -[[isEqQ(2)]]-> [False, True]
isEqQ : ({} -[[6, 7]]-> Str), ({} -[[6, 7]]-> Str) -[[isEqQ(2)]]-> [False, True]
"###
print_only_under_alias: true
);
@ -8456,4 +8470,82 @@ mod solve_expr {
"###
);
}
#[test]
fn disjoint_nested_lambdas_result_in_disjoint_parents_issue_4712() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Parser a : {} -> a
v1 : {}
v1 = {}
v2 : Str
v2 = ""
apply : Parser (a -> Str), a -> Parser Str
apply = \fnParser, valParser ->
\{} ->
(fnParser {}) (valParser)
map : a, (a -> Str) -> Parser Str
map = \simpleParser, transform ->
apply (\{} -> transform) simpleParser
parseInput = \{} ->
when [ map v1 (\{} -> ""), map v2 (\s -> s) ] is
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ -> ""
main = parseInput {} == ""
"#
),
@r###"
v1 = {}
v2 = ""
apply = \fnParser, valParser-> \{} -[9]-> (fnParser {}) valParser
map = \simpleParser, transform-> apply \{} -[12]-> transform simpleParser
parseInput =
\{}->
when [
map v1 \{} -[13]-> "",
map v2 \s -[14]-> s,
] is
_ -> ""
main = Bool.isEq (parseInput {}) ""
[ map v1 (\{} -> ""), map v2 (\s -> s) ] : List (({} -[[9 (({} -[[12 (Str -[[14]]-> Str)]]-> (Str -[[14]]-> Str))) Str, 9 (({} -[[12 ({} -[[13]]-> Str)]]-> ({} -[[13]]-> Str))) {}]]-> Str))
"###
print_only_under_alias: true
print_can_decls: true
);
}
#[test]
fn constrain_dbg_flex_var() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
polyDbg = \x ->
#^^^^^^^{-1}
dbg x
x
main = polyDbg ""
"#
),
@"polyDbg : a -[[polyDbg(1)]]-> a"
);
}
}

View File

@ -103,9 +103,9 @@ fn list() {
# Specialization lambda sets:
# @<1>: [[custom(3)]]
#Derived.decoder_list =
Decode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Decode.decodeWith #Derived.bytes (Decode.list Decode.decoder) #Derived.fmt
decodeWith #Derived.bytes (list decoder) #Derived.fmt
"###
)
})
@ -121,21 +121,18 @@ fn record_2_fields() {
# Specialization lambda sets:
# @<1>: [[custom(22)]]
#Derived.decoder_{first,second} =
Decode.custom
custom
\#Derived.bytes3, #Derived.fmt3 ->
Decode.decodeWith
decodeWith
#Derived.bytes3
(Decode.record
(record
{ second: Err NoField, first: Err NoField }
\#Derived.stateRecord2, #Derived.field ->
when #Derived.field is
"first" ->
Keep (Decode.custom
Keep (custom
\#Derived.bytes, #Derived.fmt ->
when Decode.decodeWith
#Derived.bytes
Decode.decoder
#Derived.fmt is
when decodeWith #Derived.bytes decoder #Derived.fmt is
#Derived.rec ->
{
result: when #Derived.rec.result is
@ -145,12 +142,9 @@ fn record_2_fields() {
rest: #Derived.rec.rest
})
"second" ->
Keep (Decode.custom
Keep (custom
\#Derived.bytes2, #Derived.fmt2 ->
when Decode.decodeWith
#Derived.bytes2
Decode.decoder
#Derived.fmt2 is
when decodeWith #Derived.bytes2 decoder #Derived.fmt2 is
#Derived.rec2 ->
{
result: when #Derived.rec2.result is

View File

@ -187,9 +187,9 @@ fn empty_record() {
# @<2>: [[custom(2) {}]]
#Derived.toEncoder_{} =
\#Derived.rcd ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith #Derived.bytes (Encode.record []) #Derived.fmt
appendWith #Derived.bytes (record []) #Derived.fmt
"###
)
})
@ -207,9 +207,9 @@ fn zero_field_record() {
# @<2>: [[custom(2) {}]]
#Derived.toEncoder_{} =
\#Derived.rcd ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith #Derived.bytes (Encode.record []) #Derived.fmt
appendWith #Derived.bytes (record []) #Derived.fmt
"###
)
})
@ -227,11 +227,11 @@ fn one_field_record() {
# @<2>: [[custom(2) { a : val }]] | val has Encoding
#Derived.toEncoder_{a} =
\#Derived.rcd ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
appendWith
#Derived.bytes
(Encode.record [{ value: Encode.toEncoder #Derived.rcd.a, key: "a" }])
(record [{ value: toEncoder #Derived.rcd.a, key: "a" }])
#Derived.fmt
"###
)
@ -250,14 +250,14 @@ fn two_field_record() {
# @<2>: [[custom(2) { a : val, b : val1 }]] | val has Encoding, val1 has Encoding
#Derived.toEncoder_{a,b} =
\#Derived.rcd ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
appendWith
#Derived.bytes
(Encode.record
(record
[
{ value: Encode.toEncoder #Derived.rcd.a, key: "a" },
{ value: Encode.toEncoder #Derived.rcd.b, key: "b" },
{ value: toEncoder #Derived.rcd.a, key: "a" },
{ value: toEncoder #Derived.rcd.b, key: "b" },
])
#Derived.fmt
"###
@ -290,12 +290,12 @@ fn tag_one_label_zero_args() {
# @<2>: [[custom(2) [A]]]
#Derived.toEncoder_[A 0] =
\#Derived.tag ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
appendWith
#Derived.bytes
(when #Derived.tag is
A -> Encode.tag "A" [])
A -> tag "A" [])
#Derived.fmt
"###
)
@ -314,18 +314,13 @@ fn tag_one_label_two_args() {
# @<2>: [[custom(4) [A val val1]]] | val has Encoding, val1 has Encoding
#Derived.toEncoder_[A 2] =
\#Derived.tag ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
appendWith
#Derived.bytes
(when #Derived.tag is
A #Derived.2 #Derived.3 ->
Encode.tag
"A"
[
Encode.toEncoder #Derived.2,
Encode.toEncoder #Derived.3,
])
tag "A" [toEncoder #Derived.2, toEncoder #Derived.3])
#Derived.fmt
"###
)
@ -339,30 +334,30 @@ fn tag_two_labels() {
v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]),
|golden| {
assert_snapshot!(golden, @r###"
# derived for [A U8 Str U16, B Str]
# [A val val1 val1, B val1] -[[toEncoder_[A 3,B 1](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# [A val val1 val1, B val1] -[[toEncoder_[A 3,B 1](0)]]-> (List U8, fmt -[[custom(6) [A val val1 val1, B val1]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# Specialization lambda sets:
# @<1>: [[toEncoder_[A 3,B 1](0)]]
# @<2>: [[custom(6) [A val val1 val1, B val1]]] | val has Encoding, val1 has Encoding
#Derived.toEncoder_[A 3,B 1] =
\#Derived.tag ->
Encode.custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
#Derived.bytes
(when #Derived.tag is
A #Derived.2 #Derived.3 #Derived.4 ->
Encode.tag
"A"
[
Encode.toEncoder #Derived.2,
Encode.toEncoder #Derived.3,
Encode.toEncoder #Derived.4,
]
B #Derived.5 -> Encode.tag "B" [Encode.toEncoder #Derived.5])
#Derived.fmt
"###
# derived for [A U8 Str U16, B Str]
# [A val val1 val1, B val1] -[[toEncoder_[A 3,B 1](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# [A val val1 val1, B val1] -[[toEncoder_[A 3,B 1](0)]]-> (List U8, fmt -[[custom(6) [A val val1 val1, B val1]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# Specialization lambda sets:
# @<1>: [[toEncoder_[A 3,B 1](0)]]
# @<2>: [[custom(6) [A val val1 val1, B val1]]] | val has Encoding, val1 has Encoding
#Derived.toEncoder_[A 3,B 1] =
\#Derived.tag ->
custom
\#Derived.bytes, #Derived.fmt ->
appendWith
#Derived.bytes
(when #Derived.tag is
A #Derived.2 #Derived.3 #Derived.4 ->
tag
"A"
[
toEncoder #Derived.2,
toEncoder #Derived.3,
toEncoder #Derived.4,
]
B #Derived.5 -> tag "B" [toEncoder #Derived.5])
#Derived.fmt
"###
)
},
)
@ -375,29 +370,24 @@ fn recursive_tag_union() {
v!([Nil, Cons v!(U8) v!(^lst) ] as lst),
|golden| {
assert_snapshot!(golden, @r###"
# derived for [Cons U8 $rec, Nil] as $rec
# [Cons val val1, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# [Cons val val1, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> (List U8, fmt -[[custom(4) [Cons val val1, Nil]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# Specialization lambda sets:
# @<1>: [[toEncoder_[Cons 2,Nil 0](0)]]
# @<2>: [[custom(4) [Cons val val1, Nil]]] | val has Encoding, val1 has Encoding
#Derived.toEncoder_[Cons 2,Nil 0] =
\#Derived.tag ->
Encode.custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
#Derived.bytes
(when #Derived.tag is
Cons #Derived.2 #Derived.3 ->
Encode.tag
"Cons"
[
Encode.toEncoder #Derived.2,
Encode.toEncoder #Derived.3,
]
Nil -> Encode.tag "Nil" [])
#Derived.fmt
"###
# derived for [Cons U8 $rec, Nil] as $rec
# [Cons val val1, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# [Cons val val1, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> (List U8, fmt -[[custom(4) [Cons val val1, Nil]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
# Specialization lambda sets:
# @<1>: [[toEncoder_[Cons 2,Nil 0](0)]]
# @<2>: [[custom(4) [Cons val val1, Nil]]] | val has Encoding, val1 has Encoding
#Derived.toEncoder_[Cons 2,Nil 0] =
\#Derived.tag ->
custom
\#Derived.bytes, #Derived.fmt ->
appendWith
#Derived.bytes
(when #Derived.tag is
Cons #Derived.2 #Derived.3 ->
tag "Cons" [toEncoder #Derived.2, toEncoder #Derived.3]
Nil -> tag "Nil" [])
#Derived.fmt
"###
)
},
)
@ -415,13 +405,11 @@ fn list() {
# @<2>: [[custom(4) (List val)]] | val has Encoding
#Derived.toEncoder_list =
\#Derived.lst ->
Encode.custom
custom
\#Derived.bytes, #Derived.fmt ->
Encode.appendWith
appendWith
#Derived.bytes
(Encode.list
#Derived.lst
\#Derived.elem -> Encode.toEncoder #Derived.elem)
(list #Derived.lst \#Derived.elem -> toEncoder #Derived.elem)
#Derived.fmt
"###
)

View File

@ -178,7 +178,7 @@ fn one_field_record() {
# Specialization lambda sets:
# @<1>: [[hash_{a}(0)]]
#Derived.hash_{a} =
\#Derived.hasher, #Derived.rcd -> Hash.hash #Derived.hasher #Derived.rcd.a
\#Derived.hasher, #Derived.rcd -> hash #Derived.hasher #Derived.rcd.a
"###
)
})
@ -195,7 +195,7 @@ fn two_field_record() {
# @<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
hash (hash #Derived.hasher #Derived.rcd.a) #Derived.rcd.b
"###
)
})
@ -227,7 +227,7 @@ fn tag_one_label_newtype() {
# @<1>: [[hash_[A 2](0)]]
#Derived.hash_[A 2] =
\#Derived.hasher, A #Derived.2 #Derived.3 ->
Hash.hash (Hash.hash #Derived.hasher #Derived.2) #Derived.3
hash (hash #Derived.hasher #Derived.2) #Derived.3
"###
)
})
@ -246,12 +246,10 @@ fn tag_two_labels() {
\#Derived.hasher, #Derived.union ->
when #Derived.union is
A #Derived.3 #Derived.4 #Derived.5 ->
Hash.hash
(Hash.hash
(Hash.hash (Hash.addU8 #Derived.hasher 0) #Derived.3)
#Derived.4)
hash
(hash (hash (addU8 #Derived.hasher 0) #Derived.3) #Derived.4)
#Derived.5
B #Derived.6 -> Hash.hash (Hash.addU8 #Derived.hasher 1) #Derived.6
B #Derived.6 -> hash (addU8 #Derived.hasher 1) #Derived.6
"###
)
})
@ -269,8 +267,8 @@ fn tag_two_labels_no_payloads() {
#Derived.hash_[A 0,B 0] =
\#Derived.hasher, #Derived.union ->
when #Derived.union is
A -> Hash.addU8 #Derived.hasher 0
B -> Hash.addU8 #Derived.hasher 1
A -> addU8 #Derived.hasher 0
B -> addU8 #Derived.hasher 1
"###
)
})
@ -289,10 +287,8 @@ fn recursive_tag_union() {
\#Derived.hasher, #Derived.union ->
when #Derived.union is
Cons #Derived.3 #Derived.4 ->
Hash.hash
(Hash.hash (Hash.addU8 #Derived.hasher 0) #Derived.3)
#Derived.4
Nil -> Hash.addU8 #Derived.hasher 1
hash (hash (addU8 #Derived.hasher 0) #Derived.3) #Derived.4
Nil -> addU8 #Derived.hasher 1
"###
)
})

View File

@ -5,5 +5,4 @@ mod encoding;
mod eq;
mod hash;
mod pretty_print;
mod util;

View File

@ -5,10 +5,10 @@ use bumpalo::Bump;
use roc_packaging::cache::RocCacheDir;
use ven_pretty::DocAllocator;
use crate::pretty_print::{pretty_print_def, Ctx};
use roc_can::{
abilities::{AbilitiesStore, SpecializationLambdaSets},
constraint::Constraints,
debug::{pretty_print_def, PPCtx},
def::Def,
expr::Declarations,
module::{
@ -529,8 +529,12 @@ where
interns.all_ident_ids.insert(DERIVED_MODULE, ident_ids);
DERIVED_MODULE.register_debug_idents(interns.all_ident_ids.get(&DERIVED_MODULE).unwrap());
let ctx = Ctx { interns: &interns };
let derived_program = pretty_print_def(&ctx, &derived_def);
let pp_ctx = PPCtx {
interns: &interns,
print_lambda_names: false,
home: builtin_module,
};
let derived_program = pretty_print_def(&pp_ctx, &derived_def);
check_derived_typechecks_and_golden(
derived_def,

View File

@ -4107,3 +4107,41 @@ fn issue_4349() {
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_4712() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Parser a : {} -> a
v1 : {}
v1 = {}
v2 : Str
v2 = "cd"
apply : Parser (a -> Str), a -> Parser Str
apply = \fnParser, valParser ->
\{} ->
(fnParser {}) (valParser)
map : a, (a -> Str) -> Parser Str
map = \simpleParser, transform ->
apply (\{} -> transform) simpleParser
gen = \{} ->
[ map v1 (\{} -> "ab"), map v2 (\s -> s) ]
|> List.map (\f -> f {})
|> Str.joinWith ","
main = gen {}
"#
),
RocStr::from("ab,cd"),
RocStr
);
}

View File

@ -2083,3 +2083,53 @@ fn unify_types_with_fixed_fixpoints_outside_fixing_region() {
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_set_with_imported_toplevels_issue_4733() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
fn = \s ->
instr = if s == "*" then (Op Num.mul) else (Op Num.add)
Op op = instr
\a -> op a a
main = ((fn "*") 3) * ((fn "+") 5)
"#
),
90,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn non_unary_union_with_lambda_set_with_imported_toplevels_issue_4733() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
fn = \s ->
instr =
if s == "*" then (Op Num.mul)
else if s == "+" then (Op Num.add)
else Noop
when instr is
Op op -> (\a -> op a a)
_ -> (\a -> a)
main = ((fn "*") 3) * ((fn "+") 5)
"#
),
90,
i64
);
}

View File

@ -0,0 +1,316 @@
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
dec #Attr.3;
dec #Attr.2;
ret Bool.23;
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.31 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.31;
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.38 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.38;
procedure Bool.12 (#Attr.2, #Attr.3):
let Bool.30 : Int1 = lowlevel NotEq #Attr.2 #Attr.3;
ret Bool.30;
procedure Bool.7 (Bool.19, Bool.20):
let Bool.29 : Int1 = CallByName Bool.12 Bool.19 Bool.20;
ret Bool.29;
procedure Decode.23 (Decode.94):
ret Decode.94;
procedure Decode.24 (Decode.95, Decode.114, Decode.97):
let Decode.127 : {List U8, [C {}, C Str]} = CallByName Json.293 Decode.95 Decode.97;
ret Decode.127;
procedure Decode.25 (Decode.98, Decode.99):
let Decode.126 : {} = CallByName Json.41;
let Decode.125 : {List U8, [C {}, C Str]} = CallByName Decode.24 Decode.98 Decode.126 Decode.99;
ret Decode.125;
procedure Decode.26 (Decode.100, Decode.101):
let Decode.115 : {List U8, [C {}, C Str]} = CallByName Decode.25 Decode.100 Decode.101;
let Decode.103 : List U8 = StructAtIndex 0 Decode.115;
inc Decode.103;
let Decode.102 : [C {}, C Str] = StructAtIndex 1 Decode.115;
inc Decode.102;
dec Decode.115;
let Decode.118 : Int1 = CallByName List.1 Decode.103;
if Decode.118 then
dec Decode.103;
let Decode.122 : U8 = 1i64;
let Decode.123 : U8 = GetTagId Decode.102;
let Decode.124 : Int1 = lowlevel Eq Decode.122 Decode.123;
if Decode.124 then
let Decode.104 : Str = UnionAtIndex (Id 1) (Index 0) Decode.102;
inc Decode.104;
dec Decode.102;
let Decode.119 : [C [C List U8, C ], C Str] = TagId(1) Decode.104;
ret Decode.119;
else
dec Decode.102;
let Decode.121 : [C List U8, C ] = TagId(1) ;
let Decode.120 : [C [C List U8, C ], C Str] = TagId(0) Decode.121;
ret Decode.120;
else
dec Decode.102;
let Decode.117 : [C List U8, C ] = TagId(0) Decode.103;
let Decode.116 : [C [C List U8, C ], C Str] = TagId(0) Decode.117;
ret Decode.116;
procedure Json.139 (Json.450, Json.451):
joinpoint Json.421 Json.418 Json.138:
let Json.141 : List U8 = StructAtIndex 0 Json.418;
inc Json.141;
let Json.140 : List U8 = StructAtIndex 1 Json.418;
inc Json.140;
dec Json.418;
let Json.422 : [C {}, C U8] = CallByName List.9 Json.141;
let Json.436 : U8 = 1i64;
let Json.437 : U8 = GetTagId Json.422;
let Json.438 : Int1 = lowlevel Eq Json.436 Json.437;
if Json.438 then
let Json.142 : U8 = UnionAtIndex (Id 1) (Index 0) Json.422;
let Json.424 : Int1 = CallByName Json.283 Json.142;
if Json.424 then
let Json.434 : U64 = 1i64;
let Json.430 : {List U8, List U8} = CallByName List.52 Json.141 Json.434;
let Json.431 : {} = Struct {};
let Json.428 : List U8 = CallByName Json.143 Json.430;
let Json.429 : List U8 = CallByName List.4 Json.140 Json.142;
let Json.426 : {List U8, List U8} = Struct {Json.428, Json.429};
jump Json.421 Json.426 Json.138;
else
let Json.423 : {List U8, List U8} = Struct {Json.141, Json.140};
ret Json.423;
else
let Json.435 : {List U8, List U8} = Struct {Json.141, Json.140};
ret Json.435;
in
jump Json.421 Json.450 Json.451;
procedure Json.143 (Json.432):
let Json.433 : List U8 = StructAtIndex 1 Json.432;
inc Json.433;
dec Json.432;
ret Json.433;
procedure Json.2 ():
let Json.396 : {} = Struct {};
ret Json.396;
procedure Json.22 (Json.137, Json.138):
let Json.440 : List U8 = Array [];
let Json.420 : {List U8, List U8} = Struct {Json.137, Json.440};
let Json.419 : {List U8, List U8} = CallByName Json.139 Json.420 Json.138;
ret Json.419;
procedure Json.283 (Json.284):
let Json.442 : U8 = 34i64;
let Json.441 : Int1 = CallByName Bool.7 Json.284 Json.442;
ret Json.441;
procedure Json.293 (Json.294, Json.399):
let Json.400 : {List U8, [C {}, C Str]} = CallByName Json.40 Json.294;
ret Json.400;
procedure Json.40 (Json.276):
let Json.446 : U64 = 1i64;
inc Json.276;
let Json.445 : {List U8, List U8} = CallByName List.52 Json.276 Json.446;
let Json.277 : List U8 = StructAtIndex 0 Json.445;
inc Json.277;
let Json.279 : List U8 = StructAtIndex 1 Json.445;
inc Json.279;
dec Json.445;
let Json.444 : U8 = 34i64;
let Json.443 : List U8 = Array [Json.444];
let Json.404 : Int1 = CallByName Bool.11 Json.277 Json.443;
dec Json.443;
dec Json.277;
if Json.404 then
dec Json.276;
let Json.417 : {} = Struct {};
let Json.416 : {List U8, List U8} = CallByName Json.22 Json.279 Json.417;
let Json.282 : List U8 = StructAtIndex 0 Json.416;
inc Json.282;
let Json.281 : List U8 = StructAtIndex 1 Json.416;
inc Json.281;
dec Json.416;
let Json.405 : [C {U64, U8}, C Str] = CallByName Str.9 Json.281;
let Json.413 : U8 = 1i64;
let Json.414 : U8 = GetTagId Json.405;
let Json.415 : Int1 = lowlevel Eq Json.413 Json.414;
if Json.415 then
let Json.285 : Str = UnionAtIndex (Id 1) (Index 0) Json.405;
inc Json.285;
dec Json.405;
let Json.409 : U64 = 1i64;
let Json.408 : {List U8, List U8} = CallByName List.52 Json.282 Json.409;
let Json.287 : List U8 = StructAtIndex 1 Json.408;
inc Json.287;
dec Json.408;
let Json.407 : [C {}, C Str] = TagId(1) Json.285;
let Json.406 : {List U8, [C {}, C Str]} = Struct {Json.287, Json.407};
ret Json.406;
else
dec Json.405;
let Json.412 : {} = Struct {};
let Json.411 : [C {}, C Str] = TagId(0) Json.412;
let Json.410 : {List U8, [C {}, C Str]} = Struct {Json.282, Json.411};
ret Json.410;
else
dec Json.279;
let Json.403 : {} = Struct {};
let Json.402 : [C {}, C Str] = TagId(0) Json.403;
let Json.401 : {List U8, [C {}, C Str]} = Struct {Json.276, Json.402};
ret Json.401;
procedure Json.41 ():
let Json.398 : {} = Struct {};
let Json.397 : {} = CallByName Decode.23 Json.398;
ret Json.397;
procedure List.1 (List.94):
let List.479 : U64 = CallByName List.6 List.94;
let List.480 : U64 = 0i64;
let List.478 : Int1 = CallByName Bool.11 List.479 List.480;
ret List.478;
procedure List.2 (List.95, List.96):
let List.536 : U64 = CallByName List.6 List.95;
let List.532 : Int1 = CallByName Num.22 List.96 List.536;
if List.532 then
let List.534 : U8 = CallByName List.66 List.95 List.96;
let List.533 : [C {}, C U8] = TagId(1) List.534;
ret List.533;
else
let List.531 : {} = Struct {};
let List.530 : [C {}, C U8] = TagId(0) List.531;
ret List.530;
procedure List.4 (List.106, List.107):
let List.520 : U64 = 1i64;
let List.518 : List U8 = CallByName List.70 List.106 List.520;
let List.517 : List U8 = CallByName List.71 List.518 List.107;
ret List.517;
procedure List.49 (List.366, List.367):
let List.492 : U64 = StructAtIndex 0 List.367;
let List.493 : U64 = 0i64;
let List.490 : Int1 = CallByName Bool.11 List.492 List.493;
if List.490 then
dec List.366;
let List.491 : List U8 = Array [];
ret List.491;
else
let List.487 : U64 = StructAtIndex 1 List.367;
let List.488 : U64 = StructAtIndex 0 List.367;
let List.486 : List U8 = CallByName List.72 List.366 List.487 List.488;
ret List.486;
procedure List.52 (List.381, List.382):
let List.383 : U64 = CallByName List.6 List.381;
joinpoint List.515 List.384:
let List.513 : U64 = 0i64;
let List.512 : {U64, U64} = Struct {List.384, List.513};
inc List.381;
let List.385 : List U8 = CallByName List.49 List.381 List.512;
let List.511 : U64 = CallByName Num.20 List.383 List.384;
let List.510 : {U64, U64} = Struct {List.511, List.384};
let List.386 : List U8 = CallByName List.49 List.381 List.510;
let List.509 : {List U8, List U8} = Struct {List.385, List.386};
ret List.509;
in
let List.516 : Int1 = CallByName Num.24 List.383 List.382;
if List.516 then
jump List.515 List.382;
else
jump List.515 List.383;
procedure List.6 (#Attr.2):
let List.556 : U64 = lowlevel ListLen #Attr.2;
ret List.556;
procedure List.66 (#Attr.2, #Attr.3):
let List.535 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.535;
procedure List.70 (#Attr.2, #Attr.3):
let List.521 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.521;
procedure List.71 (#Attr.2, #Attr.3):
let List.519 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.519;
procedure List.72 (#Attr.2, #Attr.3, #Attr.4):
let List.489 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4;
ret List.489;
procedure List.9 (List.283):
let List.529 : U64 = 0i64;
let List.522 : [C {}, C U8] = CallByName List.2 List.283 List.529;
let List.526 : U8 = 1i64;
let List.527 : U8 = GetTagId List.522;
let List.528 : Int1 = lowlevel Eq List.526 List.527;
if List.528 then
let List.284 : U8 = UnionAtIndex (Id 1) (Index 0) List.522;
let List.523 : [C {}, C U8] = TagId(1) List.284;
ret List.523;
else
let List.525 : {} = Struct {};
let List.524 : [C {}, C U8] = TagId(0) List.525;
ret List.524;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.258 : U64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.258;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.262 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.262;
procedure Num.24 (#Attr.2, #Attr.3):
let Num.261 : Int1 = lowlevel NumGt #Attr.2 #Attr.3;
ret Num.261;
procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4;
ret Str.274;
procedure Str.9 (Str.76):
let Str.272 : U64 = 0i64;
let Str.273 : U64 = CallByName List.6 Str.76;
let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273;
let Str.269 : Int1 = StructAtIndex 2 Str.77;
if Str.269 then
let Str.271 : Str = StructAtIndex 1 Str.77;
inc Str.271;
dec Str.77;
let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271;
ret Str.270;
else
let Str.267 : U8 = StructAtIndex 3 Str.77;
let Str.268 : U64 = StructAtIndex 0 Str.77;
dec Str.77;
let Str.266 : {U64, U8} = Struct {Str.268, Str.267};
let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266;
ret Str.265;
procedure Test.3 ():
let Test.0 : List U8 = Array [82i64, 111i64, 99i64];
let Test.8 : {} = CallByName Json.2;
inc Test.0;
let Test.1 : [C [C List U8, C ], C Str] = CallByName Decode.26 Test.0 Test.8;
let Test.7 : Str = "Roc";
let Test.6 : [C [C List U8, C ], C Str] = TagId(1) Test.7;
inc Test.1;
let Test.5 : Int1 = CallByName Bool.11 Test.1 Test.6;
expect Test.5;
let Test.4 : {} = Struct {};
ret Test.4;

View File

@ -0,0 +1,49 @@
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.23;
procedure Bool.2 ():
let Bool.24 : Int1 = true;
ret Bool.24;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.256 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.256;
procedure Num.21 (#Attr.2, #Attr.3):
let Num.257 : U64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 (Test.8):
let Test.23 : Int1 = CallByName Bool.2;
if Test.23 then
let Test.24 : Int1 = true;
ret Test.24;
else
let Test.22 : Int1 = false;
ret Test.22;
procedure Test.5 (Test.6, Test.2):
joinpoint Test.19 Test.18:
ret Test.18;
in
switch Test.2:
case 0:
let Test.20 : U64 = CallByName Num.19 Test.6 Test.6;
jump Test.19 Test.20;
default:
let Test.21 : U64 = CallByName Num.21 Test.6 Test.6;
jump Test.19 Test.21;
procedure Test.7 ():
let Test.13 : U64 = 3i64;
let Test.15 : {} = Struct {};
let Test.14 : Int1 = CallByName Test.0 Test.15;
let Test.11 : U64 = CallByName Test.5 Test.13 Test.14;
let Test.12 : U64 = 9i64;
let Test.10 : Int1 = CallByName Bool.11 Test.11 Test.12;
expect Test.10;
let Test.9 : {} = Struct {};
ret Test.9;

View File

@ -2159,3 +2159,36 @@ fn issue_4705() {
"###
)
}
#[mono_test(mode = "test")]
fn issue_4749() {
indoc!(
r###"
interface Test exposes [] imports [Json]
expect
input = [82, 111, 99]
got = Decode.fromBytes input Json.fromUtf8
got == Ok "Roc"
"###
)
}
#[mono_test(mode = "test", no_check)]
fn lambda_set_with_imported_toplevels_issue_4733() {
indoc!(
r###"
interface Test exposes [] imports []
fn = \{} ->
instr : [ Op (U64, U64 -> U64) ]
instr = if Bool.true then (Op Num.mul) else (Op Num.add)
Op op = instr
\a -> op a a
expect ((fn {}) 3) == 9
"###
)
}

View File

@ -767,18 +767,23 @@ fn write_content<'a>(
buf.push_str("[[");
let print_symbol = |symbol: &Symbol| {
let ident_str = symbol.as_str(env.interns);
let ident_index_str = symbol.ident_id().index().to_string();
let disambiguation = if ident_str != ident_index_str {
// The pretty name is a named identifier; print the ident as well to avoid
// ambguity (in shadows or ability specializations).
format!("({ident_index_str})")
} else {
"".to_string()
};
if env.home == symbol.module_id() {
format!(
"{}({})",
symbol.as_str(env.interns),
symbol.ident_id().index(),
)
format!("{}{}", ident_str, disambiguation,)
} else {
format!(
"{}.{}({})",
"{}.{}{}",
symbol.module_string(env.interns),
symbol.as_str(env.interns),
symbol.ident_id().index(),
ident_str,
disambiguation
)
}
};

View File

@ -3645,11 +3645,8 @@ impl Alias {
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Mismatch {
TypeMismatch,
IfConditionNotBool,
InconsistentIfElse,
InconsistentWhenBranches,
CanonicalizationProblem,
TypeNotInRange,
DisjointLambdaSets,
DoesNotImplementAbiity(Variable, Symbol),
}

View File

@ -320,7 +320,6 @@ impl<M: MetaCollector> Outcome<M> {
pub struct Env<'a> {
pub subs: &'a mut Subs,
compute_outcome_only: bool,
seen_recursion: VecSet<(Variable, Variable)>,
fixed_variables: VecSet<Variable>,
}
@ -329,21 +328,11 @@ impl<'a> Env<'a> {
pub fn new(subs: &'a mut Subs) -> Self {
Self {
subs,
compute_outcome_only: false,
seen_recursion: Default::default(),
fixed_variables: Default::default(),
}
}
// Computes a closure in outcome-only mode. Unifications run in outcome-only mode will check
// for unifiability, but will not modify type variables or merge them.
pub fn with_outcome_only<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.compute_outcome_only = true;
let result = f(self);
self.compute_outcome_only = false;
result
}
fn add_recursion_pair(&mut self, var1: Variable, var2: Variable) {
let pair = (
self.subs.get_root_key_without_compacting(var1),
@ -1331,7 +1320,7 @@ fn separate_union_lambdas<M: MetaCollector>(
mode: Mode,
fields1: UnionLambdas,
fields2: UnionLambdas,
) -> (Outcome<M>, SeparatedUnionLambdas) {
) -> Result<(Outcome<M>, SeparatedUnionLambdas), Outcome<M>> {
debug_assert!(
fields1.is_sorted_allow_duplicates(env.subs),
"not sorted: {:?}",
@ -1451,19 +1440,55 @@ fn separate_union_lambdas<M: MetaCollector>(
//
// If they are not unifiable, that means the two lambdas must be
// different (since they have different capture sets), and so we don't
// want to merge the variables.
let variables_are_unifiable = env.with_outcome_only(|env| {
unify_pool::<NoCollector>(env, pool, var1, var2, mode)
.mismatches
.is_empty()
});
// want to merge the variables. Instead, we'll treat the lambda sets
// are disjoint, and keep them as independent lambda in the resulting
// set.
//
// # Nested lambda sets
//
// XREF https://github.com/roc-lang/roc/issues/4712
//
// We must be careful to ensure that if unifying nested lambda sets
// results in disjoint lambdas, that the parent lambda sets are
// ultimately treated disjointly as well.
// Consider
//
// v1: {} -[ foo ({} -[ bar Str ]-> {}) ]-> {}
// ~ v2: {} -[ foo ({} -[ bar U64 ]-> {}) ]-> {}
//
// When considering unification of the nested sets
//
// [ bar Str ]
// ~ [ bar U64 ]
//
// we should not unify these sets, even disjointly, because that would
// ultimately lead us to unifying
//
// v1 ~ v2
// => {} -[ foo ({} -[ bar Str, bar U64 ]-> {}) ] -> {}
//
// which is quite wrong - we do not have a lambda `foo` that captures
// either `bar captures: Str` or `bar captures: U64`, we have two
// different lambdas `foo` that capture different `bars`. The target
// unification is
//
// v1 ~ v2
// => {} -[ foo ({} -[ bar Str ]-> {}),
// foo ({} -[ bar U64 ]-> {}) ] -> {}
let subs_snapshot = env.subs.snapshot();
let pool_snapshot = pool.len();
let outcome: Outcome<M> = unify_pool(env, pool, var1, var2, mode);
if !variables_are_unifiable {
if !outcome.mismatches.is_empty() {
// Rolling back will also pull apart any nested lambdas that
// were joined into the same set.
env.subs.rollback_to(subs_snapshot);
pool.truncate(pool_snapshot);
continue 'try_next_right;
} else {
let outcome = unify_pool(env, pool, var1, var2, mode);
whole_outcome.union(outcome);
}
let outcome = unify_pool(env, pool, var1, var2, mode);
whole_outcome.union(outcome);
}
// All the variables unified, so we can join the left + right.
@ -1487,14 +1512,14 @@ fn separate_union_lambdas<M: MetaCollector>(
}
}
(
Ok((
whole_outcome,
SeparatedUnionLambdas {
only_in_left,
only_in_right,
joined,
},
)
))
}
/// ULS-SORT-ORDER:
@ -1829,7 +1854,10 @@ fn unify_lambda_set_help<M: MetaCollector>(
only_in_right,
joined,
},
) = separate_union_lambdas(env, pool, ctx.mode, solved1, solved2);
) = match separate_union_lambdas(env, pool, ctx.mode, solved1, solved2) {
Ok((outcome, separated)) => (outcome, separated),
Err(err_outcome) => return err_outcome,
};
let all_lambdas = joined
.into_iter()
@ -3495,24 +3523,22 @@ fn unify_recursion<M: MetaCollector>(
pub fn merge<M: MetaCollector>(env: &mut Env, ctx: &Context, content: Content) -> Outcome<M> {
let mut outcome: Outcome<M> = Outcome::default();
if !env.compute_outcome_only {
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
let desc = Descriptor {
content,
rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
let desc = Descriptor {
content,
rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
outcome
.extra_metadata
.record_changed_variable(env.subs, ctx.first);
outcome
.extra_metadata
.record_changed_variable(env.subs, ctx.second);
outcome
.extra_metadata
.record_changed_variable(env.subs, ctx.first);
outcome
.extra_metadata
.record_changed_variable(env.subs, ctx.second);
env.subs.union(ctx.first, ctx.second, desc);
}
env.subs.union(ctx.first, ctx.second, desc);
outcome
}

View File

@ -91,7 +91,6 @@ RocNum : [
U128,
F32,
F64,
F128,
Dec,
]
@ -153,4 +152,4 @@ RocTagUnion : [
nonNullPayload: TypeId,
whichTagIsNull: [FirstTagIsNull, SecondTagIsNull],
},
]
]

View File

@ -571,7 +571,6 @@ struct RocType_RocDict {
#[repr(u8)]
pub enum RocNum {
Dec = 0,
F128 = 1,
F32 = 2,
F64 = 3,
I128 = 4,
@ -590,7 +589,6 @@ impl core::fmt::Debug for RocNum {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Dec => f.write_str("RocNum::Dec"),
Self::F128 => f.write_str("RocNum::F128"),
Self::F32 => f.write_str("RocNum::F32"),
Self::F64 => f.write_str("RocNum::F64"),
Self::I128 => f.write_str("RocNum::I128"),

View File

@ -1737,7 +1737,6 @@ fn type_name(id: TypeId, types: &Types) -> String {
RocType::Num(RocNum::I128) => "roc_std::I128".to_string(),
RocType::Num(RocNum::F32) => "f32".to_string(),
RocType::Num(RocNum::F64) => "f64".to_string(),
RocType::Num(RocNum::F128) => "roc_std::F128".to_string(),
RocType::Num(RocNum::Dec) => "roc_std::RocDec".to_string(),
RocType::RocDict(key_id, val_id) => format!(
"roc_std::RocDict<{}, {}>",
@ -2433,7 +2432,7 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId])
use RocNum::*;
match num {
F32 | F64 | F128 => true,
F32 | F64 => true,
I8 | U8 | I16 | U16 | I32 | U32 | I64 | U64 | I128 | U128 | Dec => false,
}
}

View File

@ -544,7 +544,6 @@ pub enum RocNum {
U128,
F32,
F64,
F128,
Dec,
}
@ -566,7 +565,6 @@ impl RocNum {
RocNum::U128 => size_of::<roc_std::U128>(),
RocNum::F32 => size_of::<f32>(),
RocNum::F64 => size_of::<f64>(),
RocNum::F128 => todo!(),
RocNum::Dec => size_of::<roc_std::RocDec>(),
};
@ -1083,11 +1081,6 @@ fn add_builtin_type<'a>(
RocType::Num(RocNum::F64),
layout,
),
F128 => types.add_anonymous(
&env.layout_cache.interner,
RocType::Num(RocNum::F128),
layout,
),
},
(Builtin::Decimal, _) => types.add_anonymous(
&env.layout_cache.interner,

View File

@ -172,7 +172,7 @@ fn unroll_newtypes_and_aliases<'a, 'env>(
var = field.into_inner();
}
Content::Alias(name, _, real_var, kind) => {
if *name == Symbol::BOOL_BOOL {
if *name == Symbol::BOOL_BOOL || name.module_id() == ModuleId::NUM {
return (newtype_containers, alias_content, var);
}
// We need to pass through aliases too, because their underlying types may have
@ -185,7 +185,7 @@ fn unroll_newtypes_and_aliases<'a, 'env>(
//
// At the end of the day what we should show to the user is the alias content, not
// what's inside, so keep that around too.
if *kind == AliasKind::Opaque && name.module_id() != ModuleId::NUM {
if *kind == AliasKind::Opaque {
newtype_containers.push(NewtypeKind::Opaque(*name));
}
alias_content = Some(content);
@ -361,10 +361,11 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
use Content::*;
use IntWidth::*;
match (alias_content, int_width) {
(Some(Alias(Symbol::NUM_UNSIGNED8, ..)), U8) => num_helper!(u8),
match (env.subs.get_content_without_compacting(raw_var), int_width) {
(Alias(Symbol::NUM_UNSIGNED8 | Symbol::NUM_U8, ..), U8) => num_helper!(u8),
(_, U8) => {
// This is not a number, it's a tag union or something else
dbg!(&alias_content);
app.call_function(main_fn_name, |_mem: &A::Memory, num: u8| {
byte_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
})
@ -387,7 +388,6 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
match float_width {
F32 => num_helper!(f32),
F64 => num_helper!(f64),
F128 => todo!("F128 not implemented"),
}
}
Layout::Builtin(Builtin::Decimal) => num_helper!(RocDec),
@ -565,7 +565,13 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
use IntWidth::*;
match int_width {
U8 => helper!(deref_u8, u8),
U8 => {
if matches!(raw_content, Content::Alias(name, ..) if name.module_id() == ModuleId::NUM) {
helper!(deref_u8, u8)
} else {
byte_to_ast(env, mem.deref_u8(addr), raw_content)
}
},
U16 => helper!(deref_u16, u16),
U32 => helper!(deref_u32, u32),
U64 => helper!(deref_u64, u64),
@ -583,7 +589,6 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
match float_width {
F32 => helper!(deref_f32, f32),
F64 => helper!(deref_f64, f64),
F128 => todo!("F128 not implemented"),
}
}
(_, Layout::Builtin(Builtin::List(elem_layout))) => {

View File

@ -32,9 +32,10 @@ pub fn get_values<'a>(
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
start: *const u8,
start_offset: usize,
variables: &[Variable],
) -> (usize, Vec<Expr<'a>>) {
let mut result = Vec::with_capacity(variables.len());
number_of_lookups: usize,
) -> (usize, Vec<Expr<'a>>, Vec<Variable>) {
let mut result = Vec::with_capacity(number_of_lookups);
let mut result_vars = Vec::with_capacity(number_of_lookups);
let memory = ExpectMemory { start };
@ -45,13 +46,20 @@ pub fn get_values<'a>(
let app = arena.alloc(app);
for (i, variable) in variables.iter().enumerate() {
let start = app.memory.deref_usize(start_offset + i * 8);
for i in 0..number_of_lookups {
let size_of_lookup_header = 8 /* pointer to value */ + 4 /* type variable */;
let start = app
.memory
.deref_usize(start_offset + i * size_of_lookup_header);
let variable = app.memory.deref_u32(
start_offset + i * size_of_lookup_header + 8, /* skip the pointer */
);
let variable = unsafe { Variable::from_index(variable) };
app.offset = start;
let expr = {
let variable = *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();
@ -76,9 +84,10 @@ pub fn get_values<'a>(
};
result.push(expr);
result_vars.push(variable);
}
(app.offset, result)
(app.offset, result, result_vars)
}
#[cfg(not(windows))]

View File

@ -25,7 +25,7 @@ use roc_mono::{ir::OptLevel, layout::Layout};
use roc_region::all::Region;
use roc_reporting::{error::expect::Renderer, report::RenderTarget};
use roc_target::TargetInfo;
use roc_types::subs::{Subs, Variable};
use roc_types::subs::Subs;
use target_lexicon::Triple;
pub struct ExpectMemory<'a> {
@ -471,7 +471,7 @@ pub fn render_dbgs_in_memory<'a>(
)
}
fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> (Vec<Symbol>, Vec<Variable>) {
fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> Vec<Symbol> {
lookups
.iter()
.filter_map(
@ -485,11 +485,11 @@ fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> (Vec<Symbol>,
if subs.is_function(*var) {
None
} else {
Some((*symbol, *var))
Some(*symbol)
}
},
)
.unzip()
.collect()
}
#[allow(clippy::too_many_arguments)]
@ -523,15 +523,7 @@ fn render_dbg_failure<'a>(
let subs = arena.alloc(&mut data.subs);
let current = ExpectLookup {
symbol: current.symbol,
var: current.var,
ability_info: current.ability_info,
};
let (_symbols, variables) = split_expect_lookups(subs, &[current]);
let (offset, expressions) = crate::get_values(
let (offset, expressions, _variables) = crate::get_values(
target_info,
arena,
subs,
@ -539,7 +531,7 @@ fn render_dbg_failure<'a>(
layout_interner,
start,
frame.start_offset,
&variables,
1,
);
renderer.render_dbg(writer, &expressions, expect_region, failure_region)?;
@ -574,24 +566,23 @@ fn render_expect_failure<'a>(
None => panic!("region {failure_region:?} not in list of expects"),
Some(current) => current,
};
let subs = arena.alloc(&mut data.subs);
let (symbols, variables) = split_expect_lookups(subs, current);
let symbols = split_expect_lookups(&data.subs, current);
let (offset, expressions) = crate::get_values(
let (offset, expressions, variables) = crate::get_values(
target_info,
arena,
subs,
&data.subs,
interns,
layout_interner,
start,
frame.start_offset,
&variables,
symbols.len(),
);
renderer.render_failure(
writer,
subs,
&mut data.subs,
&symbols,
&variables,
&expressions,

View File

@ -1256,3 +1256,15 @@ fn newtype_by_void_is_wrapped() {
r#"Ok 43 : Result (Num *) err"#,
);
}
#[test]
fn enum_tag_union_in_list() {
expect_success(
indoc!(
r#"
[E, F, G, H]
"#
),
r#"[E, F, G, H] : List [E, F, G, H]"#,
);
}

View File

@ -105,7 +105,8 @@
debugir
rust
rust-bindgen
cargo-criterion
cargo-criterion # for benchmarks
simple-http-server # to view roc website when trying out edits
]);
in {

View File

@ -23,6 +23,9 @@
},
{
"pattern": "https://web.eecs.umich.edu"
},
{
"pattern": "http://0.0.0.0:8000"
}
]
}

View File

@ -1,10 +1,9 @@
# www.roc-lang.org
## How to update site
- create a new branch, for example `update-www` based on `www`
- pull `main` into `update-www`
- update for example the file `www/public/index.html`
- do a PR against `www`
- check deploy preview
- review and merge
To view the website after you've made a change, execute:
```bash
./www/build.sh
cd www/build
simple-http-server # If you're using the nix flake simple-http-server will already be installed. Withouth nix you can install it with `cargo install simple-http-server`.
```
Open http://0.0.0.0:8000 in your browser.

View File

@ -1195,6 +1195,16 @@ expect pluralize <span class="str">"cactus"</span> <span class="str">"cacti"</sp
</samp>
<p>If you put this in a file named <code>main.roc</code> and run <code>roc test</code>, Roc will execute the two <code>expect</code>
expressions (that is, the two <code>pluralize</code> calls) and report any that returned <code>false</code>.</p>
<p>If a test fails, it will not show the actual value that differs from the expected value.
To show the actual value, you can write the expect like this:</p>
<samp>expect
funcOut <span class="op">=</span> pluralize <span class="str">"cactus"</span> <span class="str">"cacti"</span> 1
funcOut <span class="op">==</span> <span class="str">"2 cactus"</span>
</samp>
<h3 id="inline-expects"><a href="#inline-expects">Inline Expectations</a></h3>
<p>For example:</p>
<samp> <span class="kw">if</span> count <span class="op">==</span> <span class="number">1</span> then